79 lines
2.5 KiB
Python
79 lines
2.5 KiB
Python
"""应用异常定义:所有业务异常均继承自 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)
|