jsoup - базовый пример веб-сканера
Web Crawler - это программа, которая перемещается по сети и находит новые или обновленные страницы для индексации. Сканер начинает с исходных веб-сайтов или широкого диапазона популярных URL-адресов (также известных какfrontier) и выполняет поиск гиперссылок по глубине и ширине для извлечения.
Веб-сканер должен быть добрым и надежным. Доброта для сканера означает, что он соблюдает правила, установленные robots.txt, и избегает слишком частого посещения веб-сайта. Надежность означает способность избегать ловушек пауков и других злонамеренных действий. Другими хорошими атрибутами для Web Crawler являются возможность распространения среди нескольких распределенных компьютеров, расширяемость, непрерывность и возможность расставлять приоритеты в зависимости от качества страницы.
1. Шаги по созданию веб-сканера
Основные шаги для написания веб-сканера:
-
Выберите URL-адрес от границы
-
Получить код HTML
-
Разбор HTML для извлечения ссылок на другие URL
-
Проверьте, сканировали ли вы уже URL-адреса и / или видели ли вы тот же контент ранее
-
Если не добавить его в индекс
-
Для каждого извлеченного URL
-
Подтвердите, что он согласен с проверкой (robots.txt, частота сканирования)
По правде говоря, разработка и поддержка одного веб-краулера на всех страницах в Интернете… Трудно, если не невозможно, учитывая, что сейчас в сети более1 billion websites. Если вы читаете эту статью, скорее всего, вы ищете не руководство по созданию Web Crawler, а Web Scraper. Почему тогда статья называется «Basic Web Crawler»? Ну ... Потому что это броско ... Действительно! Мало кто знает разницу между сканерами и скребками, поэтому мы все склонны использовать слово «сканирование» для всего, даже для очистки данных в автономном режиме. Кроме того, поскольку для создания Web Scraper вам также необходим агент сканирования. И, наконец, потому что эта статья намерена проинформировать, а также привести пример.
2. Скелет гусеничного
Для парсинга HTML мы будем использоватьjsoup. Приведенные ниже примеры были разработаны с использованием jsoup версии 1.10.2.
pom.xml
org.jsoup jsoup 1.10.2
Итак, начнем с основного кода для веб-сканера.
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
Не позволяйте этому коду работать слишком долго. Это может занять часы без конца.
Пример вывода:
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)
Как мы уже упоминали ранее, веб-сканер ищет ссылки по ширине и глубине. Если мы представим ссылки на веб-сайте в древовидной структуре, корневым узлом или нулевым уровнем будет ссылка, с которой мы начнем, следующим уровнем будут все ссылки, которые мы нашли на нулевом уровне, и так далее.
3. Принимая во внимание глубину сканирования
Мы изменим предыдущий пример, чтобы установить глубину извлечения ссылки. Обратите внимание, что единственное истинное различие между этим примером и предыдущим состоит в том, что рекурсивный методgetPageLinks()
имеет целочисленный аргумент, который представляет глубину ссылки, которая также добавляется как условие в оператореif...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
Не стесняйтесь запускать приведенный выше код. Это заняло всего несколько минут на моем ноутбуке с глубиной 2. Пожалуйста, имейте в виду, что чем выше глубина, тем дольше будет финиш.
Пример вывода:
... >> 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. Очистка данных против Сканирование данных
Пока все хорошо для теоретического подхода по этому вопросу. Дело в том, что вы вряд ли когда-либо создадите универсальный сканер, и если вы хотите «настоящий» сканер, вы должны использовать уже существующие инструменты. Большая часть того, что делают среднестатистические разработчики, - это извлечение конкретной информации с определенных веб-сайтов, и, хотя это включает в себя создание веб-сканера, это на самом деле называется Web Scraping.
Есть очень хорошая статья Арпана Джа для PromptCloud оData Scraping vs. Data Crawling, которая лично мне очень помогла понять это различие, и я предлагаю прочитать ее.
Подводя итог, приведем таблицу из этой статьи:
Сбор данных | Сканирование данных |
---|---|
Включает извлечение данных из различных источников, включая Интернет. |
Относится к загрузке страниц из Интернета. |
Можно сделать в любом масштабе |
В основном делается в больших масштабах |
Дедупликация не обязательно является частью |
Дедупликация - важная часть |
Требуется агент сканирования и парсер |
Требуется только агент сканирования |
Время выйти из теории и привести в пример, как обещано во вступлении. Давайте представим сценарий, в котором мы хотим получить все URL-адреса статей, относящихся к Java 8, с сайта example.com. Наша цель - получить эту информацию в кратчайшие сроки и, таким образом, избежать обхода всего сайта. Кроме того, такой подход будет не только тратить ресурсы сервера, но и наше время.
5. Пример использования - извлеките все статьи о Java 8 на example.com
5.1 First thing we should do is look at the code of the website. Бегло взглянув на example.com, мы можем легко заметить разбиение на страницы на первой странице и то, что она следует шаблону/page/xx
для каждой страницы.
Это подводит нас к осознанию того, что к искомой информации легко получить доступ, получив все ссылки, которые включают/page/
. Поэтому вместо того, чтобы проходить через весь веб-сайт, мы ограничим наш поиск, используяdocument.select("a[href^="http://www.example.com/page/"]")
. С помощью этогоcss selector
мы собираем только ссылки, которые начинаются сhttp://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.
Итак, чтобы извлечь заголовки статей, мы будем получать доступ к этой конкретной информации, используяcss selector
, который ограничивает наш методselect
этой точной информацией: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"); } }
Выход: