主题
Python基于Selenium控制Chrome捕获Ajax请求(不使用代{过}{滤}理服务器,不解析网页元素,直接捕获)
你还觉得Selenium爬取效率低下吗?
一、前言
1.需求以及问题
有需求需要爬取目标网站(国外网站)的用户信息,但是这个网站反爬机制还是比较强的,直接去用requests请求ajax接口直接会失败,请求到的页面将是一个用于人机校验的谷歌验证。
因此通过我不断地试验(伪装请求头,除了必要的cookies等参数,我其他的一个个试验),最终还是没有突破它的限制,我又去GitHub上找了找相关的项目,同样的问题并没有得到解决。最终,我将目光投向了Selenium。
2.Selenium分析
使用Selenium的时候,依然不能使用无头模式(即使加上了规避检测的内容依然如此),但是对于我这个项目需求来说,无头与否影响不大,因此我只做了浏览器的最小化即可。
使用Selenium时,我们面临的最大问题就是需要解析大量的网页元素,对于数据请求效率太低。特别是对于ajax请求的接口数据而言,本质上只需要拿到ajax返回的json数据直接解析json就可以,但是Selenium却要去大量解析网页内容,等待网页渲染,实在是一键很麻烦的事情。
3.查找解决办法
因此我决定上网查找资料,查找Selenium可以直接获取ajax请求结果的办法。经过搜索查找资料,网上的办法大多都是用代{过}{滤}理服务去截获请求,这个方法就需要写一个服务端的内容,无疑加大了工作量。我并不是很想用这种方法。
4.自己分析解决
苦苦思考下,突然想到了一个方法,调用Selenium在控制台执行js脚本的方法不知道行不行得通呢,于是我打算去试一下。
二、浏览器实操测试
那么Selenium执行Js脚本,肯定得编写出需要的脚本,这里我们不用编写的过于复杂,仅仅执行请求指定的ajax接口即可。
这里我随便找一个有ajax请求的网站。(示例网站:https://book.qidian.com/info/1033601341/
)
1.打开网站,打开浏览器控制台,切换到网络选项卡,点击Fetch/XHR
,指定捕获ajax请求,刷新网页,可以看到已经有了一些ajax请求被捕获到了。
2.我们随便选一个请求,鼠标右击,复制,复制为Fetch即可。
复制的结果如下:
fetch("https://book.qidian.com/ajax/book/getFansHall?_csrfToken=YZNRZe0oR9VKe4QBccx6qIbURkXtcOCvg27LQNni&bookId=1033601341", {
"headers": {
"accept": "application/json, text/javascript, */*; q=0.01",
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
"sec-ch-ua": "\" Not A;Brand\";v=\"99\", \"Chromium\";v=\"101\", \"Microsoft Edge\";v=\"101\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"Windows\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
"x-requested-with": "XMLHttpRequest"
},
"referrer": "https://book.qidian.com/info/1033601341/",
"referrerPolicy": "strict-origin-when-cross-origin",
"body": null,
"method": "GET",
"mode": "cors",
"credentials": "include"
});
3.来到控制台,粘贴我们复制的Fetch,然后回车执行。
4.切换到网络选项卡,可以看到已经有一个新的请求被发起过了。
5.不难发现,控制台执行完Fetch后并没有得到我们想要的json数据,而是一个对象。这个时候就要重新改写一下第二步中复制的Fetch。让他的结果成为我们需要的Json数据,方便我们进行解析。
对复制的Fetch进行改写如下:
fetch("https://book.qidian.com/ajax/book/getFansHall?_csrfToken=YZNRZe0oR9VKe4QBccx6qIbURkXtcOCvg27LQNni&bookId=1033601341", {
"headers": {
"accept": "application/json, text/javascript, */*; q=0.01",
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
"sec-ch-ua": "\" Not A;Brand\";v=\"99\", \"Chromium\";v=\"101\", \"Microsoft Edge\";v=\"101\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"Windows\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
"x-requested-with": "XMLHttpRequest"
},
"referrer": "https://book.qidian.com/info/1033601341/",
"referrerPolicy": "strict-origin-when-cross-origin",
"body": null,
"method": "GET",
"mode": "cors",
"credentials": "include"
}).then((res)=>{return res.json()}).then((res)=>{console.log(res)});
这里去除了最后的分号,加上了一句代码.then((res)=>{return res.json()}).then((res)=>{console.log(res)});
改过之后再去浏览器中测试一下,可以看的到已经能正常获取到json数据了。
至此,在浏览器中的测试就完全通过了,下面我们要用代码进行实现上面的操作。
三、代码实现操作
1.使用Python+Selenium加载一个浏览器对象,打开指定的网页(这里依然使上面用测试时候的网址进行演示)。
2.对【二】中第5步中的代码再进行一次改写,目的是使代码可以通过Selenium中的execute_script()
方法执行并且可以获取到返回的结果,改写如下:
var resp = fetch("https://book.qidian.com/ajax/book/getFansHall?_csrfToken=YZNRZe0oR9VKe4QBccx6qIbURkXtcOCvg27LQNni&bookId=1033601341", {
"headers": {
"accept": "application/json, text/javascript, */*; q=0.01",
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
"sec-ch-ua": "\" Not A;Brand\";v=\"99\", \"Chromium\";v=\"101\", \"Microsoft Edge\";v=\"101\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"Windows\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
"x-requested-with": "XMLHttpRequest"
},
"referrer": "https://book.qidian.com/info/1033601341/",
"referrerPolicy": "strict-origin-when-cross-origin",
"body": null,
"method": "GET",
"mode": "cors",
"credentials": "include"
}).then((res)=>{return res.json()});
return resp
这里一共做了三处改动:
1.在fetch
前加上了var resp =
,将fetch
返回的值保存为一个变量;
2.去掉了代码最后的.then((res)=>{console.log(res)})
,原因是这一行是让结果在控制台中以json输出,其实获取json数据只需要.then((res)=>{return res.json()})
这一行代码就足以了。
3.最后加了一行return resp
,返回这个变量的值(这个值就是需要json数据)。
需要注意的是:这个网站的这个ajax请求后面会带上一个csrfToken
,因此在Python中运行时要对其进行改变,不然请求肯定是得不到需要的正确数据的。
改写完之后就是把代码放在Pyhon中执行了,下面是一点示例代码(仅演示执行ajax,不说明获取token):
print('1.加载浏览器')
browser = selenium(url='https://book.qidian.com/info/1033601341/',
driver_path='chromedriver.exe')
print('2.执行JS代码')
token = input('请输入token')
fetch = r'''var resp = fetch("https://book.qidian.com/ajax/book/getFansHall?_csrfToken=''' + token + r'''&bookId=1033601341", {
"headers": {
"accept": "application/json, text/javascript, */*; q=0.01",
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
"sec-ch-ua": "\" Not A;Brand\";v=\"99\", \"Chromium\";v=\"101\", \"Microsoft Edge\";v=\"101\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"Windows\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
"x-requested-with": "XMLHttpRequest"
},
"referrer": "https://book.qidian.com/info/1033601341/",
"referrerPolicy": "strict-origin-when-cross-origin",
"body": null,
"method": "GET",
"mode": "cors",
"credentials": "include"
}).then((res)=>{return res.json()});
return resp'''
resp = browser.execute_script(fetch)
print(resp)
input('回车退出')
browser.quit()
值得注意的一点是:复制的fetch放在Python中有很多”\“,这些”\“不能被删除,要注意在python的字符串前加r
,让字符串变为原生字符串。
下面是执行的结果展示:
1.Python执行结果:
2.浏览器控制台Network选项卡中的情况:
这里Python执行JavaScript代码在控制台是看不到记录的,直接从Network看即可。
四、声明与总结
1.首先,我在互联网找资料并没有找到此类方法,但是不排除有人已经用过,我只是想分享出来我自己思考的这个思路,如欲转载,请声明出处!
2.这个方法面对一些request请求不到的情况还是挺好用的,也不用考虑cookies的问题,且直接在浏览器中发起请求,单论ajax请求的效率还是与直接requests相差不多的。效率上也并没有特别低下,免去了直接解析网页的繁琐。
3.纯属个人见解,如有错误或者任何问题请指正!