拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

(必收藏) Python私藏项目实操分享:用TaoToken统一Key通道爬取QQ音乐评论,带你解析别人的内心世界

(必收藏) Python私藏项目实操分享:用TaoToken统一Key通道爬取QQ音乐评论,带你解析别人的内心世界

1. 从零拆解 QQ 音乐评论接口:Python 爬取 QQ 音乐评论到底难在哪

很多人第一次听到「Python 爬取 QQ 音乐评论」这个需求,脑子里浮现的画面是打开网页、右键查看源代码、复制评论。真动手才发现,评论根本不在 HTML 里,翻页还越翻越乱。我当初也是这么被坑进来的,后来才搞明白:QQ 音乐评论走的是 Ajax 异步接口,页面只是个壳,数据全靠一个fcg_global_comment_h5.fcg接口吐出来。

先说清楚这个项目能做什么、适合谁。它能帮你把某首歌下面的评论、昵称、点赞数、时间抓下来,存进 MySQL,再做词频和情感倾向统计,最后你会得到一张「大家在评论区到底在聊什么」的画像。适合已经会一点 Python 基础语法、想练手真实接口爬虫的人,也适合做数据分析、想拿真实中文语料练 NLP 的同学。它不适合完全零基础、连requests都没装过的人,因为中间涉及签名参数、分页游标、编码转换这些坑。

核心难点有三个。第一是请求参数里的动态字段。接口不是给个page=1就完事,它有一个lasthotcommentid,值是上一页最后一条评论的 ID,你必须把上一页的尾巴喂给下一页,否则拿到的永远是同一批数据。第二是返回格式是 JSONP,外面裹了一层jsoncallback4823183319594757(...),直接json.loads会报错,得先切片。第三是编码,接口outCharset写的是GB2312,但实际返回里混着 UTF-8 内容,处理不好就是一堆乱码。

我试过直接按页数硬翻,结果发现「最后一页」是假的——热评里明明有 7 月 12 号的评论,翻到所谓最后一页时间却停在 7 月 16 号,中间的数据像被吞了。后来才明白,得从后往前翻,翻到真正的数据页再往后走,才能把尾巴补齐。这个细节后面排障章节会细讲。

除了爬取本身,还有一个现实问题:这类项目往往不止调一个接口。你可能还想接一个大模型做评论情感分析、关键词抽取,或者把抓到的文本丢给模型做摘要。这时候如果每个服务都单独管一套 Key、单独配额度,维护起来非常痛苦。所以这篇会把「统一 Key 通道」这件事一起讲清楚,让爬虫和模型调用走同一套凭证管理,省得你到处贴 Key。

下面按「环境准备 → 接口参数 → 分页抓取 → 入库 → 词频统计 → 排障」的顺序走,每一步都给可复制的代码和参数,你跟着敲就能跑通。

2. TaoToken 统一 Key 通道前置准备:把爬虫和模型调用收进一个入口

在正式写爬虫之前,先把「Key 管理」这件事解决掉。原因很实际:你这个项目大概率不会只爬评论就结束。抓完评论,你很可能想接一个大模型做情感分类,或者做关键词提取。如果爬虫用一套配置、模型调用又用另一套配置,代码里到处是硬编码的 Key,换一次环境就要改十几个地方。

TaoToken 在这里扮演的角色,是一个统一的 API 调用入口。你可以把它理解成一个「总闸」:爬虫里如果需要调用模型能力,模型对话、编码辅助、配额查看都从这一个入口走,Key 只维护一份。官网地址是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 根地址是 https://taotoken.net/api ,注意 API 地址后面不加 UTM 参数,直接用它做 Base URL 就行。

具体要准备三样东西,我把它叫做「三件套」,缺一不可:

配置项作用取值来源
Base URL所有请求的根地址https://taotoken.net/api
API Key身份凭证,替代硬编码控制台 API Keys 页面生成
Model ID指定调用哪个模型模型列表里选,比如编码类、对话类

