js解析
引言
在了解如何调试js之前, 需要简单了解一下http的请求过程
1.DNS域名解析;
2.建立TCP连接;
3.发送HTTP请求;
4.服务器处理请求;
5.返回响应结果;
6.关闭TCP连接;
7.浏览器解析HTML;
8.浏览器布局渲染;
上面8个步骤被戏称为"天龙八步"
参考链接: https://zhuanlan.zhihu.com/p/32370763
简单说一下我自己的理解:
- 浏览器解析出域名对应的ip后, 建立与服务器的连接
- 向访问的url地址发起请求, 服务器处理后返回响应结果, 被浏览器接收
- 在返回的响应内容中, 会包含html,css,js和ajax等代码, 浏览器会依次加载这些代码
- 如果其中包含发送请求的代码(例如js和ajax请求), 就会再次发起这些请求, 并获取响应
- 加载完毕的响应内容会被展示在浏览器中, 直到最终获取全部响应
- 这个过程就是浏览器的渲染
但是在爬虫中, 其发起的请求只能拿到对应的响应. 而我们在浏览器中看到的页面, 很多时候跟爬虫得到的并不一样, 这是因为简单的爬虫不具备渲染页面的能力(可以借助外部工具来实现, 如selenium), 浏览器展示的结果是由多次请求/响应共同渲染而来的, 而爬虫的一次请求只能得到一个响应
因为我们爬虫每次只有一个请求,但是实际中很多请求又js连带发送,所以我们需要利用爬虫解析js去实现这些请求的发送
方案
网页中的参数来源主要由以下四种:
- 固定值, 写死在html中的参数
- 用户给的参数
- 服务器(通过ajax)返回的参数, 比如时间戳, token等
- js生成的参数
由于网站反爬措施的关系, 通过请求得到的参数很多是在前端js里生成的, 因此为了得到参数, 我们由以下方案(顺位推荐)
方案1:本地运行js, 把js代码拿下来,用python代码去执行它(参见pyexcjs),从而得到所需请求参数
方案2:解析js,破解加密方法或者生成参数值方法,python代码模拟实现这个方法, 从而得到我们需要的请求参数
方法3:selenium, 如果上面两种都不行, 这就是我们的绝招, 模拟用户使用浏览器, 获取渲染后的页面源码
本文主要介绍第二种方案
1>筛选参数
以微博登录为例, 用户名和密码作为输入的参数, 在发送给服务器之前会被js做加密处理, 由此也增加了我们使用爬虫模拟登陆的难度, 因此选择使用解析js的方法来搞定
每当我们点击登录的时候都会发送一个login请求, 如图
爬虫高级.assets\1572767081082.png)]" />
登录表单中的参数并不一定都是我们需要的, 可以通过对比多次请求中的参数, 再加上一些经验和猜想, 过滤掉固定参数 或服务器自带参数和用户输入的参数, 这是剩下的就是js生成的数值或加密数值, 如下:
爬虫高级.assets\1572768213043.png)]" />
最终得到的值:
# 图片 picture id:
pcid: yf-d0efa944bb243bddcf11906cda5a46dee9b8
# 用户名:
su: cXdlcnRxd3Jl
nonce: 2SSH2A # 未知
# 密码:
sp: e121946ac9273faf9c63bc0fdc5d1f84e563a4064af16f635000e49cbb2976d73734b0a8c65a6537e2e728cd123e6a34a7723c940dd2aea902fb9e7c6196e3a15ec52607fd02d5e5a28e18254105358e897996f0b9057afe2d24b491bb12ba29db3265aef533c1b57905bf02c0cee0c546f4294b0cf73a553aa1f7faf9f835e5
prelt: 148 # 未知
请求参数中的用户名和密码都是经过加密处理的, 因此如果需要模拟登录, 我们就需要找到这个加密的方法, 利用它来为我们的数据进行加密
找到所需的js
-
要找到加密方法, 首先我们需要先找到登录所需的js代码, 可以使用以下3种方式:
-
找事件, 在页面检查目标元素,在开发工具的子窗口里选中
Events Listeners
, 找到click事件,点击定位到js代码, 如图:爬虫高级.assets\1572769323122.png)]" />
这种方法找到的不一定是正确的, 所以推荐第二个方法
-
找请求, 在
Network
中点击列表界面的对应Initiator
跳转至对应js界面
-
通过搜索参数名进行定位
-
-
登录的js代码:
点击左下角的
{}
图标, 可以更方便查看代码
- 在这个
submit
的方法上打断点, 然后输入用户名密码, 先不点登录, 回到dev tool点击这个按钮启用调试:
-
然后再去点登录按钮, 这时候就可以开始调试, 调试工具有以下几种:
- 大右箭头
跳到下一个断点(如果没有断点就执行完)
- 弯箭头
逐句执行
- 下箭头
跳到下一个要执行的函数
- 上箭头
跳出当前执行的函数
- 右箭头
一步一步执行代码
- 子弹
爬虫高级.assets\1572770809950.png)]" /> 取消当前所有的断点
- 暂停
捕获异常时暂停
- 大右箭头
-
逐步执行代码的同时观察我们输入的参数, 发生变化的地方即为加密方法, 如下
爬虫高级.assets\1572771322663.png)]" />
-
上图中的加密方式是base64, 因此我们可以使用代码来试一下:
import base64 a = "aaaaaaaaaaaa" # 输入的用户名 print(base64.b64encode(a.encode())) # 得到的加密结果:b'YWFhYWFhYWFhYWFh' # 如果用户名包含@等特殊符号, 需要先用parse.quote()进行转义
得到的加密结果与网页上js的执行结果一致
实践
爬取百度翻译的api
import execjs
import requests
import re
import json
def get_sign(word, gtk):
with open('baidu.js', 'r') as f:
js_code = f.read()
sign = execjs.compile(js_code) # 执行定义代码, 可通过call调用
return sign.call('e', word, gtk)
def run_translate(word):
# 创建session对象
session = requests.session()
# 定义UA
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36'
}
# 获取百度翻译的页面源码
session.get('https://fanyi.baidu.com/', headers=headers)
# 百度翻译比较特殊, 需要发送两次请求, 至于原因我也不清楚
response = session.get('https://fanyi.baidu.com/', headers=headers)
# 获取参数:
token = re.findall(r"token: '(.*?)'", response.text)[0]
gtk = re.findall(r"window.gtk = '(.*?)'", response.text)[0]
# 更新请求头的参数, 为后续post请求做准备
session.headers.update({'referer': 'https://fanyi.baidu.com/'})
# 发送第一次请求, 检测输入的语言
session.post('https://fanyi.baidu.com/langdetect/', data={'query': word})
sign = get_sign(word, gtk)
# 定义第二次请求的参数
data = {
'from': 'en',
'to': 'zh',
'query': word,
'transtype': 'translang', # translang:点击按钮翻译; realtime:自动翻译
'simple_means_flag': '3',
'sign': sign,
'token': token,
}
resp = session.post('https://fanyi.baidu.com/v2transapi/', data=data).text
print(json.loads(resp)['dict_result']['simple_means']['word_means'])
if __name__ == '__main__':
run_translate(input('英译汉>>:'))
提问:
简单说一说,js在页面中的作用,我们为什么要解析它
在浏览器访问一个网站时, 网站会加载三种类型的代码, 静态代码(html/css), 动态代码(js/jq), 外部代码(媒体文件), js的作用就是为网站提供更多功能(例如动态加载数据,触发各种事件,完成前后端交互,数据加密等工作)
众所周知, 爬虫是不能执行页面渲染的, 因此我们解析js的目的, 就是为了让爬虫能够使用这些页面的渲染, 也就是上述的这些功能
参考文章:
一个TCP连接可以发送多少个HTTP请求?
关于堆栈的讲解(我见过的最经典的)
python3调用js的库之execjs