这里说一个场景,如果你每天需要从一些网站或者APP一条条数据复制到表格里面,这样效率是很差的。
这种场景适合编写程序,把网页数据采集下来,解析里面的数据,最后保存起来。
这篇文章我们讲讲怎么用 Python 的 Scrapy 去做数据采集。Scrapy 的整体架构很漂亮,它把请求调度、下载、解析、存储这几件事拆得很干净,每个 Spider 只管自己的解析逻辑。这个「关注点分离」的设计在多任务场景下确实好用,代价就是简单抓取任务也得搭一套完整的项目骨架,上手成本不算低。常规用法是敲 scrapy crawl spider_name 跑命令行,适合单次抓取或调试阶段。但问题来了,当我们要把爬虫嵌入到更大的 Python 应用里,需要动态传参、自定义输出格式、甚至把结果直接喂给下游的消息队列,CLI 那套就捉襟见肘了。命令行能传的参数有限,输出格式被框架绑死,想要在抓取完立刻触发一段业务逻辑,得绕一大圈。
Scrapy 提供了 CrawlerProcess 这个入口,允许你在 main.py 里用纯 Python 代码启动爬虫,同时动态注入配置。这个能力的关键在于,你可以在运行时往 ITEM_PIPELINES 里塞自定义的 Pipeline 类,把抓取结果截获到内存数组里,后续的格式化、推送、入库全部由主流程控制,不用写文件再读取这么绕。说白了就是把 Scrapy 从一个独立的命令行工具变成了你项目里的一个可编程组件。
实际落地的时候,很多目标站点需要登录才能看到数据。Scrapy 的 FormRequest 专门处理这种场景,核心代码如下:
yield scrapy.FormRequest(
url="[登录地址]",
formdata={'username': 'user', 'password': 'pass'},
callback=self.after_login
)
这段代码做的事情很直白,向登录接口发一个 POST 请求,带上表单字段,登录成功后把响应丢给 after_login 回调继续处理。登录之后的页面通常是一个 HTML 表格,我们用 XPath 按行列索引定位单元格:
def _get_item(self, response, row, col):
return response.xpath(f'//table/tr[{row}]/td[{col}]//text()').getall()
这里有个容易忽略的细节,getall() 拿到的是列表,一个单元格里可能存在多个文本节点,拼接的时候直接 join 了事会踩坑,空白字符和嵌套标签都会混进结果里,生产环境排查这种脏数据极其头疼。
再说生产环境。解析函数的第一行必须是状态码检查,response.status == 200 不过就别往下走了,否则你在错误页面上跑 XPath,拿到的全是垃圾数据还不好排查。很多团队漏掉这步,结果在目标站维护期间抓了一堆 503 页面的内容当正经数据入库,数据清洗的时候才发现已经晚了。
IP 封禁是另一个绕不开的问题。目标站的反爬策略一旦触发,你的请求直接 403 或者跳验证码页面。应对方案是动态 IP 轮换,把请求隐藏在代理墙后面。我们在这块用的是蜻蜓代理的动态代理池,接入成本基本就是配一个代理中间件,把请求的 IP 分散掉。
验证码这边思路也类似,写一个 Scrapy 中间件拦截验证码图片,丢给 OCR 库识别成文本再自动回填,整个链路串起来就是个自动过验证的闭环。不过 OCR 的识别率取决于验证码类型,那种扭曲变形加干扰线的图形验证码,识别准确率会掉得很快,得根据实际情况评估是不是值得做自动化。
最后一个工程考量是内容变化的跟踪。如果目标页面更新不频繁,可以当静态页面处理,调度器定时跑一次,跟上次的结果做 diff 就行,不需要每次全量抓。
转载请注明
- 蜻蜓代理 - Scrapy爬虫开发 - 表单登录与结构化数据采集
- 头条号 - 蜻蜓软件
- 微信公众号:蜻蜓软件(qingtingsoft)

