"""应用异常定义:所有业务异常均继承自 AppError 错误码规范: - 1xxx: 请求/鉴权错误 - 2xxx: 浏览器资源错误 - 3xxx: Cloudflare/反爬相关错误 - 4xxx: 页面解析错误 """ class AppError(Exception): """应用基础异常,携带错误码、HTTP 状态码和是否可重试信息""" def __init__( self, message: str, code: str, err_code: int, retryable: bool = False, status_code: int = 400, headers: dict[str, str] | None = None, ): super().__init__(message) self.message = message self.code = code self.err_code = err_code self.retryable = retryable self.status_code = status_code self.headers = headers class AuthenticationError(AppError): """鉴权失败(Bearer Token 无效或缺失)""" def __init__(self, message: str = "Invalid credentials"): super().__init__( message=message, code="AUTH_INVALID", err_code=1001, retryable=False, status_code=401, headers={"WWW-Authenticate": "Bearer"}, ) class BrowserUnavailableError(AppError): """浏览器不可用(未启用或启动失败)""" def __init__(self, message: str = "Browser pool is unavailable"): super().__init__(message=message, code="BROWSER_UNAVAILABLE", err_code=2001, retryable=True) class ResourceBusyError(AppError): """浏览器资源繁忙(获取页面槽位超时)""" def __init__(self, message: str = "Timed out while waiting for a browser slot"): super().__init__(message=message, code="RESOURCE_BUSY", err_code=2002, retryable=True) class CloudflareChallengeError(AppError): """Cloudflare 挑战未在规定时间内通过""" def __init__(self, message: str = "Cloudflare challenge did not clear in time"): super().__init__(message=message, code="CLOUDFLARE_CHALLENGE", err_code=3001, retryable=True) class UpstreamBlockedError(AppError): """上游请求被 Cloudflare 阻断(如 1020/403 错误)""" def __init__(self, message: str = "The upstream request was blocked"): super().__init__(message=message, code="UPSTREAM_BLOCKED", err_code=3002, retryable=True) class ScrapeParseError(AppError): """页面解析失败""" def __init__(self, message: str = "Failed to parse the target page"): super().__init__(message=message, code="PARSE_ERROR", err_code=4001, retryable=False)