分享

Python爬虫与反反爬虫实践

 文炳春秋 2020-10-19

因为要收集数据,所以打算自己撸一个爬虫,期间碰到网站的反爬措施,让我非常头疼,在此记录一下。

基础

爬虫的基础是不需要自己手动通过浏览器访问网页,而是通过程序构造网络请求,获取网站返回的结果。例如使用python的requests库发送请求:

import requestsurl = 'https://www.baidu.com'ret = requests.get(url)print(ret.text)

得到返回的html代码后,可以自己解析数据,获取感兴趣的内容。

前期分析

首先分析要爬的网站,本质是一个信息查询系统,提供了搜索页面。例如我想获取某个case,需要利用这个case的id或者name字段,才能搜索到这个case的页面。因为我是希望数据尽可能不同,id和name分布得比较广泛,自己构造的话特别麻烦,于是看看有没有什么别的办法。

好在网站还提供了热搜功能,可以点击热搜链接,获取每日/周/月的热搜条目,每个热搜榜单有50条信息。热搜的网站链接是www.xxx.com/search?city=0 ,并且网站还提供了各地查询系统的链接,例如tj.xxx.com/search?city=2 ,tj是天津的缩写,2应该就是城市对应的索引。

前期的信息就那么多,先构造个请求试试:

可以看到服务器网站返回的状态码是521,后面是返回的text,看起来是个js脚本。

没有正常的返回状态,我首先想到的是用浏览器打开时,本地会保存cookies,可能是cookies没对上。后来经过验证,确实如此。

绕过反爬虫

出于对安全的考虑,有些网站会做一些反爬的措施,例如前面说的需要判断user-angent和cookies,或者判断请求的ip是否在短时间内多次访问。该网站用的是知道创宇的安全服务,频繁访问会提示ip行为不正常。

爬到的结果

浏览器本质也是一个应用程序,只要ip不被封,既然可以通过浏览器访问,那么我们自己写程序来请求也是应该没有问题的。

一些常见的绕过反爬虫的措施有:

  • 构造消息头:如上所说的user-angent和cookies都包含在消息头当中。

  • 延长请求间隔:如果快速频繁的发送请求,会大量抢占服务器资源,一般这种情况下很容易被网站的安全措施检测出来并且封掉ip。所以适当的延长请求间隔,例如随机隔2-5秒不等再发送下一次请求。

  • 使用代理ip,解决ip检测问题。

这些使用和实现起来都不是很麻烦,网上资料也比较多。

实战

前面说到该网站需要cookies才能正常返回,但是该网站的cookies过期很快,我总不能用浏览器开发者工具获取cookies,然后让程序跑一会儿,每隔几分钟再手动获取cookies,再让程序继续跑吧。如果反复这样工作,那么写爬虫也就没意义了。便开始对cookies进行分析。

从浏览器的开发者工具获取到的cookies大约有10个字段,经过反复测试,能让网站正常返回的只需要两个字段,分别为__jsluid_h=011a522dbxxxxxxxxc1ce59d336e5e60__jsl_clearance=1581880640.794|0|trTB4c6b%2BZpvxxxxxxxx8YqCOOo%3D (中间打码处理)。

经过测试,如果请求的时候不自己构造cookies,默认会返回__jsluid_h :

我似乎明白了什么,看到之前返回那堆看不懂的js脚本,奥妙一定就在其中!

先尝试了将那段js脚本保存下来,包装成一个html文件打开,发现浏览器不停的刷新,也并没起什么作用。那就分析一下js脚本,原来的代码是单行的,自己整理一下并加了一些变量名和log,大概是这么个样子:

第一次脚本

将第16行的变量cmd打印出来看看,发现是另一段类似的脚本:

第二次脚本

可以看到第二段脚本已经开始设置cookies的__jsl_clearence 字段了。这些显然就是混淆后的js脚本,但是分析到这里也就大概弄明白了从发送请求到网站返回是怎么回事。之所以在本地跑这段代码会不断刷新,是因为第二行的setTimeout会让其在1.5秒后重新请求,但是我们本地没有服务处理请求让其停止,所以会不断的刷新。

而第一段脚本当中,变量y是完整的js代码 ,代码中的变量名和关键字被进行编码了,变量x存储的是用来替换的变量名和关键字,后面是解码函数。所以现在的问题变成了获取第一段脚本当中的cmd代码,执行后再获取第二段代码的document.cookie的内容即可。

可是对于python和js的交互我完全没接触过,尝试了PyExecJS和Js2Py,都没办法正常执行第一段脚本。无奈之下,我用python复现了第一段脚本,然后用Js2Py获取了cookie。在请求一次过后,构造cookies,再请求一次,就可以了:

def test():
    url = REQUEST_URL
    # url = 'https://www.baidu.com'
    request_header = get_header()
    html = requests.get(url, headers=request_header)
    print(html)
    jscode = html.text
    # print(jscode)
    # tryjs.get_cookies()为复现的js代码,以及用Js2Py获取cookies的代码
    request_cookies = try_js.get_cookies(jscode) 
    request_cookies += ';__jsluid_h=' + html.cookies['__jsluid_h']
    request_header['Cookie'] = request_cookies
    print(request_header)
    html = requests.get(url, headers=request_header, timeout=5)
    print('new connection')
    print(html)
    print(html.text)

其它

在实际操作中,我使用了西刺代理提供的代理ip,全军覆没。可能是网站的安全系统默认屏蔽了这些ip,所以最终我还是用自己的本机ip发送请求。

根据我的网络与服务器的实际情况,我将请求间隔设置为10-20秒,超时设置为15秒。

因为我并不要求爬取网站的所有数据,所以有些返回不正常的页面和条目可以忽略,在处理和清晰的时候去掉这些数据即可。

经过麓战20多小时,差点就吐了,还好最终爬虫成功跑了起来。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多