在工业数据采集场景中,纯 HTTP 框架面对动态渲染、指纹校验类站点时,极易触发目标站点的防护机制导致采集失败。而纯浏览器驱动方案虽然通过率高,但并发能力弱、资源消耗大,难以支撑中等规模的采集任务。
Selenium 与 Scrapy 双框架融合是业界常用的折中方案。由 Scrapy 承担请求调度、数据解析、持久化的流水线工作,Selenium 负责页面渲染与交互对抗防护,两者通过下载中间件无缝衔接,兼顾采集效率与通过率。
一、前期准备
本方案基于 Python 3.9+ 版本,需提前安装核心依赖库。
pipinstallscrapy selenium undetected-chromedriver两套框架的职责边界非常清晰:
- Scrapy:负责请求队列调度、页面解析、数据清洗、管道持久化,提供工程化的采集流水线
- Selenium + undetected-chromedriver:负责浏览器渲染、JS 执行、交互模拟,承担绕过站点防护的核心职责
二、核心架构设计
融合方案的核心是 Scrapy 的下载器中间件(Downloader Middleware)。
中间件拦截原本由 Twisted 异步发送的 HTTP 请求,将需要渲染的 URL 转交给 Selenium 驱动浏览器加载。
浏览器完成页面渲染后,将完整的页面源码封装为 Scrapy Response 对象,交回上层 Spider 解析。
该架构对业务层完全透明。上层 Spider 的编写方式与纯 HTTP 采集完全一致,底层切换渲染引擎无需修改业务解析代码,迁移成本极低。
实际项目中通常采用混合调度模式:静态接口与资源走原生 HTTP 通道,动态页面走浏览器渲染通道,按需分配实现效率最大化。
三、分步实操
3.1 项目初始化
首先创建标准 Scrapy 项目,在 middlewares.py 中编写自定义 Selenium 中间件。
scrapy startproject collect_democdcollect_demo scrapy genspider demo_spider example.com3.2 实现 Selenium 下载中间件
中间件负责浏览器实例的生命周期管理,以及请求的拦截与响应封装。
通过request.meta标记控制是否启用浏览器渲染,未标记的请求继续走原生下载链路。
浏览器初始化与生命周期管理:
classSeleniumRenderMiddleware:def__init__(self):options=uc.ChromeOptions()options.add_argument("--headless=new")options.add_argument("--disable-gpu")self.driver=uc.Chrome(options=options)@classmethoddeffrom_crawler(cls,crawler):middleware=cls()crawler.signals.connect(middleware.close_driver,signals.spider_closed)returnmiddlewaredefclose_driver(self,spider):self.driver.quit()请求拦截与渲染逻辑:
defprocess_request(self,request,spider):# 未标记的请求走原生下载链路ifnotrequest.meta.get("use_selenium",False):returnNoneself.driver.get(request.url)body=self.driver.page_source.encode("utf-8")returnHtmlResponse(request.url,body=body,request=request)生产环境建议通过 Spider 关闭信号主动释放浏览器资源,避免 Chrome 进程残留。可根据需求追加禁用图片、随机 UA 等启动参数。
3.3 启用中间件配置
在 settings.py 中注册中间件,并调整基础采集参数。
注意中间件优先级需高于系统默认下载中间件,才能成功拦截请求。
DOWNLOADER_MIDDLEWARES={"collect_demo.middlewares.SeleniumRenderMiddleware":543,}DOWNLOAD_DELAY=2RANDOMIZE_DOWNLOAD_DELAY=TrueCONCURRENT_REQUESTS_PER_DOMAIN=23.4 编写采集爬虫
Spider 层的写法与常规 Scrapy 爬虫完全一致。
只需在需要渲染的请求 meta 中添加use_selenium=True标记,即可自动走浏览器渲染通道。
importscrapyclassDemoSpider(scrapy.Spider):name="demo_spider"start_urls=["https://example.com/list"]defstart_requests(self):forurlinself.start_urls:yieldscrapy.Request(url,meta={"use_selenium":True})defparse(self,response):foriteminresponse.css(".item-card"):yield{"title":item.css(".title::text").get(),"link":item.css("a::attr(href)").get()}3.5 数据持久化
通过 Scrapy 原生的 Item Pipeline 实现数据落地。
支持写入 CSV、JSON、MySQL、MongoDB 等多种存储介质,与常规采集项目配置完全一致。
四、防护机制对抗策略
4.1 浏览器指纹隐匿
使用 undetected-chromedriver 替代原生 ChromeDriver,默认移除 webdriver 标识、自动化插件等常见检测特征。
额外随机化窗口尺寸、语言、时区等环境参数,避免批量采集任务的特征同质化。
针对高阶检测站点,可注入 JS 覆盖 navigator.webdriver、plugins 等敏感属性。
4.2 人机行为模拟
固定的请求间隔与操作路径是最明显的机器特征。
通过随机化下载延迟、页面加载后滚动到底部、随机悬停元素等操作,模拟真实用户的浏览行为。
单域名并发数控制在 3 以内,避免短时间内大量请求触发风控阈值。
4.3 代理 IP 池接入
在浏览器启动参数中配置代理,结合 Redis 实现 IP 池轮换。
单 IP 处理指定数量请求后自动切换,避免单 IP 访问频率过高被封禁。
优先选择高匿代理,透明代理会直接暴露真实 IP,失去防护意义。
4.4 Cookie 池复用
对于需要登录的站点,提前批量生成有效登录态并存入 Cookie 池。
每次请求随机选取 Cookie 注入浏览器,分散单账号的访问压力。
定期更新 Cookie 池,清理失效的登录态,保证采集稳定性。
五、工业级稳定性保障
5.1 超时与重试机制
设置页面加载超时时间,超时后主动终止并刷新重试,最多重试 3 次。
捕获网络异常、代理失效、页面崩溃等异常场景,失败请求重新入队调度。
使用 WebDriverWait 显式等待关键元素加载,替代固定时长 sleep,兼顾效率与可靠性。
5.2 资源泄漏防护
全局复用浏览器实例,禁止每个请求创建新的 Driver。
绑定 Spider 关闭信号,程序退出时主动调用 quit() 释放浏览器进程。
设置浏览器最大处理请求数,达到阈值后自动重启浏览器,清理缓存与内存占用。
5.3 断点续爬能力
将已采集的 URL 存入 Redis 去重集合,任务中断后可从断点恢复。
关键采集进度定期持久化,异常退出不会丢失任务状态,避免重复采集与数据遗漏。
六、常见问题排查
6.1 页面源码为空,数据不完整
多为页面未完全渲染就读取了源码。
增加显式等待逻辑,等待目标数据对应的 DOM 元素出现后,再返回响应。
若为懒加载页面,需模拟滚动操作触发数据加载后再采集。
6.2 运行久了 Driver 无响应
通常是浏览器内存泄漏或进程崩溃导致。
启用浏览器自动重启机制,处理固定请求数后重建 Driver 实例。
检查页面是否存在大量弹窗、广告未关闭,持续占用浏览器资源。
6.3 频繁触发人机验证
首先降低请求频率,增加行为模拟步骤,拉长单页面停留时间。
若仍无法解决,更换代理 IP 段,避免整个 IP 段被站点拉黑。
高阶场景可接入第三方打码服务,自动处理验证码交互。
6.4 采集速度过慢
浏览器渲染本身比纯 HTTP 慢 3~5 倍,属于正常特性。
可通过禁用图片加载、关闭不必要的扩展、开启无头模式提升速度。
多浏览器实例并行采集可提升整体吞吐量,但需同步控制总并发避免触发防护。
七、总结与选型建议
纯 Scrapy 方案适合静态页面、无强防护的站点,速度快、资源占用低,适合大规模批量采集。
纯 Selenium 方案适合交互复杂、小规模的采集场景,灵活度高但效率偏低。
双框架融合方案是中等规模、有一定防护机制的动态站点的性价比之选。
实际项目中建议采用混合调度策略:简单静态请求走原生 HTTP,复杂动态页面走浏览器渲染,在通过率与采集效率之间找到最佳平衡点。