摘要
本文内容转自网络,个人学习记录使用,请勿传播
头条新闻抓取
需求
- 将头条中的新闻咨询进行数据爬取。
数据包分析
滚轮下滑,会加载出更多的新闻咨询数据,因此,数据的加载是基于ajax请求实现的。
通过抓包工具,捕获ajax数据包,发现一个feed的数据包的响应数据中存在加载出的新闻数据。
请求参数:
1
2
3
4
5
6channel_id: 3189398999 【固定】
max_behot_time: 1650364906 【时间戳】
category: pc_profile_channel 【固定】
aid: 24 【固定】
app_name: toutiao_web 【固定】
_signature: _02B4Z6wo00901ZogFsQAAIDA4u79wxDuXSGaBBJAAATX7cy7h0X5W7RfeqRU9vU4c57nKyTqBEVpuutMIr7033MXo70zZveziaEdhwxpiZ46YXA8.L2YZz8QdKDh-ZUBUrtzyJ4A7aXdUB1621 【签名:动态变化的,具有时效性】
请求头
- cookie:请求没有携带cookie,因此cookie无需处理
- referer: https://www.toutiao.com/?wid=1650369517516
- 经过测试,需要携带referer。
前置知识点
js三目运算符
1 | 条件?值1:值2 |
js的逻辑运算
1 | var a; |
1 | //window.byted_acrawler不为空、window.byted_acrawler.sign不为空) |
执行js函数
目前可以有两种方式用于执行js函数
- 方式1:常规方式
1
2
3
4
5
6
7//函数定义
function sign(n,a){
//this的指向没有发生改变
console.log(n+a)
}
//函数调用
sign('name:','bobo')- 方式2:call机制
- call的作用:
- call函数必须通过一个被定义过的函数的名字进行调用。
- call函数有(call的调用者表示函数的参数个数+1)个参数
- 参数1:会被赋值给call的调用者表示函数内部的this
- 参数2,3,4 … :赋值给call的调用者表示函数的具体参数
- call的作用:
1
2
3
4
5
6
7//函数定义:没一个函数内部都会有一个this指针,该this就好比是python中的self
function sign(n){
//this = 123
console.log(n)
}
//基于call机制的函数调用
sign.call(123,456) //参数123会被赋值给sign函数内部的this,456会被赋值给sing的参数n。练习:
1 | window.byted_acrawler.call(n,i) 等同于:window.byted_acrawler(i) |
函数的参数
1 | function sign(){ |
对象合并
1 | v1 = {'name':'bobo'}; |
编译执行js代码
方式1:nodejs
1 | //v1.js |
在终端中:
1 | node v1.js |
制定一个python脚本,来编译执行js程序
1 | import subprocess |
注意:至此,我们就可以通过python程序来执行js程序!
方式2:pyexecJs
可以通过python程序来执行js程序
浏览器环境的模拟(重点)
什么是nodejs
- Node.js 是能够在服务器端运行 JavaScript 的开放源代码、跨平台运行环境
- node.js是一个开发环境,可以实现在服务器端运行js代码。
- 在 Node.js 出现之前,JavaScript 通常作为客户端程序设计语言使用,以JavaScript 写出的程序常在用户的浏览器上运行。Node.js 的出现使 JavaScript 也能用于服务端编程。
- 某些网站的后台系统中就会使用nodejs作为后端语言。有的网站则使用python。
- node.js环境安装成功后,则会自动安装好一个npm工具,该工具是用来管理和下载第三方的js模块,类似于python的pip。
什么是浏览器环境?为何需要它?
有些时候,我们从浏览器上拷贝下来的js代码,在改写的时候会失败,终极原因就是缺少了浏览器环境。
1
2
3
4
5function sign(){
return navigator.userAgent
};
sign();
//在发条改写工具中改写失败,但是在浏览器的console可以执行成功。
浏览器环境是指,js代码运行时有可能需要依赖的代码环境。比如在浏览器开发者工具的console中可以读取到如下几个内置对象的值(这些值都是浏览器给的)
1 | window,document,navigator,location等内置对象。 |
注意:有的网站的加密解密操作会借用到这些内置对象中的某些值,因此我们在实现逆向过程中缺少了这些值(没有浏览器环境)就会报错!
实现模拟浏览器环境操作
环境安装:
nodejs开发环境(已装)
jsdom(nodejs中的一个模块)
- jsdom就是用nodejs实现的用于测试的虚拟浏览器。
- 安装:(最好使用管理员身份运行下述终端指令)
1
2
3
4npm install node-gyp@latest
npm explore -g npm -- npm i node-gyp@latest //这个是一些依赖环境的自动补充,否则直接安装jsdom可能会报错
npm install jsdom -g
//-g是全局安装,不携带是局部位置安装或者:(如果使用上述操作安装失败,则使用如下方式)
1
2
3sudo npm install -g yarn
sudo npm install -g tyarn
sudo npm install -g jsdom安装canvas
1
npm install canvas -g
补全/模拟浏览器环境
在浏览器的开发者工具的console中,会看到window这个内置对象,其中包含了如下浏览器环境中才有的对象:
window.navigator
window.document
window.location等等
jsdom的基本使用
1 | //v2.js |
终端:
node v2.js
jsdom的完全使用(推荐)
1 | //v3.js |
终端:
node v2.js
global关键字
- 在nodejs中默认会有一个global的关键字(全局变量)
1 | //v4.js |
终端:
node v4.js
发现:global本身是一个字典,可以存储所有的全局变量。自己定义的全局变量都会存储到global这个字典中。
1 | global.v1 = 'bobo'; |
具体的请求分析
滚轮下滑,会加载出更多的新闻咨询数据,因此,数据的加载是基于ajax请求实现的。
通过抓包工具,捕获ajax数据包,发现一个feed的数据包的响应数据中存在加载出的新闻数据。
请求方式:get
请求参数:
1
2
3
4
5
6channel_id: 3189398999 【固定】
max_behot_time: 1650364906 【时间戳】
category: pc_profile_channel 【固定】
aid: 24 【固定】
app_name: toutiao_web 【固定】
_signature: _02B4Z6wo00901ZogFsQAAIDA4u79wxDuXSGaBBJAAATX7cy7h0X5W7RfeqRU9vU4c57nKyTqBEVpuutMIr7033MXo70zZveziaEdhwxpiZ46YXA8.L2YZz8QdKDh-ZUBUrtzyJ4A7aXdUB1621 【签名:动态变化的,具有时效性】
请求头
- cookie:请求没有携带cookie,因此cookie无需处理
- referer: https://www.toutiao.com/?wid=1650369517516
- 经过测试,需要携带referer。
全局搜索_signature
发现在index数据包中出现了_signature关键字,在可疑位置打断点,刷新请求确定断点:
1
2
3
4
5var r = S(n, e);
e.params = H(H({}, e.params), {}, {
_signature: r
});
//r就是动态变化请求参数的值,这个值是由S(n, e)调用返回的,S函数的内部实现
1
2
3
4
5
6
7
8
9
10
11
12function S(e, t) {
var n, r;
var a = "".concat(location.protocol, "//").concat(location.host, "/toutiao");
if (false)
;var o = {
url: a + e
};
if (t.data)
o.body = t.data;
var i = (null === (r = null === (n = window.byted_acrawler) || void 0 === n ? void 0 : n.sign) || void 0 === r ? void 0 : r.call(n, o)) || "";
return i //返回的i就是签名的值(动态变化的请求参数的值),在此处打上一个断点,然后是的程序走到该断点的位置,观察i是不是我们想要的签名数据。
}处理返回值i:
1
2
3
4
5
6
7var i = (null === (r = null === (n = window.byted_acrawler) || void 0 === n ? void 0 : n.sign) || void 0 === r ? void 0 : r.call(n, o)) || "";
//上述表达式前置知识点中分析过,且得到了结果:window.byted_acrawler.sign.call(n, o)==》window.byted_acrawler.sign(o)
//至此:
var i = window.byted_acrawler.sign(o)
//此处的参数o(固定值:同一个板块下是固定,不同板块是变化):
{"url": "https://www.toutiao.com/api/pc/list/feed?offset=0&channel_id=94349549395&max_behot_time=0&category=pc_profile_channel&disable_raw_data=true&aid=24&app_name=toutiao_web"
}
改写sign
- 进入到sign的实现内部,发现了sign的原始实现
1
2
3
4
5
6
7(U = function e() {
var f = arguments;
return e.y > 0 ? K(b, e.c, e.l, f, e.z, this, null, 0) : (e.y++,
K(b, e.c, e.l, f, e.z, this, null, 0)) //打断点,执行到该断点位置
}
//分析返回值:三目运算,发现e.y这个表达式返回true,因此返回值就是K(b, e.c, e.l, f, e.z, this, null, 0)。
//因此其实sign函数返回值是有K函数返回的,因此只需要改写K函数即可!改写K,会发现K的参数比较多,每一个参数的生成机制都很复杂,函数的调用栈比较深。
测试:直接将当前K函数所在的js文件中的代码全部复制,保存到一个js文件中(v20.js),在创建一个html文件(v20_face.html)实现简易化的改写,是否可以成功:
v20_face.html
1
2
3
4
5
6
7
8
9
10
11
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="v20.js"></script>
</head>
<body>
</body>
</html>- 直接在浏览器中执行html,在浏览器的console可以看到window.byted_acrawler.sign返回的结果,因此基于浏览器环境,js代码改写成功了!
补充浏览器环境
大致观测改写的代码
1
2
3
4
5var glb;
//函数定义
(glb = "undefined" == typeof window ? global : window)._$jsvmprt = function(b, e, f) {xxx}
,//函数调用
(glb = "undefined" == typeof window ? global : window)._$jsvmprt('xxx',['xx','xx',...],undefined)
1 | //v30.js |
通过node运行改写好后的js程序会报错:
export是nodejs中的一个关键字,将外部文件中的方法作用在当前的js代码中(好比是python的import)
修改js代码:
将函数的第二个参数列表中第三个元素(携带了export关键字的语句:”undefined” != typeof exports ? exports : void 0)该语句在浏览器console中执行返回的是undefine,因此在代码中就可以将该语句改为void 0(undefine),从而就不出现exports关键字。程序运行就没问题了。
python脚本获取签名
1
2
3
4import subprocess
url = 'https://www.toutiao.com/api/pc/list/feed?offset=0&channel_id=94349549395&max_behot_time=0&category=pc_profile_channel&disable_raw_data=true&aid=24&app_name=toutiao_web'
signature = subprocess.getoutput('node v30.js %s'%url)
print(signature)