DIYgod/RSSHub · error · Error

Got error code ${data.code} while fetching in browser mode:

Error message

Got error code ${data.code} while fetching in browser mode: ${data.message}

What it means

Thrown by fetchVideoListFromBrowser (the Playwright fallback) after it captured a 200 JSON response from wbi/arc/search but the parsed body has a non-zero `code`. It mirrors error 118 but for the browser path: the request succeeded at the network layer yet Bilibili rejected it at the application layer. Because this only fires after the API path already failed, hitting it means both code paths are being rejected.

Source

Thrown at lib/routes/bilibili/video.ts:200

        onBeforeLoad: async (page) => {
            if (cookie) {
                await applyCookie(page, cookie);
            }

            await page.route('**/*', (route) => {
                const request = route.request();
                allowedBrowserRequestTypes.has(request.resourceType()) ? route.continue() : route.abort();
            });
        },
        gotoConfig: { waitUntil: 'domcontentloaded' },
    });

    try {
        const response = await waitForVideoListResponseFromVideoPage(page, videoUrl);
        const data = (await response.json()) as VideoListResponse;
        if (data.code) {
            logger.error(JSON.stringify(data.data));
            throw new Error(`Got error code ${data.code} while fetching in browser mode: ${data.message}`);
        }

        if (!data.data) {
            throw new Error('Bilibili browser response does not contain video list data');
        }

        return data.data;
    } finally {
        await destroy();
    }
}

async function getVideoList(uid: string): Promise<VideoListData> {
    try {
        return await fetchVideoListFromApi(uid);
    } catch (error) {
        logger.warn(`[bilibili/video] API request failed, falling back to browser mode: ${error}`);
        return fetchVideoListFromBrowser(uid);

View on GitHub (pinned to bed535e087)

Solutions

  1. Refresh BILIBILI_COOKIE_{uid} from a new bilibili.com login — it is shared by both paths via cache.getConfiguredCookie/applyCookie.
  2. If no cookie is set, configure one; the browser path currently relies on an anonymous session which bilibili commonly rejects with -352.
  3. Rotate the egress IP or slow down polling — a -352 on the browser path means the IP itself is flagged.
  4. Inspect the logged data.data (logger.error(JSON.stringify(data.data))) to decode the exact code; -101 = session, -352/-479 = risk control.
  5. If both paths persistently fail, the account/IP is hard-blocked; pause the feed and use a different account or proxy.

Example fix

// before
const data = (await response.json()) as VideoListResponse;
if (data.code) {
    logger.error(JSON.stringify(data.data));
    throw new Error(`Got error code ${data.code} while fetching in browser mode: ${data.message}`);
}

// after (distinguish auth from risk control)
const data = (await response.json()) as VideoListResponse;
if (data.code) {
    logger.error(`[bilibili/video] browser api code ${data.code}: ${JSON.stringify(data.data)}`);
    if (data.code === -101) {
        throw new ConfigNotFoundError(`Bilibili cookie invalid/expired in browser mode (uid ${uid})`);
    }
    throw new Error(`Got error code ${data.code} while fetching in browser mode: ${data.message ?? 'no message'}`);
}
Defensive patterns

Strategy: retry

Validate before calling

// Confirm the cookie that applyCookie will inject is still valid before entering browser mode.
import ofetch from '@/utils/ofetch';
import cache from './cache';
async function browserCookieValid() {
  const cookie = cache.getConfiguredCookie();
  if (!cookie) return false;
  const nav = await ofetch<{ code: number }>('https://api.bilibili.com/x/web-interface/nav', { headers: { Cookie: cookie } });
  return nav.code === 0;
}

Type guard

interface VideoListResponse { code?: number; data?: VideoListData; message?: string }
function isVideoListOk(r: VideoListResponse): r is { code: 0; data: VideoListData } {
  return r.code === 0 && !!r.data;
}

Try / catch

// At the getVideoList() call site: try API, then browser once, then give up with a clear message.
try {
  return await fetchVideoListFromApi(uid);
} catch (apiErr) {
  try {
    return await fetchVideoListFromBrowser(uid);
  } catch (browserErr) {
    const m = String(browserErr);
    if (m.includes('-101')) throw new Error(`Bilibili cookie expired for uid ${uid} (refresh BILIBILI_COOKIE_*)`);
    if (m.includes('-352') || m.includes('-479')) throw new Error(`Bilibili risk control active for uid ${uid} (rotate IP/account, slow down)`);
    throw browserErr;
  }
}

Prevention

When it happens

Trigger: getVideoList(uid): fetchVideoListFromApi threw (error 118), so the browser fallback runs; the browser-side wbi/arc/search XHR returns 200 JSON with code !== 0 — typically -352/-479 (risk control, now also flagging the browser session) or -101 (the applied cookie is invalid/expired).

Common situations: BILIBILI_COOKIE_* is expired or belongs to a restricted account, so both the direct API and the browser session are rejected; RSSHub IP under heavy anti-crawler ban; buvid/wbi state missing in the browser context.

Related errors


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