"""带限速、重试、SSL 宽松回退的 HTTP 抓取。""" import time import requests import urllib3 from config import MAX_RETRIES, REQUEST_DELAY, REQUEST_TIMEOUT, USER_AGENT # 站点证书在部分环境下校验失败(curl 需 -k),先严格再回退宽松 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) _session = requests.Session() _session.headers.update({"User-Agent": USER_AGENT}) _last_request_ts = 0.0 def _throttle() -> None: """简单限速:保证两次请求之间至少间隔 REQUEST_DELAY 秒。""" global _last_request_ts now = time.monotonic() wait = REQUEST_DELAY - (now - _last_request_ts) if wait > 0: time.sleep(wait) _last_request_ts = time.monotonic() def _get(url: str, **kwargs) -> requests.Response: """GET,SSL 校验失败时自动回退到不校验证书。""" try: return _session.get(url, timeout=REQUEST_TIMEOUT, verify=True, **kwargs) except requests.exceptions.SSLError: return _session.get(url, timeout=REQUEST_TIMEOUT, verify=False, **kwargs) def fetch_text(url: str) -> str: """抓取并返回 UTF-8 文本,带重试。""" last_err: Exception | None = None for attempt in range(1, MAX_RETRIES + 1): _throttle() try: resp = _get(url) resp.raise_for_status() resp.encoding = resp.apparent_encoding or "utf-8" return resp.text except requests.RequestException as e: last_err = e time.sleep(0.8 * attempt) raise RuntimeError(f"抓取失败 {url}: {last_err}") def fetch_bytes(url: str) -> bytes: """抓取二进制(图片等),带重试。""" last_err: Exception | None = None for attempt in range(1, MAX_RETRIES + 1): _throttle() try: resp = _get(url) resp.raise_for_status() return resp.content except requests.RequestException as e: last_err = e time.sleep(0.8 * attempt) raise RuntimeError(f"抓取失败 {url}: {last_err}")