Перейти к основному содержимому

Руководство по Crawler4j

· 7 мин. чтения

1. Введение

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

В этом руководстве мы узнаем, как использовать Crawler4j для настройки и запуска наших собственных поисковых роботов. Crawler4j — это проект Java с открытым исходным кодом, который позволяет нам легко это делать.

2. Настройка

Давайте воспользуемся Maven Central , чтобы найти самую последнюю версию и добавить зависимость Maven:

<dependency>
<groupId>edu.uci.ics</groupId>
<artifactId>crawler4j</artifactId>
<version>4.4.0</version>
</dependency>

3. Создание сканеров

3.1. Простой HTML-краулер

Начнем с создания базового поискового робота, который просматривает HTML-страницы на https://foreach.com .

Давайте создадим наш сканер, расширив WebCrawler в нашем классе сканера и определив шаблон для исключения определенных типов файлов:

public class HtmlCrawler extends WebCrawler {

private final static Pattern EXCLUSIONS
= Pattern.compile(".*(\\.(css|js|xml|gif|jpg|png|mp3|mp4|zip|gz|pdf))$");

// more code
}

В каждом классе сканера мы должны переопределить и реализовать два метода: shouldVisit и visit .

Теперь давайте создадим наш метод shouldVisit , используя созданный нами шаблон EXCLUSIONS :

@Override
public boolean shouldVisit(Page referringPage, WebURL url) {
String urlString = url.getURL().toLowerCase();
return !EXCLUSIONS.matcher(urlString).matches()
&& urlString.startsWith("https://www.foreach.com/");
}

Затем мы можем выполнить обработку посещенных страниц в методе посещения :

@Override
public void visit(Page page) {
String url = page.getWebURL().getURL();

if (page.getParseData() instanceof HtmlParseData) {
HtmlParseData htmlParseData = (HtmlParseData) page.getParseData();
String title = htmlParseData.getTitle();
String text = htmlParseData.getText();
String html = htmlParseData.getHtml();
Set<WebURL> links = htmlParseData.getOutgoingUrls();

// do something with the collected data
}
}

После того, как наш краулер написан, нам нужно настроить и запустить его:

File crawlStorage = new File("src/test/resources/crawler4j");
CrawlConfig config = new CrawlConfig();
config.setCrawlStorageFolder(crawlStorage.getAbsolutePath());

int numCrawlers = 12;

PageFetcher pageFetcher = new PageFetcher(config);
RobotstxtConfig robotstxtConfig = new RobotstxtConfig();
RobotstxtServer robotstxtServer= new RobotstxtServer(robotstxtConfig, pageFetcher);
CrawlController controller = new CrawlController(config, pageFetcher, robotstxtServer);

controller.addSeed("https://www.foreach.com/");

CrawlController.WebCrawlerFactory<HtmlCrawler> factory = HtmlCrawler::new;

controller.start(factory, numCrawlers);

Мы настроили каталог временного хранилища, указали количество потоков сканирования и задали краулеру начальный URL.

Также следует отметить, что метод CrawlController.start() является блокирующей операцией . Любой код после этого вызова будет выполняться только после завершения работы сканера.

3.2. ImageCrawler

По умолчанию Crawler4j не сканирует двоичные данные. В следующем примере мы включим эту функцию и просканируем все файлы JPEG в ForEach.

Начнем с определения класса ImageCrawler с конструктором, который принимает каталог для сохранения изображений:

public class ImageCrawler extends WebCrawler {
private final static Pattern EXCLUSIONS
= Pattern.compile(".*(\\.(css|js|xml|gif|png|mp3|mp4|zip|gz|pdf))$");

private static final Pattern IMG_PATTERNS = Pattern.compile(".*(\\.(jpg|jpeg))$");

private File saveDir;

public ImageCrawler(File saveDir) {
this.saveDir = saveDir;
}

// more code

}

Далее реализуем метод shouldVisit :

@Override
public boolean shouldVisit(Page referringPage, WebURL url) {
String urlString = url.getURL().toLowerCase();
if (EXCLUSIONS.matcher(urlString).matches()) {
return false;
}

if (IMG_PATTERNS.matcher(urlString).matches()
|| urlString.startsWith("https://www.foreach.com/")) {
return true;
}

return false;
}

Теперь мы готовы реализовать метод посещения :

@Override
public void visit(Page page) {
String url = page.getWebURL().getURL();
if (IMG_PATTERNS.matcher(url).matches()
&& page.getParseData() instanceof BinaryParseData) {
String extension = url.substring(url.lastIndexOf("."));
int contentLength = page.getContentData().length;

// write the content data to a file in the save directory
}
}

Запуск нашего ImageCrawler аналогичен запуску HttpCrawler , но нам нужно настроить его для включения двоичного содержимого:

CrawlConfig config = new CrawlConfig();
config.setIncludeBinaryContentInCrawling(true);

// ... same as before

CrawlController.WebCrawlerFactory<ImageCrawler> factory = () -> new ImageCrawler(saveDir);

controller.start(factory, numCrawlers);

3.3. Сбор данных

Теперь, когда мы рассмотрели пару основных примеров, давайте расширим наш HtmlCrawler , чтобы собирать некоторые основные статистические данные во время сканирования.

Во-первых, давайте определим простой класс для хранения нескольких статистических данных:

public class CrawlerStatistics {
private int processedPageCount = 0;
private int totalLinksCount = 0;

public void incrementProcessedPageCount() {
processedPageCount++;
}

public void incrementTotalLinksCount(int linksCount) {
totalLinksCount += linksCount;
}

// standard getters
}

Затем давайте изменим наш HtmlCrawler , чтобы он принимал экземпляр CrawlerStatistics через конструктор:

private CrawlerStatistics stats;

