{"record":{"id":"e4e31e820f0ce3d7","repo":"DIYgod/RSSHub","slug":"bilibili-browser-mode-returned-unexpected-video-li","errorCode":null,"errorMessage":"Bilibili browser mode returned unexpected video list API status ${response.status()}","messagePattern":"Bilibili browser mode returned unexpected video list API status (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"lib/routes/bilibili/video.ts","lineNumber":111,"sourceCode":"};\n\nconst getVideoListResponse = async (responsePromise: ReturnType<typeof waitForVideoListResponse>) => {\n    const videoListResponseResult = await responsePromise;\n    if ('error' in videoListResponseResult) {\n        throw new Error(`Bilibili browser mode did not receive a video list response within ${browserResponseTimeout}ms: ${getErrorMessage(videoListResponseResult.error)}`);\n    }\n\n    return videoListResponseResult.response;\n};\n\nconst waitForVideoListResponseFromVideoPage = async (page: Page, videoUrl: string): Promise<BrowserResponse> => {\n    const videoListResponsePromise = waitForVideoListResponse(page);\n    await navigateToVideoPage(page, videoUrl);\n\n    const response = await getVideoListResponse(videoListResponsePromise);\n\n    if (response.status() !== 200) {\n        throw new Error(`Bilibili browser mode returned unexpected video list API status ${response.status()}`);\n    }\n\n    const contentType = response.headers()['content-type'];\n    if (!contentType?.includes('application/json')) {\n        throw new Error(`Bilibili browser mode returned non-JSON response with status ${response.status()}; BILIBILI_COOKIE_* may be required`);\n    }\n\n    return response;\n};\n\nasync function applyCookie(page: Page, cookie: string) {\n    const cookies = cookie\n        .split(';')\n        .map((item) => item.trim())\n        .filter(Boolean)\n        .map((item) => {\n            const equalIndex = item.indexOf('=');\n            if (equalIndex <= 0) {","sourceCodeStart":93,"sourceCodeEnd":129,"githubUrl":"https://github.com/DIYgod/RSSHub/blob/bed535e0879dc71c5aff6f1e7bd1ac21ede40115/lib/routes/bilibili/video.ts#L93-L129","documentation":"Thrown by waitForVideoListResponseFromVideoPage when the captured wbi/arc/search XHR response has an HTTP status other than 200. The response was matched and received on time, but Bilibili answered with an error status (commonly 403, 412, or 302 to a login/captcha page). It indicates the request reached the API but was rejected at the gateway layer before any JSON body was returned.","triggerScenarios":"fetchVideoListFromBrowser(uid): the page navigated, the arc/search XHR fired, and Bilibili responded with e.g. 412 Precondition (risk control), 403 Forbidden, or a redirect to a login page — and that non-200 status is captured by waitForResponse.","commonSituations":"No BILIBILI_COOKIE_* set so the browser session is unauthenticated and bilibili blocks the API; RSSHub IP flagged by anti-crawler; bilibili returned a captcha interstitial; account/IP under temporary ban.","solutions":["Set BILIBILI_COOKIE_{uid} (or any BILIBILI_COOKIE_*) so applyCookie injects the session before navigation.","Identify the status: 412/403 = anti-crawler (slow down, rotate IP/account), 302/3xx = login redirect (cookie missing/expired).","Reduce request frequency; run RSSHub behind a residential IP or proxy that bilibili does not flag.","Retry after a cooldown — gateway blocks are often transient."],"exampleFix":"// before\nif (response.status() !== 200) {\n    throw new Error(`Bilibili browser mode returned unexpected video list API status ${response.status()}`);\n}\n\n// after (surface status text + url for diagnosis)\nif (response.status() !== 200) {\n    throw new Error(`Bilibili browser mode returned status ${response.status()} (${response.statusText()}) for ${response.url()}; set BILIBILI_COOKIE_* or retry`);\n}","handlingStrategy":"retry","validationCode":"// Pre-flight: confirm the wbi/arc/search endpoint answers 200 from your IP.\nimport got from '@/utils/got';\nasync function videoApiReachable(uid: string) {\n  try {\n    const r = await got(`https://api.bilibili.com/x/space/wbi/arc/search?mid=${uid}&ps=1&pn=1`);\n    return r.statusCode === 200;\n  } catch { return false; }\n}","typeGuard":"// Narrow a Playwright response into a usable 200 JSON response.\nfunction isOkJsonResponse(response: { status(): number; headers(): Record<string,string> }): boolean {\n  return response.status() === 200 && (response.headers()['content-type'] ?? '').includes('application/json');\n}","tryCatchPattern":"try {\n  if (response.status() !== 200) throw new Error(`status ${response.status()}`);\n} catch (e) {\n  const m = e instanceof Error ? e.message : '';\n  if (m.includes('412') || m.includes('403') || m.includes('302')) {\n    // anti-crawler / login redirect — backoff and retry once with a fresh page\n    await backoffRetry();\n  }\n  throw e;\n}","preventionTips":["Set BILIBILI_COOKIE_* so applyCookie authenticates the browser context before navigation.","Route RSSHub through a non-flagged egress IP; 412/403 on the browser path usually means the IP is risk-controlled.","Slow polling to >= 10 minute intervals to avoid tipping anti-crawler.","Log response.status() and response.url() so a non-200 is immediately diagnosable."],"tags":["bilibili","playwright","browser","anti-crawler","http-status","rsshub"],"backgroundTag":null,"analyzedSha":"bed535e0879dc71c5aff6f1e7bd1ac21ede40115","analyzedAt":"2026-08-12T19:29:35.364Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}