"""爬取专用安全防护:SSRF 校验、User-Agent、robots.txt 开关。 不复用 url_fetcher._get_validator() 的环境变量路径,直接使用 config.get_ssrf_policy(), 避免双路径漂移。 """ from urllib.parse import urlparse from yuxi.config import config from yuxi.utils.net_security import SSRFValidator, log_ssrf_block _crawl_validator: SSRFValidator | None = None def get_crawl_validator() -> SSRFValidator: """构建爬取专用的 SSRFValidator,使用 config.get_ssrf_policy()。 不复用 url_fetcher._get_validator() 的环境变量路径,避免双路径漂移。 每次调用构建新实例会导致 DNS Pinning 缓存失效,故采用模块级单例。 """ global _crawl_validator if _crawl_validator is None: _crawl_validator = SSRFValidator(config.get_ssrf_policy()) return _crawl_validator async def validate_crawl_url(url: str) -> tuple[bool, str]: """爬取前的 SSRF 校验入口。 返回 (是否通过, 原因描述)。通过时 reason 为校验器返回的通过描述。 命中黑名单时记录审计日志(仅主机名,不暴露内网信息)。 """ validator = get_crawl_validator() ok, reason = await validator.validate_url(url) if not ok: parsed = urlparse(url) log_ssrf_block(parsed.hostname or url, reason, context="web-crawl") return ok, reason def get_user_agent() -> str: """爬取专用 User-Agent,不伪装为浏览器。 与 url_fetcher.py 的伪装 UA 独立,遵循 PRD"不伪装"原则。 """ return "YuxiCrawler/1.0 (+https://github.com/ForcePilot/yuxi)" def should_respect_robots() -> bool: """是否尊重 robots.txt,仅管理员可关闭。""" return config.web_crawl_respect_robots