jsoup - Ein einfaches Beispiel für einen Webcrawler

AWeb Crawler ist ein Programm, das im Web navigiert und neue oder aktualisierte Seiten für die Indizierung findet. Der Crawler beginnt mit Seed-Websites oder einer Vielzahl beliebter URLs (auch alsfrontier bezeichnet) und sucht in Tiefe und Breite nach zu extrahierenden Hyperlinks.
Ein Web Crawler muss freundlich und robust sein. Freundlichkeit für einen Crawler bedeutet, dass er die Regeln der robots.txt einhält und vermeidet, eine Website zu oft zu besuchen. Robustheit bezieht sich auf die Fähigkeit, Spinnenfallen und anderes böswilliges Verhalten zu vermeiden. Andere gute Attribute für einen Web-Crawler sind die Verteilung auf mehrere verteilte Maschinen, die Erweiterbarkeit, die Kontinuität und die Fähigkeit, basierend auf der Seitenqualität Prioritäten zu setzen.
1. Schritte zum Erstellen eines Webcrawlers
Die grundlegenden Schritte zum Schreiben eines Web Crawlers sind:
-
Wählen Sie eine URL von der Grenze
-
Holen Sie sich den HTML-Code
-
Analysieren Sie den HTML-Code, um Links zu anderen URLs zu extrahieren
-
Überprüfen Sie, ob Sie die URLs bereits gecrawlt haben und / oder ob Sie zuvor denselben Inhalt gesehen haben
-
Wenn nicht, fügen Sie es dem Index hinzu
-
Für jede extrahierte URL
-
Bestätigen Sie, dass die Überprüfung genehmigt wurde (robots.txt, Crawling-Häufigkeit).
Um ehrlich zu sein, ist die Entwicklung und Pflege eines Web Crawlers auf allen Seiten im Internet… schwierig, wenn nicht unmöglich, wenn man bedenkt, dass derzeit über1 billion websitesonline sind. Wenn Sie diesen Artikel lesen, suchen Sie wahrscheinlich nicht nach einer Anleitung zum Erstellen eines Web-Crawlers, sondern nach einem Web-Scraper. Warum heißt der Artikel dann "Basic Web Crawler"? Na ja ... weil es eingängig ist ... Wirklich! Nur wenige Menschen kennen den Unterschied zwischen Crawlern und Scrapern. Daher verwenden wir alle das Wort „Crawlen“ für alles, auch für das Offline-Scraping von Daten. Außerdem benötigen Sie zum Erstellen eines Web Scrapers einen Crawl-Agenten. Und schließlich, weil dieser Artikel sowohl informieren als auch ein praktikables Beispiel liefern soll.
2. Das Skelett eines Crawlers
Für die HTML-Analyse verwenden wirjsoup. Die folgenden Beispiele wurden mit jsoup Version 1.10.2 entwickelt.
pom.xml
org.jsoup jsoup 1.10.2
Beginnen wir also mit dem grundlegenden Code für einen Web-Crawler.
BasicWebCrawler.java
package com.example.basicwebcrawler;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.io.IOException;
import java.util.HashSet;
public class BasicWebCrawler {
private HashSet links;
public BasicWebCrawler() {
links = new HashSet();
}
public void getPageLinks(String URL) {
//4. Check if you have already crawled the URLs
//(we are intentionally not checking for duplicate content in this example)
if (!links.contains(URL)) {
try {
//4. (i) If not add it to the index
if (links.add(URL)) {
System.out.println(URL);
}
//2. Fetch the HTML code
Document document = Jsoup.connect(URL).get();
//3. Parse the HTML to extract links to other URLs
Elements linksOnPage = document.select("a[href]");
//5. For each extracted URL... go back to Step 4.
for (Element page : linksOnPage) {
getPageLinks(page.attr("abs:href"));
}
} catch (IOException e) {
System.err.println("For '" + URL + "': " + e.getMessage());
}
}
}
public static void main(String[] args) {
//1. Pick a URL from the frontier
new BasicWebCrawler().getPageLinks("http://www.example.com/");
}
}
Note
Lassen Sie diesen Code nicht zu lange laufen. Es kann Stunden dauern, ohne zu enden.
Beispielausgabe:
http://www.example.com/Android TutorialAndroid TutorialJava I/O TutorialJava I/O TutorialJava XML TutorialJava XML TutorialJava JSON TutorialJava JSON TutorialJava Regular Expression TutorialJava Regular Expression TutorialJDBC Tutorial...(+ many more links)
Wie bereits erwähnt, sucht ein Web Crawler in Breite und Tiefe nach Links. Wenn wir uns die Links auf einer Website in einer baumartigen Struktur vorstellen, wäre der Wurzelknoten oder die Ebene Null der Link, mit dem wir beginnen, die nächste Ebene wären alle Links, die wir auf Ebene Null usw. gefunden haben.
3. Kriechtiefe berücksichtigen
Wir werden das vorherige Beispiel modifizieren, um die Tiefe der Linkextraktion festzulegen. Beachten Sie, dass der einzig wahre Unterschied zwischen diesem und dem vorherigen Beispiel darin besteht, dass die rekursivegetPageLinks()-Methode ein ganzzahliges Argument enthält, das die Tiefe der Verknüpfung darstellt, die auch als Bedingung in derif...else-Anweisung hinzugefügt wird.
WebCrawlerWithDepth.java
package com.example.depthwebcrawler;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.io.IOException;
import java.util.HashSet;
public class WebCrawlerWithDepth {
private static final int MAX_DEPTH = 2;
private HashSet links;
public WebCrawlerWithDepth() {
links = new HashSet<>();
}
public void getPageLinks(String URL, int depth) {
if ((!links.contains(URL) && (depth < MAX_DEPTH))) {
System.out.println(">> Depth: " + depth + " [" + URL + "]");
try {
links.add(URL);
Document document = Jsoup.connect(URL).get();
Elements linksOnPage = document.select("a[href]");
depth++;
for (Element page : linksOnPage) {
getPageLinks(page.attr("abs:href"), depth);
}
} catch (IOException e) {
System.err.println("For '" + URL + "': " + e.getMessage());
}
}
}
public static void main(String[] args) {
new WebCrawlerWithDepth().getPageLinks("http://www.example.com/", 0);
}
}
Note
Fühlen Sie sich frei, den obigen Code auszuführen. Auf meinem Laptop mit einer Tiefe von 2 dauerte es nur ein paar Minuten. Bitte denken Sie daran, je höher die Tiefe, desto länger dauert die Fertigstellung.
Beispielausgabe:
... >> Depth: 1 [https://docs.gradle.org/current/userguide/userguide.html] >> Depth: 1 [http://hibernate.org/orm/] >> Depth: 1 [https://jax-ws.java.net/] >> Depth: 1 [http://tomcat.apache.org/tomcat-8.0-doc/index.html] >> Depth: 1 [http://www.javacodegeeks.com/] For 'http://www.javacodegeeks.com/': HTTP error fetching URL >> Depth: 1 [http://beust.com/weblog/] >> Depth: 1 [https://dzone.com] >> Depth: 1 [https://wordpress.org/] >> Depth: 1 [http://www.liquidweb.com/?RID=example] >> Depth: 1 [http://www.example.com/privacy-policy/]
4. Daten-Scraping vs. Daten-Crawling
Soweit so gut für eine theoretische Herangehensweise. Tatsache ist, dass Sie kaum jemals einen generischen Crawler erstellen werden. Wenn Sie einen „echten“ Crawler möchten, sollten Sie bereits vorhandene Tools verwenden. Das meiste, was der durchschnittliche Entwickler tut, ist das Extrahieren bestimmter Informationen von bestimmten Websites. Auch wenn dies das Erstellen eines Web-Crawlers umfasst, wird dies als Web-Scraping bezeichnet.
Es gibt einen sehr guten Artikel von Arpan Jha für PromptCloud überData Scraping vs. Data Crawling, der mir persönlich sehr geholfen hat, diese Unterscheidung zu verstehen, und ich würde empfehlen, ihn zu lesen.
Um es mit einer Tabelle aus diesem Artikel zusammenzufassen:
| Daten-Scraping | Daten-Crawling |
|---|---|
Umfasst das Extrahieren von Daten aus verschiedenen Quellen, einschließlich dem Web |
Bezieht sich auf das Herunterladen von Seiten aus dem Web |
Kann in jedem Maßstab durchgeführt werden |
Meistens in großem Maßstab |
Deduplizierung ist nicht unbedingt ein Teil |
Die Deduplizierung ist ein wesentlicher Bestandteil |
Benötigt Crawler und Parser |
Benötigt nur Crawler |
Zeit, aus der Theorie auszusteigen und ein tragfähiges Beispiel zu finden, wie im Intro versprochen. Stellen wir uns ein Szenario vor, in dem wir alle URLs für Artikel, die sich auf Java 8 beziehen, von example.com abrufen möchten. Unser Ziel ist es, diese Informationen in kürzester Zeit abzurufen und so das Crawlen durch die gesamte Website zu vermeiden. Außerdem verschwendet dieser Ansatz nicht nur die Ressourcen des Servers, sondern auch unsere Zeit.
5. Fallstudie - Extrahieren Sie alle Artikel für "Java 8" auf example.com
5.1 First thing we should do is look at the code of the website. Wenn wir einen kurzen Blick auf example.com werfen, können wir leicht feststellen, dass das Paging auf der Startseite für jede Seite einem/page/xx-Muster folgt.

Dies bringt uns zu der Erkenntnis, dass auf die gesuchten Informationen leicht zugegriffen werden kann, indem alle Links abgerufen werden, die/page/ enthalten. Anstatt die gesamte Website zu durchlaufen, beschränken wir unsere Suche mitdocument.select("a[href^="http://www.example.com/page/"]"). Mit diesencss selector sammeln wir nur die Links, die mithttp://example.com/page/ beginnen.
5.2 Next thing we notice is that the titles of the articles -which is what we want- are wrapped in <h2></h2> and <a href=""></a> tags.

Um die Artikeltitel zu extrahieren, greifen wir mit einemcss selector auf diese spezifischen Informationen zu, wodurch unsereselect-Methode auf genau diese Informationen beschränkt wird:document.select("h2 a[href^="http://www.example.com/"]");
5.3 Finally, we will only keep the links in which the title contains ‘Java 8’ and save them to a file.
Extractor.java
package com.example.extractor;
package com.example;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
public class Extractor {
private HashSet links;
private List> articles;
public Extractor() {
links = new HashSet<>();
articles = new ArrayList<>();
}
//Find all URLs that start with "http://www.example.com/page/" and add them to the HashSet
public void getPageLinks(String URL) {
if (!links.contains(URL)) {
try {
Document document = Jsoup.connect(URL).get();
Elements otherLinks = document.select("a[href^=\"http://www.example.com/page/\"]");
for (Element page : otherLinks) {
if (links.add(URL)) {
//Remove the comment from the line below if you want to see it running on your editor
System.out.println(URL);
}
getPageLinks(page.attr("abs:href"));
}
} catch (IOException e) {
System.err.println(e.getMessage());
}
}
}
//Connect to each link saved in the article and find all the articles in the page
public void getArticles() {
links.forEach(x -> {
Document document;
try {
document = Jsoup.connect(x).get();
Elements articleLinks = document.select("h2 a[href^=\"http://www.example.com/\"]");
for (Element article : articleLinks) {
//Only retrieve the titles of the articles that contain Java 8
if (article.text().matches("^.*?(Java 8|java 8|JAVA 8).*$")) {
//Remove the comment from the line below if you want to see it running on your editor,
//or wait for the File at the end of the execution
//System.out.println(article.attr("abs:href"));
ArrayList temporary = new ArrayList<>();
temporary.add(article.text()); //The title of the article
temporary.add(article.attr("abs:href")); //The URL of the article
articles.add(temporary);
}
}
} catch (IOException e) {
System.err.println(e.getMessage());
}
});
}
public void writeToFile(String filename) {
FileWriter writer;
try {
writer = new FileWriter(filename);
articles.forEach(a -> {
try {
String temp = "- Title: " + a.get(0) + " (link: " + a.get(1) + ")\n";
//display to console
System.out.println(temp);
//save to file
writer.write(temp);
} catch (IOException e) {
System.err.println(e.getMessage());
}
});
writer.close();
} catch (IOException e) {
System.err.println(e.getMessage());
}
}
public static void main(String[] args) {
Extractor bwc = new Extractor();
bwc.getPageLinks("http://www.example.com");
bwc.getArticles();
bwc.writeToFile("Java 8 Articles");
}
}
Ausgabe:
