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 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
Не позволяйте этому коду работать слишком долго. Это может занять часы без конца.
Пример вывода:
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 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
Не стесняйтесь запускать приведенный выше код. Это заняло всего несколько минут на моем ноутбуке с глубиной 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 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");
}
}
Выход:
