我为什么放弃Scrapy转投Playwright?爬虫框架选择的真相

一个让我损失48小时的教训

上个月接了个爬虫单子,对方要爬一个教育类网站,页面结构不复杂,但所有核心数据都通过JavaScript动态渲染。我第一反应:Scrapy嘛,老本行了。结果一跑,全乱码,频繁被ban,忙活两天没产出。最后换成Playwright,6小时搞定。48小时的代价让我重新审视爬虫框架的选择——选错了,后面全是坑。

Scrapy是被高估的

Scrapy在2024年仍然是最流行的Python爬虫框架之一,但它的问题同样明显。

异步处理存粹是噩梦。Scrapy基于Twisted,回调地狱式写法。你看一段Scrapy代码,往往要追踪parse函数里层层嵌套的yield。

# Scrapy的典型回调写法# 注意:解析一个页面要三个回调,人脑很难跟踪classMySpider(scrapy.Spider):name='example'start_urls=['http://example.com']defparse(self,response):# 提取详情页链接后,交给下一个回调urls=response.css('a::attr(href)').getall()forurlinurls:yieldscrapy.Request(url,callback=self.parse_detail)defparse_detail(self,response):# 再处理二次请求item_url=response.css('.next-link').attrib['href']yieldscrapy.Request(item_url,callback=self.parse_final)defparse_final(self,response):# 最终提取数据yield{'data':response.text}

这种写法在三个以上回调时就变成面条。而Playwright用async/await,逻辑平铺:

# Playwright的异步写法——线性、直观asyncdefrun_scraper():asyncwithasync_playwright()asp:browser=awaitp.chromium.launch()page=awaitbrowser.new_page()# 打开首页awaitpage.goto('http://example.com')# 获取所有详情页链接urls=awaitpage.eval_on_selector_all('a','elements => elements.map(e => e.href)')# 直接循环处理每个详情页forurlinurls[:5]:# 前5个awaitpage.goto(url)# 再点击一个按钮获取更多数据awaitpage.click('.next-link')# 提取最终数据data=awaitpage.text_content('body')print(data)awaitbrowser.close()

动态内容支持是Scrapy的硬伤。Scrapy原生只拿HTML,对于JS渲染的数据,你需要额外集成Splash或Selenium。这不只是多装个包的问题,调试Splash中间件写个配置要折腾一整天。而Playwright天生就是浏览器,自动执行JS。

我指出的踩坑点

  1. Scrapy的Downloader中间件顺序经常搞错。新手总以为中间件是按代码顺序执行,实际上是按优先级数字排序,默认一个中间件550-1000,你自定义中间件如果不设置优先级,就会在默认中间件之后执行,这点坑过至少5个朋友。
  2. scrapy.Request传cookie时,直接用cookies参数会导致请求头重复,正确做法是用headers里的Cookie字段。
# 错误写法:Scrapy的cookies参数容易出问题yieldscrapy.Request(url,cookies={'sessionid':'abc123'})# 正确写法:直接塞headersyieldscrapy.Request(url,headers={'Cookie':'sessionid=abc123'})
  1. 默认并发16个请求,但很多网站防火墙会封快速连续请求。别指望自动限速,你得手动设置CONCURRENT_REQUESTS_PER_DOMAIN = 2

为什么Playwright是2024年更好的选择

坦白讲,如果今天让我从零开始选爬虫框架,我会直接上Playwright。三个理由:

第一,天然处理JS动态页面。现在90%的风控网站都不简单返回HTML,而是先返回空壳,再通过XHR或fetch加载数据。Scrapy需要手动监听网络请求,Playwright可以直接等网络空闲:

# Playwright等待所有网络请求完成awaitpage.goto('https://example.com',wait_until='networkidle')# 此时页面已经完全渲染,包括所有异步加载的数据data=awaitpage.content()

第二,反爬能力更强。Playwright可以模拟真实浏览器指纹、操作鼠标移动、甚至随机等待时间。我在一个项目中同时用Scrapy+Splash和Playwright,Playwright的通过率比Scrapy高3倍。具体说,Scrapy抓政务网站(如社保查询)基本秒封,Playwright却能稳定运行超过48小时。