public HtmlCrawler(CrawlerStatistics stats) {
this.stats = stats;
}

С нашим новым объектом CrawlerStatistics давайте изменим метод посещения , чтобы собирать то, что нам нужно:

@Override
public void visit(Page page) {
String url = page.getWebURL().getURL();
stats.incrementProcessedPageCount();

if (page.getParseData() instanceof HtmlParseData) {
HtmlParseData htmlParseData = (HtmlParseData) page.getParseData();
String title = htmlParseData.getTitle();
String text = htmlParseData.getText();
String html = htmlParseData.getHtml();
Set<WebURL> links = htmlParseData.getOutgoingUrls();
stats.incrementTotalLinksCount(links.size());

// do something with collected data
}
}

Теперь вернемся к нашему контроллеру и предоставим HtmlCrawler экземпляр CrawlerStatistics :

CrawlerStatistics stats = new CrawlerStatistics();
CrawlController.WebCrawlerFactory<HtmlCrawler> factory = () -> new HtmlCrawler(stats);

3.4. Несколько сканеров

Основываясь на наших предыдущих примерах, давайте теперь посмотрим, как мы можем запускать несколько поисковых роботов с одного и того же контроллера.

Рекомендуется, чтобы каждый сканер использовал свой собственный каталог временного хранения , поэтому нам нужно создать отдельные конфигурации для каждого из них, которые мы будем запускать.

CrawlControllers могут совместно использовать один RobotstxtServer , но в остальном нам нужна копия всего.

До сих пор мы использовали метод CrawlController.start для запуска наших сканеров и отметили, что это метод блокировки. Для запуска нескольких мы будем использовать CrawlerControlller.startNonBlocking в сочетании с CrawlController.waitUntilFinish .

Теперь давайте создадим контроллер для одновременного запуска HtmlCrawler и ImageCrawler :

File crawlStorageBase = new File("src/test/resources/crawler4j");
CrawlConfig htmlConfig = new CrawlConfig();
CrawlConfig imageConfig = new CrawlConfig();

// Configure storage folders and other configurations

PageFetcher pageFetcherHtml = new PageFetcher(htmlConfig);
PageFetcher pageFetcherImage = new PageFetcher(imageConfig);

RobotstxtConfig robotstxtConfig = new RobotstxtConfig();
RobotstxtServer robotstxtServer = new RobotstxtServer(robotstxtConfig, pageFetcherHtml);

CrawlController htmlController
= new CrawlController(htmlConfig, pageFetcherHtml, robotstxtServer);
CrawlController imageController
= new CrawlController(imageConfig, pageFetcherImage, robotstxtServer);

// add seed URLs

CrawlerStatistics stats = new CrawlerStatistics();
CrawlController.WebCrawlerFactory<HtmlCrawler> htmlFactory = () -> new HtmlCrawler(stats);

File saveDir = new File("src/test/resources/crawler4j");
CrawlController.WebCrawlerFactory<ImageCrawler> imageFactory
= () -> new ImageCrawler(saveDir);

imageController.startNonBlocking(imageFactory, 7);
htmlController.startNonBlocking(htmlFactory, 10);

htmlController.waitUntilFinish();
imageController.waitUntilFinish();

4. Конфигурация

Мы уже видели кое-что из того, что мы можем настроить. Теперь давайте рассмотрим некоторые другие общие настройки.

Настройки применяются к экземпляру CrawlConfig , который мы указываем в нашем контроллере.

4.1. Ограничение глубины сканирования

По умолчанию наши поисковые роботы будут сканировать как можно глубже. Чтобы ограничить их глубину, мы можем установить глубину сканирования:

crawlConfig.setMaxDepthOfCrawling(2);

Исходные URL-адреса считаются на глубине 0, поэтому глубина сканирования 2 будет проходить на два уровня дальше исходного URL-адреса.

4.2. Максимальное количество страниц для выборки

Еще один способ ограничить количество страниц, которые будут охватывать наши сканеры, — установить максимальное количество страниц для сканирования:

crawlConfig.setMaxPagesToFetch(500);

4.3. Максимальное количество исходящих ссылок

Мы также можем ограничить количество исходящих ссылок с каждой страницы:

crawlConfig.setMaxOutgoingLinksToFollow(2000);

4.4. Вежливость Задержка

Поскольку очень эффективные поисковые роботы могут легко создавать нагрузку на веб-серверы, у crawler4j есть то, что он называет задержкой из-за вежливости. По умолчанию установлено значение 200 миллисекунд. Мы можем изменить это значение, если нам нужно:

crawlConfig.setPolitenessDelay(300);

4.5. Включить двоичный контент

Мы уже использовали опцию включения бинарного контента в наш ImageCrawler :

crawlConfig.setIncludeBinaryContentInCrawling(true);

4.6. Включить HTTPS

По умолчанию сканеры будут включать страницы HTTPS, но мы можем отключить это:

crawlConfig.setIncludeHttpsPages(false);

4.7. Возобновляемое сканирование

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

crawlConfig.setResumableCrawling(true);

4.8. Строка агента пользователя

Строка пользовательского агента по умолчанию для crawler4j — crawler4j . Давайте настроим это:

crawlConfig.setUserAgentString("foreach demo (https://github.com/yasserg/crawler4j/)");

Мы только что рассмотрели некоторые из основных конфигураций. Мы можем взглянуть на класс CrawConfig , если нас интересуют некоторые из более сложных или малоизвестных параметров конфигурации.

5. Вывод

В этой статье мы использовали crawler4j для создания собственных поисковых роботов. Мы начали с двух простых примеров сканирования HTML и изображений. Затем мы использовали эти примеры, чтобы увидеть, как мы можем собирать статистику и одновременно запускать несколько поисковых роботов.

Полные примеры кода доступны на GitHub .