
在实际的爬虫开发项目里最容易被低估的问题不是解析规则写不对而是明明已经在本地跑通了换一个站点或者隔几天再跑整条任务却突然失效。原因通常不是代码本身而是我们过早地假设了目标站点可用页面结构没变、接口没过期、编码没换、反爬没升级。要减少这种返工可以在真正执行抓取之前加一道探测probe流程。所谓探测就是用一个极小的请求先检查目标站点的可达性、正文类型、robots 约束、渲染方式、登录墙和关键数据是否存在再根据探测结果判断当前方案能不能继续。这个思路和部署前的健康检查很像非常适合做爬虫任务编排、采集平台接入新数据源以及需要自动化验证的爬虫服务。下面用最小可运行代码从零实现一个带探测能力的 Python 爬虫脚手架演示如何用一份探测报告决定抓取任务是否真的值得执行。1. 为什么爬虫要先探测站点而不是直接开抓1.1 爬虫失效往往不是代码错了而是前提变了很多爬虫项目的维护成本都来自“目标站点悄悄变化”。今天还能正常解析的页面明天可能因为加了登录跳转、改成了div[data-id]、把数据迁移到接口异步加载导致昨天的代码全部失效。这时候回滚、改 Selector、重新调试费时费力。这里的关键问题是我们在没有验证前提的情况下直接做了“承诺”。爬虫代码一旦进入生产任务就等于承诺“当前站点可以用当前方案抓取”。但 Web 页面天然是不稳定的它由目标方控制随时可能调整。正确的做法是把抓取任务拆成两个阶段探测阶段用少量请求确认站点当前状态是否满足抓取条件。抓取阶段只有在探测通过后才批量请求并解析数据。这个拆分很像数据库迁移前的 preflight 检查也像服务发布前的健康检查。先确认环境再执行高风险动作。1.2 探测是什么它和试抓有什么区别探测不是“试着抓一次数据”。它只关心关于页面的元信息目标是回答“这个站点现在能不能抓、按现有方案能不能抓、抓之前还需要解决什么问题”。试抓的问题是成本太高。一个完整的抓取流程通常包含多次分页请求、数据清洗、入库、去重。如果目标站点结构已经变化这些步骤都白跑了还可能把脏数据写进库里。更糟的是完整抓取会比探测发送更多请求更容易触发目标站点的限流或风控。探测只做最轻量的请求通常是页面本身加上robots.txt有时再加一次资源类型判断。它不依赖具体业务字段也不执行入库逻辑所以失败时可以快速定位是哪一层出了问题。1.3 一份合格的探测报告应回答哪些问题在设计探测模块时可以把以下问题作为验收标准URL 是否可访问最终状态码是什么。响应内容是不是 HTML还是 PDF、图片、JSON 接口。页面声明的编码和实际内容编码是否一致。目标站点是否通过robots.txt禁止抓取。是否存在登录墙、验证码或者 403/503 这类风控特征。页面内容是否依赖 JavaScript 动态渲染。我们在配置里声明的目标关键词或 DOM 样本是否真实存在。页面文本量是否足够是否存在空壳页。这些问题都得到明确答案后爬虫任务才能进入“可执行”或“不可执行”的状态。1.4 探测结果不是永久承诺探测报告只能代表“探测这一刻”的站点状态。目标站点后续仍可能改版、加验证码、换域名所以探测结论不能写死成“这个站点永远能抓”。更合理的表达是“在 HTTP 200、页面是 HTML、robots 允许、无验证码、非动态渲染、目标字段存在这些条件都满足时当前爬虫方案可以工作。”这样不仅更准确也让任务失败时能通过探测报告快速定位原因。2. 搭建 probe-first 爬虫项目结构2.1 环境与依赖示例项目使用 Python 3.10核心依赖如下依赖版本建议作用Python3.10使用 dataclass 和现代类型标注requests2.31发送探测请求beautifulsoup44.12解析 HTML提取标题和文本lxml5.0作为 BeautifulSoup 的解析器创建虚拟环境后先安装依赖python -m venv .venv source .venv/bin/activate pip install -r requirements.txtrequirements.txt内容requests2.31,3 beautifulsoup44.12,5 lxml5.0,6版本号建议以实际安装时 pip 解析出的稳定版本为准。这里写宽松版本是为了避免镜像源或 Python 版本不同导致安装失败。2.2 项目目录和模块边界最小可运行的项目结构如下probe_first_scraper/ ├── requirements.txt ├── probe_site.py ├── decider.py ├── run_job.py ├── crawler.py └── samples/ └── mock_server.py每个文件只负责一件事文件职责