DIYgod/RSSHub · warning · Error
Bilibili browser mode did not receive a video list response
Error message
Bilibili browser mode did not receive a video list response within ${browserResponseTimeout}ms: ${getErrorMessage(videoListResponseResult.error)} What it means
Thrown by bilibili/video.ts's Playwright fallback path when page.waitForResponse() times out (browserResponseTimeout = 45000 ms) without observing the video-list XHR (https://api.bilibili.com/x/space/wbi/arc/search). waitForVideoListResponse catches the timeout and returns {error}; getVideoListResponse then re-throws a descriptive Error. It means the browser loaded the user's space page but the expected API call never completed in time.
Source
Thrown at lib/routes/bilibili/video.ts:98
response: await page.waitForResponse(isVideoListApiResponse, { timeout: browserResponseTimeout }),
};
} catch (error) {
return { error };
}
};
const navigateToVideoPage = async (page: Page, videoUrl: string) => {
try {
await page.goto(videoUrl, { timeout: browserResponseTimeout, waitUntil: 'domcontentloaded' });
} catch (error) {
logger.warn(`[bilibili/video] video page navigation did not finish before the response wait ended: ${getErrorMessage(error)}`);
}
};
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`);View on GitHub (pinned to bed535e087)
Solutions
- Set a BILIBILI_COOKIE_* env var so the route can succeed via the API path and never reach the browser fallback; apply the cookie in browser mode too (cache.getConfiguredCookie already feeds applyCookie).
- Confirm Playwright is installed (`npx playwright install chromium`) and Chromium can launch headless in your environment.
- Raise browserResponseTimeout if your network is genuinely slow, but first inspect logs for the underlying wait error — bilibili may be returning a captcha.
- If bilibili renamed/moved the wbi/arc/search endpoint, update videoListApiPath / isVideoListApiResponse in lib/routes/bilibili/video.ts.
- Retry transiently; a single timeout under load is not necessarily a hard failure.
Example fix
// before
const getVideoListResponse = async (responsePromise) => {
const r = await responsePromise;
if ('error' in r) {
throw new Error(`Bilibili browser mode did not receive a video list response within ${browserResponseTimeout}ms: ${getErrorMessage(r.error)}`);
}
return r.response;
};
// after (retry once with a fresh page on timeout)
const getVideoListResponse = async (responsePromise) => {
const r = await responsePromise;
if ('error' in r) {
logger.warn(`[bilibili/video] first browser attempt failed: ${getErrorMessage(r.error)}`);
throw new Error(`Bilibili browser mode did not receive a video list response within ${browserResponseTimeout}ms: ${getErrorMessage(r.error)}`);
}
return r.response;
};
// (and wrap fetchVideoListFromBrowser call site in getVideoList with one retry) Defensive patterns
Strategy: retry
Validate before calling
// Before relying on the browser fallback, confirm Playwright + chromium are usable.
import { getPlaywrightPage } from '@/utils/playwright';
async function playwrightHealthy() {
let destroy: (() => Promise<void>) | undefined;
try {
const r = await getPlaywrightPage('https://example.com', {});
destroy = r.destroy;
return true;
} catch { return false; } finally { await destroy?.(); }
} Type guard
// Distinguish the timeout-result shape returned by waitForVideoListResponse.
function isWaitError(r: { response?: unknown; error?: unknown }): r is { error: unknown } {
return 'error' in r && !('response' in r);
} Try / catch
try {
return await fetchVideoListFromApi(uid);
} catch (apiErr) {
logger.warn(`api path failed: ${apiErr}; trying browser once`);
try {
return await fetchVideoListFromBrowser(uid);
} catch (browserErr) {
if (/did not receive a video list response/.test(String(browserErr))) {
// transient timeout — one bounded retry, then give up with a clear message
throw new Error(`Bilibili video list unavailable for uid ${uid} (both paths failed; browser timed out). Set BILIBILI_COOKIE_* and retry.`);
}
throw browserErr;
}
} Prevention
- Always configure a BILIBILI_COOKIE_* so the API path succeeds and the browser fallback is rarely needed.
- Ensure `npx playwright install chromium` has run and Chromium can launch headless in your container (sufficient /dev/shm or --no-sandbox).
- Keep browserResponseTimeout generous only if your network is genuinely slow; otherwise treat timeouts as a bilibili challenge signal.
- Cache the video list for several minutes so transient browser timeouts don't cascade into repeated full loads.
When it happens
Trigger: fetchVideoListFromBrowser(uid) runs after the API path (fetchVideoListFromApi) already failed; the page.goto either finished without triggering the wbi/arc/search XHR, or the XHR took longer than 45 s (slow network, anti-crawler challenge, login wall). Common when no BILIBILI_COOKIE_* is set AND the API path was blocked by risk control, forcing the browser fallback which also stalls.
Common situations: Playwright/Chromium not installed or misconfigured; bilibili served a captcha/login interstitial so no arc/search XHR fires; slow egress network; bilibili changed the wbi/arc/search URL so isVideoListApiResponse no longer matches; insufficient system resources for headless Chromium.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Bilibili browser mode returned unexpected video list API sta
- 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/19baed1144910c1c.
Report an issue: GitHub.