爬虫攻防战 🕷
我们是守方,我负责活动的前端页面和node服务,作为先锋,拦一些比较简单的爬虫,并留一些陷阱。
参加者攻破前端页面以后,就要靠我方神盾的规则来拦截可疑ip,限流以及拉黑。
活动简单来说就是通过爬取特定的网站上的信息,综合参与者的的请求被识别为爬虫的次数,来评分。
活动页介绍
共有两个主要页面,一个列表页,每页10个公司,带有分页功能,以及公司的详情页。
参加者需要爬取5000家公司的相应信息来得分。
活动的页面选择使用 Pug(html模板语言) + Nest.js(node服务) 通过后端直接渲染页面的方式来完成。
得分项及反爬措施
一共五个得分点:
1. 公司名称
最简单的一项,只要请求页面就可以直接拿到,没有任何的反爬措施,白送的1分。
2. 热线电话
400热线电话使用雪碧图(精灵图)显示,略提升了一些难度。
准备了10张包含 0-9 10个数字且顺序不同的图片,node服务每次随机获取一张,并返回对应电话的位移量,用css在页面上显示完整的电话。
【破解方法】
方法一:将所有可能性的底图全部爬下来,然后找到位置偏移量的对应关系 Mapping,正则或是程序定位到css的偏移量,从 Map 里找到对应数字。
方法二:OCR
3. 联系人手机号
页面初次加载时,手机号从服务渲染出来就是带有星号的,需要通过一个 Ajax 请求来获取详细的号码。
请求返回的手机号经过了一些加密处理,原理简单来说就是把数字的 charCode 偏移几位,然后转成 base64。客户端拿到以后用相同的方法,逆向解就可以拿到真实的手机号。
node 层加密过程:
1 | export const encode = (str: string, digit = 10) => { |
客户端解密过程:
1 | function es(_0x380035) { |
虽然是混淆的,但是还是在客户端的源码里可以找到,只要有node或是浏览器的运行环境,就可以轻松运行 es() 来解(完全不需要管过程)。
手机号这里还留了一个陷阱,我们网页加了设备指纹,如果用浏览器正常打开,在请求的header里会带有相关的字段,如果是通过 Api 打过来的则没有。
对于没有相关header的请求,会返回一个假的加密过的手机号。
【破解方法】
方法一:从浏览器调试工具里不难找到 es 这个 Function,解混淆,然后用爬虫所用语言重写一遍,拿到手机号后调用重写的方法即可反解。
方法二:OCR
4. 联系人名字
这里自己把自己坑了,用的 WebSocket 获取联系人,结果没考虑到 socket 只有连的那下有个http请求,后面都是保持连接直接通讯,所以根本不会过Ingress,并且中反爬的规则,也无法跳验证码拦截。
点击获取详情按钮后:
最后决定加一个限流,如果获取太快服务层就主动断开 socket,需要重连。
【破解方法】
方法一:只要接通连上服务端的 WebSocket,模拟浏览器的 headers,以及握手方式,浏览器传什么,模拟就传什么,最后总能拿到,而且连上一次就不需要断开了。
方法二:OCR
5. 公司地址
这里用了字体反爬。
服务层渲染的是一个混淆后的地址(文字),需要通过相应的字体文件,用css font-family 来显示正确的内容。
【破解方法】
方法一:获取到所以有可能性的字体文件,然后使用相关工具解除文字和文字或文字和字符的对应关系,然后反解。
方法二:OCR
以上五项就是得分点,5000条数据,满分5万分,分高者取胜。
6. 页码处理
列表页的地址栏会带有页码,例如:?p=12
为了让「爬虫」们不要轻易就用p=1、2、3、4的循环去打开页面,将页码简单加了个密,原理和手机号显示是一样的,每个数字的 charCode 位移几位,然后 base64 转换,就成了下面的样子:
这样一来大家只能模拟去点击下一页来获取页码,而不是直接猜,当然这种加密是很简单的,看这个字符串就长得很像 base64,所以还是可以花时间猜出来。
7. 设备指纹
每个访问者第一次访问后都会留下指纹,放到 localStorage,再下一次访问的时候,同一个指纹的行为和 ip 进行分析。
【破解方法】
方法一:开代理并且频繁切 ip 的话基本可以无视这个。
神盾
当然前端的反爬再花里胡哨也是有局限的,如果要照顾SEO,那就更不能做什么太多的防爬措施了。并且在如今相当成熟的 OCR 面前,前端反爬都是浮云。
所以作为先锋,以上我做的只是拖慢大家的爬取的时间。真正的盾还是在神盾系统。我们会收集用户的真实IP,然后进行分析,识别为爬虫的则会返回验证码滑块,如果多次命中规则,则会限流。
而我在前端的工作本质是增加需要获取全部信息接口的数量,接入神盾的SDK,获取正确的IP给到神盾,并加入设备指纹。这次爬虫攻防活动也是为了收集爬虫的行为,为以后神盾规则优化做铺垫。
DONE.
第一名以3w分左右获胜。