/|Веб-искатель – это программа, которая перемещается по Сети и находит новые или обновленные страницы для индексирования. Искатель начинает с начальных веб-сайтов или широкого спектра популярных 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 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.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 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.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 HashSet links;
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/”