第三,调试体验碾压。Playwright有官方Playwright Inspector,可以一步步回放每一步操作。Scrapy调试要么打印response.text,要么用scrapy shell,体验糟糕。

但Playwright也不是万能药。它更重资源,每个浏览器实例吃几百兆内存。如果只是爬静态API接口,用Playwright就是杀鸡用牛刀。

轻量方案Requests+BeautifulSoup的适用边界

很多人觉得Scrapy很重,就转向Requests+BeautifulSoup。这套组合确实轻快,但只适用于两个场景:

  1. 目标页面是静态HTML(服务端渲染)
  2. 只需要单页面抓取,不需要爬取整站链路

我曾用Requests爬一个博客站,十几行代码搞定:

importrequestsfrombs4importBeautifulSoup# 直接请求,没有回调,没有中间件resp=requests.get('https://blog.example.com')soup=BeautifulSoup(resp.text,'lxml')titles=soup.select('h2.article-title')fortintitles:print(t.text.strip())

但一遇到翻页、登录、验证码,这套方案就废了。你需要手动管理cookie,构造多步请求。Scrapy至少帮你处理了cookie自动管理、重试、去重。所以当需要爬5个以上页面且有登录需求时,直接上Playwright更省事。

我和一个同事做过测试:同样爬取一个包含1000个详情页的电商网站,Scrapy爬虫代码写了300行(含中间件、管道、item定义),Playwright只用了150行。性能上,Playwright慢20%-30%,但代码可维护性差距巨大。

别被Scrapy的生态忽悠

Scrapy有大量第三方组件:scrapy-splash、scrapy-selenium、scrapy-redis等。但这些组件质量参差不齐。比如scrapy-redis,在2023年就停止维护了,最新版Scrapy 2.11(2024年6月发布)与scrapy-redis存在兼容问题。你花时间研究这些组件,不如直接学Playwright。

还有Scrapy的回调模型,对函数式编程不熟悉的人就是折磨。我曾在一个项目里需要嵌套5层回调,调试到怀疑人生。后来重构为Playwright的async/await模式,逻辑清晰了,bug减少80%。

2024年6月的Stack Overflow调研显示,Playwright在自动化领域的关注度首次超过Scrapy。这不是偶然,而是趋势。

踩坑总结

  • Scrapy的日志系统:默认INFO级别打印大量信息,生产环境建议设为WARNING:LOG_LEVEL = 'WARNING'
  • Playwright的浏览器生命周期:不要重复launch browser,应该复用BrowserContext。每个Context相当于一个独立的浏览器实例,但开销小得多。
# 错误:每次请求都打开一个新浏览器asyncdefscrape_all():forurlinurls:asyncwithasync_playwright()asp:browser=awaitp.chromium.launch()page=awaitbrowser.new_page()awaitpage.goto(url)awaitbrowser.close()# 正确:复用浏览器实例asyncdefscrape_all():asyncwithasync_playwright()asp:browser=awaitp.chromium.launch()context=awaitbrowser.new_context()forurlinurls:page=awaitcontext.new_page()awaitpage.goto(url)awaitpage.close()awaitbrowser.close()
  • 反爬优先级:别依赖User-Agent随机化,现在网站更多通过TLS指纹、WebDriver检测来封你。Playwright可以用--disable-blink-features=AutomationControlled参数关闭自动化标记。

我的最终建议

如果你的爬虫目标网站超过50%需要JS渲染,或者需要处理登录、验证码,直接选Playwright。如果只是抓几个静态页面,Requests+BeautifulSoup足够了。Scrapy只适合那种需要大规模、纯静态内容的爬虫,比如爬取整个Wikipedia数据——但2024年,这类场景越来越少。

你可能会说Scrapy性能比Playwright好,确实,纯数据抓取场景Scrapy每秒能处理上千请求,Playwright只能处理几十个。但现实是,大多数网站都不允许你每秒发一千个请求。你把并发降到2-5时,两者的差距微不足道。

有人问我为什么不用Scrapy+Playwright混合?试过,最后发现光调度两种工具的内存和异常处理就累死人。不如只选一个,精通它。

我想听听你踩过哪些爬虫框架的坑?特别是那些看起来很美、用起来很脏的框架,欢迎在评论区开喷。