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

  1. 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).
  2. Confirm Playwright is installed (`npx playwright install chromium`) and Chromium can launch headless in your environment.
  3. Raise browserResponseTimeout if your network is genuinely slow, but first inspect logs for the underlying wait error — bilibili may be returning a captcha.
  4. If bilibili renamed/moved the wbi/arc/search endpoint, update videoListApiPath / isVideoListApiResponse in lib/routes/bilibili/video.ts.
  5. 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

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

Related errors


AI-assisted analysis of DIYgod/RSSHub@bed535e087 (2026-08-12). Data as JSON: /api/errors/19baed1144910c1c. Report an issue: GitHub.