生成 Key 的入口在控制台的 API Keys 页面,地址是 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_campaign=rewrite 。进去之后新建一个 Key,复制出来存到环境变量里,别直接写进代码。我习惯用.env文件加python-dotenv,这样本地跑和服务器跑都不用改代码。

如果你后面想用 Claude Code 这类编码工具来辅助写爬虫脚本,它的接入配置也是同一套三件套,文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_campaign=rewrite 。Claude Code 的接入方式是把 Base URL、Key、Model ID 填进它的配置文件,具体路径和字段后面配置章节会给完整片段。

这里要强调一点:TaoToken 是统一调用入口,不是让你拿它替代数据库或爬虫框架。爬虫该用requests还是requests,入库该用pymysql还是pymysql,它只负责把「调用外部模型能力」这件事的凭证和配额管起来。配额查看在控制台 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_campaign=rewrite ,跑批量任务前先看一眼余量,避免跑到一半断掉。

环境准备清单:

pip install requests pymysql python-dotenv jieba

jieba是后面做中文词频用的,先装上。MySQL 本地装一个,建库建表脚本下一节给。Python 版本 3.8 以上都行,我用 3.10 实测没问题。

.env文件长这样:

TAOTOKEN_BASE_URL=https://taotoken.net/api TAOTOKEN_API_KEY=你的Key粘贴在这里 TAOTOKEN_MODEL_ID=你的模型ID MYSQL_HOST=127.0.0.1 MYSQL_USER=root MYSQL_PASSWORD=你的密码 MYSQL_PORT=3306 MYSQL_DB=QQ_Music

这样爬虫脚本和模型调用脚本都从环境变量读,换机器只改.env。踩过的坑是:有人把 Key 直接 commit 到 Git,结果被扫到滥用。用.env加.gitignore是最低成本的防护。

3. 可复制配置:请求头、分页参数与 settings 片段

这一节是全文最核心的可复制部分。先把请求头和参数配置写死成常量,再讲分页逻辑。

QQ 音乐评论接口的完整地址是:

https://c.y.qq.com/base/fcgi-bin/fcg_global_comment_h5.fcg

请求头只需要一个 User-Agent 就够,别加太多花里胡哨的字段,反而容易触发风控:

HEADERS = { "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/69.0.3497.100 Safari/537.36" }

参数里真正会变的是三个:pagenum、lasthotcommentid、jsoncallback。pagenum是页数,从 0 开始;lasthotcommentid是上一页最后一条评论的 ID;jsoncallback实测不影响结果,固定一个值就行。其余参数照抄:

PARAMS = { "g_tk": "5381", "jsonpCallback": "jsoncallback4823183319594757", "loginUin": "0", "hostUin": "0", "format": "jsonp", "inCharset": "utf8", "outCharset": "GB2312", "notice": "0", "platform": "yqq", "needNewCode": "0", "cid": "205360772", "reqtype": "2", "biztype": "1", "topid": "213910991", "cmd": "8", "needmusiccrit": "0", "pagenum": "0", "pagesize": "25", "lasthotcommentid": "", "callback": "jsoncallback4823183319594757", "domain": "qq.com", "ct": "24", "cv": "101010", }

其中topid是歌曲 ID,cid是评论分类 ID,换歌的时候改topid即可。pagesize是每页条数,25 是默认值,别贪心调太大,容易被限。

建库建表脚本,直接复制执行:

import pymysql db = pymysql.connect(host="127.0.0.1", user="root", password="你的密码", port=3306) cursor = db.cursor() cursor.execute("CREATE DATABASE IF NOT EXISTS QQ_Music " "DEFAULT CHARACTER SET utf8mb4") db.close() db = pymysql.connect(host="127.0.0.1", user="root", password="你的密码", port=3306, db="QQ_Music") cursor = db.cursor() sql = """ CREATE TABLE IF NOT EXISTS comments ( nike VARCHAR(255) NOT NULL, comment VARCHAR(255) NOT NULL, praisenum INT NOT NULL, comment_id VARCHAR(255) NOT NULL, time VARCHAR(255) NOT NULL, PRIMARY KEY (comment_id) ) """ cursor.execute(sql) db.close()

