apify/crawlee · error · Error

Selector '${selector}' not found.

Error message

Selector '${selector}' not found.

What it means

CheerioCrawler's `waitForSelector` helper is synchronous over the already-parsed Cheerio document; it cannot wait. If the selector matches zero elements at call time it throws 'Selector not found', unlike browser-based crawlers where it would poll until timeout.

Source

Thrown at packages/cheerio-crawler/src/internals/cheerio-crawler.ts:301

            enqueueLinks: async (options: EnqueueLinksOptions = {}) => {
                const baseUrl = resolveBaseUrlForEnqueueLinksFiltering({
                    enqueueStrategy: options.strategy,
                    finalRequestUrl: crawlingContext.request.loadedUrl,
                    originalRequestUrl: crawlingContext.request.url,
                    userProvidedBaseUrl: options.baseUrl,
                });

                const urls = await extractLinks(options);

                return addRequests(urls, {
                    ...options,
                    baseUrl,
                    strategy: options.strategy ?? EnqueueStrategy.SameHostname,
                });
            },
            waitForSelector: async (selector: string, _timeoutMs?: number) => {
                if (crawlingContext.$(selector).get().length === 0) {
                    throw new Error(`Selector '${selector}' not found.`);
                }
            },
            parseWithCheerio: async (selector?: string, timeoutMs?: number) => {
                if (selector) {
                    await crawlingContext.waitForSelector(selector, timeoutMs);
                }

                return crawlingContext.$;
            },
        };
    }
}

/**
 * Creates new {@apilink Router} instance that works based on request labels.
 * This instance can then serve as a `requestHandler` of your {@apilink CheerioCrawler}.
 * Defaults to the {@apilink CheerioCrawlingContext}.
 *

View on GitHub (pinned to dbe57fb09c)

Solutions

  1. Fix the selector or verify the element truly exists in the served HTML.
  2. Remove waitForSelector calls in CheerioCrawler — it does not wait; the document is already fully parsed.
  3. If content is client-rendered, switch to PlaywrightCrawler/PuppeteerCrawler.
  4. Check `$(selector).length` yourself before calling to avoid the throw.

Example fix

// before
await context.waitForSelector('div.results');
// after
if (context.$('div.results').length === 0) { log.warning('no results in static HTML'); } else { await context.parseWithCheerio('div.results'); }
Defensive patterns

Strategy: validation

Validate before calling

if (context.$('div.results').length === 0) { log.warning('selector missing in static HTML'); return; }

Type guard

function selectorExists(ctx: { $: CheerioAPI }, selector: string): boolean { return ctx.$(selector).length > 0; }

Try / catch

try { await context.waitForSelector(sel); } catch (err) { if (err.message.startsWith("Selector '")) { /* static HTML lacks element */ } else { throw err; } }

Prevention

When it happens

Trigger: Calling `waitForSelector(selector)` (directly or via `parseWithCheerio(selector)`) when the loaded page's Cheerio DOM contains no elements matching the selector.

Common situations: Porting code from PlaywrightCrawler/PuppeteerCrawler where waitForSelector waits for dynamically injected content; typos in selectors; content rendered client-side that Cheerio's static HTML never sees.

Related errors


AI-assisted analysis of apify/crawlee@dbe57fb09c (2026-08-30). Data as JSON: /api/errors/39a425900d666016. Report an issue: GitHub.