jsoup - Exemple basique de robot d'indexation Web
UnWeb Crawler est un programme qui navigue sur le Web et trouve des pages nouvelles ou mises à jour pour l'indexation. Le Crawler commence par des sites Web d'amorçage ou un large éventail d'URL populaires (également appeléesfrontier) et recherche en profondeur et en largeur les liens hypertexte à extraire.
Un robot d'exploration Web doit être gentil et robuste. La gentillesse d'un Crawler signifie qu'il respecte les règles définies par le robots.txt et évite de visiter trop souvent un site Web. La robustesse fait référence à la capacité d'éviter les pièges à araignées et autres comportements malveillants. D'autres bons attributs pour un Web Crawler sont la distributivité entre plusieurs machines distribuées, l'extensibilité, la continuité et la capacité de prioriser en fonction de la qualité de la page.
1. Étapes pour créer un robot d'indexation Web
Les étapes de base pour écrire un robot d'indexation Web sont les suivantes:
-
Choisissez une URL de la frontière
-
Récupérer le code HTML
-
Analyser le code HTML pour extraire les liens vers d'autres URL
-
Vérifiez si vous avez déjà exploré les URL et / ou si vous avez déjà vu le même contenu
-
Sinon, ajoutez-le à l'index
-
Pour chaque URL extraite
-
Confirmer qu'il accepte d'être vérifié (robots.txt, fréquence d'exploration)
À vrai dire, développer et maintenir un Web Crawler sur toutes les pages d'Internet est… Difficile, voire impossible, étant donné qu'il y a plus de1 billion websitesen ligne en ce moment. Si vous lisez cet article, il est probable que vous ne cherchiez pas un guide pour créer un Web Crawler mais un Web Scraper. Pourquoi l'article s'appelle-t-il alors "Basic Web Crawler"? Eh bien… Parce que c'est accrocheur… Vraiment! Peu de gens connaissent la différence entre les robots d'exploration et les grattoirs, nous avons donc tous tendance à utiliser le mot "analyse" pour tout, même pour le grattage de données hors ligne. De plus, parce que pour construire un Web Scraper, vous avez également besoin d'un agent d'analyse. Et enfin, parce que cet article vise à informer et à fournir un exemple viable.
2. Le squelette d'un robot
Pour l'analyse HTML, nous utiliseronsjsoup. Les exemples ci-dessous ont été développés à l'aide de la version 1.10.2 de jsoup.
pom.xml
org.jsoup jsoup 1.10.2
Commençons donc par le code de base d'un robot d'exploration Web.
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 HashSetlinks; 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
Ne laissez pas ce code s'exécuter trop longtemps. Cela peut prendre des heures sans se terminer.
Exemple de sortie:
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)
Comme nous l'avons mentionné précédemment, un robot d'exploration Web recherche en largeur et en profondeur les liens. Si nous imaginons les liens sur un site Web dans une structure arborescente, le nœud racine ou niveau zéro serait le lien par lequel nous commençons, le niveau suivant serait tous les liens que nous avons trouvés au niveau zéro et ainsi de suite.
3. Prise en compte de la profondeur d'exploration
Nous allons modifier l'exemple précédent pour définir la profondeur d'extraction du lien. Notez que la seule vraie différence entre cet exemple et le précédent est que la méthode récursivegetPageLinks()
a un argument entier qui représente la profondeur du lien qui est également ajoutée en tant que condition dans l'instructionif...else
.
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 HashSetlinks; 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
N'hésitez pas à exécuter le code ci-dessus. Cela n'a pris que quelques minutes sur mon ordinateur portable avec une profondeur réglée sur 2. Veuillez garder à l'esprit que plus la profondeur est élevée, plus il faudra de temps pour terminer.
Exemple de sortie:
... >> 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. Scraping de données vs. Analyse des données
Jusqu'ici tout va bien pour une approche théorique en la matière. Le fait est que vous ne construirez presque jamais un robot générique, et si vous en voulez un "réel", vous devez utiliser des outils qui existent déjà. La plupart de ce que fait le développeur moyen est une extraction d'informations spécifiques à partir de sites Web spécifiques et même si cela comprend la création d'un robot d'exploration Web, il s'agit en fait de Web Scraping.
Il y a un très bon article d'Arpan Jha pour PromptCloud surData Scraping vs. Data Crawling qui m'a personnellement beaucoup aidé à comprendre cette distinction et je suggère de le lire.
Pour le résumer avec un tableau extrait de cet article:
Grattage des données | Analyse des données |
---|---|
Implique l'extraction de données à partir de diverses sources, y compris le Web |
Fait référence au téléchargement de pages à partir du Web |
Peut être fait à n'importe quelle échelle |
Principalement fait à grande échelle |
La déduplication ne fait pas nécessairement partie |
La déduplication est une partie essentielle |
Nécessite un agent d'exploration et un analyseur syntaxique |
Nécessite uniquement un agent d'exploration |
Il est temps de sortir de la théorie et de devenir un exemple viable, comme promis dans l'intro. Imaginons un scénario dans lequel nous souhaitons obtenir toutes les URL des articles relatifs à Java 8 à partir d’exemple.com. Notre objectif est de récupérer ces informations dans les plus brefs délais et d'éviter ainsi de parcourir tout le site Web. En outre, cette approche ne gaspillera pas seulement les ressources du serveur, mais aussi notre temps.
5. Étude de cas - Extrayez tous les articles pour «Java 8» sur example.com
5.1 First thing we should do is look at the code of the website. En jetant un coup d'œil rapide à example.com, nous pouvons facilement remarquer la pagination sur la première page et qu'elle suit un modèle/page/xx
pour chaque page.
Cela nous amène à réaliser que les informations que nous recherchons sont facilement accessibles en récupérant tous les liens qui incluent/page/
. Donc, au lieu de parcourir tout le site Web, nous limiterons notre recherche en utilisantdocument.select("a[href^="http://www.example.com/page/"]")
. Avec cecss selector
, nous ne collectons que les liens commençant parhttp://example.com/page/
.
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.
Donc, pour extraire les titres des articles, nous accèderons à ces informations spécifiques en utilisant uncss selector
qui restreint notre méthodeselect
à ces informations exactes: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 HashSetlinks; 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"); } }
Sortie: