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请求, 如图
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UefzSUVZ-1573125994896)(E:\Fire\笔记\爬虫高级.assets\1572767081082.png)]](https://i-blog.csdnimg.cn/blog_migrate/199efaa68966b4fb18646c9cc5fda189.png)
登录表单中的参数并不一定都是我们需要的, 可以通过对比多次请求中的参数, 再加上一些经验和猜想, 过滤掉固定参数 或服务器自带参数和用户输入的参数, 这是剩下的就是js生成的数值或加密数值, 如下:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iUJX7zJA-1573125994899)(E:\Fire\笔记\爬虫高级.assets\1572768213043.png)]](https://i-blog.csdnimg.cn/blog_migrate/dd1d59aec8f12b6fa2038f04d38e42d8.png)
最终得到的值:
# 图片 picture id:
pcid: yf-d0efa944bb243bddcf11906cda5a46dee9b8
# 用户名:
su: cXdlcnRxd3Jl
nonce: 2SSH2A # 未知
# 密码:
sp: e121946ac9273faf9c63bc0fdc5d1f84e563a4064af16f635000e49cbb2976d73734b0a8c65a6537e2e728cd123e6a34a7723c940dd2aea902fb9e7c6196e3a15ec52607fd02d5e5a28e18254105358e897996f0b9057afe2d24b491bb12ba29db3265aef533c1b57905bf02c0cee0c546f4294b0cf73a553aa1f7faf9f835e5
prelt: 148 # 未知
请求参数中的用户名和密码都是经过加密处理的, 因此如果需要模拟登录, 我们就需要找到这个加密的方法, 利用它来为我们的数据进行加密
找到所需的js
-
要找到加密方法, 首先我们需要先找到登录所需的js代码, 可以使用以下3种方式:
-
找事件, 在页面检查目标元素,在开发工具的子窗口里选中
Events Listeners, 找到click事件,点击定位到js代码, 如图:![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4igWFpYz-1573125994900)(E:\Fire\笔记\爬虫高级.assets\1572769323122.png)]](https://i-blog.csdnimg.cn/blog_migrate/2188afd3b3135fcb55c96ff58fd3bba8.png)
这种方法找到的不一定是正确的, 所以推荐第二个方法
-
找请求, 在
Network中点击列表界面的对应Initiator跳转至对应js界面
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vhMPtIPD-1573125994902)()]](https://i-blog.csdnimg.cn/blog_migrate/49ffe120a01a380cef85eb23b04ba7d4.png)
-
通过搜索参数名进行定位

-
-
登录的js代码:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gT2Rb8dq-1573125994902)()]](https://i-blog.csdnimg.cn/blog_migrate/24afb223ec6bfb07f25f52e5e9474b69.png)
点击左下角的
{}图标, 可以更方便查看代码
- 在这个
submit的方法上打断点, 然后输入用户名密码, 先不点登录, 回到dev tool点击这个按钮启用调试:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NVnckBwQ-1573125994902)()]](https://i-blog.csdnimg.cn/blog_migrate/09ed936e99cc40f5a24cd9bfdfd7d9c6.png)
-
然后再去点登录按钮, 这时候就可以开始调试, 调试工具有以下几种:
- 大右箭头
跳到下一个断点(如果没有断点就执行完) - 弯箭头
逐句执行 - 下箭头
跳到下一个要执行的函数 - 上箭头
跳出当前执行的函数 - 右箭头
一步一步执行代码 - 子弹
取消当前所有的断点 - 暂停
捕获异常时暂停
- 大右箭头
-
逐步执行代码的同时观察我们输入的参数, 发生变化的地方即为加密方法, 如下
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3M3Ulrai-1573125994907)(E:\Fire\笔记\爬虫高级.assets\1572771322663.png)]](https://i-blog.csdnimg.cn/blog_migrate/786fe704fc808863c42f8eabfb27068f.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的目的, 就是为了让爬虫能够使用这些页面的渲染, 也就是上述的这些功能
参考文章:
207

被折叠的 条评论
为什么被折叠?



