{"record":{"id":"a5a2e2d0c674c939","repo":"jackwener/OpenCLI","slug":"douyin-search-parser-found-result-cards-without-st","errorCode":null,"errorMessage":"Douyin search parser found result cards without stable video url or description","messagePattern":"Douyin search parser found result cards without stable video url or description","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/douyin/search.js","lineNumber":301,"sourceCode":"                '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');\n        }\n        if (result.cards.length === 0) {\n            throw new EmptyResultError('douyin search', `No Douyin videos matched \"${keyword}\".`);\n        }\n        const projected = projectSearchCards(result.cards, limit);\n        if (projected.invalidCount > 0) {\n            throw new CommandExecutionError('Douyin search parser found result cards without stable video url or description');\n        }\n        if (projected.rows.length === 0) {\n            throw new EmptyResultError('douyin search', `No Douyin videos matched \"${keyword}\".`);\n        }\n        return projected.rows;\n    },\n});\n","sourceCodeStart":283,"sourceCodeEnd":309,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/douyin/search.js#L283-L309","documentation":"CommandExecutionError thrown when projectSearchCards reports invalidCount > 0: Douyin rendered result cards, but at least one lacked a stable video URL or description, so the adapter refuses to emit partially-garbage rows. This protects downstream consumers of the tiktok-compatible row schema.","triggerScenarios":"Douyin markup change breaks the a[href*='/video/'] hook or the leaf-text shape heuristics; promoted/ad cards in the list have different structure; mixed content (live/hot cards) interleaved with video cards.","commonSituations":"After a Douyin front-end deploy; when search results include ads or recommended non-video cards; locale variations altering text shapes (万/亿 suffixes).","solutions":["Inspect the failing card HTML in the bound browser and update projectSearchCards' extraction hooks.","Retry — if only ad cards appeared this run, a rerun may return all-valid cards.","Relax the all-or-nothing policy locally by filtering invalid rows instead of throwing, if partial results are acceptable."],"exampleFix":"// before\nif (projected.invalidCount > 0) {\n    throw new CommandExecutionError('Douyin search parser found result cards without stable video url or description');\n}\n// after (lenient variant)\nif (projected.rows.length === 0) {\n    throw new CommandExecutionError('Douyin search parser found no valid result cards');\n}","handlingStrategy":"validation","validationCode":"function cardLooksValid(card) {\n  return typeof card?.url === 'string' && /\\/video\\/\\d+/.test(card.url) && typeof card?.desc === 'string' && card.desc.length > 0;\n}","typeGuard":"function isValidSearchRow(row) {\n  return typeof row?.url === 'string' && row.url.startsWith('https://www.douyin.com/video/')\n    && typeof row?.desc === 'string';\n}","tryCatchPattern":"try {\n  rows = await douyinSearch(keyword);\n} catch (e) {\n  if (/without stable video url or description/.test(e.message)) {\n    console.error('Douyin markup drift or ad cards; inspect page HTML and update extractors');\n  }\n  throw e;\n}","preventionTips":["Re-verify extraction hooks after known Douyin front-end deploys.","Sample rendered card HTML periodically in a smoke test.","Prefer stable hooks (data-e2e, /video/ links) over obfuscated classnames."],"tags":["parsing","dom-extraction","douyin","selectors"],"backgroundTag":"dom-extraction-schema-drift","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}