jackwener/OpenCLI · error · CommandExecutionError

Douyin search extraction failed: ${error instanceof Error ?

Error message

Douyin search extraction failed: ${error instanceof Error ? error.message : String(error)}

What it means

The search command injects WAIT_AND_EXTRACT_JS into the Douyin search page and unwraps the result. Any failure inside the page evaluator — timeout waiting for results, login wall detection throwing, script errors — is caught and rethrown as this CommandExecutionError with the underlying message, so the browser-side cause is preserved.

Source

Thrown at clis/douyin/search.js:276

    domain: 'www.douyin.com',
    strategy: Strategy.COOKIE,
    args: [
        { name: 'query', required: true, positional: true, help: '搜索关键词' },
        { name: 'limit', type: 'int', default: 10, help: `结果数量 (1-${MAX_SEARCH_LIMIT})` },
    ],
    columns: ['rank', 'desc', 'author', 'url', 'plays', 'likes', 'comments', 'shares'],
    func: async (page, kwargs) => {
        const limit = parseSearchLimit(kwargs.limit);
        const keyword = String(kwargs.query ?? '').trim();
        if (!keyword) {
            throw new ArgumentError('douyin search 需要 <query> 关键词');
        }
        await page.goto(`https://www.douyin.com/search/${encodeURIComponent(keyword)}?type=video`);
        let result;
        try {
            result = unwrapEvaluateResult(await page.evaluate(WAIT_AND_EXTRACT_JS(RENDER_TIMEOUT_MS)));
        } catch (error) {
            throw new CommandExecutionError(`Douyin search extraction failed: ${error instanceof Error ? error.message : String(error)}`);
        }
        if (!result || typeof result !== 'object') {
            throw new CommandExecutionError('Douyin search: unexpected evaluator payload shape');
        }
        if (result.state === 'login_wall') {
            throw new AuthRequiredError(
                'www.douyin.com',
                'Douyin search results are blocked behind a login wall — log in at https://www.douyin.com in Chrome first.',
            );
        }
        if (result.state === 'empty') {
            throw new EmptyResultError('douyin search', `No Douyin videos matched "${keyword}".`);
        }
        if (result.state === 'timeout') {
            throw new CommandExecutionError('Douyin search did not render result cards within the timeout. Open the same search in Chrome and verify login/security state before retrying.');
        }
        if (!Array.isArray(result.cards)) {
            throw new CommandExecutionError('Douyin search: evaluator returned malformed cards payload');

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Read the wrapped inner message to identify timeout vs selector vs auth cause
  2. Retry with a stable/faster network or increase RENDER_TIMEOUT_MS if timeouts dominate
  3. Log in the automation browser if results are behind an anonymous gate (login_wall)
  4. Update WAIT_AND_EXTRACT_JS selectors after a Douyin page-structure change

Example fix

// before
const res = await douyin.search({ query: 'cats', limit: 10 }); // anonymous browser
// after (pre-authenticated context)
const res = await douyin.search({ query: 'cats', limit: 10 }); // with logged-in session/retry
// or wrap:
try { ... } catch (e) { if (/timeout/i.test(e.message)) await sleep(5000); /* retry once */ }
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-flight: ensure session is usable and network is up before scraping
const ok = await fetch('https://www.douyin.com', { method: 'HEAD' }).then(r => r.ok).catch(() => false);
if (!ok) throw new Error('douyin.com unreachable; fix network before search');

Type guard

function isSearchResult(v) {
  return v !== null && typeof v === 'object' && Array.isArray(v.videos);
}

Try / catch

try {
  const res = await douyin.search({ query, limit });
} catch (e) {
  if (e instanceof CommandExecutionError && /extraction failed/.test(e.message)) {
    if (/timeout/i.test(e.message)) { await sleep(5000); /* retry once */ }
    else if (/login/i.test(e.message)) { /* refresh session */ }
    else console.error('Selectors may be stale; update extraction script:', e.message);
  } else throw e;
}

Prevention

When it happens

Trigger: page.evaluate of WAIT_AND_EXTRACT_JS throws or the returned payload fails unwrapEvaluateResult — render timeout after RENDER_TIMEOUT_MS (15s), DOM selectors changed on Douyin's search page, in-page JS exception, or navigation interrupted.

Common situations: Douyin front-end redesign breaking extraction selectors; slow network or heavy page causing the 15s render timeout; anonymous gating/login wall; rate limiting serving an interstitial page without expected nodes.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/a2d3130191a7c50b. Report an issue: GitHub.