「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的相关操作二

相关文章
|
8天前
|
数据采集 存储 JSON
Python网络爬虫:Scrapy框架的实战应用与技巧分享
【10月更文挑战第27天】本文介绍了Python网络爬虫Scrapy框架的实战应用与技巧。首先讲解了如何创建Scrapy项目、定义爬虫、处理JSON响应、设置User-Agent和代理,以及存储爬取的数据。通过具体示例,帮助读者掌握Scrapy的核心功能和使用方法,提升数据采集效率。
46 6
|
1天前
|
数据采集 Web App开发 监控
高效爬取B站评论:Python爬虫的最佳实践
高效爬取B站评论:Python爬虫的最佳实践
|
2天前
|
数据采集 存储 JSON
Python爬虫开发中的分析与方案制定
Python爬虫开发中的分析与方案制定
|
7天前
|
数据采集 JSON 测试技术
Python爬虫神器requests库的使用
在现代编程中,网络请求是必不可少的部分。本文详细介绍 Python 的 requests 库,一个功能强大且易用的 HTTP 请求库。内容涵盖安装、基本功能(如发送 GET 和 POST 请求、设置请求头、处理响应)、高级功能(如会话管理和文件上传)以及实际应用场景。通过本文,你将全面掌握 requests 库的使用方法。🚀🌟
27 7
|
6天前
|
数据采集 Web App开发 JavaScript
爬虫策略规避:Python爬虫的浏览器自动化
爬虫策略规避:Python爬虫的浏览器自动化
|
6天前
|
数据采集 存储 XML
Python实现网络爬虫自动化:从基础到实践
本文将介绍如何使用Python编写网络爬虫,从最基础的请求与解析,到自动化爬取并处理复杂数据。我们将通过实例展示如何抓取网页内容、解析数据、处理图片文件等常用爬虫任务。
|
3月前
|
机器学习/深度学习 数据采集 数据可视化
基于爬虫和机器学习的招聘数据分析与可视化系统,python django框架,前端bootstrap,机器学习有八种带有可视化大屏和后台
本文介绍了一个基于Python Django框架和Bootstrap前端技术,集成了机器学习算法和数据可视化的招聘数据分析与可视化系统,该系统通过爬虫技术获取职位信息,并使用多种机器学习模型进行薪资预测、职位匹配和趋势分析,提供了一个直观的可视化大屏和后台管理系统,以优化招聘策略并提升决策质量。
167 4
|
3月前
|
数据采集 存储 搜索推荐
打造个性化网页爬虫:从零开始的Python教程
【8月更文挑战第31天】在数字信息的海洋中,网页爬虫是一艘能够自动搜集网络数据的神奇船只。本文将引导你启航,用Python语言建造属于你自己的网页爬虫。我们将一起探索如何从无到有,一步步构建一个能够抓取、解析并存储网页数据的基础爬虫。文章不仅分享代码,更带你理解背后的逻辑,让你能在遇到问题时自行找到解决方案。无论你是编程新手还是有一定基础的开发者,这篇文章都会为你打开一扇通往数据世界的新窗。
|
4月前
|
数据采集 存储 JSON
从零到一构建网络爬虫帝国:HTTP协议+Python requests库深度解析
【7月更文挑战第31天】在网络数据的海洋中,使用Python的`requests`库构建网络爬虫就像探索未知的航船。HTTP协议指导爬虫与服务器交流,收集信息。HTTP请求包括请求行、头和体,响应则含状态行、头和体。`requests`简化了发送各种HTTP请求的过程。
82 4
|
1月前
|
数据采集 存储 数据挖掘
深入探索 Python 爬虫:高级技术与实战应用
本文介绍了Python爬虫的高级技术,涵盖并发处理、反爬虫策略(如验证码识别与模拟登录)及数据存储与处理方法。通过asyncio库实现异步爬虫,提升效率;利用tesseract和requests库应对反爬措施;借助SQLAlchemy和pandas进行数据存储与分析。实战部分展示了如何爬取电商网站的商品信息及新闻网站的文章内容。提醒读者在实际应用中需遵守法律法规。
171 66