Хранение запросов
Crawlee предлагает несколько типов хранилищ запросов для различных задач. Запросы сохраняются локально на диск в директорию, указанную в переменной окружения CRAWLEE_STORAGE_DIR
. Если эта переменная не задана, Crawlee по умолчанию использует директорию ./storage
в текущей рабочей папке.
Очередь запросов
Очередь запросов - это хранилище URL-адресов для обхода. Очередь используется для глубокого обхода веб-сайтов, когда мы начинаем с нескольких URL и рекурсивно переходим по ссылкам на другие страницы. Структура данных поддерживает как обход в ширину, так и в глубину.
Каждый запуск проекта Crawlee связан с очередью запросов по умолчанию. Обычно она используется для хранения URL-адресов, которые нужно обойти в рамках конкретного запуска краулера. Её использование необязательно.
В Crawlee очередь запросов представлена классом RequestQueue
.
Очередью запросов управляет класс MemoryStorage
. Данные хранятся в памяти и периодически сохраняются в локальную директорию, указанную в переменной CRAWLEE_STORAGE_DIR
, по следующему пути:
{CRAWLEE_STORAGE_DIR}/request_queues/{QUEUE_ID}/entries.json
{QUEUE_ID}
- это имя или идентификатор очереди запросов. У очереди по умолчанию ID равен default
, если не переопределить его через переменную окружения CRAWLEE_DEFAULT_REQUEST_QUEUE_ID
.
entries.json
содержит массив запросов.
Вот пример использования очереди запросов:
- Использование с краулером
- Явное использование с краулером
- Базовые операции
import { CheerioCrawler } from 'crawlee';
// Краулер автоматически обрабатывает запросы из очереди.
// Он используется таким же образом для краулеров Puppeteer/Playwright.
const crawler = new CheerioCrawler({
// Обратите внимание, что мы здесь не указываем requestQueue
async requestHandler({ $, crawler, enqueueLinks }) {
// Добавляем новый запрос в очередь
await crawler.addRequests([{ url: 'https://example.com/new-page' }]);
// Добавляем найденные ссылки на странице в очередь
await enqueueLinks();
},
});
// Добавляем начальные запросы.
// Обратите внимание, что мы здесь не открываем очередь запросов явно перед этим.
await crawler.addRequests([
{ url: 'https://example.com/1' },
{ url: 'https://example.com/2' },
{ url: 'https://example.com/3' },
// ...
]);
// Запускаем краулер
await crawler.run();
import { RequestQueue, CheerioCrawler } from 'crawlee';
// Открываем очередь запросов по умолчанию, связанную с текущим запуском
const requestQueue = await RequestQueue.open();
// Добавляем начальные запросы
await requestQueue.addRequests([
{ url: 'https://example.com/1' },
{ url: 'https://example.com/2' },
{ url: 'https://example.com/3' },
// ...
]);
// Краулер автоматически обрабатывает запросы из очереди.
// Он используется таким же образом для краулеров Puppeteer/Playwright
const crawler = new CheerioCrawler({
requestQueue,
async requestHandler({ $, request, enqueueLinks }) {
// Добавляем новый запрос в очередь
await requestQueue.addRequests([{ url: 'https://example.com/new-page' }]);
// Добавляем найденные ссылки на странице в очередь
await enqueueLinks();
},
});
// Запускаем краулер
await crawler.run();
import { RequestQueue } from 'crawlee';
// Открываем очередь запросов по умолчанию, связанную с запуском краулера
const requestQueue = await RequestQueue.open();
// Добавляем начальный набор запросов (может быть массивом из одного запроса)
await requestQueue.addRequests([
{ url: 'https://example.com/1' },
{ url: 'https://example.com/2' },
{ url: 'https://example.com/3' },
]);
// Открываем именованную очередь запросов
const namedRequestQueue = await RequestQueue.open('named-queue');
// Удаляем именованную очередь запросов
await namedRequestQueue.drop();
Более подробный пример использования очереди запросов с краулером можно найти в разделе Puppeteer Crawler.
Список запросов
Список запросов создается исключительно для запуска краулера и только если его использование явно указано в коде. Использование списка необязательно.
В Crawlee список запросов представлен классом RequestList
.
Вот пример базовых операций со списком запросов:
import { RequestList, PuppeteerCrawler } from 'crawlee';
// Подготовим массив источников с URL для посещения
const sources = [
{ url: 'http://www.example.com/page-1' },
{ url: 'http://www.example.com/page-2' },
{ url: 'http://www.example.com/page-3' },
];
// Открываем список запросов
// Имя списка используется для сохранения источников и состояния в key-value хранилище
const requestList = await RequestList.open('my-list', sources);
// Краулер автоматически обработает запросы из списка
// Работает аналогично для Cheerio/Playwright краулеров
const crawler = new PuppeteerCrawler({
requestList,
async requestHandler({ page, request }) {
// Обработка страницы (извлечение данных, создание скриншота и т.д.)
// На этом этапе новые запросы в список добавить уже нельзя
},
});
Что выбрать?
При использовании очереди запросов мы обычно начинаем с нескольких начальных URL (например, страниц категорий на сайте интернет-магазина), а затем программно добавляем новые URL (например, страницы отдельных товаров) - очередь поддерживает динамическое добавление и удаление запросов. В список запросов после его инициализации новые URL добавить нельзя, как и удалить существующие, поскольку он неизменяем.
С другой стороны, очередь запросов не оптимизирована для пакетного добавления или удаления большого количества URL. Технически это возможно, но запросы добавляются в очередь по одному, что занимает значительное время при большом количестве запросов. Список запросов может содержать даже миллионы URL, и их добавление в список займет значительно меньше времени по сравнению с очередью.
Обратите внимание, что очередь и список запросов могут использоваться одним краулером одновременно. В таких случаях каждый запрос из списка сначала помещается в очередь (на первую позицию, даже если очередь не пуста), а затем обрабатывается из нее. Это необходимо для предотвращения повторной обработки одного и того же URL (сначала из списка, а затем возможно из очереди). На практике такая комбинация может быть полезна, когда есть множество начальных URL, но краулер будет динамически добавлять новые URL.
В Crawlee практически нет необходимости комбинировать очередь запросов со списком запросов (хотя технически это возможно).
Раньше не было возможности добавлять начальные запросы в очередь пакетами (добавлять массив запросов), т.е. мы могли добавлять запросы только по одному с помощью функции addRequest()
.
Однако теперь мы можем использовать функцию addRequests()
, которая добавляет запросы пакетами. Таким образом, вместо комбинирования очереди и списка запросов, мы можем использовать только очередь запросов для таких сценариев. Смотрите примеры ниже.
- Очередь запросов
- Очередь + Список запросов
// Это рекомендуемый способ.
// Обратите внимание, что мы не используем список запросов вообще,
// и не используем очередь запросов явно здесь.
import { PuppeteerCrawler } from 'crawlee';
// Подготавливаем массив источников с URL для посещения (он может содержать миллионы URL)
const sources = [
{ url: 'http://www.example.com/page-1' },
{ url: 'http://www.example.com/page-2' },
{ url: 'http://www.example.com/page-3' },
// ...
];
// Краулер автоматически обрабатывает запросы из очереди.
// Он используется таким же образом для краулеров Cheerio/Playwright
const crawler = new PuppeteerCrawler({
async requestHandler({ crawler, enqueueLinks }) {
// Добавляем новый запрос в очередь
await crawler.addRequests(['http://www.example.com/new-page']);
// Добавляем найденные ссылки на странице в очередь
await enqueueLinks();
// Запросы выше будут добавлены в очередь
// и будут обработаны после того, как начальные запросы будут обработаны.
},
});
// Добавляем начальный массив источников в очередь
// и запускаем краулер
await crawler.run(sources);
// Это технически правильно, но
// мы должны явно открыть/использовать как очередь запросов, так и список запросов.
// Мы рекомендуем использовать очередь запросов и добавлять запросы пакетно.
import { RequestList, RequestQueue, PuppeteerCrawler } from 'crawlee';
// Подготавливаем массив источников с URL для посещения (он может содержать миллионы URL)
const sources = [
{ url: 'http://www.example.com/page-1' },
{ url: 'http://www.example.com/page-2' },
{ url: 'http://www.example.com/page-3' },
// ...
];
// Открываем список запросов с начальным массивом источников
const requestList = await RequestList.open('my-list', sources);
// Открываем очередь запросов по умолчанию. Нет необходимости добавлять какие-либо запросы в очередь
const requestQueue = await RequestQueue.open();
// Краулер автоматически обрабатывает запросы из списка и очереди.
// Он используется таким же образом для краулеров Cheerio/Playwright
const crawler = new PuppeteerCrawler({
requestList,
requestQueue,
// Каждый запрос из списка запросов добавляется в очередь запросов по одному.
// В этот момент запрос с таким URL существует и в списке, и в очереди
async requestHandler({ crawler, enqueueLinks }) {
// Добавляем новый запрос в очередь
await crawler.addRequests(['http://www.example.com/new-page']);
// Добавляем найденные ссылки на странице в очередь
await enqueueLinks();
// Запросы выше будут добавлены в очередь (но не в список)
// и будут обработаны после того, как список запросов станет пустым.
// Больше запросов не могут быть добавлены в список здесь
},
});
// Запускаем краулер
await crawler.run();
Очистка хранилищ
Хранилища по умолчанию очищаются перед запуском краулера, если не указано иное. Это происходит при первой попытке открыть хранилище (например, через RequestQueue.open()
) или при использовании вспомогательных методов для работы с хранилищем по умолчанию (например, crawler.addRequests()
). Если мы не работаем с хранилищами явно в нашем коде, очистка произойдет при выполнении метода run
краулера. Если нам нужно очистить хранилища раньше, мы можем явно использовать вспомогательную функцию purgeDefaultStorages()
:
import { purgeDefaultStorages } from 'crawlee';
await purgeDefaultStorages();
Вызов этой функции очистит директорию хранилища запросов по умолчанию (а также список запросов, хранящийся в key-value хранилище по умолчанию). Это сокращение для выполнения (опционального) метода purge
интерфейса StorageClient
. Другими словами, будет вызван метод purge
текущей реализации хранилища. Можно гарантировать, что очистка произойдет только один раз для данного контекста выполнения, если установить onlyPurgeOnce
в значение true
в объекте options
.