{"record":{"id":"88773d72b739f803","repo":"jackwener/OpenCLI","slug":"could-not-parse-a-bookid-from-the-reader-url","errorCode":null,"errorMessage":"Could not parse a bookId from the reader URL","messagePattern":"Could not parse a bookId from the reader URL","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/weread/book-search.js","lineNumber":258,"sourceCode":"    }\n    const htmlEntries = await loadSearchHtmlEntries(bookQuery);\n    selected.readerUrl = resolveReaderUrlForBook(selected, htmlEntries);\n    const readerMetadata = await loadReaderMetadata(selected.readerUrl);\n    return {\n        ...selected,\n        ...Object.fromEntries(Object.entries(readerMetadata ?? {}).filter(([, value]) => value != null && value !== '' && !(Array.isArray(value) && value.length === 0))),\n    };\n}\n\nasync function resolveBookTarget(target, bookRank) {\n    if (/^https?:\\/\\//i.test(target)) {\n        const readerUrl = parseWereadReaderUrl(target);\n        if (!readerUrl) {\n            throw new ArgumentError('book URL must be a https://weread.qq.com/web/reader/<id> URL');\n        }\n        const metadata = await loadReaderMetadata(readerUrl);\n        if (!metadata?.bookId) {\n            throw new CommandExecutionError('Could not parse a bookId from the reader URL');\n        }\n        return metadata;\n    }\n    if (/^\\d+$/.test(target)) {\n        const metadata = {};\n        metadata.bookId = target;\n        metadata.title = '';\n        metadata.author = '';\n        metadata.readerUrl = '';\n        metadata.chapters = [];\n        return metadata;\n    }\n    return searchBookByQuery(target, bookRank);\n}\n\nasync function searchWithinBook(bookId, query, limit, fragmentSize) {\n    const rows = [];\n    let maxIdx = 0;","sourceCodeStart":240,"sourceCodeEnd":276,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/weread/book-search.js#L240-L276","documentation":"resolveBookTarget accepted the target as a well-formed reader URL, but loadReaderMetadata (which scrapes the page's window.__INITIAL_STATE__) returned metadata without a bookId. The CLI cannot identify the book, so it throws CommandExecutionError.","triggerScenarios":"parseWereadReaderUrl succeeded but the fetched reader page's embedded initial state lacks bookId — e.g. WeRead returned a login/captcha/error page instead of the book page, or the page template changed.","commonSituations":"Not logged in or session expired so WeRead serves an interstitial; anti-bot / rate limiting serving degraded HTML; a WeRead frontend update renaming or removing the bookId field in __INITIAL_STATE__.","solutions":["Retry with a valid logged-in session/cookie so the real book page loads.","Verify the URL actually resolves to a live book (open it in a browser).","If the page loads fine in a browser, inspect window.__INITIAL_STATE__ for schema changes and update the extractor.","Pass the numeric bookId directly instead of the URL."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":"function metadataHasBookId(m) {\n  return m != null && typeof m === 'object' && typeof m.bookId === 'string' && m.bookId.length > 0;\n}","tryCatchPattern":"try {\n  return await resolveBookTarget(url, rank);\n} catch (e) {\n  if (e instanceof CommandExecutionError && e.message.includes('Could not parse a bookId')) {\n    await sleep(2000);\n    return resolveBookTarget(url, rank); // transient page / rate limit\n  }\n  throw e;\n}","preventionTips":["Keep a valid logged-in session/cookie so WeRead serves the real book page.","Throttle requests to avoid anti-bot interstitials.","Prefer numeric bookId to skip page scraping entirely."],"tags":["scraping","auth","weread"],"backgroundTag":"page-scrape-schema-changed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}