{"record":{"id":"fc57d2bd9891f063","repo":"jackwener/OpenCLI","slug":"tieba-may-have-blocked-the-result-page-or-the-dom","errorCode":null,"errorMessage":"Tieba may have blocked the result page, or the DOM structure may have changed","messagePattern":"Tieba may have blocked the result page, or the DOM structure may have changed","errorType":"exception","errorClass":"EmptyResultError","httpStatus":null,"severity":"error","filePath":"clis/tieba/search.js","lineNumber":105,"sourceCode":"    strategy: Strategy.COOKIE,\n    browser: true,\n    navigateBefore: false,\n    args: [\n        { name: 'keyword', positional: true, required: true, type: 'string', help: 'Search keyword' },\n        // Restrict unsupported pages before the browser session starts.\n        { name: 'page', type: 'int', default: 1, choices: ['1'], help: 'Page number (currently only 1 is supported)' },\n        { name: 'limit', type: 'int', default: 20, help: 'Number of items to return' },\n    ],\n    columns: ['rank', 'id', 'title', 'forum', 'author', 'time', 'url'],\n    func: async (page, kwargs) => {\n        assertSupportedPage(kwargs);\n        const limit = normalizeTiebaLimit(kwargs.limit);\n        // Use the default browser settle path so we do not read a stale page.\n        await page.goto(getSearchUrl(kwargs));\n        const raw = await page.evaluate(buildExtractSearchResultsEvaluate(limit));\n        const items = buildTiebaSearchItems(Array.isArray(raw) ? raw : [], limit);\n        if (!items.length) {\n            throw new EmptyResultError('tieba search', 'Tieba may have blocked the result page, or the DOM structure may have changed');\n        }\n        return items;\n    },\n});\n","sourceCodeStart":87,"sourceCodeEnd":110,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/tieba/search.js#L87-L110","documentation":"`tieba search` navigates to the search URL, runs the extraction script, and builds result items. If zero items are extracted from the result page, it throws this EmptyResultError instead of returning an empty list, because a normal Tieba search results page always renders at least one result for a valid query. The throw distinguishes 'we got blocked or the DOM changed' from a legitimate empty search.","triggerScenarios":"Calling `tieba search --query <q>` when the extraction finds no result nodes: an anti-bot/captcha wall replaced the results under the same URL, the query returns results only after login, or Tieba changed the search-results markup so selectors no longer match.","commonSituations":"Baidu rate-limiting a burst of searches from one IP; headless-browser detection; searching niche/rare terms while logged out; Tieba frontend redesign; stale cookies triggering a verification page.","solutions":["Reduce search frequency / add delays between calls and retry — transient rate-limit walls are the most common cause.","Use a logged-in, cookie-persisted browser profile; anonymous searches are blocked more aggressively.","Verify the query manually at the Tieba search URL in the same browser session and complete any captcha shown.","If the page renders results manually but extraction still fails, update the library — the search-result selectors are outdated."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  return await cli.search({ query });\n} catch (e) {\n  if (e instanceof EmptyResultError) {\n    await sleep(randomInt(2000, 6000)); // anti-bot backoff\n    return cli.search({ query });\n  }\n  throw e;\n}","preventionTips":["Throttle searches and add jittered delays between calls.","Run with a logged-in, cookie-persisted browser profile.","Avoid bursts from datacenter IPs; prefer residential egress.","After Tieba UI updates, smoke-test one search to confirm selectors still extract results."],"tags":["tieba","search","empty-result","anti-bot"],"backgroundTag":"scraping-blocked-or-dom-changed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}