注意主键我改成了comment_id,原方案用comment当主键,一旦有两条相同内容的评论就会插入失败。用评论 ID 更稳。

如果你要用 Claude Code 辅助写这个脚本,它的配置文件片段如下(路径按你本地实际调整):

{ "baseUrl": "https://taotoken.net/api", "apiKey": "你的Key", "model": "你的ModelID" }

Cline 的 MCP 配置同理,三件套填全:

{ "mcpServers": { "taotoken": { "baseUrl": "https://taotoken.net/api", "apiKey": "你的Key", "model": "你的ModelID" } } }

Codex 的auth.json也是同一套逻辑,Base URL、Key、Model ID 三个字段填对就行。这里不展开每个工具的完整路径,核心是记住:任何工具接入,都是 Base URL + Key + Model ID 三件套,缺一个就连不上。

4. 分页抓取与验证:从请求签名到评论入库的完整链路

分页逻辑是整篇文章最容易翻车的地方。核心思路:维护一个LAST_COMMENT_ID变量,每抓完一页,把这一页最后一条评论的 ID 更新进去,下一页请求带上它。

先写请求函数,带重试:

import re import json import time import requests def get_html(url, headers, params=None, tries=3): try: response = requests.get(url=url, headers=headers, params=params, timeout=10) response.raise_for_status() response.encoding = "utf-8" return response except requests.HTTPError: print("connect failed") if tries > 0: print("reconnect...") time.sleep(2) return get_html(url, headers, params, tries - 1) print("3 times failure") return None

解析函数要注意 JSONP 切片。返回内容形如jsoncallback4823183319594757({...}),前面有 29 个字符,后面有 3 个字符();加换行),所以切片是html[29:-3]:

def parse_html(html): content = json.loads(html[29:-3]) items = content["comment"]["commentlist"] result = [] for item in items: result.append({ "nike": item.get("nick", ""), "comment": item.get("rootcommentcontent", ""), "praisenum": item.get("praisenum", 0), "comment_id": item.get("commentid", ""), "time": item.get("time", ""), }) return result

主循环,边抓边入库:

import pymysql URL = "https://c.y.qq.com/base/fcgi-bin/fcg_global_comment_h5.fcg" LAST_COMMENT_ID = "" db = pymysql.connect(host="127.0.0.1", user="root", password="你的密码", port=3306, db="QQ_Music", charset="utf8mb4") cursor = db.cursor() for page in range(0, 20): PARAMS["pagenum"] = str(page) PARAMS["lasthotcommentid"] = LAST_COMMENT_ID resp = get_html(URL, HEADERS, PARAMS) if resp is None: break rows = parse_html(resp.text) if not rows: print("no more data, stop at page", page) break for row in rows: sql = ("INSERT IGNORE INTO comments " "(nike, comment, praisenum, comment_id, time) " "VALUES (%s, %s, %s, %s, %s)") cursor.execute(sql, (row["nike"], row["comment"], row["praisenum"], row["comment_id"], row["time"])) db.commit() LAST_COMMENT_ID = rows[-1]["comment_id"] print(f"page {page} done, last_id={LAST_COMMENT_ID}") time.sleep(1) db.close()

运行验证:先跑 3 页,看控制台输出的last_id是否每页都在变。如果不变,说明lasthotcommentid没生效,检查是不是把PARAMS里的键名写错了。入库后查一下:

SELECT COUNT(*) FROM comments; SELECT nike, comment, time FROM comments LIMIT 5;

正常结果应该是条数在增长,时间字段有值。如果comment字段是乱码,检查连接是否带了charset="utf8mb4"。

词频统计用jieba:

