# 商城搜索的 HTTP:解 cookie、认 JSON、失败按次数重试。 from __future__ import annotations import json import time from typing import Any from urllib.parse import unquote import requests import urllib3 from mall_config import API_MAX_RETRIES, API_REQUEST_TIMEOUT_SECONDS, API_RETRY_BASE_SECONDS urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) # 去掉 cookie 值两端引号并做 URL 解码。空值当空串。 def parse_cookie_value(raw_value: str | None) -> str: if not raw_value: return "" return unquote(raw_value.strip().strip('"')) # 把商城响应收成 JSON 对象。剥掉 BOM 和防劫持前缀。不是对象就报平台和查询键。 def parse_response_json_or_raise( response: requests.Response, platform_name: str, query_key: str, ) -> dict[str, Any]: try: payload_json = response.json() except ValueError as exc: raw_text = response.text or "" cleaned_text = raw_text.lstrip("\ufeff").strip() for prefix in (")]}',", "while(1);"): if cleaned_text.startswith(prefix): cleaned_text = cleaned_text[len(prefix) :].strip() try: payload_json = json.loads(cleaned_text) except ValueError: snippet = cleaned_text.replace("\r", " ").replace("\n", " ")[:200] raise RuntimeError( f"{platform_name} 接口返回非 JSON,查询键 {query_key}," f"HTTP {response.status_code},响应片段:{snippet}" ) from exc if not isinstance(payload_json, dict): raise RuntimeError( f"{platform_name} 接口 JSON 根结构异常,查询键 {query_key}:{type(payload_json)}" ) return payload_json # POST 商城搜索。HTTP 或 JSON 失败时按次数重试,仍失败就报平台和查询键。 def post_json_with_retry( session: requests.Session, url: str, *, platform_name: str, query_key: str, **request_kwargs: Any, ) -> dict[str, Any]: last_error: Exception | None = None for attempt in range(1, API_MAX_RETRIES + 1): try: response = session.post( url, timeout=API_REQUEST_TIMEOUT_SECONDS, verify=False, **request_kwargs, ) response.raise_for_status() return parse_response_json_or_raise(response, platform_name, query_key) except (requests.RequestException, RuntimeError) as exc: last_error = exc if attempt >= API_MAX_RETRIES: break sleep_seconds = API_RETRY_BASE_SECONDS * attempt print( f"[重试] {platform_name} 查询键 {query_key} 第 {attempt}/{API_MAX_RETRIES} 次失败:" f"{exc},{sleep_seconds:.1f} 秒后重试。" ) time.sleep(sleep_seconds) raise RuntimeError( f"{platform_name} 接口请求失败,查询键 {query_key}," f"重试 {API_MAX_RETRIES} 次后仍未成功:{last_error}" ) from last_error