{"record":{"id":"8c26010bcbda78ed","repo":"jackwener/OpenCLI","slug":"aibase-daily-selector-drift-reason","errorCode":null,"errorMessage":"AIbase daily selector drift: ${reason}","messagePattern":"AIbase daily selector drift: (.+?)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/aibase/news.js","lineNumber":64,"sourceCode":"          seen.add(url);\n          rows.push({\n            rank: rows.length + 1,\n            title: anchor.innerText || anchor.textContent || '',\n            url,\n          });\n        }\n        return { ok: true, rows };\n      })()\n    `;\n}\n\nfunction toRows(payload, limit) {\n    if (!payload || typeof payload !== 'object') {\n        throw new CommandExecutionError('AIbase daily page returned an unreadable payload');\n    }\n    if (!payload.ok) {\n        const reason = typeof payload.reason === 'string' && payload.reason.trim() ? payload.reason.trim() : 'selector-drift';\n        throw new CommandExecutionError(\n            `AIbase daily selector drift: ${reason}`,\n            payload.title ? `Page title: ${payload.title}` : undefined,\n        );\n    }\n    const rows = (Array.isArray(payload.rows) ? payload.rows : [])\n        .map((row, index) => ({\n            rank: index + 1,\n            title: normalizeText(row.title),\n            url: normalizeText(row.url),\n        }))\n        .filter((row) => row.title && row.url);\n    if (rows.length === 0) {\n        throw new EmptyResultError('aibase news', 'AIbase daily page loaded, but no article rows with title and URL were extracted.');\n    }\n    return rows.slice(0, limit).map((row, index) => ({ ...row, rank: index + 1 }));\n}\n\nasync function loadAibaseNews(page, args) {","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/aibase/news.js#L46-L82","documentation":"toRows throws this CommandExecutionError when the extraction script reports payload.ok === false, meaning the page loaded but the expected DOM selectors matched nothing. The reason comes from the script ('selector-missing') or defaults to 'selector-drift', and the error detail may include the page title to help diagnose what actually rendered.","triggerScenarios":"The injected script finds zero anchors matching '.bg-white .grid a[href]' or 'a[href*=\"/zh/daily/\"]' and returns {ok:false, reason:'selector-missing', ...}; or returns ok:false with any other reason.","commonSituations":"AIbase redesigned its daily page (class names/structure changed); an anti-bot or consent interstitial replaced the article grid; the page served a regional variant or an empty daily edition with no articles; A/B tests altering markup.","solutions":["Check the Page title detail in the error to identify what page actually rendered (captcha, consent wall, redesign).","Retry later — if it's a temporary interstitial or empty daily edition it may resolve.","Update the CSS selectors in buildExtractAibaseNewsJs to match the site's current DOM (inspect the live page).","Clear/refresh the browser session (opencli aibase login) if a stale session or cookie wall is causing the alternate page."],"exampleFix":"// before\nconst anchors = Array.from(document.querySelectorAll('.bg-white .grid a[href], a[href*=\"/zh/daily/\"]'))\n// after (selectors updated to the site's new markup)\nconst anchors = Array.from(document.querySelectorAll('.daily-list a.article-link, a[href*=\"/zh/daily/\"]'))","handlingStrategy":"fallback","validationCode":"null","typeGuard":"function hasExpectedRows(payload) {\n  return !!payload && typeof payload === 'object' && payload.ok === true && Array.isArray(payload.rows) && payload.rows.length > 0;\n}","tryCatchPattern":"try {\n  await runCommand(['aibase', 'news']);\n} catch (e) {\n  if (e instanceof CommandExecutionError && e.message.includes('selector drift')) {\n    console.error('Site markup changed; check Page title detail and update selectors');\n  } else throw e;\n}","preventionTips":["Read the reason and Page title in the error detail to identify interstitials vs redesigns","Pin/re-test scraping commands after site redesigns","Keep extraction selectors resilient (multiple fallback selectors)","Retry later for temporary interstitials or empty daily editions"],"tags":["browser-scraping","selector-drift","cli"],"backgroundTag":"selector-drift","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}