「Python」爬虫-8.断点调试-网易云评论爬取

简介: >通过前面几篇文章的学习,这里我们以爬取网易云评论为例,来进行一次综合实战。本文涉及到的知识点主要是断点调试,讲述如何模拟加密。

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第21天, 点击查看活动详情

关于爬虫相关,欢迎先阅读一下我的前几篇文章😶‍🌫️😶‍🌫️😶‍🌫️:

「Python」爬虫-1.入门知识简介 - 掘金 (juejin.cn)

「Python」爬虫-2.xpath解析和cookie,session - 掘金 (juejin.cn)

「Python」爬虫-3.防盗链处理 - 掘金 (juejin.cn)

「Python」爬虫-4.selenium的使用 - 掘金 (juejin.cn)

「Python」爬虫-5.m3u8(视频)文件的处理 - 掘金 (juejin.cn)

「Python」爬虫-6.爬虫效率的提高 - 掘金 (juejin.cn)

「Python」爬虫-7.验证码的识别 - 掘金 (juejin.cn)

参考链接:

1.点击查看【bilibili】

通过前面几篇文章的学习,这里我们以爬取网易云评论为例,来进行一次综合实战。本文涉及到的知识点主要是断点调试,讲述如何模拟加密。

网易云评论爬取综合案例

本次爬取的目标网站 -> 网易云音乐 (163.com)

本文以爬取毛不易的《呓语》这首歌的评论为例来进行具体的分析。

网址 -> 呓语 - https://music.163.com/#/song?id=1417862046

首先需要找到正确的请求(记得刷新页面)。具体抓包的方式之前有提到过。

先清空所有请求,然后只查看Fetch/XHR,并刷新页面,然后就会找到一个以get?csrf_token=开头的请求,我们直接点开预览,如下图:

image.png

很显然,我们看到了comments,也就是评论,这就是我们想要的东西。接着展开,如图:

image.png

可以看到评论的内容确实是在这个请求里面的。

请求url如下图:

image.png

我们再看到负载中-表单数据,发现表单中有两个参数params以及encSecKey。很显然,这极有可能就是被加密了,想要直接拿到评论内容应该是没有那么容易的。

image.png

