NanmiCoder/MediaCrawler · error · ForbiddenError

{response.text}

Error message

{response.text}

What it means

ForbiddenError raised by ZhiHuClient.request when a signed request returns HTTP 403. For zhihu this means the signature (x-zse-96) was rejected or the session/IP is forbidden; the exception message carries the raw response body for diagnosis.

Source

Thrown at media_platform/zhihu/client.py:109

            url: Request URL
            **kwargs: Other request parameters such as headers, body, etc.

        Returns:

        """
        # Check if proxy is expired before each request
        await self._refresh_proxy_if_expired()

        # return response.text
        return_response = kwargs.pop('return_response', False)

        async with make_async_client(proxy=self.proxy) as client:
            response = await client.request(method, url, timeout=self.timeout, **kwargs)

        if response.status_code != 200:
            utils.logger.error(f"[ZhiHuClient.request] Requset Url: {url}, Request error: {response.text}")
            if response.status_code == 403:
                raise ForbiddenError(response.text)
            elif response.status_code == 404:  # Content without comments also returns 404
                return {}

            raise DataFetchError(response.text)

        if return_response:
            return response.text
        try:
            data: Dict = response.json()
            if data.get("error"):
                utils.logger.error(f"[ZhiHuClient.request] Request error: {data}")
                raise DataFetchError(data.get("error", {}).get("message"))
            return data
        except json.JSONDecodeError:
            utils.logger.error(f"[ZhiHuClient.request] Request error: {response.text}")
            raise DataFetchError(response.text)

    async def get(self, uri: str, params=None, **kwargs) -> Union[Response, Dict, str]:

View on GitHub (pinned to d6f7c5bb90)

Solutions

  1. Refresh zhihu cookies (re-login) first - cheapest fix - and retry.
  2. If 403 persists with fresh cookies, update the zhihu signing implementation (x-zse-96 algorithm version) to match the current site build.
  3. Enable/rotate proxies if the IP is the blocked factor.
  4. Catch ForbiddenError at loop level to back off instead of immediate retries.

Example fix

// before
data = await zhihu_client.request("GET", url)
// after
from media_platform.zhihu.exception import ForbiddenError
try:
    data = await zhihu_client.request("GET", url)
except ForbiddenError as e:
    utils.logger.error(f"zhihu 403: {e}")
    await asyncio.sleep(30)
    raise
Defensive patterns

Strategy: retry

Try / catch

from media_platform.zhihu.exception import ForbiddenError
try:
    data = await zhihu_client.request("GET", url)
except ForbiddenError as e:
    utils.logger.error(f"zhihu 403 body: {e}")
    await asyncio.sleep(60)
    raise

Prevention

When it happens

Trigger: Any zhihu API call where the signing algorithm version no longer matches the site (signature rejected with 403), the cookie is expired so the signature is computed over stale credentials, or the IP is blocked by zhihu risk control.

Common situations: Zhihu updates their signature algorithm and the local signer becomes outdated; long crawl with expired login; datacenter IP flagged by zhihu.

Related errors


AI-assisted analysis of NanmiCoder/MediaCrawler@d6f7c5bb90 (2026-08-15). Data as JSON: /api/errors/2f2b94cc9c9f53f9. Report an issue: GitHub.