高效爬取某易云音乐:Python JS 逆向与多线程结合实践

一、爬取目标与技术难点分析

本次爬取目标为某易云音乐指定歌曲的基本信息(如歌名、歌手、专辑)及评论数据(包括评论内容、点赞数、用户信息)。技术难点主要集中在两个方面:一是某易云音乐接口参数的 JS 加密机制,核心参数如 <font style="color:rgba(0, 0, 0, 0.85) !important;">params</font><font style="color:rgba(0, 0, 0, 0.85) !important;">encSecKey</font> 通过前端 JS 动态生成,无法直接构造请求;二是单线程爬取大量评论数据效率低下,需引入并发机制提升速度。

二、JS 逆向破解加密逻辑

1. 定位加密入口

通过 Chrome 开发者工具抓包分析,发现获取评论的接口为 <font style="color:rgba(0, 0, 0, 0.85) !important;">https://music.163.com/weapi/comment/resource/comments/get</font>,请求方式为 POST,参数包含 <font style="color:rgba(0, 0, 0, 0.85) !important;">params</font><font style="color:rgba(0, 0, 0, 0.85) !important;">encSecKey</font>。在 Sources 面板中搜索关键词,定位到加密逻辑所在的 JS 文件(通常为 <font style="color:rgba(0, 0, 0, 0.85) !important;">core_*.js</font>),并找到加密函数(如 <font style="color:rgba(0, 0, 0, 0.85) !important;">window.asrsea</font>)。

2. 分析加密算法

通过格式化 JS 代码,发现加密过程基于 AES 和 RSA 算法:

  • <font style="color:rgb(0, 0, 0);">params</font> 采用 AES-128-CBC 加密,密钥固定为 <font style="color:rgb(0, 0, 0);">0CoJUm6Qyw8W8jud</font>,偏移量为随机 16 位字符串;
  • <font style="color:rgb(0, 0, 0);">encSecKey</font> 是对随机生成的 16 位密钥进行 RSA 加密后的结果,公钥固定。

3. Python 还原加密逻辑

使用 <font style="color:rgba(0, 0, 0, 0.85) !important;">pycryptodome</font> 库实现 AES 和 RSA 加密,还原 JS 加密过程:

import base64
from Crypto.Cipher import AES
from Crypto.PublicKey import RSA
from Crypto.Util.number import bytes_to_long, long_to_bytes
import random

# AES加密函数
def aes_encrypt(text, key):
    iv = '0102030405060708'  # 偏移量固定为16位
    pad = 16 - len(text) % 16
    text = text + pad * chr(pad)
    cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC, iv.encode('utf-8'))
    encrypted = cipher.encrypt(text.encode('utf-8'))
    return base64.b64encode(encrypted).decode('utf-8')

# RSA加密函数
def rsa_encrypt(text, pub_key, modulus):
    text = text[::-1]
    rs = int(bytes_to_long(text.encode('utf-8'))) ** int(pub_key, 16) % int(modulus, 16)
    return format(rs, 'x').zfill(256)

# 生成加密参数
def get_enc_params(data):
    # 固定公钥和模数
    pub_key = '010001'
    modulus = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'
    # 随机16位密钥
    secret_key = ''.join(random.sample('abcdefghijklmnopqrstuvwxyz0123456789', 16))
    # AES加密数据
    params = aes_encrypt(aes_encrypt(data, '0CoJUm6Qyw8W8jud'), secret_key)
    # RSA加密密钥
    enc_sec_key = rsa_encrypt(secret_key, pub_key, modulus)
    return {'params': params, 'encSecKey': enc_sec_key}

三、多线程提升爬取效率

1. 多线程架构设计

采用生产者 - 消费者模式:主线程解析评论分页参数,生产者线程生成请求任务,消费者线程执行爬取并存储数据,使用 <font style="color:rgba(0, 0, 0, 0.85) !important;">queue</font> 模块实现任务队列。

2. 多线程爬取实现

import requests
import json
from queue import Queue
from threading import Thread
import time
import base64
from Crypto.Cipher import AES
from Crypto.PublicKey import RSA
from Crypto.Util.number import bytes_to_long, long_to_bytes
import random

# 代理配置
proxyHost = "www.16yun.cn"
proxyPort = "5445"
proxyUser = "16QMSOML"
proxyPass = "280651"
proxyAuth = f"{proxyUser}:{proxyPass}"
proxyAuthEncoded = base64.b64encode(proxyAuth.encode()).decode()

# 构建代理字典
proxies = {
    "http": f"http://{proxyHost}:{proxyPort}",
    "https": f"https://{proxyHost}:{proxyPort}"
}

# 构建代理头部(如果需要认证)
proxy_headers = {
    "Proxy-Authorization": f"Basic {proxyAuthEncoded}"
}

# AES加密函数
def aes_encrypt(text, key):
    iv = '0102030405060708'  # 偏移量固定为16位
    pad = 16 - len(text) % 16
    text = text + pad * chr(pad)
    cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC, iv.encode('utf-8'))
    encrypted = cipher.encrypt(text.encode('utf-8'))
    return base64.b64encode(encrypted).decode('utf-8')

# RSA加密函数
def rsa_encrypt(text, pub_key, modulus):
    text = text[::-1]
    rs = int(bytes_to_long(text.encode('utf-8'))) ** int(pub_key, 16) % int(modulus, 16)
    return format(rs, 'x').zfill(256)

# 生成加密参数
def get_enc_params(data):
    # 固定公钥和模数
    pub_key = '010001'
    modulus = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'
    # 随机16位密钥
    secret_key = ''.join(random.sample('abcdefghijklmnopqrstuvwxyz0123456789', 16))
    # AES加密数据
    params = aes_encrypt(aes_encrypt(data, '0CoJUm6Qyw8W8jud'), secret_key)
    # RSA加密密钥
    enc_sec_key = rsa_encrypt(secret_key, pub_key, modulus)
    return {'params': params, 'encSecKey': enc_sec_key}

