{"record":{"id":"a88b173622d51516","repo":"datawhalechina/hello-agents","slug":"tmdb-json","errorCode":null,"errorMessage":"TMDB 返回非 JSON","messagePattern":"TMDB 返回非 JSON","errorType":"exception","errorClass":"MovieServiceError","httpStatus":null,"severity":"error","filePath":"Co-creation-projects/aatanxiao12-beep-YingQian/backend/app/services/movie_service.py","lineNumber":137,"sourceCode":"        except httpx.HTTPError as e:\n            raise MovieServiceError(f\"TMDB 网络错误: {e}\") from e\n\n        if resp.status_code == 401:\n            raise MovieServiceError(\n                \"TMDB 鉴权失败：请检查 Access Token / API Key\",\n                status_code=401,\n            )\n        if resp.status_code == 404:\n            raise MovieServiceError(\"影片不存在或已下架\", status_code=404)\n        if resp.status_code >= 400:\n            raise MovieServiceError(\n                f\"TMDB 请求失败: HTTP {resp.status_code}, {resp.text[:200]}\"\n            )\n\n        try:\n            return resp.json()\n        except ValueError as e:\n            raise MovieServiceError(\"TMDB 返回非 JSON\") from e\n\n    def _poster_url(self, poster_path: Optional[str]) -> Optional[str]:\n        \"\"\"把 TMDB 相对 poster_path 拼成可访问的完整图片 URL。\"\"\"\n        if not poster_path:\n            return None\n        base = self.settings.tmdb_image_base_url.rstrip(\"/\")\n        return f\"{base}{poster_path}\"\n\n    def _parse_year(self, release_date: Optional[str]) -> Optional[int]:\n        \"\"\"从 release_date（YYYY-MM-DD）解析上映年份。\"\"\"\n        if not release_date or len(release_date) < 4:\n            return None\n        try:\n            return int(release_date[:4])\n        except ValueError:\n            return None\n\n    def ensure_genres(self) -> None:","sourceCodeStart":119,"sourceCodeEnd":155,"githubUrl":"https://github.com/datawhalechina/hello-agents/blob/606a07d341a47be773fab7f4b71177f53f96b2c3/Co-creation-projects/aatanxiao12-beep-YingQian/backend/app/services/movie_service.py#L119-L155","documentation":"MovieServiceError raised when resp.json() raises ValueError on a 2xx TMDB response — the body is not valid JSON. httpx raises json.JSONDecodeError (a ValueError subclass) here. Causes: an intermediary (captive portal, corporate proxy, CDN edge) returning HTML; response compressed/garbled; TMDB rarely returning an error page with 200.","triggerScenarios":"Captive-portal Wi-Fi injecting an HTML login page on the API request; a proxy or firewall replacing the response body; response truncated mid-body by a connection drop (httpx would usually raise ReadError instead, but short bodies can parse as invalid JSON); Accept header negotiation returning non-JSON error content.","commonSituations":"Container in a network that transparently redirects unauthenticated traffic; hotel/airport Wi-Fi; a misconfigured reverse proxy in front of the service rewriting responses; extremely rare TMDB edge malfunctions.","solutions":["Log resp.headers.get('content-type') and a body snippet when this fires — HTML content-type immediately implicates an intermediary","Whitelist api.themoviedb.org in the proxy/captive-portal environment or route through an authenticated proxy","Treat as retryable once: one re-request often bypasses a one-off bad intermediate response","Set headers={'Accept': 'application/json'} explicitly (already done) and verify no middleware strips it"],"exampleFix":"# before\ntry:\n    return resp.json()\nexcept ValueError as e:\n    raise MovieServiceError(\"TMDB 返回非 JSON\") from e\n\n# after\ntry:\n    return resp.json()\nexcept ValueError as e:\n    ct = resp.headers.get(\"content-type\", \"\")\n    snippet = resp.text[:120].replace(\"\\n\", \" \")\n    raise MovieServiceError(\n        f\"TMDB 返回非 JSON (content-type={ct}, body~ {snippet})\"\n    ) from e","handlingStrategy":"try-catch","validationCode":"# verify the response smells like JSON before parsing\nct = resp.headers.get('content-type', '')\nif 'json' not in ct:\n    raise MovieServiceError(f'TMDB 返回非 JSON (content-type={ct})')","typeGuard":null,"tryCatchPattern":"except MovieServiceError as e:\n    if '非 JSON' in str(e):\n        return retry_once()  # captive portals/proxies usually poison one response\n    raise","preventionTips":["Check content-type includes application/json before resp.json() for a clearer error","Whitelist API hosts through proxies; disable captive networks for servers","Log a body snippet on decode failure to identify the intercepting middlebox"],"tags":["tmdb","httpx","json","proxy","python"],"backgroundTag":null,"analyzedSha":"606a07d341a47be773fab7f4b71177f53f96b2c3","analyzedAt":"2026-08-14T22:57:27.446Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}