Python基于Selenium控制Chrome捕获Ajax请求(省去解析网页的繁琐)
# 主题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请求被捕获到了。
!(https://s1.328888.xyz/2022/05/15/qKHx2.png)
2.我们随便选一个请求,鼠标右击,复制,复制为Fetch即可。
!(https://s1.328888.xyz/2022/05/15/qKdT7.png)
复制的结果如下:
```javascript
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,然后回车执行。
!(https://s1.328888.xyz/2022/05/15/qKDhM.png)
4.切换到网络选项卡,可以看到已经有一个新的请求被发起过了。
!(https://s1.328888.xyz/2022/05/15/qKlNX.png)
5.不难发现,控制台执行完Fetch后并没有得到我们想要的json数据,而是一个对象。这个时候就要重新改写一下第二步中复制的Fetch。让他的结果成为我们需要的Json数据,方便我们进行解析。
对复制的Fetch进行改写如下:
```javascript
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数据了。
!(https://s1.328888.xyz/2022/05/15/qK5fZ.png)
至此,在浏览器中的测试就完全通过了,下面我们要用代码进行实现上面的操作。
## 三、代码实现操作
1.使用Python+Selenium加载一个浏览器对象,打开指定的网页(这里依然使上面用测试时候的网址进行演示)。
2.对【二】中第5步中的代码再进行一次改写,目的是使代码可以通过Selenium中的`execute_script()`方法执行并且可以获取到返回的结果,改写如下:
```javascript
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):
```python
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执行结果:
!(https://s1.328888.xyz/2022/05/15/qZdc4.png)
2.浏览器控制台Network选项卡中的情况:
!(https://s1.328888.xyz/2022/05/15/qZlNB.png)
这里Python执行JavaScript代码在控制台是看不到记录的,直接从Network看即可。
## 四、声明与总结
1.首先,我在互联网找资料并没有找到此类方法,但是不排除有人已经用过,我只是想分享出来我自己思考的这个思路,如欲转载,请声明出处!
2.这个方法面对一些request请求不到的情况还是挺好用的,也不用考虑cookies的问题,且直接在浏览器中发起请求,单论ajax请求的效率还是与直接requests相差不多的。效率上也并没有特别低下,免去了直接解析网页的繁琐。
3.纯属个人见解,如有错误或者任何问题请指正! YuanFang0w0 发表于 2022-5-17 11:03
不是,我是因为他这个ajax用request请求直接就反爬了。不是参数拿不到。是所有参数齐全的情况下,我用req ...
你可以了解一下 ws技术 本帖最后由 话痨司机啊 于 2022-5-17 09:56 编辑
问一下,写个JS ajax用execjs直接调用请求应该跟这个一样吧~,逆向JS直接把csftoken也搞下来,就可以本地运行了,不需要再Copy token去了吧~感觉你绕了个弯子,不知道我说的对不对,不过也是种办法,在扣不出来js的时候
LBF.define("qd/js/component/common.4ba51.js", function(require, exports, module) {
var e = require("ui.Nodes.Node")
, d = (require("qd/js/component/browserSupport.1ad6c.js"),
require("qd/js/component/login.29081.js"),
require("qidian.report"))
, t = require("util.Cookie")
, r = require("qd/js/component/third.a7feb.js");
module.exports = e.inherit({
el: "body",
events: {},
elements: {},
render: function() {
return this.setElement(this.el),
this.init(),
this
},
init: function() {
var e = r.getThirdData()
, t = e.ua
, a = (e.isThird && (r.hideAdvertising(),
r.isIframe() && r.hideLoginBox(),
document.getElementById("reg-btn") && (document.getElementById("reg-btn").href = "//passport.qidian.com/reg.html?appid=10&areaid=" + e.areaid + "&target=iframe&ticket=1&auto=1&autotime=30&returnUrl=https%3A%2F%2Fwww.qidian.com"),
document.getElementById("fixed-reg") && (document.getElementById("fixed-reg").href = "//passport.qidian.com/reg.html?appid=10&areaid=" + e.areaid + "&target=iframe&ticket=1&auto=1&autotime=30&returnUrl=https%3A%2F%2Fwww.qidian.com")),
"https://pay.yuewen.com/pc/index?appId=10&areaId=" + e.areaid + "&&returnUrl=" + location.href)
, e = ($("body").on("click", ".qqlivepay", function(e) {
"QQLive" == t && (external && external.OpenQQLive && external.OpenQQLive(a),
e.preventDefault())
}),
g_data.bookInfo && g_data.bookInfo.bookId || g_data.pageJson && g_data.pageJson.bookId)
, n = g_data.chapter && g_data.chapter.id
, i = g_data.chapter && g_data.chapter.vipStatus
, o = g_data.readSetting && g_data.readSetting.rt;
"undefined" == typeof g_data.isWebSiteType || 1 === g_data.isWebSiteType ? d.init({
isQD: !0,
cname: "QDpclog",
bid: e,
cid: n,
ua: t,
bookstatus: i,
rt: o
}) : 0 === g_data.isWebSiteType && d.init({
isQD: !0,
cname: "QDmmlog",
bid: e,
cid: n,
ua: t,
bookstatus: i,
rt: o
}),
this.checkLang(),
this.pageLoaded(),
$.ajax({
url: "/ajax/Help/getCode"
}).done(function(e) {
0 === e.code && $(".footer .advice").prop("href", "http://yw.95ib.com/online/?cid=0&uid=10&code=" + e.data)
})
},
pageLoaded: function() {
$("html").addClass("loaded")
},
createSender: function(e) {
var t = new Image;
t.onload = t.onerror = function() {
t = null
}
,
t.src = e
},
checkLang: function() {
"zht" == t.get("lang") && (require["async"]("qd/css/tradition_font.d9168.css"),
require["async"]("qd/js/component/chinese.d5874.js", function(e) {
$("#switchEl").html("简体版"),
$(".lang").css("fontFamily", "T_FZZCYSK"),
e.trans2Tradition("html")
}))
}
})
}); Chromedriver嘛?这个其实还有一些更好的方法,但效率还是说不上很高吧 学习一下 狄人3 发表于 2022-5-15 22:59
Chromedriver嘛?这个其实还有一些更好的方法,但效率还是说不上很高吧
我觉得只是ajax的话感觉跟requests差的不多 谢楼主分享,刚好用上。 乍一看好像还行,仔细一看貌似没啥用 不错不错,学习一下! Venda 发表于 2022-5-15 23:40
乍一看好像还行,仔细一看貌似没啥用
至少对我来说我这个项目是用上了。 感谢,多了一种思路