Posted on:
Last modified:
本站/公众号/专栏不误正业好久了,今天终于写一篇爬虫的文章,然而并没有案例,也没有代码, 只有踩过的坑和心法。
写了这么多年爬虫了,经常还是会撞上反爬机制。虽然大多数时候都能解决,但是毕竟反爬机制多种多样,有时候遇到一个许久不见的反爬机制,也会感到手生,一时想不上来应对方法,而浪费不少时间。最近又写了几个爬虫,接下来一段时间又不写了,趁着手还比较熟,记录一下备忘,方便大家也方便自己。
之前写过一篇常用的反爬虫封禁手段概览, 但是主要是从反爬的角度来的,这篇主要从写爬虫的角度来说说。
开章明义,当遇到反爬机制时,想要做到把数据爬下来,无非四个方法:
好多文章为了显示自己高大上,吹些什么高并发呀,分布式,机器学习破解验证码的幺蛾子,都是扯淡。与其扯这些东西,不如老老实实把数据爬下来才是王道,如果非要扯上一些 fancy 的东西,那把监控做好比啥都重要。
补充说明一下,本文探讨的是数据收集型的小型爬虫,也就是你要对少数站点在较短时间内收集大量信息。而非搜索引擎型全网爬虫,即对大量站点在较长时间内收集综合信息。(全网当然要上高并发了
我们知道计算机程序按瓶颈不同大概分为两类,CPU 密集型和 IO 密集型。CPU 密集型就是偏重计算的任务,比如说编解码啥的;IO 密集型就是偏重于网络的任务,比如说下载或者 web 服务器。那么爬虫是哪种呢?你估计要回答 IO 密集型,恭喜你答对了。但是这不是我想说的重点,重点是爬虫不光是 IO 密集型的任务,实际上我想把它称作 IP 密集型任务。如果你不能增大自己 IP 的数量,我实在不知道所谓的高并发有啥卵用。
什么是 IP 密集型任务呢?按照上面的定义我们知道,也就是说,对爬虫来说,最瓶颈的地方其实是你持有的 IP 的数量!作为一个合格的爬虫编写者,你肯定已经擅长伪造各种 HTTP headers, 破解 JS 的加密参数,但是唯独一个 -- 来源 IP -- 你是无法伪造的。好多看起来很难搞的事情,如果对方站点的小霸王服务器撑得住,只要加上足够的 IP 就很简单啦,不用绞尽脑汁去想各种策略了。
上面说了,所谓的"高并发"对爬虫没有任何卵用,那么像是 Scrapy 这种采用了协程以便提高并发的框架我就不是很懂了。以前我专门写过一篇为什么不要用 Scrapy 的文章,所以这里就不再展开细说了。
另外如果你爬虫写多了肯定有自己的一套东西了,这时候你可能会有自己的一个小框架,这是可以的。但是我还是想提两点:
言归正传,我们开始说当拿到一个站点需要爬取时该如何处理。
首先开始 easy 模式。如果你要抓的网站结构比较简单,而你要的数据也比较少。那么你首先要考虑的是不要编写爬虫. 在浏览器控制台里写个 js 表达式 console.log
一下说不定就把数据导出来了。
如果你要的数据稍微多一点时,这时候点开一个页面然后复制数据出来可能就比较复杂了。这时候可以考虑写个小脚本,别直接 while True
写个死循环就了事儿,每爬一个页面至少 time.sleep(1)
是对对方网站最起码的尊重。当然你的老板可能要数据比较急,但是多少也要悠着点。
发送 http 请求时,Host, Connection, Accept, User-Agent, Referer, Accept-Encoding, Accept-Language 这七个头必须添加,因为正常的浏览器都会有这 7 个头。 其中:
把这些填充上,至少不会被最低级的反爬手段封禁了。稍微敏感点的网站,都会禁掉 curl
和 python-urllib
这种 User-Agent 的。
另一个比较重要的事情是 Cookie,有些网站比较傻,你直接不处理 Cookie 就每次都把你当新用户,不过这种网站比较少了。还有的网站需要在首页获得一个匿名 Cookie 然后一直携带这个匿名 Cookie 就好了。还有一些网站需要 Cookie 每次请求后更新,就像浏览器一样。这些都是比较简单的方法,一开始试试就知道是哪种了。对于需要登录后的 Cookie 的,那是登录反爬的范畴,后面详谈。
陷阱链接,有一些网站上会有一些隐藏的链接,通过 CSS 或者其他方式让正常用户看不到,而爬虫不管啊,拿到链接就是一个爬,以此来识别爬虫和正常用户,不过这也都是小众做法,很少遇到了。
初学者在这里可能遇到第一个坑:动态网页。这时候可能是个好事儿,也可能是个坏事儿。如果是动态网页,数据自然是 ajax 加载的,如果 ajax 请求没有参数验证的话,那么就简单了,只是从解析 html 变成了解析 json 而已。
另一种情况是接口是需要参数验证的,这时候又分两种处理方式:
有的网站对浏览器甚至还做了一些限制,他会检测你的浏览器是正常的用户浏览器还是用程序控制的自动化浏览器。不过这都不是问题,无非就是伪造一下 webdriver 对象和 navigator 对象罢了。这个我也写过一篇具体文章讲如何伪造。
当然这时候也可能遇到情况比较简单的特殊情况,那就是对方的某个更新接口是固定的,而且加密参数里面没有时间戳,那么直接重复请求这个接口就行了。一般来说这种情况算是"瞎猫撞见死耗子", 多数情况下,接口的签名都校验了搜索的参数和时间戳,也就是你变换关键词,或者想重放请求的话是行不通的,这时候就老老实实破解吧。
一般破解 JS 其实也都不难,常用的信息摘要,或者加密方法也没多少。不过先别接着破解,花上五分钟搜索一下有没有别人已经破解过了,可能就省了你半天到几天的功夫,何乐而不为呢?
实在要开始破解的话,在 JS 的控制台中全局搜索 (Opt+Cmd+F) 一下 AES, MD5 之类的关键词,可能就有收获。另一方面在 ajax 请求上加上断点,逐步找到加密的函数。
找到加密函数之后,如果简单一点的,直接写在一个函数里的,可以抽取出来直接调用 node 执行算出参数,或者你比较勤快用 Python 重写一下都可以。然而比较棘手的是有些函数是和 window 对象或者 DOM 绑定在一起的,这时候也可以考虑把整个 JS 文件保存下来,补全需要的接口。
正如我们前面说的,作为一个爬虫老手,伪造和破解简单的加密都是基本功了,比较蛋疼的还是封禁 IP, 这你下什么苦功夫也没法解决的,是一个资本问题。
当我们爬取的速率比较快的时候,就可能被对方拉黑 IP, 这时候有可能是临时性拉黑,有可能是持续性拉黑,有可能是永久性拉黑。
永久性拉黑比较狠,也没啥办法,直接换 IP 吧。需要区分的是临时性的拉黑和持续性拉黑。如果是临时性拉黑,也就是你的请求超过阈值了就会请求失败,但是过段时间自己又恢复了,那么你程序逻辑也不用改,反正就一直请求呗,总有数据的。如果是持续性拉黑就比较坑了,也就是说如果你被拉黑了还不停止请求,那么就一直出不了小黑屋,必须停下来 sleep 几秒。这时候你的程序的逻辑就需要适应这种机制。如果是单独的脚本还好,对于一些标准化的系统就需要考虑这些机制。
一种比较简单的策略是,sleep 的间隔应该指数增加,比如第一次 sleep 10 秒,发现还是被限制,那么就 sleep 20 秒,直到一个比较大的上限或者是解除封禁。
当我们需要换 IP 的时候,肯定不能手工去记得过几分钟换一下子 IP 了,那也太烦人了,一般是需要一个 IP 池子的。
代理 IP 按照质量和来源又分为几类:
网上有一些站点会提供一些免费的代理 IP, 估计他们都是扫来的。这些 IP 可能都有无数的程序在扫描,使用他们,所以可以说是公用的 IP 了。通过收集验证这些 IP, 可以构造一个代理池子。如果实在很穷,或者抓取量不是很大,可以用这种 IP. 虽然这些 IP 特别慢,失败率特别高,总比用自己的一个出口 IP 要好一些。
比较稳定的机房 IP. 这种一般就需要花钱买了,稍微想多抓点数据,一个月 ¥100 那是起步。对付大多数的抓取已经足够了。
对于有一些变态的站点,他们甚至会验证来源 IP 的用途。比如说一看你 IP 来自阿里云机房,啥也不说直接拉黑。这时候就需要所谓的"民用 IP"了。这种有专门的厂商和 App 合作来提供民用网络出口,也可以自己买 ADSL 机器自动拨号搭建,反正成本都是非常非常高了,一个月至少 1000 起步了。
IP 毕竟算是匿名的。对于一些数据更敏感的网站来说,他们可能要求你登录后才能访问。如果数据不多,那么直接用自己账户跑一下就完了。如果每个账户的访问额度有限,或者要爬的数据特别多,那可能需要注册不少账户,这时候只要不是付费账户,那么其实都好说(除了微信). 你可以选择:
这时候不要急着去写个脚本自动化注册啥的,可能你并不需要那么多的账户。
比需要账户稍微弱一点的限制是验证码,图文验证码现在都不是问题了,直接打码平台或者训练个模型都很简单。复杂一点的点按,图片选字等就当饭后甜点吧,弄得出来弄不出来全看运气了。在这里我想说的一点是,请分辨一下你要爬的网站是每次请求必须验证码,还是在封禁 IP 之前出验证码。如果不是每次请求都出验证码,直接加大代理池吧,没必要抠这些东西,真的,时间才是最宝贵的。
不过这里需要特别注意的一点是:一定要考虑清楚其中的法律风险,需要账户访问已经说明这不是公开数据了,可能会触发对方的商业利益或者触犯用户的隐私,一定三思而后爬。
如果一个网站只采用一种手段,那么我们分析起问题来就简单了。然而遗憾的是,基本没这种好事儿。比如说一个网站可能即检测了浏览器的 webdriver, 而且还要封 IP, 这时候你就得用浏览器再加上代理,有时候给浏览器设置代理这件事情还挺复杂。还有可能你用账户 Cookies 爬起来飞快,但是竟然还会封 IP, 哼哧哼哧加上了代理池,又发现账户不能换登录 IP, 简直想骂人。
还有一些神仙网站,比如说淘宝或者裁判文书网,可能本文说的都完全没有任何价值,不过好在我大概不会碰这些网站了。
其实我发现一般常见爬虫任务无非几种:
首选的肯定是无状态的接口,搜索接口在大多说网站还是可以直接就拿来用的。如果有需要登录的,也有不需要登录的接口,那想都不用想,肯定爬不需要登录的接口。哪怕登录了,好多还是会封 IP, 何必呢?有些网站的详情或者翻页可能就需要登录了,实在没办法也只能走这些接口。这时候一定要做好登录出口和普通爬虫的代理池隔离。
要判断清楚爬虫任务是爬全量数据还是增量数据。一般来说爬全量数据的需求都有点扯,一定要和需求方 argue 一下,可能对方根本就没想清楚,觉得先把数据存下来然后慢慢再想怎么用,对于这种傻 X 需求一定要顶回去,别急着跪舔或者炫技。如果一定要爬全量,那也可以慢慢来,不用非着急忙慌的把对方网站都搞挂了,这并不是啥值得炫耀的,而是应该被鄙视。而且一般网站也不会把全量数据让你都能看到,比如可能只能翻 500 页,这时候只能通过细分查询条件来尽可能多地获得一些数据。增量的话一般就好说一点,就像上面说的,定时刷一下更新或者推荐接口就好了。
要爬 App 吗?一般来说现在的网站还是有足够的数据的,除非是一些只有 App 而没有网站的站点,那就没办法了。App 的破解和 JS 其实思路一样,但是可能好多 App 加了壳,或者把加密算法写到了 C 里面,相比 JS 来说,完全不是一个数量级了。对于 App 的破解我基本一窍不通,也就是靠写一些网页爬虫混口饭吃。
你应该庆幸的一点是,你需要写的只是一个爬虫,而不是发帖的机器人,一般网站对于这种制造垃圾数据的防范机制肯定比爬虫要复杂很多。
既然谈到破解 App 了,那么再多说一点。有不少同学觉得从爬虫出发,往深了发展就是逆向,那可就大错特错了,有两点:
这里再次特别强调一下:破解别人的 App 很有可能是非法行为,需要负法律责任。
当你费劲吧啦把数据搞定了,还有一个灵魂问题,爬来的数据可信吗?
如果用来分析的数据本来就是错的,那么得出的结论必然也是有问题的。比如 2016 年美国大选中,由于川普的支持者经常被侮辱,导致在电话调查选民中,大家都声称自己支持希拉里,可是实际上大家都投给了川普。电话调查的结果本来就是错的,所以大家都认为希拉里会赢。川普团队则采取的是问选民你认为你的邻居会投谁,从而得到了正确结果。
爬虫爬到的数据中也有可能是有问题的,比如租房网站的假房源,招聘网站上的虚假职位,用户故意不填写真实信息以保护隐私等等;微信文章被刷多的阅读数;而且编写不良的爬虫很可能误入蜜罐,得到的数据更有问题。
比如说借助爬来的新闻分析房产数据,实际上住建部禁止发布涨价相关预测,也就是对于市场的情绪表达是有影响的,那么我们如果按照这个数据来做预测显然是不对的。
总体来说,遇到蜜罐的情况是比较简单的,因为对方伪造数据也需要花费精力,所以伪造的数据一般都是粗制滥造,特征很明显的。而其他的一些假数据问题都是业务问题了,在爬虫程序层面不好也不用解决。
所以总结下来,我觉得遇到一个网站的需要考虑的思路是:
time.sleep(5)
慢慢跑着,也就是尽量不要触发封禁。总之,一次解决一个问题,不要同时触发两个反爬问题,容易按下葫芦起了瓢。
就是这些吧,本文核心观点 -- 最简单粗暴的还是加大电量(误加 IP 池,如果一个不够,那就两个。加钱能解决的问题都不是问题。好多同学可能觉得你这叫哪门子爬虫啊,分布式系统也没有,最好玩的逆向你说去网上抄别人的答案,哪还有毛意思啊!然而,很遗憾,这才是现实世界,对于业务来说,爬虫最重要的是你拿到有用的数据,而不是写代码写牛逼了,有这时间回家陪陪家人不好么~
本文没有任何参考文献,纯意识流瞎写。文中引用了之前写的几篇文章,懒得贴了,感兴趣自己在网站或者公众号找吧。
PS: 监控很重要,爬虫最怕跑着跑着对面改版了或者加反爬了,有了监控才好及时发现问题。关于监控,强烈推荐 Prometheus, 可以参考我以前的文章。
© 2016-2022 Yifei Kong. Powered by ynotes
All contents are under the CC-BY-NC-SA license, if not otherwise specified.
Opinions expressed here are solely my own and do not express the views or opinions of my employer.