# 全局配置
session = requests.Session()
session.headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36',
    'Referer': 'https://music.163.com/',
    "Proxy-Authorization": f"Basic {proxyAuthEncoded}"  # 添加代理认证头部
}
# 设置session的代理
session.proxies = proxies

comment_queue = Queue()  # 评论任务队列
result_list = []  # 存储爬取结果

# 消费者线程:爬取评论
def crawl_comment():
    while not comment_queue.empty():
        try:
            page, resource_id = comment_queue.get()
            # 构造请求数据
            data = json.dumps({
                'rid': f'R_SO_4_{resource_id}',
                'offset': (page - 1) * 20,
                'limit': 20,
                'csrf_token': ''
            })
            enc_params = get_enc_params(data)
            # 发送请求(使用session的代理配置)
            response = session.post(
                'https://music.163.com/weapi/comment/resource/comments/get', 
                data=enc_params,
                timeout=15  # 添加超时设置
            )
            result = response.json()
            if 'comments' in result:
                comments = [{'content': c['content'], 'likeCount': c['likedCount'], 'user': c['user']['nickname']} for c in result['comments']]
                result_list.extend(comments)
                print(f'第{page}页评论爬取完成,共{len(comments)}条')
            comment_queue.task_done()
            time.sleep(0.5)  # 避免请求过快
        except Exception as e:
            print(f'爬取失败:{e}')
            comment_queue.task_done()

# 生产者线程:生成任务
def produce_task(resource_id, total_page):
    for page in range(1, total_page + 1):
        comment_queue.put((page, resource_id))

# 主函数
def main(resource_id, total_page):
    # 生成任务
    produce_task(resource_id, total_page)
    # 创建消费者线程
    threads = []
    for _ in range(5):  # 启动5个线程
        t = Thread(target=crawl_comment)
        t.start()
        threads.append(t)
    # 等待所有任务完成
    comment_queue.join()
    for t in threads:
        t.join()
    print(f'爬取完成,共获取{len(result_list)}条评论')

if __name__ == '__main__':
    # 爬取歌曲ID为186016的评论,共10页
    main(186016, 10)

四、数据存储与优化

1. 数据持久化

将爬取的评论数据存储到 CSV 文件:

import csv

def save_to_csv(data, filename):
    with open(filename, 'w', newline='', encoding='utf-8') as f:
        writer = csv.DictWriter(f, fieldnames=['content', 'likeCount', 'user'])
        writer.writeheader()
        writer.writerows(data)

# 在main函数中添加保存逻辑
save_to_csv(result_list, 'music_comments.csv')

2. 爬取优化策略

  • 请求频率控制:通过 <font style="color:rgb(0, 0, 0);">time.sleep()</font> 避免触发反爬机制;
  • 异常重试:添加重试机制处理网络波动;
  • 代理池引入:使用代理 IP 分散请求来源;
  • 数据去重:通过评论 ID 去重,确保数据唯一性。

五、总结与风险提示

本文通过 JS 逆向破解某易云音乐加密机制,并结合多线程技术实现高效爬取,成功获取歌曲评论数据。但需注意,爬虫行为需遵守平台 <font style="color:rgba(0, 0, 0, 0.85) !important;">robots.txt</font> 协议及相关法律法规,避免过度爬取对服务器造成压力。

03-26
### 逆向工程反编译概述 逆向工程是一种通过对软件的目标代码进行分析,将其转化为更高级别的表示形式的过程。这一过程通常用于研究现有系统的内部结构、功能以及实现细节。在Java和Android领域,反编译工具被广泛应用于逆向工程中。 #### Java逆向工程中的Jad反编译工具 Jad是一款经典的Java反编译工具,能够将`.class`字节码文件转换为可读的`.java`源代码[^1]。虽然它可能无法完全恢复原始源代码,但它提供了足够的信息来帮助开发者理解已编译的Java程序逻辑。Jad支持多种反编译模式,并允许用户自定义规则以适应不同的需求。此外,其命令行接口和图形界面使得复杂代码的分析变得更加便捷。 #### Android逆向工程中的JEB反编译工具 针对Android应用的逆向工程,JEB是由PNF Software开发的一款专业级工具[^2]。相较于其他同类产品,JEB不仅具备强大的APK文件反编译能力,还能对Dalvik字节码执行高效而精准的操作。它的核心优势在于以下几个方面: - **广泛的平台兼容性**:除Android外,还支持ARM、MIPS等多种架构的二进制文件反汇编。 - **混淆代码解析**:内置模块能有效应对高度混淆的代码,提供分层重构机制以便于深入分析。 - **API集成支持**:允许通过编写Python或Java脚本来扩展功能并完成特定任务。 #### APK反编译流程及其意义 当涉及到具体的APK包时,可以通过一系列步骤提取其中的信息来进行全面的安全评估或者学习目的的研究工作[^3]。这些步骤一般包括但不限于获取资产目录(`assets`)内的资源数据;解密XML配置文档如`AndroidManifest.xml`定位应用程序启动点;最后利用上述提到的各种专用软件重现整个项目框架供进一步探讨。 ```bash # 使用apktool反编译APK示例 apktool d your_app.apk -o output_directory/ ``` 以上命令展示了如何借助开源工具ApkTool轻松拆卸目标安卓档案至于探索的状态下。 ### 结论 无论是传统的桌面端还是现代移动端环境里头,恰当运用合适的反编译解决方案都是达成逆向工程项目成功不可或缺的一环。每种工具有各自专精之处,在实际应用场景当中应当依据具体需求做出明智的选择。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值