Answer Overflow
10/07/2026, 7:34 PMFukami
09/21/2026, 8:56 AMfierDeToiMonGrand
08/30/2026, 1:12 AMCheerioCrawler and need a way to conditionally drop/abort requests that are already in the RequestQueue when a runtime condition is met.
The goal is to completely discard the request so it:
- Does not make the network request.
- Does not execute the requestHandler.
- Does not count as a failed request or log an error.
## What I tried
### 1. Throwing a custom error in preNavigationHooks
ts
preNavigationHooks: [
async ({ request }) => {
if (shouldAbortRequest(request)) {
request.noRetry = true;
throw new CustomAbortError();
}
},
]
I also swallow the error in errorHandler and failedRequestHandler.
Problem: Crawlee still marks the request as FAILED, logs Request failed and reached maximum retries, and increments failed request metrics.
### 2. Using request.skipNavigation = true
ts
preNavigationHooks: [
async ({ request }) => {
if (shouldAbortRequest(request)) {
request.skipNavigation = true;
}
},
]
Problem: This skips the HTTP request, but requestHandler still executes unless I add manual guard logic.
## Question
Is there an official/idiomatic way to completely cancel an enqueued request so it is silently discarded without:
- Making the network request.
- Running the route/request handler.
- Being treated as a failed request.
Thanks!VFactoriel
08/13/2026, 7:46 AMAnswer Overflow
07/24/2026, 4:35 AMusama
06/21/2026, 9:28 AMclairvoyant
05/23/2026, 9:42 AMSirkFun
04/16/2026, 12:35 PMAnswer Overflow
02/27/2026, 6:50 AMcryptorex
02/24/2026, 6:29 PMmaxRequestsPerCrawl but this setting is to prevent infinite looping of certain sites. What am I missing?
Current setting is maxRequestsPerCrawl: 20000
Thanks.NeoNomade | Scraping hellhound
02/20/2026, 2:28 PMTsukimaJ
02/19/2026, 6:38 AMWitcher King
02/15/2026, 2:26 PMHamid Jutt
02/11/2026, 12:41 PMWei
02/01/2026, 1:15 PMMichael
01/28/2026, 1:23 PMconst configuration = {
purgeOnStart: false,
availableMemoryRatio: 0.75,
maxMemoryOverloadedRatio: 0.75,
maxEventLoopOverloadedRatio: 0.75,
maxCpuOverloadedRatio: 0.75,
maxClientOverloadedRatio: 0.7,
desiredConcurrencyRatio: 0.5,
persistStorage: true,
persistStateIntervalMillis: 10000,
}
const crawler = new PlaywrightCrawler(
{
keepAlive: true,
minConcurrency: 126,
maxConcurrency: 252,
maxRequestRetries: 5,
requestHandlerTimeoutSecs: 10,
proxyConfiguration,
useSessionPool: false, // for performance and less detection must be false
sessionPoolOptions: { maxPoolSize: 426 },
persistCookiesPerSession: false,nehalist
01/18/2026, 12:28 PMyaml
jobs:
- name: "amazon.de"
enabled: true
crawler:
id: "test"
enabled: true
config:
urls:
- "https://example.com"
- name: "amazon.fr"
enabled: true
crawler:
id: "test"
enabled: true
config:
urls:
- "https://example.com"
this config is processed via p-queue and, depending on the crawler id, I want to run a specific crawler, e.g.:
typescript
export const testCrawler = createCrawler({
id: "test",
configSchema: z.object({
urls: z.array(z.string()),
}),
handler: async ({ urls }) => {
if (!crawler) {
crawler = new PlaywrightCrawler({
async requestHandler({ request, log }) {
log.info(`Processing: ${request.url}`);
},
});
}
await crawler.run(urls);
},
});
different sites might use the crawler but a different requestHandler. currently when running this I get
> This crawler instance is already running, you can add more requests to it via crawler.addRequests()
so it's not possible to spawn multiple crawlers of the same type at the same time? would kinda mess up my mental model (and the current impl) a bit. if so, I guess I need to "collect" all data before running the actual crawler? since different crawler "definitions" (e.g. testCrawler) require different configurations, this could get messynehalist
01/12/2026, 9:38 PMPlaywrightCrawler while site Y uses CheerioCrawler. I wrap this logic in a definition like
class SiteXCrawlerDefinition {
getCrawler() { return new PlaywrightCrawler() }
}
since I do want to add an interface to the crawler definition classes I'd need a return type for getCrawler. BasicCrawler doesn't work - is there another type for "just any crawlee crawler"?fierDeToiMonGrand
01/09/2026, 8:33 PMcrawlee@3.12.1 with apify@^3.0.0 (3.2.6), I encounter two blocking issues
1. TypeScript compilation error - Type mismatch in proxy configuration:
typescript
const proxyConfiguration = await Actor.createProxyConfiguration();
const crawler = new CheerioCrawler({
proxyConfiguration: proxyConfiguration, // ❌ Type error
requestHandler: router,
});
2. Runtime error when running locally (even after ignoring TypeScript with as any):
Error: Detected incompatible Crawlee version used by the SDK.
User installed 3.12.1 but the SDK uses 3.15.3.
at checkCrawleeVersion (node_modules/apify/utils.js:68)
at Actor.init (node_modules/apify/main.js:123)
### Why I Need 3.12.1 Specifically
Cost Impact: For the same run with identical input, Crawlee 3.15.3 generates roughly double the request queue writes compared to 3.12.1:
(See attached screenshot)
I don't know what changed between these versions, but 3.15.3 is significantly more expensive to run.
### My Current Solution (Need Validation)
To force deduplication locally and replicate Apify's behavior, I added npm overrides to package.json:
json
{
"dependencies": {
"apify": "^3.0.0",
"crawlee": "3.12.1"
},
"overrides": {
"@crawlee/core": "3.12.1",
"@crawlee/types": "3.12.1",
"@crawlee/utils": "3.12.1"
}
}
### My Questions
1. Is using npm "overrides" the correct approach here? Will this cause issues when deploying to Apify platform?
2. Why does Crawlee 3.15.3 generate 2x more request queue writes than 3.12.1? What changed between these versions that causes this cost increase?
https://cdn.discordapp.com/attachments/1459284049213390930/1459284049670439085/image.png?ex=697bc42d&is=697a72ad&hm=cdbd36bd99fd062bbd4206205376088a70de3b9b6f17430b956ddad86820a2a1&
https://cdn.discordapp.com/attachments/1459284049213390930/1459284050295656488/image.png?ex=697bc42d&is=697a72ad&hm=89afe8139411796d65547e38c74c182e5726b16c91336a58baa1f3bc2b4e523d&fierDeToiMonGrand
01/07/2026, 2:53 PMsendRequest to skip the database write costs. However, the target site is strict with blocking.
My question is: If I use sendRequest inside a handler, is manually passing session and proxyInfo.url enough to maintain the exact same fingerprint/IP as the main crawler?
I want to avoid the "Details" request looking like a fresh bot hit. Here is the pattern I'm trying to implement:
typescript
// CURRENT EXPENSIVE APPROACH:
// Enqueues a whole new request, costing 1 Write + 1 Handled per item
/*
await crawler.addRequests([{
url: detailUrl,
label: 'EXTRA_DATA',
userData: { ... }
}]);
*/
// PROPOSED COST-SAVING APPROACH:
// Fetches inline to avoid touching the Request Queue DB
router.addHandler('ITEM_PAGE', async ({ request, session, proxyInfo, sendRequest }) => {
// 1. Scrape the main item data
const mainData = extractMainData();
const detailUrl = extractDetailUrl();
// 2. Fetch extra details inline without queueing
if (detailUrl) {
// My concern: Does this perfectly mimic the parent request?
const { body } = await sendRequest({
url: detailUrl,
session: session, // Passing current session
proxyUrl: proxyInfo?.url, // Passing current proxy IP
});
const extraData = extractExtraData(body);
}
await pushData(mainData);
});
Thanks!fierDeToiMonGrand
12/29/2025, 1:12 AMReclaiming failed request back to the list or queue. Expected `RequestOptions.url` to be of type `string` but received type `undefined`
I want something like that
import { v4 as uuidv4 } from 'uuid';
const itemRequests: Partial<Source>[] = dataResults.map((result) => {
const itemKey = result.item.key; // generic key
return {
uniqueKey: request.userData.saveOnlyUniqueItems ? itemKey : uuidv4(),
label: labels.SPECIFIC_ITEM, // generic label
userData: {
...request.userData,
itemData: result.item, // pass the data generically
},
};
});Answer Overflow
12/22/2025, 10:06 AMAnswer Overflow
12/16/2025, 9:44 PMvwkd
12/02/2025, 1:25 AMuserData to the category handler. However, since a URL is only scraped once, only the first category gets saved and all others discarded.
Here's a minimal example.
ts
import { Router } from "crawlee";
export const router = Router.create();
// ... other handlers
// imagine the category handler is called twice
router.addHandler("CATEGORY", async ({ enqueueLinks }) => {
// ... parsing
// result of first call
await enqueueLinks({
urls: ["https://example.com/my-product"],
userData: { category: "foo" },
label: "DETAIL",
});
// result of second call
await enqueueLinks({
urls: ["https://example.com/my-product"],
userData: { category: "bar" },
label: "DETAIL",
});
});
router.addHandler("DETAIL", async ({ request }) => {
// todo: how to get both "foo" and "bar" here?
const category = request.userData.category;
// ... saving
});Louis Deconinck
12/01/2025, 9:37 PMApifyBot
11/28/2025, 6:00 PMfourier
11/24/2025, 11:38 AMMichael
11/13/2025, 7:57 AM<html><head></head><body></body></html> ? even after adding await waitForSelector('#myID') how i suppose to deal with that ? thanksmemo23
11/11/2025, 11:43 PMAnswer Overflow
10/27/2025, 4:03 PM