然后我们跳转到发起程序,看到如图中的调用堆栈。直接点第一个(第一个是最近被调用的

image.png

点击之后就可得到json格式的信息,再点击左下角的花括号,格式化一下代码:

image.png

在定位的位置先打上断点

image.png

一直请求,直到到我们需要的位置为止.(我们需要的是含有comment的url

image.png

image.png

通过观察作用域中的request中的url,知道出现comment为止。

接下来需要找到加密的代码在什么位置

可以使用Ctrl + F查找关键字,前面请求的数据是paramsencSecKey,也可以找到加密的代码所处的位置。

另一种是观察堆栈的请求数据在哪个地方发生了变化导致的数据加密,也可以定位到相同的位置。

image.png

一个一个堆栈点击,并观察data里面的变化,u3x.be3x的下一个堆栈就已经没有加密的东西了,所以加密发生在u3x.be3x这个里面。

image.png

可以设置断点,一步一步的跑,直到观察到data发生变化时
image.png

先找到第一个调用的堆栈,然后在send处设置断点,然后点击刷新直接会跳转到加密的那个语句里面,如下图所示位置,这时候去控制台打印,就能得到所需要的东西。

搜索bKf2x,跳转到13364

image.png

在控制台尝试输入:

bva9R(["流泪","强"])
bva9R(Tu4y.md)

返回结果如下:

image.png

这两串在控制台返回的是一样的值,所以在模拟加密的时候将其固定写死。

如何得到iencSecKey

定位到13365行,打个断点:
image.png

上图已经找到了发生加密的位置,
继续寻找源头,如下图:
image.png

h.ebcText处打断点,然后下一步,就可以得到i的值

image.png

这里给个搜索加密参数的技巧,搜索window.arsea往往可以较为快速的找到加密的位置。

然后我们需要想办法已有的参数进行加密,然而每个网站的加密算法和逻辑都不一定一致,所以想网易云的api发送请求的时候,那必然是照着他的逻辑来了,不然你可能请求不到任何东西。

return h.encText那一段代码复制下来,进行处理:

d()函数和图中的是对应的:

 function d(d, e, f, g) {        d:数据 e:010001 f:很长 g:0CoJUm6Qyw8W8jud

image.png

根据上述图片,前面我们已经发现了bva9R(["流泪","强"])bva9R(Tu4y.md)都是固定值。
e010001f是一串很长的字符串。

再看到以下代码:

var h = {}    # 空对象
  , i = a(16); 

观察a()函数

function a(a = 16) { # 返回16位随机的字符串
        var d, e, b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", c = "";
        for (d = 0; a > d; d += 1) # 循环16次
            e = Math.random() * b.length, # 随机数 1.2345
            e = Math.floor(e), # 取整
            c += b.charAt(e); # 去字符串中的xxx位置 b
        return c
    }

所以a函数的作用就是返回随机的字符串,i是16位的随机值,不妨把i设置成定值

再看下面:

return h.encText = b(d, g),
        h.encText = b(h.encText, i),
        h.encSecKey = c(i, e, f),
        h

以上三行等价于 :

h.encText = b(d,g)
h.encText = b(h.encText, i) 
h.encSecKey = c(i,e,f) 
return h

先执行h.encText = b(d,g)
传进b()中的参数中d是数据,g是密钥。

观察b()函数:

function b(a, b) {    # a是要加密的内容--即data, b--密钥
        var c = CryptoJS.enc.Utf8.parse(b)     # b是密钥
          , d = CryptoJS.enc.Utf8.parse("0102030405060708")
          , e = CryptoJS.enc.Utf8.parse(a)    # e是数据
          , f = CryptoJS.AES.encrypt(e, c, {    # c 加密的密钥
            iv: d,    # 偏移量
            mode: CryptoJS.mode.CBC # 模式:CBC
        });
        return f.toString()
    }

然后在执行h.encText = b(h.encText, i),这里把之前的返回值h.encText继续放到了b()函数里,说明进行了两次加密。

数据 + g => b => 第一次加密返回值 + i => b => params

然后执行的是h.encSecKey = c(i,e,f)

观察c(i,e,f)函数 , 已知e:010001i是密钥

function c(a, b, c) {            # c里面不产生随机数
        var d, e;
        return setMaxDigits(131),
        d = new RSAKeyPair(b,"",c),
        e = encryptedString(d, a)
    }

c()中的参数f是通过e函数得到的。而
f.encText = c(a + e, b, d) c函数是不产生随机数的,但是a()函数产生随机数,所以只需要让a()函数不产生随机数,也就是把function a(a)中的参数a固定,那么a()函数返回的值就是固定的了,而不是随机的字符串。

整个分析的js代码如下

# 1.找到未加密的参数  # window.arsea(参数,xxx,xxx)\
# 2.想办法把参数进行加密(必须参考网易的逻辑),params -> encText , encSecKey -> encSecKey 
# 3.请求到网易,拿到评论信息
'''
处理加密过程

function() {
    function a(a = 16) { # 返回16位随机的字符串
        var d, e, b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", c = "";
        for (d = 0; a > d; d += 1) # 循环16次
            e = Math.random() * b.length, # 随机数 1.2345
            e = Math.floor(e), # 取整
            c += b.charAt(e); # 去字符串中的xxx位置 b
        return c
    }
    function b(a, b) {    # a是要加密的内容
        var c = CryptoJS.enc.Utf8.parse(b)     # b是密钥
          , d = CryptoJS.enc.Utf8.parse("0102030405060708")
          , e = CryptoJS.enc.Utf8.parse(a)    # e是数据
          , f = CryptoJS.AES.encrypt(e, c, {    # c 加密的密钥
            iv: d,    # 偏移量
            mode: CryptoJS.mode.CBC # 模式:CBC
        });
        return f.toString()
    }
    function c(a, b, c) {            # c里面不产生随机数
        var d, e;
        return setMaxDigits(131),
        d = new RSAKeyPair(b,"",c),
        e = encryptedString(d, a)
    }
    function d(d, e, f, g) {        d:数据 e:010001 f:很长 g:0CoJUm6Qyw8W8jud
        var h = {}    # 空对象
          , i = a(16); # i是16位的随机值,把i设置成定值
        return h.encText = b(d, g),
        h.encText = b(h.encText, i),
        h.encSecKey = c(i, e, f),
        h
        
    }
    /* 以上三行等价于 ->
        *  h.encText = b(d,g)     # g是密钥
        *  h.encText = b(h.encText, i) # 返回的就是params   i也是密钥
        *  h.encSecKey = c(i,e,f) # 得到的就是encSecKey e和f是固定的,所以这时候把i也固定,就说明返回的encSecKey也是固定的
        *  return h
        *  两次加密:
        *     数据 + g  => b => 第一次加密 + i => b = params
        */
    function e(a, b, d, e) {
        var f = {};
        return f.encText = c(a + e, b, d),
        f
    }
    window.asrsea = d,
    window.ecnonasr = e
}();
'''

至此,我们再来捋一下,我们需要请求的url为

url = "https://music.163.com/weapi/comment/resource/comments/get?csrf_token="

由于发送请求的参数是paramsencSecKey,而这个参数往往是具有时效性的,所以我们需要模拟一下网易云的加密算法,从而得到paramsencSecKey参数。再向api发送请求,并得到返回的text,也就是我们想要的评论了!

/*请求的参数*/
csrf_token: ""
cursor: "-1"
offset: "0"
orderType: "1"
pageNo: "1"
pageSize: "20"
rid: "R_SO_4_1417862046"
threadId: "R_SO_4_1417862046"

对于function d(d, e, f, g)函数中的参数如下:

# 服务于d的
f = "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
g = "0CoJUm6Qyw8W8jud"
e = "010001"
i = "d8YcSIZJWOho8lxf" # 手动固定的  ->别人的函数中是随机的

通过固定的i结合前面分析的js代码,可以得到encSecKey

def get_encSecKey():    # 由于i是固定的,所以encSecKey就是固定的,c()函数的结果就是固定的
    return "abad643b9dfb5ab1456db763d10c39f633729bec3edc4f22a433772d0eb1a0b6dcf44a22d734565b7525c0e32a3b930ff1ac79a2cbade5b91bf9a9887bd3fa04b0468a4f450cdfcf41afb00402272fc860ff21960eee003e3f7b29f1066a6385dd53f33a647c5ef7c83377d2ce4bd44e0e72cdd753a559a327327ecbd5d5080b"

前面的function a(a)函数返回的是长度为16的字符串,所以这里构造一个转化为16长度倍数的函数如下:

# 转化成16的倍数,为下方的加密算法服务
def to_16(data):
    pad = 16 - len(data) % 16
    data += chr(pad) * pad
    return data

整个加密过程如下:

# 加密过程
def enc_params(data, key):  # 加密过程
    iv = "0102030405060708" # 偏移量
    data = to_16(data)
    aes = AES.new(key=key.encode("utf-8"), IV=iv.encode("utf-8"), mode=AES.MODE_CBC)  # 创建加密器
    bs = aes.encrypt(data.encode("utf-8"))  # 加密 
    # 加密的内容的长度必须是16的倍数
    
    ans = str(b64encode(bs), "utf-8")
    return ans  # 转换成字符串返回

由前面分析可知,前面进行了两次加密所以函数设计如下:

# 把参数进行加密
def get_params(data):  # 默认这里接收到的是字符串
    first = enc_params(data, g) # 第一次加密
    second = enc_params(first, i) # 第二次加密
    return second  # 返回的是params

最后向url发送带参数的请求即可:

# 发送请求,
resp = requests.post(url, data={
    "params": get_params(json.dumps(data)),
    "encSecKey": get_encSecKey()
})

完整代码如下:

import requests
import json

from Crypto.Cipher import AES
from base64 import b64encode

url = "https://music.163.com/weapi/comment/resource/comments/get?csrf_token="

# 请求的方式是post
data = {
    "csrf_token": "",
    "cursor": "-1",
    "offset": "0",
    "orderType": "1",
    "pageNo": "1",
    "pageSize": "20",
    "rid": "R_SO_4_1417862046",
    "threadId": "R_SO_4_1417862046"
}

# 服务于d的
f = "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
g = "0CoJUm6Qyw8W8jud"
e = "010001"
i = "d8YcSIZJWOho8lxf" # 手动固定的  ->别人的函数中是随机的


def get_encSecKey():    # 由于i是固定的,所以encSecKey就是固定的,c()函数的结果就是固定的
    return "abad643b9dfb5ab1456db763d10c39f633729bec3edc4f22a433772d0eb1a0b6dcf44a22d734565b7525c0e32a3b930ff1ac79a2cbade5b91bf9a9887bd3fa04b0468a4f450cdfcf41afb00402272fc860ff21960eee003e3f7b29f1066a6385dd53f33a647c5ef7c83377d2ce4bd44e0e72cdd753a559a327327ecbd5d5080b"

# 转化成16的倍数,为下方的加密算法服务
def to_16(data):
    pad = 16 - len(data) % 16
    data += chr(pad) * pad
    return data

# 加密过程
def enc_params(data, key):  # 加密过程
    iv = "0102030405060708" # 偏移量
    data = to_16(data)
    aes = AES.new(key=key.encode("utf-8"), IV=iv.encode("utf-8"), mode=AES.MODE_CBC)  # 创建加密器
    bs = aes.encrypt(data.encode("utf-8"))  # 加密 加密的内容的长度必须是16的倍数
    ans = str(b64encode(bs), "utf-8")
    return ans  # 转换成字符串返回

# 把参数进行加密
def get_params(data):  # 默认这里接收到的是字符串
    first = enc_params(data, g)
    second = enc_params(first, i)
    return second  # 返回的是params

# 发送请求,
resp = requests.post(url, data={
    "params": get_params(json.dumps(data)),
    "encSecKey": get_encSecKey()
})

print(resp.text)
到此,断点调试相关内容就更新到这儿了,如果对你有帮助的话,就请留下足迹吧~🎈

往期好文推荐🪶

「MongoDB」Win10版安装教程

「Python」数字推盘游戏

「Python」sklearn第一弹-标准化和非线性转化

「Python」turtle绘制图形🎈

「Python」Pandas-DataFrame的相关操作二

相关文章
|
2天前
|
数据采集 中间件 Python
Scrapy爬虫:利用代理服务器爬取热门网站数据
Scrapy爬虫:利用代理服务器爬取热门网站数据
|
2天前
|
数据采集 Web App开发 数据处理
Lua vs. Python:哪个更适合构建稳定可靠的长期运行爬虫?
Lua vs. Python:哪个更适合构建稳定可靠的长期运行爬虫?
|
2天前
|
数据采集 Web App开发 Java
Python 爬虫:Spring Boot 反爬虫的成功案例
Python 爬虫:Spring Boot 反爬虫的成功案例
|
2天前
|
数据采集 Python
使用Python实现简单的Web爬虫
本文将介绍如何使用Python编写一个简单的Web爬虫,用于抓取网页上的信息。通过分析目标网页的结构,利用Python中的requests和Beautiful Soup库,我们可以轻松地提取所需的数据,并将其保存到本地或进行进一步的分析和处理。无论是爬取新闻、股票数据,还是抓取图片等,本文都将为您提供一个简单而有效的解决方案。
|
2天前
|
数据采集 存储 XML
如何利用Python构建高效的Web爬虫
本文将介绍如何使用Python语言以及相关的库和工具,构建一个高效的Web爬虫。通过深入讨论爬虫的基本原理、常用的爬虫框架以及优化技巧,读者将能够了解如何编写可靠、高效的爬虫程序,实现数据的快速获取和处理。
|
2天前
|
数据采集 Web App开发 iOS开发
爬取B站评论:Python技术实现详解
爬取B站评论:Python技术实现详解
|
2天前
|
数据采集 Web App开发 数据可视化
Python爬虫技术与数据可视化:Numpy、pandas、Matplotlib的黄金组合
Python爬虫技术与数据可视化:Numpy、pandas、Matplotlib的黄金组合
|
2天前
|
数据采集 XML 数据处理
使用Python实现简单的Web爬虫
本文将介绍如何使用Python编写一个简单的Web爬虫,用于抓取网页内容并进行简单的数据处理。通过学习本文,读者将了解Web爬虫的基本原理和Python爬虫库的使用方法。
|
2天前
|
数据采集 存储 API
网络爬虫与数据采集:使用Python自动化获取网页数据
【4月更文挑战第12天】本文介绍了Python网络爬虫的基础知识,包括网络爬虫概念(请求网页、解析、存储数据和处理异常)和Python常用的爬虫库requests(发送HTTP请求)与BeautifulSoup(解析HTML)。通过基本流程示例展示了如何导入库、发送请求、解析网页、提取数据、存储数据及处理异常。还提到了Python爬虫的实际应用,如获取新闻数据和商品信息。
|
2天前
|
数据采集 存储 大数据
Python爬虫:数据获取与解析的艺术
本文介绍了Python爬虫在大数据时代的作用,重点讲解了Python爬虫基础、常用库及实战案例。Python因其简洁语法和丰富库支持成为爬虫开发的优选语言。文中提到了requests(发送HTTP请求)、BeautifulSoup(解析HTML)、Scrapy(爬虫框架)、Selenium(处理动态网页)和pandas(数据处理分析)等关键库。实战案例展示了如何爬取电商网站的商品信息,包括确定目标、发送请求、解析内容、存储数据、遍历多页及数据处理。最后,文章强调了遵守网站规则和尊重隐私的重要性。
30 2