Skip to content

Commit 0c1fbcf

Browse files
authored
refactor!: Finalize transition to IRequestManager and IRequestLoader (#3719)
closes #2797
1 parent 79ceb4e commit 0c1fbcf

47 files changed

Lines changed: 1220 additions & 1059 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/guides/request_loaders.mdx

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
---
2+
id: request-loaders
3+
title: Request loaders
4+
description: How to manage the requests your crawler will go through.
5+
---
6+
7+
import ApiLink from '@site/src/components/ApiLink';
8+
9+
import Tabs from '@theme/Tabs';
10+
import TabItem from '@theme/TabItem';
11+
import CodeBlock from '@theme/CodeBlock';
12+
13+
import RlBasicSource from '!!raw-loader!./request_loaders_rl_basic.ts';
14+
import SitemapBasicSource from '!!raw-loader!./request_loaders_sitemap_basic.ts';
15+
import RlTandemExplicitSource from '!!raw-loader!./request_loaders_rl_tandem_explicit.ts';
16+
import RlTandemHelperSource from '!!raw-loader!./request_loaders_rl_tandem_helper.ts';
17+
import SitemapTandemExplicitSource from '!!raw-loader!./request_loaders_sitemap_tandem_explicit.ts';
18+
import SitemapTandemHelperSource from '!!raw-loader!./request_loaders_sitemap_tandem_helper.ts';
19+
20+
Request loaders extend the functionality of the <ApiLink to="core/class/RequestQueue">`RequestQueue`</ApiLink>, providing additional tools for managing URLs and requests. If you are new to Crawlee and unfamiliar with the <ApiLink to="core/class/RequestQueue">`RequestQueue`</ApiLink>, consider starting with the [Request storage](./request-storage) guide first. Request loaders define how requests are fetched and stored, enabling various use cases such as reading URLs from a static list, a sitemap, an external API, or combining multiple sources together.
21+
22+
## Overview
23+
24+
The request loader abstractions are built around two interfaces and a couple of helpers:
25+
26+
- <ApiLink to="core/interface/IRequestLoader">`IRequestLoader`</ApiLink>: The base interface for reading requests in a crawl.
27+
- <ApiLink to="core/interface/IRequestManager">`IRequestManager`</ApiLink>: Extends `IRequestLoader` with write capabilities (adding and reclaiming requests).
28+
- <ApiLink to="core/class/RequestManagerTandem">`RequestManagerTandem`</ApiLink>: Combines a read-only `IRequestLoader` with a writable `IRequestManager`.
29+
30+
And the concrete request loader implementations:
31+
32+
- <ApiLink to="core/class/RequestList">`RequestList`</ApiLink>: A lightweight implementation for managing a static list of URLs.
33+
- <ApiLink to="core/class/SitemapRequestLoader">`SitemapRequestLoader`</ApiLink>: A specialized loader that reads URLs from XML and plain-text sitemaps following the [Sitemaps protocol](https://www.sitemaps.org/protocol.html), with filtering capabilities.
34+
35+
Below is a class diagram that illustrates the relationships between these components and the <ApiLink to="core/class/RequestQueue">`RequestQueue`</ApiLink>:
36+
37+
```mermaid
38+
---
39+
config:
40+
class:
41+
hideEmptyMembersBox: true
42+
---
43+
44+
classDiagram
45+
46+
%% ========================
47+
%% Abstract interfaces
48+
%% ========================
49+
50+
class IRequestLoader {
51+
<<interface>>
52+
+ getTotalCount()
53+
+ getPendingCount()
54+
+ getHandledCount()
55+
+ fetchNextRequest()
56+
+ markRequestHandled()
57+
+ isEmpty()
58+
+ isFinished()
59+
+ toTandem()
60+
}
61+
62+
class IRequestManager {
63+
<<interface>>
64+
+ addRequest()
65+
+ addRequestsBatched()
66+
+ reclaimRequest()
67+
+ purge()
68+
}
69+
70+
%% ========================
71+
%% Concrete classes
72+
%% ========================
73+
74+
class RequestQueue
75+
76+
class RequestList
77+
78+
class SitemapRequestLoader
79+
80+
class RequestManagerTandem
81+
82+
%% ========================
83+
%% Inheritance arrows
84+
%% ========================
85+
86+
IRequestLoader <|-- IRequestManager
87+
IRequestLoader <|.. RequestList
88+
IRequestLoader <|.. SitemapRequestLoader
89+
IRequestManager <|.. RequestQueue
90+
IRequestManager <|.. RequestManagerTandem
91+
```
92+
93+
:::info Crawler usage
94+
95+
A crawler reads its requests from a single <ApiLink to="core/interface/IRequestManager">`IRequestManager`</ApiLink>, passed via the `requestManager` option. A <ApiLink to="core/class/RequestQueue">`RequestQueue`</ApiLink> is itself a request manager, so it can be passed directly. A read-only loader (such as <ApiLink to="core/class/RequestList">`RequestList`</ApiLink>) cannot — combine it with a queue into a tandem first, see the [Request manager tandem](#request-manager-tandem) section below.
96+
97+
:::
98+
99+
## Request loaders
100+
101+
The <ApiLink to="core/interface/IRequestLoader">`IRequestLoader`</ApiLink> interface defines the foundation for fetching requests during a crawl. It provides methods for basic operations like retrieving the next request, marking requests as handled, and checking whether the loader is empty or finished. It is intentionally **read-only** — it does not allow adding new requests. Concrete implementations such as <ApiLink to="core/class/RequestList">`RequestList`</ApiLink> build on this interface to handle specific scenarios. You can create your own custom loader that reads from an external file, web endpoint, database, or any other data source.
102+
103+
### Request list
104+
105+
The <ApiLink to="core/class/RequestList">`RequestList`</ApiLink> manages a static list of URLs to crawl. The list is created for a single crawler run and, unlike a queue, cannot have requests added to or removed from it after initialization. It can hold a large number of URLs (even millions) with significantly lower overhead than enqueueing them one by one.
106+
107+
Here is a basic example of working with the <ApiLink to="core/class/RequestList">`RequestList`</ApiLink>:
108+
109+
<CodeBlock language="typescript">
110+
{RlBasicSource}
111+
</CodeBlock>
112+
113+
### Sitemap request loader
114+
115+
The <ApiLink to="core/class/SitemapRequestLoader">`SitemapRequestLoader`</ApiLink> is a specialized request loader that reads URLs from sitemaps following the [Sitemaps protocol](https://www.sitemaps.org/protocol.html). It supports both XML and plain-text sitemap formats and is particularly useful when you want to crawl a website systematically by following its sitemap structure. Loading happens in the background, so crawling can start before the sitemap is fully parsed.
116+
117+
:::note
118+
119+
The `SitemapRequestLoader` is designed specifically for sitemaps that follow the standard Sitemaps protocol. HTML pages containing links are not supported by this loader — those should be handled by regular crawlers using the `enqueueLinks` functionality.
120+
121+
:::
122+
123+
The loader supports filtering URLs using glob patterns and regular expressions, allowing you to include or exclude specific types of URLs.
124+
125+
<CodeBlock language="typescript">
126+
{SitemapBasicSource}
127+
</CodeBlock>
128+
129+
## Request managers
130+
131+
The <ApiLink to="core/interface/IRequestManager">`IRequestManager`</ApiLink> interface extends `IRequestLoader` with **write** capabilities. In addition to reading requests, a request manager can add new requests and reclaim failed ones. This is essential for dynamic crawling, where new URLs emerge during the crawl, or when requests fail and need to be retried. The <ApiLink to="core/class/RequestQueue">`RequestQueue`</ApiLink> is the primary built-in request manager — see the [Request storage](./request-storage) guide for details.
132+
133+
## Request manager tandem
134+
135+
The <ApiLink to="core/class/RequestManagerTandem">`RequestManagerTandem`</ApiLink> class combines the read-only capabilities of an `IRequestLoader` (like <ApiLink to="core/class/RequestList">`RequestList`</ApiLink>) with the read-write capabilities of an `IRequestManager` (like <ApiLink to="core/class/RequestQueue">`RequestQueue`</ApiLink>). This is useful when you need to load initial requests from a static source (such as a file, sitemap, or database) and also dynamically add or retry requests during the crawl.
136+
137+
Under the hood, the tandem checks whether the read-only loader still has pending requests. If so, each request from the loader is transferred to the manager (the queue) before being processed. Any newly added or reclaimed requests go directly to the manager side. Because every request passes through the queue, deduplication and retries are handled consistently and a single URL is not crawled multiple times.
138+
139+
The easiest way to build a tandem is the <ApiLink to="core/interface/IRequestLoader#toTandem">`toTandem()`</ApiLink> helper available on the loaders. Called without arguments, it pairs the loader with the default <ApiLink to="core/class/RequestQueue">`RequestQueue`</ApiLink>; you can also pass a specific request manager to use instead.
140+
141+
### Request list with request queue
142+
143+
This setup is useful when you have a static list of URLs to crawl, but also need to handle dynamic requests discovered during the crawl. Requests from the <ApiLink to="core/class/RequestList">`RequestList`</ApiLink> are processed first by being enqueued into the <ApiLink to="core/class/RequestQueue">`RequestQueue`</ApiLink>, which handles persistence and retries.
144+
145+
<Tabs groupId="request_manager_tandem">
146+
<TabItem value="request_manager_tandem_helper" label="Using toTandem helper" default>
147+
<CodeBlock language="typescript">
148+
{RlTandemHelperSource}
149+
</CodeBlock>
150+
</TabItem>
151+
<TabItem value="request_manager_tandem_explicit" label="Explicit usage">
152+
<CodeBlock language="typescript">
153+
{RlTandemExplicitSource}
154+
</CodeBlock>
155+
</TabItem>
156+
</Tabs>
157+
158+
### Sitemap request loader with request queue
159+
160+
Similarly, you can combine a <ApiLink to="core/class/SitemapRequestLoader">`SitemapRequestLoader`</ApiLink> with a <ApiLink to="core/class/RequestQueue">`RequestQueue`</ApiLink>. This is particularly useful when you want to crawl URLs from a sitemap while also handling dynamic requests discovered during the crawl. URLs from the sitemap are processed first by being enqueued into the queue, which handles persistence and retries.
161+
162+
<Tabs groupId="sitemap_request_manager_tandem">
163+
<TabItem value="sitemap_request_manager_tandem_helper" label="Using toTandem helper" default>
164+
<CodeBlock language="typescript">
165+
{SitemapTandemHelperSource}
166+
</CodeBlock>
167+
</TabItem>
168+
<TabItem value="sitemap_request_manager_tandem_explicit" label="Explicit usage">
169+
<CodeBlock language="typescript">
170+
{SitemapTandemExplicitSource}
171+
</CodeBlock>
172+
</TabItem>
173+
</Tabs>
174+
175+
## Conclusion
176+
177+
This guide introduced the request loader abstractions: the read-only <ApiLink to="core/interface/IRequestLoader">`IRequestLoader`</ApiLink>, the writable <ApiLink to="core/interface/IRequestManager">`IRequestManager`</ApiLink>, and the <ApiLink to="core/class/RequestManagerTandem">`RequestManagerTandem`</ApiLink> that combines them, along with the <ApiLink to="core/class/RequestList">`RequestList`</ApiLink> and <ApiLink to="core/class/SitemapRequestLoader">`SitemapRequestLoader`</ApiLink> implementations. You also saw how to pair a loader with a queue using the `toTandem()` helper to handle both static and dynamically discovered requests.
178+
179+
If you have questions or need assistance, feel free to reach out on our [GitHub](https://github.com/apify/crawlee) or join our [Discord community](https://discord.com/invite/jyEM2PRvMU). Happy scraping!
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { RequestList } from 'crawlee';
2+
3+
// Open a request list with a static set of URLs.
4+
// The name is used to persist the list's state in the default key-value store.
5+
const requestList = await RequestList.open('my-list', [
6+
'https://crawlee.dev/',
7+
'https://crawlee.dev/docs',
8+
'https://crawlee.dev/api',
9+
]);
10+
11+
// Iterate over the requests manually (a crawler does this for you under the hood).
12+
for await (const request of requestList) {
13+
console.log(request.url);
14+
await requestList.markRequestHandled(request);
15+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { CheerioCrawler, RequestList, RequestManagerTandem, RequestQueue } from 'crawlee';
2+
3+
// A static list of URLs to start from (can hold millions of URLs).
4+
const requestList = await RequestList.open('my-list', ['https://crawlee.dev/', 'https://crawlee.dev/docs']);
5+
6+
// A writable queue that holds requests discovered during the crawl.
7+
const requestQueue = await RequestQueue.open();
8+
9+
// Combine them: the tandem reads from the list first, transferring each request
10+
// into the queue, and lets you enqueue new requests during the crawl.
11+
const requestManager = new RequestManagerTandem(requestList, requestQueue);
12+
13+
const crawler = new CheerioCrawler({
14+
requestManager,
15+
async requestHandler({ enqueueLinks }) {
16+
// Newly discovered links go to the queue side of the tandem.
17+
await enqueueLinks();
18+
},
19+
});
20+
21+
await crawler.run();
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { CheerioCrawler, RequestList } from 'crawlee';
2+
3+
// A static list of URLs to start from.
4+
const requestList = await RequestList.open('my-list', ['https://crawlee.dev/', 'https://crawlee.dev/docs']);
5+
6+
// `toTandem()` is a shortcut that pairs the loader with a request queue.
7+
// Without arguments it opens the default `RequestQueue`.
8+
const requestManager = await requestList.toTandem();
9+
10+
const crawler = new CheerioCrawler({
11+
requestManager,
12+
async requestHandler({ enqueueLinks }) {
13+
await enqueueLinks();
14+
},
15+
});
16+
17+
await crawler.run();
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { SitemapRequestLoader } from 'crawlee';
2+
3+
// Open a sitemap request list. The sitemap is fetched and parsed in the background,
4+
// so crawling can start before the whole sitemap is loaded.
5+
const sitemapRequestLoader = await SitemapRequestLoader.open({
6+
sitemapUrls: ['https://crawlee.dev/sitemap.xml'],
7+
// Optionally filter the URLs read from the sitemap:
8+
// globs: ['https://crawlee.dev/docs/**'],
9+
});
10+
11+
for await (const request of sitemapRequestLoader) {
12+
console.log(request.url);
13+
await sitemapRequestLoader.markRequestHandled(request);
14+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { CheerioCrawler, RequestManagerTandem, RequestQueue, SitemapRequestLoader } from 'crawlee';
2+
3+
// Read the initial URLs from a sitemap.
4+
const sitemapRequestLoader = await SitemapRequestLoader.open({
5+
sitemapUrls: ['https://crawlee.dev/sitemap.xml'],
6+
});
7+
8+
// A writable queue for requests discovered during the crawl.
9+
const requestQueue = await RequestQueue.open();
10+
11+
const requestManager = new RequestManagerTandem(sitemapRequestLoader, requestQueue);
12+
13+
const crawler = new CheerioCrawler({
14+
requestManager,
15+
async requestHandler({ enqueueLinks }) {
16+
await enqueueLinks();
17+
},
18+
});
19+
20+
await crawler.run();
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { CheerioCrawler, SitemapRequestLoader } from 'crawlee';
2+
3+
// Read the initial URLs from a sitemap.
4+
const sitemapRequestLoader = await SitemapRequestLoader.open({
5+
sitemapUrls: ['https://crawlee.dev/sitemap.xml'],
6+
});
7+
8+
// Pair the loader with the default `RequestQueue` via the `toTandem()` shortcut.
9+
const requestManager = await sitemapRequestLoader.toTandem();
10+
11+
const crawler = new CheerioCrawler({
12+
requestManager,
13+
async requestHandler({ enqueueLinks }) {
14+
await enqueueLinks();
15+
},
16+
});
17+
18+
await crawler.run();

0 commit comments

Comments
 (0)