网易云音乐自动打卡脚本:Python+云函数实现每日300首听歌量升级

网易云音乐自动打卡脚本:Python+云函数实现每日300首听歌量升级 简介一份基于Python的网易云音乐全自动打卡工具专为希望快速提升账号等级的用户设计。通过调用官方接口每天自动完成300首歌的听歌任务单账号耗时约1分钟全程无需人工干预支持无服务器云函数部署并可将每日任务进度实时推送到微信实现移动端轻量管理。包体共35个文件、仅37KB以Markdown文档、Python脚本、YAML配置为主同时包含JSON、HTML、JS、ICO等辅助文件涵盖自动化脚本、部署配置、任务说明与前端展示页面结构清晰便于二次开发。目前已有628人学习下载适合有一定Python基础的开发者或追求账号成长效率的网易云音乐重度用户。凭借完整源码与配套文档使用者可快速搭建属于自己的无人值守打卡服务。1. 网易云音乐全自动每日打卡 300 首账号等级升级的批处理思路网易云音乐的等级体系挂在“听歌量”上想要 Lv8、Lv9 甚至 Lv10靠手动听完 300 首根本不现实。我最初也试过让它自动切歌结果切太快不计数切太慢又费时翻来覆去换歌单也收效甚微。后来把这套流程拆成 Python 脚本模拟网页端播放上报每天自动打满 300 首有效播放跑完把结果推到微信再部署到无服务器云函数上定时执行机器关机也不影响。这篇博文面向的读者是手里有几个网易云音乐账号、明白这类自动化存在风控边界、并且愿意自己维护脚本、观察日志的那批人。2. 打卡判定机制weapi 加密签名、Cookie 与 300 首有效标准2.1 网易云音乐网页接口为什么走 weapi网易云音乐的网页端写操作接口都挂在https://music.163.com/weapi/路径下POST 过去的请求体不是普通 JSON而是params和encSecKey两个字段。流程是这样的前端随机生成一个 16 位密钥把它用 Web 端内置的 RSA 公钥加密成encSecKey然后用这个随机密钥对业务参数做 AES-128-CBC 加密得到params。服务器拿到请求后反向解包。复刻 weapi 签名并不涉及破解它只是把浏览器前端本来要做的事在 Python 里重做一遍。下面是最常见的实现只依赖 pycryptodomeimport base64 import binascii import json import random from Crypto.Cipher import AES from Crypto.PublicKey import RSA from Crypto.Cipher import PKCS1_v1_5 AES_SECRET 0CoJUm6Qyw8W8jud AES_IV b0102030405060708 RSA_PUBLIC_KEY paste_public_key_here # 完整公钥从开源weapi实现中复制 def aes_encrypt(text: str, key: str) - str: pad 16 - len(text.encode()) % 16 text chr(pad) * pad crypt AES.new(key.encode(), AES.MODE_CBC, AES_IV) return base64.b64encode(crypt.encrypt(text.encode())).decode() def rsa_encrypt(text: str) - str: pub_key RSA.importKey(RSA_PUBLIC_KEY) cipher PKCS1_v1_5.new(pub_key) enc cipher.encrypt(text.encode()) return binascii.hexlify(enc).decode() def gen_weapi_payload(data: dict) - dict: random_key .join(random.choice(0123456789abcdef) for _ in range(16)) first aes_encrypt(json.dumps(data), AES_SECRET) params aes_encrypt(first, random_key) return {params: params, encSecKey: rsa_encrypt(random_key)}逻辑说明先随机生成 16 位随机密钥第一层 AES 用固定密钥0CoJUm6Qyw8W8jud加密原始 JSON第二层再用随机密钥加密第一层密文encSecKey由 RSA 公钥加密随机密钥得到。这样做是为了复刻前端加密层而不需要理解背后的安全设计。AES_IV是固定的 16 字节字符串RSA_PUBLIC_KEY在 Node 版 NeteaseCloudMusicApi 的util/crypto.js里可以找到完整值直接替换即可。安装依赖用pip install pycryptodome requests。2.2 抓取 CookieMUSIC_U 决定登录态是否有效浏览器登录网易云音乐网页版后打开开发者工具在 Network 里随便挑一个music.163.com的请求从 Request Headers 中整段复制 Cookie。真正起作用的字段没有想象中多Cookie 字段作用典型特征MUSIC_U登录态核心标识很长包含用户身份信息__csrfCSRF 令牌部分写接口需要会话级OS标记客户端类型固定为 PC脚本发送请求时重点带上MUSIC_U其余字段跟着完整 Cookie 一起注入即可。这里要提醒一句Cookie 等价于账号密码不要提交到 Git 仓库也不要粘贴在公开工单里云函数部署时使用环境变量保存。2.3 300 首有效播放的判定标准网易云音乐的等级经验主要跟着“听歌数量”走但“听歌数量”不是按点击次数计算的。结合开源脚本和社区反推的通用规则至少有三条直接影响能否计入每日 300 首上限单曲播放进度超过一定比例社区常见说法是二分之一到三分之二才会计入一次有效播放同一首歌在一天内重复播放不会重复计数单日计入上限是 300 首超出部分只增加播放次数不增加等级经验。我用一批账号对比过单首上报间隔小于 5 秒时大量请求不计数只盯着一个 300 首歌单反复跑第二天有效计数也会明显下降。这也是后文把候选曲库拉到 1200 首以上的根本原因。2.4 打卡依赖的标准接口打卡动作实际落在下面几个接口上接口路径加密方式用途/weapi/feedback/weblogweapi上报播放日志/api/v6/playlist/detail普通 HTTPS拉取歌单歌曲列表/api/v1/user/detail/{uid}普通 HTTPS查询累计听歌数weblog的 payload 通常是这样的结构{ logs: [{ action: play, json: { type: song, id: 29671925, time: 120, source: songlist } }] }该接口每次上报一首歌logs虽然是数组但一次性塞几十条上去会触发风控正常网页端也是一条一条上报。source填songlist模拟来自歌单的播放行为。time字段建议随机落在 90 到 150 秒之间始终报完整歌曲时长反而容易被识别为机器特征。3. Python 打卡执行器歌单预处理、并发上报与随机间隔3.1 歌单预处理候选曲库要准备 1200 首以上打卡脚本的真正工作量不在 POST而在歌曲池质量。曲库太薄第二天全是重复歌曲有效计数直接腰斩。常见做法是准备 4 到 6 个不同风格歌单每个歌单取前 200 到 600 首去重后作为当天随机曲库。/api/v6/playlist/detail不用 weapi 签名带 Cookie 普通请求即可def fetch_playlist_songs(session, playlist_id): url https://music.163.com/api/v6/playlist/detail params {id: playlist_id, n: 1000, s: 0} resp session.get(url, paramsparams, timeout15) data resp.json() return [item[id] for item in data[playlist][trackIds]]这段代码只取trackIds里的歌曲 ID目的是减少响应体大小毕竟一次要拉几个歌单。n控制返回数量超过 1000 接口会截断。多个歌单的 ID 合并后做set去重再随机打散一天跑 300 首时基本不会遇到重复。3.2 weapi 上报封装与并发主循环把第 2 章的加密函数和 requests 组合成weapi_post再写打卡类。我这里用的是线程池而不是 asyncio逻辑更直白排错时每个人都读得懂import random import threading import time from concurrent.futures import ThreadPoolExecutor def weapi_post(session, url, payload): enc gen_weapi_payload(payload) resp session.post(url, dataenc, timeout15) return resp.json() class NeteasePuncher: def __init__(self, session, song_ids, target300): self.session session self.song_ids song_ids self.target target self.success 0 self.lock threading.Lock() def report_one(self, song_id): payload {logs: [{action: play, json: { type: song, id: song_id, time: random.choice([90, 120, 150]), source: songlist}}]} try: resp weapi_post(self.session, https://music.163.com/weapi/feedback/weblog, payload) if isinstance(resp, dict) and resp.get(code) in (200, 201): with self.lock: self.success 1 except Exception: pass time.sleep(random.uniform(3, 8)) def run(self): candidates self.song_ids[:] random.shuffle(candidates) pool ThreadPoolExecutor(max_workers6) futures [pool.submit(self.report_one, candidates[i % len(candidates)]) for i in range(self.target)] for f in futures: f.result() pool.shutdown() return self.success参数说明max_workers6是针对同账号频率限制折中的结果6 个并发足够把 300 首压到几分钟完成又不容易触发风控random.uniform(3, 8)是每首上报后的随机等待模拟人的聆听节奏固定间隔比随机间隔更容易被识别单条异常直接吞掉最后只看success计数不影响整体流程。如果曲库数量小于目标取模补足不会卡死。3.3 参数调优与风控观察点参数推荐值影响max_workers6并发越高风控越敏感sleep_interval3~8 秒随机低于 3 秒容易丢计数time90~150 随机高于歌曲时长会异常候选曲库≥1200 首防止当日重复不计数单日目标300超过上限无收益风控观察有一条原则连续三天success都是 300但listenSongs没有日增量优先检查time和sleep_interval不要先怀疑 Cookie 失效。Cookie 失效会直接报 301 或 401排查路径完全不同。4. 微信提醒Server酱与 PushPlus 双通道完成与失败都要送达4.1 为什么推送要放在打卡脚本里之前我把通知单独放到另一个定时任务结果打卡脚本半夜自己崩了通知还按时发第二天打开微信以为打卡成功一查日志发现实际 0 首。后来统一原则谁执行谁负责通知。执行完成和异常都直接推微信失败时的推送优先级更高逻辑上就是两个分支。4.2 Server酱与 PushPlus 选型对比通道免费额度请求方式适合规模Server酱每天 5 条sctapi.ftqq.com/{key}.send表单提交单账号、低频PushPlus每天 200 条pushplus.plus/sendJSON多账号、高频、带 Markdown两个通道都要先关注对应的微信服务号拿到唯一 token 再调用 HTTP 接口。对大多数场景PushPlus 更合适因为可以一个 token 下挂多个账号内容模板用 Markdown 也方便呈现表格。4.3 封装两个推送函数import requests def send_serverchan(send_key, title, content): url fhttps://sctapi.ftqq.com/{send_key}.send resp requests.post(url, data{title: title, desp: content}, timeout10) return resp.json().get(code) 0 def send_pushplus(token, title, content): url http://www.pushplus.plus/send payload {token: token, title: title, content: content, template: markdown} resp requests.post(url, jsonpayload, timeout10) return resp.json().get(code) 200Server酱 的desp支持 Markdown适合把成功数、失败数、耗时放在一张卡片里。PushPlus 的template必须显式传markdown否则默认 html 模板会吞掉换行微信里看到的是一整段挤在一起的文字。4.4 把推送挂进打卡主流程def run_and_notify(account): session build_session(account[cookie]) song_ids [] for pid in account[playlist_ids]: song_ids.extend(fetch_playlist_songs(session, pid)) song_ids list(set(song_ids)) puncher NeteasePuncher(session, song_ids, targetaccount[target]) count puncher.run() title f网易云打卡完成 {count}/{account[target]} content f候选曲库 {len(song_ids)} 首执行完成 if account[channel] pushplus: send_pushplus(account[token], title, content) else: send_serverchan(account[token], title, content) return countbuild_session的作用是把 Cookie 字符串解析成字典requests.Session统一注入 Cookie 和 User-Agent这里不单独展开。异常分支建议在上层捕获任何一步抛异常都直接推送“打卡失败”而不是等第二天看日志。实际运维中失败告警的价值远高于成功通知。如果账号超过三个就在run_and_notify外层套循环重复调用。PushPlus 的 200 条额度完全够用Server酱 的 5 条配额则需要在多账号场景下取舍。5. 无服务器云函数部署腾讯云 SCF 定时触发机器关机也能打满 3005.1 为什么用无服务器而不是 VPS打卡任务是典型的定时批处理每天固定时间跑几分钟然后结束。VPS 需要常年开机成本高还要维护系统补丁。无服务器云函数按执行时间和调用次数计费一天一次几百秒的调用基本是几分钱量级。更关键的是定时触发器和微信通知天然契合执行完即退出没有常驻进程可被攻破。5.2 把入口改造为云函数 handler腾讯云 SCF 的 Python 运行时会固定调用main_handler(event, context)。把run_and_notify包进去配置全部从环境变量读取import os def main_handler(event, context): account { cookie: os.environ[MUSIC_COOKIE], playlist_ids: [int(x) for x in os.environ[PLAYLIST_IDS].split(,)], target: int(os.environ.get(PUNCH_TARGET, 300)), channel: os.environ.get(PUSH_CHANNEL, pushplus), token: os.environ[PUSH_TOKEN], } count run_and_notify(account) return {statusCode: 200, count: count}os.environ在实例启动时注入腾讯云控制台里配置的环境变量以键值对存在冷启动阶段就能读到。PUNCH_TARGET用 get 带默认值方便先部署成功再调目标而不是每次都要改代码重新上传。5.3 本地打包依赖并上传SCF 控制台不支持在线安装第三方包需要把依赖和代码一起打成 zipmkdir -p build pip install requests pycryptodome -t build/ cp music163_puncher.py build/ cd build zip -r ../music163_punch.zip .然后在控制台新建函数运行环境选 Python 3.8上传 zip入口函数填music163_puncher.main_handler。内存选 256MB 就够这类任务不消耗大内存。5.4 环境变量与定时触发器配置环境变量示例值说明MUSIC_COOKIEMUSIC_Uxxxx;__csrfxxxx完整 Cookie 字符串PLAYLIST_IDS3778678,3779629逗号分隔的歌单 IDPUNCH_TARGET300单日目标PUSH_CHANNELpushplus推送通道名PUSH_TOKEN9e6a...微信推送 token触发器在控制台“触发器”页新建类型选定时触发。腾讯云 Cron 表达式是 7 段秒 分 时 日 月 星期 年。每天 08:30 触发写作0 30 8 * * * *如果你习惯标准 6 段 Cron这里少写第一位会直接不触发这是最容易踩的坑。5.5 执行超时与日志排错SCF 默认超时只有 3 秒打卡脚本跑几分钟必然超时必须把执行超时调到控制台允许的最大值。以腾讯云 SCF 为例同步调用上限是 900 秒6 并发跑 300 首按 3 到 8 秒间隔计算大约 3 到 5 分钟余量足够。排错先看控制台的“日志查询”里面能看到每次调用的 print 输出返回code: 301Cookie 过期重新抓取后更新环境变量返回code: -460触发风控并发降到 3间隔拉到 10 秒以上当天停止重试count远小于 300多半是候选曲库不足导致重复歌曲太多更换歌单池再跑。6. 验证与进阶用 listenSongs 确认增量按等级目标估算升级周期6.1 关键日志字段不管在本地还是云函数跑最后都要输出结构化字段。顺手加一行日志把目标数、实际成功数、耗时记录清楚import logging logging.basicConfig(levellogging.INFO, format%(asctime)s %(levelname)s %(message)s) logging.info(target%d done%d cost%.1fs, puncher.target, count, elapsed)日志里看到done300只代表 300 次上报成功并不等价于 300 首有效播放所以要回到账号侧做核对。6.2 用听歌数据接口核对网易云音乐老接口/api/v1/user/detail/{uid}会返回一个listenSongs字段代表累计听歌数def fetch_listen_songs(session, uid): resp session.get(fhttps://music.163.com/api/v1/user/detail/{uid}, timeout10) return resp.json()[profile][listenSongs]连续三天记录listenSongs如果每天增量接近 300说明脚本真正进入了等级经验如果每天只涨几十甚至不涨问题大概率不在登录态而在上报参数和间隔。6.3 升级周期估算设当前累计听歌数为 C目标累计听歌数为 T每日有效增量约 D剩余天数就是(T - C) / D。把这个公式写进每天的推送里比盯等级数字直观得多。例如从 Lv6 到 Lv8 需要补 2 万首每日增量稳定在 280约 72 天。6.4 进阶分级打卡与时间扰动多账号不要整齐划一。主力号每天 300小号工作日 100、周末 300。每天执行时间在基准点随机偏移 10 到 30 分钟避免几个账号同一秒触发同一个云函数实例。每周更新一次歌单池把打卡率下降的歌单替换掉并手动触发一次云函数验证。对刚接入的账号建议前三周每天手动核对listenSongs确认增量稳定后再回到周粒度观察。本文还有配套的精品资源点击获取