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

news/2024/7/19 9:15:29 标签: 爬虫, JS

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\笔记\<a class=爬虫高级.assets\1572767081082.png)]" />

登录表单中的参数并不一定都是我们需要的, 可以通过对比多次请求中的参数, 再加上一些经验和猜想, 过滤掉固定参数 或服务器自带参数和用户输入的参数, 这是剩下的就是js生成的数值或加密数值, 如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iUJX7zJA-1573125994899)(E:\Fire\笔记\<a class=爬虫高级.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\笔记\<a class=爬虫高级.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\笔记\<a class=爬虫高级.assets\1572770809950.png)]" /> 取消当前所有的断点
    7. 暂停 捕获异常时暂停
  2. 逐步执行代码的同时观察我们输入的参数, 发生变化的地方即为加密方法, 如下

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3M3Ulrai-1573125994907)(E:\Fire\笔记\<a class=爬虫高级.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


http://www.niftyadmin.cn/n/1487917.html

相关文章

Strom的trident小例子

上代码: 1 public class TridentFunc {2 3 /**4 * 类似于普通的bolt5 */6 public static class MyFunction extends BaseFunction{7 Override8 public void execute(TridentTuple tuple, TridentCollector collector) {9 In…

Python爬虫攻略(2)Selenium+多线程爬取链家网二手房信息

申明&#xff1a;本文对爬取的数据仅做学习使用&#xff0c;请勿使用爬取的数据做任何商业活动&#xff0c;侵删 前戏 安装Selenium: pip install selenium 如果下载速度较慢, 推荐使用国内源: pip install selenium -i https://pypi.tuna.tsinghua.edu.cn/simple 本次爬虫…

c#基础 里氏转换

1、里氏转换1)、子类可以赋值给父类2)、如果父类中装的是子类对象&#xff0c;那么可以讲这个父类强转为子类对象。 2、子类对象可以调用父类中的成员&#xff0c;但是父类对象永远都只能调用自己的成员。 1、里氏转换 1)、子类可以赋值给父类:如果有一个地方需要一个父类作为参…

Python爬虫攻略(3)链家网爬虫 Selenium+Requests多线程

申明&#xff1a;本文对爬取的数据仅做学习使用&#xff0c;请勿使用爬取的数据做任何商业活动&#xff0c;侵删 本文是对上一篇文章中代码的优化: Python爬虫攻略(2)>Selenium多线程爬取链家网二手房信息 先上代码: 更多的信息在代码注释中 #!/usr/bin/env python #-*- …

nodejs中req.body对请求参数的解析问题

首先&#xff0c;先了解一下关于http协议里定义的四种常见数据的post方法&#xff0c;分别是&#xff1a; application/www-form-ulrencoded multipart/form-data application/json text/xml Express依赖bodyParser对请求的包体进行解析。默认支持application/json,application…

JavaWeb中的监听器

JavaWeb中的监听器 l 事件源&#xff1a;三大域&#xff01; ServletContext 生命周期监听&#xff1a;ServletContextListener&#xff0c;它有两个方法&#xff0c;一个在出生时调用&#xff0c;一个在死亡时调用&#xff1b; void contextInitialized(Servlet…

Python 导出导入安装包

python导出安装包 pip freeze > requirements.txt python导入安装包 pip install -r requirements.txt

UE4物理动画使用

Rigid Body Body的创建。 对重要骨骼创建Body&#xff0c;保证Body控制的是表现和变化比较大的骨骼。 需要对Root创建Body并绑定&#xff0c;设置为Kinematic且不启用物理。原因是UPrimitiveComponent::SyncComponentToRBPhysics会取RootBody的位置来同步Component的位置。 Bod…