{"record":{"id":"a2d3130191a7c50b","repo":"jackwener/OpenCLI","slug":"douyin-search-extraction-failed-error-instanceo","errorCode":null,"errorMessage":"Douyin search extraction failed: ${error instanceof Error ? error.message : String(error)}","messagePattern":"Douyin search extraction failed: (.+?)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/douyin/search.js","lineNumber":276,"sourceCode":"    domain: 'www.douyin.com',\n    strategy: Strategy.COOKIE,\n    args: [\n        { name: 'query', required: true, positional: true, help: '搜索关键词' },\n        { name: 'limit', type: 'int', default: 10, help: `结果数量 (1-${MAX_SEARCH_LIMIT})` },\n    ],\n    columns: ['rank', 'desc', 'author', 'url', 'plays', 'likes', 'comments', 'shares'],\n    func: async (page, kwargs) => {\n        const limit = parseSearchLimit(kwargs.limit);\n        const keyword = String(kwargs.query ?? '').trim();\n        if (!keyword) {\n            throw new ArgumentError('douyin search 需要 <query> 关键词');\n        }\n        await page.goto(`https://www.douyin.com/search/${encodeURIComponent(keyword)}?type=video`);\n        let result;\n        try {\n            result = unwrapEvaluateResult(await page.evaluate(WAIT_AND_EXTRACT_JS(RENDER_TIMEOUT_MS)));\n        } catch (error) {\n            throw new CommandExecutionError(`Douyin search extraction failed: ${error instanceof Error ? error.message : String(error)}`);\n        }\n        if (!result || typeof result !== 'object') {\n            throw new CommandExecutionError('Douyin search: unexpected evaluator payload shape');\n        }\n        if (result.state === 'login_wall') {\n            throw new AuthRequiredError(\n                'www.douyin.com',\n                'Douyin search results are blocked behind a login wall — log in at https://www.douyin.com in Chrome first.',\n            );\n        }\n        if (result.state === 'empty') {\n            throw new EmptyResultError('douyin search', `No Douyin videos matched \"${keyword}\".`);\n        }\n        if (result.state === 'timeout') {\n            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.');\n        }\n        if (!Array.isArray(result.cards)) {\n            throw new CommandExecutionError('Douyin search: evaluator returned malformed cards payload');","sourceCodeStart":258,"sourceCodeEnd":294,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/douyin/search.js#L258-L294","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Read the wrapped inner message to identify timeout vs selector vs auth cause","Retry with a stable/faster network or increase RENDER_TIMEOUT_MS if timeouts dominate","Log in the automation browser if results are behind an anonymous gate (login_wall)","Update WAIT_AND_EXTRACT_JS selectors after a Douyin page-structure change"],"exampleFix":"// before\nconst res = await douyin.search({ query: 'cats', limit: 10 }); // anonymous browser\n// after (pre-authenticated context)\nconst res = await douyin.search({ query: 'cats', limit: 10 }); // with logged-in session/retry\n// or wrap:\ntry { ... } catch (e) { if (/timeout/i.test(e.message)) await sleep(5000); /* retry once */ }","handlingStrategy":"try-catch","validationCode":"// Pre-flight: ensure session is usable and network is up before scraping\nconst ok = await fetch('https://www.douyin.com', { method: 'HEAD' }).then(r => r.ok).catch(() => false);\nif (!ok) throw new Error('douyin.com unreachable; fix network before search');","typeGuard":"function isSearchResult(v) {\n  return v !== null && typeof v === 'object' && Array.isArray(v.videos);\n}","tryCatchPattern":"try {\n  const res = await douyin.search({ query, limit });\n} catch (e) {\n  if (e instanceof CommandExecutionError && /extraction failed/.test(e.message)) {\n    if (/timeout/i.test(e.message)) { await sleep(5000); /* retry once */ }\n    else if (/login/i.test(e.message)) { /* refresh session */ }\n    else console.error('Selectors may be stale; update extraction script:', e.message);\n  } else throw e;\n}","preventionTips":["Use a logged-in browser profile to avoid anonymous gating/login walls","Retry timeouts once with backoff before failing","Pin/selectors-test against Douyin's search DOM; update after redesigns","Run from a network path with low latency to douyin.com"],"tags":["network","scraping","timeout","browser"],"backgroundTag":"page-extraction-failed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}