Добавление URL-адресов
В предыдущем разделе вы создали простой краулер, который загружает HTML одной страницы, читает её заголовок и выводит его в консоль. Вот исходный код:
import { CheerioCrawler } from 'crawlee';
const crawler = new CheerioCrawler({
async requestHandler({ $, request }) {
const title = $('title').text();
console.log(`Заголовок страницы "${request.url}": ${title}.`);
}
})
await crawler.run(['https://crawlee.dev']);
Теперь мы улучшим этот пример. Добавим больше URL-адресов в очередь, благодаря чему краулер будет находить новые ссылки, добавлять их в RequestQueue
и обрабатывать их.
Как работает краулинг
Процесс довольно прост:
- Находим новые ссылки на странице.
- Фильтруем только те, которые ведут на тот же домен (в нашем случае
crawlee.dev
). - Добавляем их в
RequestQueue
. - Посещаем новые ссылки.
- Повторяем процесс.
Далее вы познакомитесь с функцией enqueueLinks
, которая упрощает краулинг до одного вызова функции. Для сравнения мы также покажем эквивалентное решение без использования enqueueLinks
.
enqueueLinks
Функция enqueueLinks
учитывает контекст. Это означает, что она автоматически получает информацию о текущей обрабатываемой странице из контекста, и вам не нужно явно указывать аргументы. Она находит ссылки с помощью функции Cheerio $
и автоматически добавляет их в RequestQueue
работающего краулера.
Ограничение краулинга с помощью maxRequestsPerCrawl
При тестировании кода или когда ваш краулер может потенциально найти миллионы ссылок, полезно установить максимальное ограничение на количество обрабатываемых страниц. Для этого используется опция maxRequestsPerCrawl
:
const crawler = new CheerioCrawler({
maxRequestsPerCrawl: 20,
// ...
});
Это означает, что новые запросы не будут начаты после завершения 20-го запроса. Фактическое количество обработанных запросов может быть немного больше из-за параллелизации, так как выполняющиеся запросы не будут принудительно прерваны.
Поиск новых ссылок
Существует множество подходов к поиску ссылок при краулинге. В нашем случае мы будем искать элементы <a>
с атрибутом href
, так как это наиболее распространенный случай. Например:
<a href="https://crawlee.dev/docs/introduction">Это ссылка на введение в Crawlee</a>
Поскольку это самый распространенный случай, он является поведением по умолчанию для enqueueLinks
.
- с enqueueLinks
- без enqueueLinks
import { CheerioCrawler } from 'crawlee';
const crawler = new CheerioCrawler({
// Let's limit our crawls to make our
// tests shorter and safer.
maxRequestsPerCrawl: 20,
// enqueueLinks является аргументом requestHandler
async requestHandler({ $, request, enqueueLinks }) {
const title = $('title').text();
console.log(`The title of "${request.url}" is: ${title}.`);
// Функция enqueueLinks является контекстно-зависимой,
// поэтому не требует никаких параметров.
await enqueueLinks();
},
});
await crawler.run(['https://crawlee.dev']);
import { CheerioCrawler } from 'crawlee';
import { URL } from 'node:url';
const crawler = new CheerioCrawler({
// Давайте ограничим наши краулинг, чтобы наши
// тесты были короче и безопаснее.
maxRequestsPerCrawl: 20,
async requestHandler({ request, $ }) {
const title = $('title').text();
console.log(`The title of "${request.url}" is: ${title}.`);
// Без enqueueLinks, мы сначала должны извлечь все
// URL-адреса из страницы с помощью Cheerio.
const links = $('a[href]')
.map((_, el) => $(el).attr('href'))
.get();
// Затем мы должны разрешить относительные URL-адреса,
// иначе они будут недоступны для краулинга.
const absoluteUrls = links.map((link) => new URL(link, request.loadedUrl).href);
// Наконец, мы должны добавить URL-адреса в очередь
await crawler.addRequests(absoluteUrls);
},
});
await crawler.run(['https://crawlee.dev']);
Если вам нужно переопределить стандартный выбор элементов в enqueueLinks
, вы можете использовать аргумент selector
:
await enqueueLinks({
selector: 'div.has-link'
});
Фильтрация ссылок на тот же домен
Веб-сайты обычно содержат множество ссылок, ведущих на другие ресурсы. Это нормально, но при сканировании сайта мы обычно хотим обрабатывать только один конкретный сайт, а не позволять нашему краулеру уходить на Google, Facebook или Twitter. Поэтому нам нужно отфильтровать внешние ссылки и оставить только те, которые ведут на тот же домен.
- с enqueueLinks
- без enqueueLinks
import { CheerioCrawler } from 'crawlee';
const crawler = new CheerioCrawler({
maxRequestsPerCrawl: 20,
async requestHandler({ $, request, enqueueLinks }) {
const title = $('title').text();
console.log(`The title of "${request.url}" is: ${title}.`);
// По умолчанию enqueueLinks работает на том же имени хоста,
// поэтому не требует никаких параметров.
// Это гарантирует, что поддомен останется тем же.
await enqueueLinks();
},
});
await crawler.run(['https://crawlee.dev']);
import { CheerioCrawler } from 'crawlee';
import { URL } from 'node:url';
const crawler = new CheerioCrawler({
maxRequestsPerCrawl: 20,
async requestHandler({ request, $ }) {
const title = $('title').text();
console.log(`The title of "${request.url}" is: ${title}.`);
const links = $('a[href]')
.map((_, el) => $(el).attr('href'))
.get();
// Кроме того, мы теперь также должны
// получить их имя хоста для фильтрации.
const { hostname } = new URL(request.loadedUrl);
const absoluteUrls = links.map((link) => new URL(link, request.loadedUrl));
// Мы используем имя хоста для фильтрации ссылок,
// которые указывают на другой домен, даже поддомен.
const sameHostnameLinks = absoluteUrls
.filter((url) => url.hostname === hostname)
.map((url) => ({ url: url.href }));
// Наконец, мы должны добавить URL-адреса в очередь
await crawler.addRequests(sameHostnameLinks);
},
});
await crawler.run(['https://crawlee.dev']);
По умолчанию enqueueLinks
работает только с тем же хостом. Это не включает поддомены. Чтобы включить поддомены в сканирование, используйте аргумент strategy
:
await enqueueLinks({
strategy: 'same-domain'
});
При запуске кода вы увидите, как краулер сначала выводит заголовок первой страницы, затем сообщение о добавлении в очередь с количеством URL-адресов, после чего заголовок первой обработанной страницы из очереди и так далее.
Пропуск повторяющихся URL-адресов
Пропуск повторяющихся URL-адресов критически важен, поскольку повторное посещение одной и той же страницы приведет к дублированию результатов. Это автоматически обрабатывается RequestQueue
, который исключает дубликаты запросов, используя их uniqueKey
. Этот uniqueKey
автоматически генерируется из URL запроса путем приведения URL к нижнему регистру, лексической сортировки параметров запроса, удаления фрагментов и некоторых других преобразований, гарантирующих, что в очереди будут только уникальные URL-адреса.
Расширенные аргументы фильтрации
Хотя настройки по умолчанию для enqueueLinks
часто могут быть именно тем, что вам нужно, функция также предоставляет точный контроль над тем, какие URL-адреса должны быть добавлены в очередь. Один из способов мы уже упоминали выше - использование EnqueueStrategy
. Вы можете использовать стратегию All
, если хотите следовать по всем ссылкам независимо от домена, или добавлять в очередь ссылки, ведущие на тот же домен, используя стратегию SameDomain
.
await enqueueLinks({
strategy: 'all', // исследовать весь интернет
});
Фильтрация URL-адресов с помощью шаблонов
Для еще большего контроля вы можете использовать globs
, regexps
и pseudoUrls
для фильтрации URL-адресов. Каждый из этих аргументов всегда является массивом, но их содержимое может принимать различные формы. Смотрите справочник для получения дополнительной информации о них и других опциях.
Если вы указываете один из этих параметров, стратегия по умолчанию same-hostname
не будет применяться, если явно не указана в опциях.
await enqueueLinks({
globs: ['http?(s)://apify.com/*/*'],
});
Преобразование запросов
Для полного контроля у нас есть transformRequestFunction
. Непосредственно перед созданием нового Request
и добавлением его в RequestQueue
, эта функция может быть использована для его пропуска или изменения содержимого, такого как userData
, payload
или, что наиболее важно, uniqueKey
. Это полезно, когда вам нужно добавить в очередь несколько запросов с одинаковым URL, но разными методами или полезной нагрузкой. Другой вариант использования - динамическое обновление или создание userData
.
await enqueueLinks({
globs: ['http?(s)://apify.com/*/*'],
transformRequestFunction(req) {
// игнорировать все ссылки, заканчивающиеся на `.pdf`
if (req.url.endsWith('.pdf')) return false;
return req;
},
});
Вот и всё! enqueueLinks()
- это только один пример мощных вспомогательных функций Crawlee. Все они разработаны, чтобы упростить вашу работу, позволяя сосредоточиться на получении данных и оставив рутинное управление сканированием инструментам.
Следующие шаги
Далее вы начнете свой проект по сбору данных с реального веб-сайта и узнаете еще несколько приемов работы с Crawlee.