DIYgod/RSSHub · warning · Error
Bilibili browser mode returned unexpected video list API sta
Error message
Bilibili browser mode returned unexpected video list API status ${response.status()} What it means
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.
Source
Thrown at lib/routes/bilibili/video.ts:111
};
const getVideoListResponse = async (responsePromise: ReturnType<typeof waitForVideoListResponse>) => {
const videoListResponseResult = await responsePromise;
if ('error' in videoListResponseResult) {
throw new Error(`Bilibili browser mode did not receive a video list response within ${browserResponseTimeout}ms: ${getErrorMessage(videoListResponseResult.error)}`);
}
return videoListResponseResult.response;
};
const waitForVideoListResponseFromVideoPage = async (page: Page, videoUrl: string): Promise<BrowserResponse> => {
const videoListResponsePromise = waitForVideoListResponse(page);
await navigateToVideoPage(page, videoUrl);
const response = await getVideoListResponse(videoListResponsePromise);
if (response.status() !== 200) {
throw new Error(`Bilibili browser mode returned unexpected video list API status ${response.status()}`);
}
const contentType = response.headers()['content-type'];
if (!contentType?.includes('application/json')) {
throw new Error(`Bilibili browser mode returned non-JSON response with status ${response.status()}; BILIBILI_COOKIE_* may be required`);
}
return response;
};
async function applyCookie(page: Page, cookie: string) {
const cookies = cookie
.split(';')
.map((item) => item.trim())
.filter(Boolean)
.map((item) => {
const equalIndex = item.indexOf('=');
if (equalIndex <= 0) {View on GitHub (pinned to bed535e087)
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.
Example fix
// before
if (response.status() !== 200) {
throw new Error(`Bilibili browser mode returned unexpected video list API status ${response.status()}`);
}
// after (surface status text + url for diagnosis)
if (response.status() !== 200) {
throw new Error(`Bilibili browser mode returned status ${response.status()} (${response.statusText()}) for ${response.url()}; set BILIBILI_COOKIE_* or retry`);
} Defensive patterns
Strategy: retry
Validate before calling
// Pre-flight: confirm the wbi/arc/search endpoint answers 200 from your IP.
import got from '@/utils/got';
async function videoApiReachable(uid: string) {
try {
const r = await got(`https://api.bilibili.com/x/space/wbi/arc/search?mid=${uid}&ps=1&pn=1`);
return r.statusCode === 200;
} catch { return false; }
} Type guard
// Narrow a Playwright response into a usable 200 JSON response.
function isOkJsonResponse(response: { status(): number; headers(): Record<string,string> }): boolean {
return response.status() === 200 && (response.headers()['content-type'] ?? '').includes('application/json');
} Try / catch
try {
if (response.status() !== 200) throw new Error(`status ${response.status()}`);
} catch (e) {
const m = e instanceof Error ? e.message : '';
if (m.includes('412') || m.includes('403') || m.includes('302')) {
// anti-crawler / login redirect — backoff and retry once with a fresh page
await backoffRetry();
}
throw e;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Bilibili browser mode did not receive a video list response
- Bilibili browser mode returned non-JSON response with status
- Got error code ${data.code} while fetching in browser mode:
- 对应 uid 的 Bilibili 用户 请求失败
- response.message ?? `Error code ${response.code}`
AI-assisted analysis of DIYgod/RSSHub@bed535e087 (2026-08-12).
Data as JSON: /api/errors/e4e31e820f0ce3d7.
Report an issue: GitHub.