{"record":{"id":"d8063247cb54b6f3","repo":"jackwener/OpenCLI","slug":"page-articlepage","errorCode":null,"errorMessage":"未抓取到创作者后台文章 (page=${articlePage})。可能页面尚未完成渲染或无文章。","messagePattern":"未抓取到创作者后台文章 \\(page=(.+?)\\)。可能页面尚未完成渲染或无文章。","errorType":"exception","errorClass":"EmptyResultError","httpStatus":null,"severity":"error","filePath":"clis/toutiao/articles.js","lineNumber":42,"sourceCode":"        try {\n            await page.goto(`https://mp.toutiao.com/profile_v4/manage/content/all?page=${articlePage}`);\n            await page.wait('networkidle');\n            await page.wait(3);\n            text = await page.evaluate(`\n(async () => {\n    await new Promise(r => setTimeout(r, 2000));\n    return document.body.innerText || '';\n})()\n`);\n        } catch (error) {\n            throw new CommandExecutionError(`toutiao articles render failed: ${error?.message || error}`);\n        }\n        if (looksToutiaoAuthWallText(text)) {\n            throw new AuthRequiredError('mp.toutiao.com', 'Toutiao creator articles require a logged-in mp.toutiao.com browser session');\n        }\n        const rows = parseToutiaoArticlesText(text);\n        if (rows.length === 0) {\n            throw new EmptyResultError(\n                'toutiao articles',\n                `未抓取到创作者后台文章 (page=${articlePage})。可能页面尚未完成渲染或无文章。`,\n            );\n        }\n        return rows;\n    },\n});\n\nexport { parseToutiaoArticlesText };\nexport const __test__ = {\n    parseToutiaoArticlesText,\n    parseArticlesPage,\n};\n","sourceCodeStart":24,"sourceCodeEnd":56,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/toutiao/articles.js#L24-L56","documentation":"EmptyResultError thrown when the Toutiao creator dashboard content-management page loaded and was NOT detected as an auth wall, but parseToutiaoArticlesText() extracted zero article rows from the rendered page text. The library raises instead of returning [] so callers can distinguish 'no articles' from 'scrape failed'.","triggerScenarios":"Calling the `toutiao articles` CLI command (page 1-4) when the document body text of https://mp.toutiao.com/profile_v4/manage/content/all?page=N contains no rows the parser recognizes: the table has not finished rendering after networkidle + page.wait(3) + the in-page 2000ms delay, the requested page exceeds available content, the account has zero articles, or a Toutiao layout change broke parseToutiaoArticlesText.","commonSituations":"Slow network leaving the article table unrendered when the fixed waits elapse; querying page 2-4 on an account with fewer than a full page of articles; brand-new creator account with nothing published; site redesign changing table markup/text so the parser matches nothing.","solutions":["Re-run the command — slow rendering is the most common cause; retry after a few seconds or on a faster connection.","Try page=1 first; if page=1 also fails the account likely has no articles or the session is stale (run toutiao auth verify).","Log in at https://mp.toutiao.com/profile_v4/manage/content/all in the same browser profile and confirm the article table renders with data.","If the table renders for a human but parsing still fails, update parseToutiaoArticlesText in clis/toutiao/utils.js to match the changed layout.","Increase the wait budget (page.wait(3) and the in-page 2000ms delay) in clis/toutiao/articles.js on slow networks."],"exampleFix":"// before\nconst rows = parseToutiaoArticlesText(text);\nif (rows.length === 0) {\n  throw new EmptyResultError('toutiao articles', `未抓取到创作者后台文章 (page=${articlePage})...`);\n}\n// after: one retry in case rendering was slow\nlet rows = parseToutiaoArticlesText(text);\nif (rows.length === 0) {\n  await page.reload();\n  await page.wait(5);\n  rows = parseToutiaoArticlesText(await page.evaluate('document.body.innerText'));\n}\nif (rows.length === 0) {\n  throw new EmptyResultError('toutiao articles', `未抓取到创作者后台文章 (page=${articlePage})...`);\n}","handlingStrategy":"retry","validationCode":"// Preflight: confirm the session is live so a soft auth wall doesn't masquerade as empty\nconst cookies = await page.getCookies({ url: 'https://mp.toutiao.com' });\nif (!cookies.some(c => c.name === 'sessionid' && c.value)) {\n  throw new Error('Login to mp.toutiao.com before fetching articles');\n}\nconst requestedPage = Number(kwargs.page ?? 1);\nif (!Number.isInteger(requestedPage) || requestedPage < 1 || requestedPage > 4) {\n  throw new Error('page must be an integer 1-4');\n}","typeGuard":"function hasToutiaoArticleRows(value) {\n  return Array.isArray(value) && value.length > 0 &&\n    value.every(r => r && typeof r.title === 'string' && r.title.length > 0);\n}","tryCatchPattern":"try {\n  const rows = await toutiaoArticles({ page: 1 });\n} catch (err) {\n  if (err.name === 'EmptyResultError') {\n    const retry = await toutiaoArticles({ page: 1 }).catch(() => []); // one retry for slow render\n    console.warn('No toutiao articles (or page failed to render):', retry.length);\n  } else if (err.name === 'AuthRequiredError') {\n    console.error('Session expired — re-run login flow');\n  } else {\n    throw err;\n  }\n}","preventionTips":["Always start with page=1 before querying deeper pages (2-4) that may not exist.","Run toutiao auth verify before scraping to rule out soft auth walls that yield zero rows.","Retry once automatically before accepting an empty result — slow rendering is the most common cause.","Monitor Toutiao's layout; if parseToutiaoArticlesText starts matching nothing after a redesign, update it promptly.","Keep the page.wait budgets generous on slow networks (the fixed waits in articles.js)."],"tags":["scraping","empty-result","browser-automation","toutiao"],"backgroundTag":"empty-result-set","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}