Рубрики
Без рубрики

jsoup – Базовый пример веб-искателя

– jsoup – Базовый пример веб-искателя

/|Веб-искатель – это программа, которая перемещается по Сети и находит новые или обновленные страницы для индексирования. Искатель начинает с начальных веб-сайтов или широкого спектра популярных URL-адресов (также известных как граница ) и выполняет поиск по глубине и ширине гиперссылок для извлечения.

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

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

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

  1. Выберите URL-адрес с границы
  2. Извлеките HTML-код
  3. Проанализируйте HTML, чтобы извлечь ссылки на другие URL-адреса
  4. Проверьте, просматривали ли вы уже URL-адреса и/или видели ли вы тот же контент раньше
    • Если нет, добавьте его в индекс
  5. Для каждого извлеченного 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/");
    }

}

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

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

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");
    }
}

Выход:

Рекомендации

  1. PromptCloud – Очистка данных против обхода данных
  2. Быстрое облако – Как работает веб-искатель
  3. Википедия – Веб-искатель
  4. Что такое Граница обхода?

Оригинал: “https://mkyong.com/java/jsoup-basic-web-crawler-example/”