{"record":{"id":"e892c8b47e145726","repo":"DIYgod/RSSHub","slug":"html-e892c8","errorCode":null,"errorMessage":"无法在 HTML 缓存中提取核心数据对象","messagePattern":"无法在 HTML 缓存中提取核心数据对象","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"lib/routes/comic-walker/manga.ts","lineNumber":61,"sourceCode":"            headers: {\n                'Accept-Language': 'ja,en-US;q=0.9,en;q=0.8',\n            },\n        });\n\n        const $ = load(response);\n        const nextDataText = $('#__NEXT_DATA__').text();\n\n        if (!nextDataText) {\n            throw new Error('无法解析页面 HTML 数据，可能触发了反爬策略或页面结构巨变');\n        }\n\n        const nextData = JSON.parse(nextDataText);\n        const queries = nextData.props?.pageProps?.dehydratedState?.queries || [];\n\n        const workQuery = queries.find((q: any) => q.queryKey?.includes('/api/contents/details/work') || (Array.isArray(q.queryKey) && q.queryKey.some((k: any) => typeof k === 'string' && k.includes('work'))));\n\n        if (!workQuery || !workQuery.state?.data) {\n            throw new Error('无法在 HTML 缓存中提取核心数据对象');\n        }\n\n        const data = workQuery.state.data;\n        const work = data.work;\n\n        if (!work) {\n            throw new Error('成功获取数据对象，但未找到作品基本信息');\n        }\n\n        const mangaTitle = work.title || $('title').text();\n        const mangaAuthor = work.authors?.map((author: any) => author.name).join(', ');\n        const mangaDescription = work.summary || '';\n        const coverImage = work.bookCover || work.thumbnail;\n\n        const firstEpisodes = getEpisodes(data.firstEpisodes);\n        const latestEpisodes = getEpisodes(data.latestEpisodes);\n        const extraEpisodes = getEpisodes(data.episodes);\n","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/DIYgod/RSSHub/blob/bed535e0879dc71c5aff6f1e7bd1ac21ede40115/lib/routes/comic-walker/manga.ts#L43-L79","documentation":"After parsing the __NEXT_DATA__ JSON from comic-walker, the route searches the dehydrated React Query cache for a query whose key references '/api/contents/details/work'. If no matching query is found or it has no state.data, this error is thrown. The query key format is the fragile coupling point — any change in the API path naming breaks the lookup.","triggerScenarios":"nextData.props.pageProps.dehydratedState.queries is searched with a queryKey containing '/api/contents/details/work' or a key element containing 'work'. The find() returns undefined either because the queries array is empty or the query keys no longer match the expected pattern.","commonSituations":"Comic Walker renames their internal API endpoint (e.g., from '/api/contents/details/work' to '/api/contents/work/detail'); the dehydratedState structure changes in a Next.js or React Query version upgrade; the manga ID leads to a page where the work query was never fetched (different page type rendered).","solutions":["Log the actual query keys in the dehydratedState to see the new naming pattern.","Broaden the search to match on partial key patterns, or check for alternative query shapes.","Visit the page in a browser, inspect __NEXT_DATA__ in DevTools, and compare the queries array to the expected structure.","Update the queryKey matching logic to use the new pattern once identified."],"exampleFix":"// before\nconst workQuery = queries.find((q: any) =>\n    q.queryKey?.includes('/api/contents/details/work') ||\n    (Array.isArray(q.queryKey) && q.queryKey.some((k: any) => typeof k === 'string' && k.includes('work')))\n);\n\n// after — log available keys and broaden match\nif (!workQuery) {\n    const keys = queries.map((q: any) => JSON.stringify(q.queryKey));\n    throw new Error(`work query not found in dehydrated cache. Available keys: ${keys.join(' | ')}`);\n}","handlingStrategy":"validation","validationCode":"const queries = nextData.props?.pageProps?.dehydratedState?.queries || [];\nif (queries.length === 0) {\n    throw new Error('No React Query entries in dehydrated state');\n}\nconst workQuery = queries.find((q: any) =>\n    q.queryKey?.includes('/api/contents/details/work') ||\n    (Array.isArray(q.queryKey) && q.queryKey.some((k: any) => typeof k === 'string' && k.includes('work')))\n);\nif (!workQuery) {\n    logger.warn(`Available query keys: ${queries.map(q => JSON.stringify(q.queryKey)).join(', ')}`);\n}","typeGuard":"function hasWorkQuery(queries: any[]): queries is Array<{ queryKey: any[]; state: { data: { work: any } } }> {\n    return queries.some(q => q?.state?.data?.work != null);\n}","tryCatchPattern":null,"preventionTips":["Log available query keys when the expected query is not found — this immediately reveals API naming changes.","Use a more specific queryKey match rather than a loose 'includes(\"work\")' check to avoid false matches.","Monitor the comic-walker API for endpoint renames by periodically checking the page's __NEXT_DATA__ in a browser."],"tags":["scraping","react-query","parsing","comic-walker","api-change"],"backgroundTag":null,"analyzedSha":"bed535e0879dc71c5aff6f1e7bd1ac21ede40115","analyzedAt":"2026-08-12T19:29:35.364Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}