/|Веб-искатель – это программа, которая перемещается по Сети и находит новые или обновленные страницы для индексирования. Искатель начинает с начальных веб-сайтов или широкого спектра популярных URL-адресов (также известных как граница ) и выполняет поиск по глубине и ширине гиперссылок для извлечения.
Веб-искатель должен быть добрым и надежным. Доброта для Краулера означает, что он уважает правила, установленные robots.txt и избегает слишком частого посещения веб-сайта. Надежность относится к способности избегать паучьих ловушек и другого вредоносного поведения. Другими хорошими атрибутами для веб-искателя являются распределенность между несколькими распределенными машинами, расширяемость, непрерывность и возможность определения приоритетов на основе качества страницы.
1. Шаги по созданию веб-искателя
Основными шагами для написания веб-искателя являются:
- Выберите URL-адрес с границы
- Извлеките HTML-код
- Проанализируйте HTML, чтобы извлечь ссылки на другие URL-адреса
- Проверьте, просматривали ли вы уже URL-адреса и/или видели ли вы тот же контент раньше
- Если нет, добавьте его в индекс
- Для каждого извлеченного URL-адреса
- Подтвердите, что он согласен на проверку (robots.txt, частота ползания)
По правде говоря, разработка и поддержка одного веб-искателя на всех страницах в Интернете – это… Сложно, если не невозможно, учитывая, что сейчас в Сети более 1 миллиарда веб-сайтов . Если вы читаете эту статью, скорее всего, вы ищете не руководство по созданию веб-сканера, а веб-скребок. Тогда почему статья называется “Базовый веб-искатель”? Что ж… Потому что это запоминающееся… Действительно! Мало кто знает разницу между сканерами и скребками, поэтому мы все склонны использовать слово “сканирование” для всего, даже для очистки данных в автономном режиме. Кроме того, потому что для создания веб-скребка вам тоже нужен агент обхода. И, наконец, потому, что эта статья призвана информировать, а также служить жизнеспособным примером.
2. Скелет краулера
Для синтаксического анализа HTML мы будем использовать jsoup . Приведенные ниже примеры были разработаны с использованием jsoup версии 1.10.2.
org.jsoup jsoup 1.10.2
Итак, давайте начнем с базового кода для веб-сканера.
package com.mkyong.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.mkyong.com/"); } }
Пример Вывода:
http://www.mkyong.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
оператор.
package com.mkyong.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.mkyong.com/", 0); } }
Пример Вывода:
... >> 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=mkyong] >> Depth: 1 [http://www.mkyong.com/privacy-policy/]
4. Очистка данных против Обход Данных
Пока что это хорошо для теоретического подхода к этому вопросу. Дело в том, что вы вряд ли когда-нибудь создадите универсальный искатель, и если вы хотите “настоящий”, вам следует использовать уже существующие инструменты. Большая часть того, что делает среднестатистический разработчик, – это извлечение конкретной информации с конкретных веб-сайтов, и хотя это включает в себя создание веб-сканера, на самом деле это называется веб-очисткой.
Есть очень хорошая статья Арпана Джа для PromptCloud на Очистка данных против Сканирование данных , которое лично мне очень помогло понять это различие, и я бы предложил его прочитать.
Чтобы подвести итог, приведем таблицу, взятую из этой статьи:
Относится к загрузке страниц из Интернета | Включает в себя извлечение данных из различных источников, включая Интернет |
В основном делается в больших масштабах | Может быть сделано в любом масштабе |
Дедупликация является важной частью | Дедупликация не обязательно является частью |
Нужен только агент обхода | Требуется агент обхода и анализатор |
Пришло время перейти от теории к реальному примеру, как и было обещано во вступлении. Давайте представим сценарий, в котором мы хотим получить все URL-адреса статей, относящихся к Java 8, из mkyong.com . Наша цель – получить эту информацию в кратчайшие сроки и, таким образом, избежать обхода всего веб-сайта. Кроме того, такой подход приведет не только к потере ресурсов сервера, но и нашего времени.
5. Тематическое исследование – Извлечение всех статей для “Java 8” на mkyong.com
5.1 Первое, что мы должны сделать, это посмотреть код веб-сайта. Бросив быстрый взгляд на mkyong.com мы можем легко заметить подкачку на первой странице и то, что она соответствует шаблону /страница/xx
для каждой страницы.
Это подводит нас к осознанию того, что информация, которую мы ищем, легко доступна, если получить все ссылки, которые включают /страницу/
. Поэтому вместо того, чтобы просматривать весь веб-сайт, мы ограничим наш поиск с помощью document.select("a[href^=\"http://www.mkyong.com/page/\"]")
. С помощью этого селектора css
мы собираем только ссылки, которые начинаются с http://mkyong.com/page/
.
5.2 Следующее, что мы замечаем, это то, что названия статей – а это то, что мы хотим – заключены в и
href="">
теги. href=””>
Поэтому, чтобы извлечь названия статей, мы получим доступ к этой конкретной информации с помощью селектора css
, который ограничивает наш метод select
этой точной информацией: document.select("h2 a[href^=\"http://www.mkyong.com/\"]");
5.3 Наконец, мы сохраним только ссылки, в названии которых содержится “Java 8”, и сохраним их в файл
.
package com.mkyong.extractor; package com.mkyong; 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.mkyong.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.mkyong.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.mkyong.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.mkyong.com"); bwc.getArticles(); bwc.writeToFile("Java 8 Articles"); } }
Выход:
Рекомендации
- PromptCloud – Очистка данных против обхода данных
- Быстрое облако – Как работает веб-искатель
- Википедия – Веб-искатель
- Что такое Граница обхода?
Оригинал: “https://mkyong.com/java/jsoup-basic-web-crawler-example/”