{"record":{"id":"66f6bfc281f30395","repo":"jackwener/OpenCLI","slug":"no-catalog-rows-extracted-the-url-may-not-be-a-c","errorCode":null,"errorMessage":"No catalog rows extracted — the URL may not be a course page or the login session has expired","messagePattern":"No catalog rows extracted — the URL may not be a course page or the login session has expired","errorType":"exception","errorClass":"EmptyResultError","httpStatus":null,"severity":"warning","filePath":"clis/xiaoe/catalog.js","lineNumber":202,"sourceCode":"  return result;\n})()`;\n}\n\nasync function getXiaoeCatalog(page, args) {\n    const url = requireXiaoePageUrl(args.url, 'catalog');\n    let rows;\n    try {\n        await page.goto(url, { waitUntil: 'load', settleMs: 8000 });\n        rows = await page.evaluate(buildCatalogScript());\n    } catch (error) {\n        const message = error instanceof Error ? error.message : String(error);\n        throw new CommandExecutionError(\n            `Failed to read xiaoe catalog: ${message}`,\n            'page may not have rendered or auth may be required',\n        );\n    }\n    if (!Array.isArray(rows) || rows.length === 0) {\n        throw new EmptyResultError(\n            'xiaoe/catalog',\n            'No catalog rows extracted — the URL may not be a course page or the login session has expired',\n        );\n    }\n    return rows;\n}\n\nexport const catalogCommand = cli({\n    site: 'xiaoe',\n    name: 'catalog',\n    access: 'read',\n    description: '小鹅通课程目录（支持普通课程、专栏、大专栏）',\n    domain: 'h5.xet.citv.cn',\n    strategy: Strategy.COOKIE,\n    browser: true,\n    args: [\n        { name: 'url', required: true, positional: true, help: '课程页面 URL' },\n    ],","sourceCodeStart":184,"sourceCodeEnd":220,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/xiaoe/catalog.js#L184-L220","documentation":"Navigation succeeded but the catalog extraction returned either a non-array or an empty array, so getXiaoeCatalog throws EmptyResultError for 'xiaoe/catalog'. The library distinguishes this from a hard failure: the page loaded, but no product rows could be extracted.","triggerScenarios":"buildCatalogScript() evaluated on the loaded page returns [] or null/undefined — the URL is not actually a course/catalog page, or the page rendered in a logged-out/anti-bot state that hides the item list.","commonSituations":"Passing a storefront home or preview URL instead of a course catalog page; login session expired so the list renders empty; Xiaoe markup changed and the extraction selectors no longer match; region/permission gating hiding items from the account.","solutions":["Verify the URL is a real xiaoe course catalog page (h5.xet.citv.cn/p/course/... style).","Re-login via the xiaoe auth flow and retry — expired sessions often yield empty lists.","Open the URL in a browser with the same session and confirm items are visible.","Update buildCatalogScript() selectors if the catalog markup changed."],"exampleFix":"// before\nawait getXiaoeCatalog(page, 'https://appxxxx.h5.xet.citv.cn/');\n// after\nawait getXiaoeCatalog(page, 'https://appxxxx.h5.xet.citv.cn/p/t_pc/goods_pc_list/pd_id=xxxx'); // actual catalog page URL","handlingStrategy":"validation","validationCode":"const u = new URL(url);\nif (!u.hostname.endsWith('h5.xet.citv.cn') || !u.pathname.startsWith('/p/')) {\n  throw new Error('not a xiaoe catalog page URL');\n}","typeGuard":"function isCatalogUrl(u) {\n  try { const p = new URL(u); return p.protocol === 'https:' && p.hostname.endsWith('.h5.xet.citv.cn') && /\\/p\\//.test(p.pathname); } catch { return false; }\n}","tryCatchPattern":"try {\n  rows = await getXiaoeCatalog(page, url);\n} catch (e) {\n  if (e instanceof EmptyResultError) {\n    await runXiaoeLogin(page); // stale session often yields empty list\n    rows = await getXiaoeCatalog(page, url);\n  } else throw e;\n}","preventionTips":["Confirm the URL is a course/catalog page, not the storefront home.","Re-login when results come back empty — expired sessions render empty lists.","Spot-check extraction selectors against the live page after Xiaoe updates."],"tags":["scraping","empty-result","url-validation","xiaoe"],"backgroundTag":"empty-scrape-result","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}