爬虫进阶知识>解析网站中的JavaScript

js解析

引言

在了解如何调试js之前, 需要简单了解一下http的请求过程

1.DNS域名解析;
2.建立TCP连接;
3.发送HTTP请求;
4.服务器处理请求;
5.返回响应结果;
6.关闭TCP连接;
7.浏览器解析HTML;
8.浏览器布局渲染;

上面8个步骤被戏称为"天龙八步"

参考链接: https://zhuanlan.zhihu.com/p/32370763

简单说一下我自己的理解:

  1. 浏览器解析出域名对应的ip后, 建立与服务器的连接
  2. 向访问的url地址发起请求, 服务器处理后返回响应结果, 被浏览器接收
  3. 在返回的响应内容中, 会包含html,css,js和ajax等代码, 浏览器会依次加载这些代码
  4. 如果其中包含发送请求的代码(例如js和ajax请求), 就会再次发起这些请求, 并获取响应
  5. 加载完毕的响应内容会被展示在浏览器中, 直到最终获取全部响应
  6. 这个过程就是浏览器的渲染

但是在爬虫中, 其发起的请求只能拿到对应的响应. 而我们在浏览器中看到的页面, 很多时候跟爬虫得到的并不一样, 这是因为简单的爬虫不具备渲染页面的能力(可以借助外部工具来实现, 如selenium), 浏览器展示的结果是由多次请求/响应共同渲染而来的, 而爬虫的一次请求只能得到一个响应

因为我们爬虫每次只有一个请求,但是实际中很多请求又js连带发送,所以我们需要利用爬虫解析js去实现这些请求的发送

方案

网页中的参数来源主要由以下四种:

  1. 固定值, 写死在html中的参数
  2. 用户给的参数
  3. 服务器(通过ajax)返回的参数, 比如时间戳, token等
  4. js生成的参数

由于网站反爬措施的关系, 通过请求得到的参数很多是在前端js里生成的, 因此为了得到参数, 我们由以下方案(顺位推荐)

方案1:本地运行js, 把js代码拿下来,用python代码去执行它(参见pyexcjs),从而得到所需请求参数
方案2:解析js,破解加密方法或者生成参数值方法,python代码模拟实现这个方法, 从而得到我们需要的请求参数
方法3:selenium, 如果上面两种都不行, 这就是我们的绝招, 模拟用户使用浏览器, 获取渲染后的页面源码

本文主要介绍第二种方案

1>筛选参数

以微博登录为例, 用户名和密码作为输入的参数, 在发送给服务器之前会被js做加密处理, 由此也增加了我们使用爬虫模拟登陆的难度, 因此选择使用解析js的方法来搞定

每当我们点击登录的时候都会发送一个login请求, 如图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UefzSUVZ-1573125994896)(E:\Fire\笔记\爬虫高级.assets\1572767081082.png)]

登录表单中的参数并不一定都是我们需要的, 可以通过对比多次请求中的参数, 再加上一些经验和猜想, 过滤掉固定参数 或服务器自带参数和用户输入的参数, 这是剩下的就是js生成的数值或加密数值, 如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iUJX7zJA-1573125994899)(E:\Fire\笔记\爬虫高级.assets\1572768213043.png)]

最终得到的值:

# 图片 picture id:
pcid: yf-d0efa944bb243bddcf11906cda5a46dee9b8
# 用户名:
su: cXdlcnRxd3Jl
nonce: 2SSH2A	# 未知
# 密码:
sp: e121946ac9273faf9c63bc0fdc5d1f84e563a4064af16f635000e49cbb2976d73734b0a8c65a6537e2e728cd123e6a34a7723c940dd2aea902fb9e7c6196e3a15ec52607fd02d5e5a28e18254105358e897996f0b9057afe2d24b491bb12ba29db3265aef533c1b57905bf02c0cee0c546f4294b0cf73a553aa1f7faf9f835e5
prelt: 148	# 未知

请求参数中的用户名和密码都是经过加密处理的, 因此如果需要模拟登录, 我们就需要找到这个加密的方法, 利用它来为我们的数据进行加密

找到所需的js

  1. 要找到加密方法, 首先我们需要先找到登录所需的js代码, 可以使用以下3种方式:

    1. 找事件, 在页面检查目标元素,在开发工具的子窗口里选中Events Listeners, 找到click事件,点击定位到js代码, 如图: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4igWFpYz-1573125994900)(E:\Fire\笔记\爬虫高级.assets\1572769323122.png)]

      这种方法找到的不一定是正确的, 所以推荐第二个方法

    2. 找请求, 在Network中点击列表界面的对应Initiator跳转至对应js界面
      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vhMPtIPD-1573125994902)()]

    3. 通过搜索参数名进行定位 在这里插入图片描述

  2. 登录的js代码:
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gT2Rb8dq-1573125994902)()]

点击左下角的{}图标, 可以更方便查看代码

  1. 在这个submit的方法上打断点, 然后输入用户名密码, 先不点登录, 回到dev tool点击这个按钮启用调试:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NVnckBwQ-1573125994902)()]

  1. 然后再去点登录按钮, 这时候就可以开始调试, 调试工具有以下几种:

    1. 大右箭头[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-skoTYbsC-1573125994903)()] 跳到下一个断点(如果没有断点就执行完)
    2. 弯箭头[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bWby0Bb9-1573125994903)()] 逐句执行
    3. 下箭头[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TvnnHccl-1573125994904)()] 跳到下一个要执行的函数
    4. 上箭头[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MAcE4Gpv-1573125994904)()] 跳出当前执行的函数
    5. 右箭头[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GNdpA4m7-1573125994905)()] 一步一步执行代码
    6. 子弹[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mM2OHbLH-1573125994906)(E:\Fire\笔记\爬虫高级.assets\1572770809950.png)] 取消当前所有的断点
    7. 暂停 捕获异常时暂停
  2. 逐步执行代码的同时观察我们输入的参数, 发生变化的地方即为加密方法, 如下

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3M3Ulrai-1573125994907)(E:\Fire\笔记\爬虫高级.assets\1572771322663.png)]

  3. 上图中的加密方式是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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值