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

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

持续创作,加速成长!这是我参与「掘金日新计划 · 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的相关操作二

相关文章
|
10天前
|
数据采集 存储 XML
Python爬虫:深入探索1688关键词接口获取之道
在数字化经济中,数据尤其在电商领域的价值日益凸显。1688作为中国领先的B2B平台,其关键词接口对商家至关重要。本文介绍如何通过Python爬虫技术,合法合规地获取1688关键词接口,助力商家洞察市场趋势,优化营销策略。
|
8天前
|
数据采集 JSON 开发者
Python爬虫京东商品详情数据接口
京东商品详情数据接口(JD.item_get)提供商品标题、价格、品牌、规格、图片等详细信息,适用于电商数据分析、竞品分析等。开发者需先注册账号、创建应用并申请接口权限,使用时需遵循相关规则,注意数据更新频率和错误处理。示例代码展示了如何通过 Python 调用此接口并处理返回的 JSON 数据。
|
12天前
|
XML 数据采集 数据格式
Python 爬虫必备杀器,xpath 解析 HTML
【11月更文挑战第17天】XPath 是一种用于在 XML 和 HTML 文档中定位节点的语言,通过路径表达式选取节点或节点集。它不仅适用于 XML,也广泛应用于 HTML 解析。基本语法包括标签名、属性、层级关系等的选择,如 `//p` 选择所有段落标签,`//a[@href='example.com']` 选择特定链接。在 Python 中,常用 lxml 库结合 XPath 进行网页数据抓取,支持高效解析与复杂信息提取。高级技巧涵盖轴的使用和函数应用,如 `contains()` 用于模糊匹配。
|
15天前
|
数据采集 XML 存储
构建高效的Python网络爬虫:从入门到实践
本文旨在通过深入浅出的方式,引导读者从零开始构建一个高效的Python网络爬虫。我们将探索爬虫的基本原理、核心组件以及如何利用Python的强大库进行数据抓取和处理。文章不仅提供理论指导,还结合实战案例,让读者能够快速掌握爬虫技术,并应用于实际项目中。无论你是编程新手还是有一定基础的开发者,都能在这篇文章中找到有价值的内容。
|
13天前
|
数据采集 JavaScript 前端开发
Python爬虫能处理动态加载的内容吗?
Python爬虫可处理动态加载内容,主要方法包括:使用Selenium模拟浏览器行为;分析网络请求,直接请求API获取数据;利用Pyppeteer控制无头Chrome。这些方法各有优势,适用于不同场景。
|
21天前
|
数据采集 存储 数据可视化
Python数据分析:揭秘"黑神话:悟空"Steam用户评论趋势
Python数据分析:揭秘"黑神话:悟空"Steam用户评论趋势
|
21天前
|
数据采集 监控 搜索推荐
python爬虫的基本使用
本文介绍了Python爬虫的基本概念及其广泛应用,包括搜索引擎、数据挖掘、网络监控、舆情分析和信息聚合等领域。通过安装`urllib`和`BeautifulSoup`库,展示了如何编写简单代码实现网页数据的抓取与解析。爬虫技术在大数据时代的重要性日益凸显,为各行业提供了高效的数据获取手段。
31 1
|
21天前
|
数据采集 JavaScript 程序员
探索CSDN博客数据:使用Python爬虫技术
本文介绍了如何利用Python的requests和pyquery库爬取CSDN博客数据,包括环境准备、代码解析及注意事项,适合初学者学习。
60 0
|
1月前
|
数据采集 存储 JSON
Python网络爬虫:Scrapy框架的实战应用与技巧分享
【10月更文挑战第27天】本文介绍了Python网络爬虫Scrapy框架的实战应用与技巧。首先讲解了如何创建Scrapy项目、定义爬虫、处理JSON响应、设置User-Agent和代理,以及存储爬取的数据。通过具体示例,帮助读者掌握Scrapy的核心功能和使用方法,提升数据采集效率。
90 6
|
4月前
|
机器学习/深度学习 数据采集 数据可视化
基于爬虫和机器学习的招聘数据分析与可视化系统,python django框架,前端bootstrap,机器学习有八种带有可视化大屏和后台
本文介绍了一个基于Python Django框架和Bootstrap前端技术,集成了机器学习算法和数据可视化的招聘数据分析与可视化系统,该系统通过爬虫技术获取职位信息,并使用多种机器学习模型进行薪资预测、职位匹配和趋势分析,提供了一个直观的可视化大屏和后台管理系统,以优化招聘策略并提升决策质量。
209 4