import jieba from collections import Counter db = pymysql.connect(host="127.0.0.1", user="root", password="你的密码", port=3306, db="QQ_Music", charset="utf8mb4") cursor = db.cursor() cursor.execute("SELECT comment FROM comments") texts = [row[0] for row in cursor.fetchall()] db.close() words = [] for t in texts: words.extend(jieba.lcut(t)) stop = set("的 了 是 我 你 他 在 就 都 也 和 与".split()) counter = Counter(w for w in words if w not in stop and len(w) > 1) for word, cnt in counter.most_common(20): print(word, cnt)

跑完你会看到高频词,比如「青春」「回忆」「好听」这类,这就是「解析别人内心世界」的入口。

5. 常见报错排查:401、local proxy failed、reading choices 逐个击破

这一节按真实报错来。你跑上面代码时,大概率会遇到下面几个。

报错一:json.decoder.JSONDecodeError: Expecting value: line 1 column 1

原因几乎都是 JSONP 切片位置不对。不同时间接口返回的前缀长度可能变。稳妥做法是用正则提取括号里的内容:

import re m = re.search(r"\((\{.*\})\)", html, re.S) content = json.loads(m.group(1))

报错二:pymysql.err.IntegrityError: Duplicate entry

主键冲突。如果你沿用原方案用comment当主键,相同评论会撞。改成comment_id主键,或者插入时用INSERT IGNORE。

报错三:requests.exceptions.ProxyError: local proxy failed

这个报错通常是你本地环境变量里配了代理,但代理不可用。检查HTTP_PROXY、HTTPS_PROXY环境变量,临时清掉:

unset HTTP_PROXY unset HTTPS_PROXY

代码里也可以显式禁用代理:

session = requests.Session() session.trust_env = False resp = session.get(URL, headers=HEADERS, params=PARAMS)

报错四:调用模型接口时401 Unauthorized

这是 Key 的问题。检查三件套是否齐全:Base URL 是不是https://taotoken.net/api,Key 有没有多余空格,Model ID 是否填对。401 基本都是凭证没带上或带错。可以在控制台 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_campaign=rewrite 重新生成一个 Key 对比测试。

报错五:reading choices相关报错

这类报错一般出现在模型返回结构解析时,说明你按错误的字段路径取数据。先打印完整响应体,确认字段名,再改取值路径。别猜字段名。

报错六:翻页拿到重复数据

回到开头那个坑:直接点「最后一页」拿不到真实数据,得从后往前翻。解决办法是记录已入库的comment_id集合,插入前判断是否已存在,重复就跳过。同时把pagenum递增和lasthotcommentid更新绑定在一起,两者必须同步变。

排障顺序建议:先确认单页请求能返回数据 → 再确认切片解析正确 → 再确认入库无冲突 → 最后才开分页循环。别一上来就跑 20 页,出错都不知道哪一步挂的。

6. 把爬虫和模型调用接进统一通道:下一步怎么走

评论抓下来只是原料,真正有意思的是拿它做分析。你可以把抓到的评论文本批量丢给模型做情感分类,或者做主题聚类。这时候统一 Key 通道的价值就体现出来了:爬虫脚本和模型调用脚本共用一套.env,不用在多个平台之间来回切换凭证。

模型对话入口在 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_campaign=rewrite ,你可以先在里面试几条评论,看模型对中文情感判断的效果,再决定要不要批量跑。如果只是偶尔分析,用对话页手动试就行;如果要长期跑批量任务,建议走 Coding Plan,配额和调用更稳定,地址是 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_campaign=rewrite 。

接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_campaign=rewrite ,里面有完整的请求示例和字段说明。API Keys 管理页在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_campaign=rewrite ,跑批量前先去控制台 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_campaign=rewrite 看一眼配额余量。

最后给一个实用技巧:把爬虫的LAST_COMMENT_ID持久化到数据库或文件里,中断后能从上次的位置继续,不用从头再爬。这个改动很小,但能省你很多重复劳动。

返回列表