Python 爬虫总被 403?requests、Scrapy、Playwright、curl_cffi 对比和增量抓取步骤

🔑 关键词:Python爬虫,反爬,curl_cffi,Playwright,Scrapy

📖 摘要:一个业余爬虫作者的踩坑记录:从 requests 硬怼到 curl_cffi + Scrapy 限流,包含请求库对比、代理池、Redis 去重、增量抓取和 SQLite WAL 参数。

Python 爬虫总被 403?requests、Scrapy、Playwright、curl_cffi 对比和增量抓取步骤

图片

先说结论,可能会得罪一些教程:2025 年还拿 requests.get() 硬怼列表页,然后怪代理不行的,基本属于拿木棍打坦克。我去年帮朋友盯一个二手书价格,站点不算大,日活可能就几万。最开始我用 requests + BeautifulSoup,单机 8 线程,跑 20 分钟,HTTP 200 占比 23%,剩下是 403、429、还有一堆“请稍后重试”的假页面。我当时不信邪,换了 20 个免费代理,结果更惨,成功的那几次返回的是代理商的广告页。后来我才承认:反爬不是单点问题,它是在看你的 TLS 指纹、请求头顺序、IP 段、访问节奏、Cookie 生命周期,甚至你点开详情页之前有没有先滑列表页。

图片

对比维度我拉了个表,不是学院派评测,就是自己踩坑后记的。requests 最简单,同步,TLS 指纹是 Python 默认的,很多 WAF 一眼识别;httpx 支持 HTTP/2 和异步,但默认指纹也没好到哪去;curl_cffi 我用的最多,因为它能模拟 Chrome 的 TLS/JA3,impersonate='chrome124' 这种参数不是玄学,至少在我那个书站上,同样 IP 同样 UA,成功率从 23% 拉到 87% 左右。Playwright 更重,启动一个 Chromium 大概 1.8 秒,开 3 个上下文内存就 450MB 上下,但遇到必须执行 JS 才出价格的页面,它省命。Scrapy 不是请求库,它是调度框架,强在并发、重试、去重、管道,但如果你不配 AUTOTHROTTLE,它也能把站打挂。我的独立观点:选型别问“哪个最强”,先问你愿意为“身份可信”付多少内存和钱。Playwright 是重装步兵,curl_cffi 是轻骑兵,Scrapy 是后勤部,requests 是自行车——都能到终点,但路况不一样。

图片

真正让我少封号的,不是某个库,而是把采集器当成限流系统。步骤大概是这样:1)先定 URL 指纹,我用 sha1(domain + path + 关键query),入库唯一索引;2)列表页只抓字段:标题、链接、更新时间,存 SQLite,10 万条插入大概 12 秒,够用;3)详情页用 curl_cffi 异步,impersonate='chrome120',每个域名 CONCURRENT_REQUESTS_PER_DOMAIN=4,DOWNLOAD_DELAY=0.8,再开 AUTOTHROTTLE_ENABLED=True,AUTOTHROTTLE_TARGET_CONCURRENCY=2.0;4)代理池不要一上来全用,我先直连跑,403 超过 5% 才切代理,代理按域名给权重,失败 3 次冷却 10 分钟;5)去重用 Redis 布隆过滤器,10 万 URL 大概 6MB 内存,误判率设 0.01,m=9585058,k=7,能挡住大部分重复详情页;6)失败重试用指数退避,第 1 次 2 秒,第 2 次 8 秒,第 3 次 30 秒,还失败就进死信队列,别死磕。代码不长,核心是 curl_cffi.requests.AsyncSession(impersonate='chrome124'),配合 asyncio.Semaphore(8)。但这里有个小瑕疵:我一开始把 Semaphore 设成 50,结果本地 NAT 先崩了,路由器重启两次,被家人骂了一顿。

图片

增量抓取是另一个被低估的点。很多教程教全量跑,每天重抓 10 万页,数据没变,IP 先没了。我现在会加三张表:seed_url、page_state、change_log。page_state 存 url_hash, last_status, etag, last_modified, content_hash, next_check_at。如果响应带 ETag 或 Last-Modified,下次带 If-None-Match / If-Modified-Since,304 就跳过解析;没有的话,抓完正文算 simhash,汉明距离小于 3 视为没变。TTL 也不是拍脑袋:价格类 15 分钟,库存类 5 分钟,文章类 24 小时,冷门详情页 7 天。这样下来,同样 10 万 URL,日请求从 10 万降到 1.2 万左右,代理成本从每月 300 元降到 80 元上下。还有个坑:SQLite 并发写会 database is locked,我后来开 WAL 模式,PRAGMA journal_mode=WAL; PRAGMA synchronous=NORMAL;,写入才稳定。别小看这些参数,它们比换十个 User-Agent 有用。

图片

最后说合规和心态。我见过有人抓公开裁判文书,也见过有人抓手机号,前者可能游走边界,后者直接别碰。《个人信息保护法》和 robots.txt 不是摆设,尤其是登录后才能看的数据、验证码后面的数据、别人明确拒绝的接口,不要因为“技术上能抓”就去抓。我的做法是只抓公开、非个人、低频更新的数据,请求里带一个能联系到我的 UA,比如 MyBookWatcher/1.0 (+contact: example@example.com),虽然有点傻,但至少对方想封你之前知道你是谁。如果你只是想练手,我建议从 1000 个 URL、单域名、QPS 不超过 1 开始,把日志、重试、去重、断点续爬跑通,再谈分布式。爬虫的终点不是“抓得更多”,而是“用更少请求拿到够用的数据”。这句话有点鸡汤,但我被 403 教育了半个月后,是真的信了。

图片

🏷️ 标签: