# Scrapy一个老派但实用的Python爬虫框架在Python的爬虫生态系统里Scrapy算是个老江湖了。它最早在2008年就诞生这些年虽然不断有新工具冒出来但Scrapy依然在工作岗位上游刃有余。这篇文章从我实际踩坑和使用的角度聊聊这个框架。它到底是什么如果把写爬虫比作烹饪Scrapy就像是给你一套标准厨房——有锅有灶有刀有案板需要什么东西基本都能在架子上找到。它不是一个简单的HTTP请求库也不是一个HTML解析器而是一个完整的网页抓取框架。Scrapy的核心设计思路是异步非阻塞的。它基于Twisted事件驱动引擎这意味着当你爬取几百个页面时不需要像写同步代码那样一个个等着响应。这个设计选择挺有意思——在它诞生的年代asyncio还没进入Python标准库Twisted几乎是唯一成熟的异步方案。虽然现在有了aiohttp、httpx这些新选择但Scrapy基于Twisted的架构经过十多年的打磨稳定性和性能都相当可靠。它的工作流程画出来大概是这样的引擎Engine负责调度整个流程调度器Scheduler管理待爬取的URL队列下载器Downloader负责发请求拿响应爬虫Spider负责解析数据项目管道Item Pipeline负责处理抓取到的数据。它能做什么实际工作中Scrapy最擅长的是大规模的结构化数据采集。比如你要抓取某个电商平台的所有商品信息包括标题、价格、评价等几千个字段或者监控某个新闻网站的文章更新——这类任务Scrapy做起来很顺手。举个例子我们团队之前需要收集某个二手交易平台的房源信息每天要抓取上百万条记录。用Scrapy配合自建的去重服务、代理池和定时任务整个过程运行了大半年除了偶尔需要更换反爬策略基本没出过大问题。Scrapy还内置了非常多实用的功能。自动限速机制可以防止把人家网站打挂HTTP缓存能减少重复请求日志系统方便排查问题扩展机制允许自定义各种中间件。这些功能单独拿出来每个都能写一大段但Scrapy把它们整合得比较自然。怎么使用先来个最快的上手。一个基本的Scrapy项目大概长这样scrapy startproject myproject cd myproject scrapy genspider example example.com在spiders目录下有一个爬虫文件里面的代码结构通常是这样importscrapyclassExampleSpider(scrapy.Spider):nameexampleallowed_domains[example.com]start_urls[http://example.com]defparse(self,response):foriteminresponse.css(div.item):yield{title:item.css(h3::text).get(),price:item.css(span.price::text).get()}next_pageresponse.css(a.next::attr(href)).get()ifnext_page:yieldresponse.follow(next_page,self.parse)看起来很简单对吧但实际写起来最大的坑往往在CSS选择器和XPath表达式的调试上。我倒觉得与其花时间琢磨那些复杂的表达式不如多熟悉用response.css()和response.xpath()配合.re()进行正则提取的方式。比如提取邮箱地址直接用CSS选择器加正则比写XPath要简洁得多emailsresponse.css(div.content::text).re(r[a-zA-Z0-9._%-][a-zA-Z0-9.-]\.[a-zA-Z]{2,})管道Pipeline是Scrapy里一个比较有深度的设计。在处理数据的时候可以把它看作一个流水线——捕捉到的数据从爬虫出来经过一系列处理步骤去重、清洗、存储等。每个步骤都可以写成一个类通过优先级调整执行顺序。举例来说你可以在管道里先做数据验证然后做字段补全最后写入数据库。哪个环节出错了也好排查不会所有逻辑都揉在爬虫文件里。最佳实践聊几个实际项目中踩过的坑总结出来的经验。第一个是关于请求去重。Scrapy默认的RFPDupeFilter基于请求的URL和请求体做指纹校验这在大部分情况下够用。但如果你爬的网站有动态参数或者URL里有无关的随机数就需要重写去重逻辑。个人更倾向于用请求的标准化URL做去重配合BloomFilter来降低内存占用——几千万个URL的去重用一个400MB左右的BloomFilter就够用了。第二个是关于中间件的责任划分。很多新手喜欢把所有逻辑都塞进Downloader Middleware包括Cookie管理、代理切换、请求重试、自定义请求头等等。但最好的做法是让每个中间件只负责一件事。比如专门写个ProxyMiddleware管代理再写个RetryMiddleware管重试和请求超时。这样调试的时候禁用某个中间件就能精确定位问题来源。第三个关于数据存储的策略选择。如果抓取的数据量在百万级别以下最简单的做法是直接用Item Pipeline写入JSON文件。数据量再大一点可以考虑批量写入。一条一条写数据库就像用勺子舀水如果改为每100条或每500条一次写入性能提升是很明显的。这个缓冲池在Pipeline里实现起来也不复杂classBatchPipeline:def__init__(self,batch_size500):self.batch_sizebatch_size self.buffer[]defprocess_item(self,item,spider):self.buffer.append(dict(item))iflen(self.buffer)self.batch_size:self.flush()returnitemdefflush(self):# 写数据库或写入文件passdefclose_spider(self,spider):ifself.buffer:self.flush()和同类技术对比经常有人问我Scrapy和RequestsBeautifulSoup、Selenium、PySpider这些工具怎么选。说实话选择其实取决于具体的应用场景。如果只是偶尔抓取几十个网页做数据分析用Requests配合BeautifulSoup完全够用。就像在家里做饭没必要把专业厨房的设备都搬出来。Scrapy在这类小任务上反而显得笨重——项目初始化、配置settings.py、调试思路都得切换到框架模式。如果需要处理大量JavaScript渲染或复杂的交互操作直接上Scrapy效果不太好。虽然配合Splash或者Scrapy-Playwright可以解决但调度和稳定的成本比直接用Selenium或者Playwright要高。有次爬一个复杂的单页应用折腾了将近一周的Splash配置结果后来用Playwright重写两天就搞定了。在这个场景下Scrapy还真不是最佳选择。PySpider比较有意思它提供了一个基于浏览器的开发界面和分布式架构这些Scrapy是欠缺的。但PySpider已经好几年没更新了Python3.10之后基本跑不起来。相比之下Scrapy保持了持续更新生态也更活跃周边插件丰富得多了。总的来说Scrapy最适合的场景是有清晰数据结构的、需要规模化采集的文本信息。如果你要做一个每天抓取几十万甚至上百万页的爬虫Scrapy的成熟度和稳定性是其他工具很难替代的。它不像那些新玩具那么时髦但确实能稳定地产出——这一点做久了就明白了。