jsoup - базовый пример веб-сканера

jsoup - базовый пример веб-сканера

web-crawler-spider-logo

Web Crawler - это программа, которая перемещается по сети и находит новые или обновленные страницы для индексации. Сканер начинает с исходных веб-сайтов или широкого диапазона популярных URL-адресов (также известных какfrontier) и выполняет поиск гиперссылок по глубине и ширине для извлечения.

Веб-сканер должен быть добрым и надежным. Доброта для сканера означает, что он соблюдает правила, установленные robots.txt, и избегает слишком частого посещения веб-сайта. Надежность означает способность избегать ловушек пауков и других злонамеренных действий. Другими хорошими атрибутами для Web Crawler являются возможность распространения среди нескольких распределенных компьютеров, расширяемость, непрерывность и возможность расставлять приоритеты в зависимости от качества страницы.

1. Шаги по созданию веб-сканера

Основные шаги для написания веб-сканера:

  1. Выберите URL-адрес от границы

  2. Получить код HTML

  3. Разбор HTML для извлечения ссылок на другие URL

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

Пример вывода:

Как мы уже упоминали ранее, веб-сканер ищет ссылки по ширине и глубине. Если мы представим ссылки на веб-сайте в древовидной структуре, корневым узлом или нулевым уровнем будет ссылка, с которой мы начнем, следующим уровнем будут все ссылки, которые мы нашли на нулевом уровне, и так далее.

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 для каждой страницы.

jsoup-web-crawler-example-1

Это подводит нас к осознанию того, что к искомой информации легко получить доступ, получив все ссылки, которые включают/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.

jsoup-web-crawler-example-2

Итак, чтобы извлечь заголовки статей, мы будем получать доступ к этой конкретной информации, используя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");
    }
}

Выход:

jsoup-web-crawler-example-3