
1. 小红书评论抓取到情感分析链路到底卡在哪小红书评论抓取这件事单看爬虫部分其实不算难一级评论走comments接口二级及以下走sub_comments接口靠cursor游标翻页has_more判断是否继续。真正让人头疼的是抓完之后——几千条评论躺在 CSV 里你想知道用户到底是夸还是骂靠人眼一条条看根本不现实。这时候就需要情感分析。传统做法是本地跑一个 BERT 情感分类模型但问题也很明显模型动辄几百 MB环境依赖一堆GPU 不一定有CPU 推理慢得让人想砸键盘。更别说你还想顺带做意图识别、关键词抽取、甚至生成一份评论摘要报告本地模型根本扛不住这种多任务需求。我试过用统一 API 的方式来解决这个问题爬虫负责把评论抓下来清洗成结构化数据情感分析交给大模型 API 来做。这样本地只需要一个requests就能跑通全链路不用装 PyTorch不用下模型权重换台机器照样能跑。这篇就按「抓取 → 清洗 → 分析 → 验证」四步走把可复制的配置和代码都给你。适合谁看会一点 Python、想快速搭一套评论分析流程的人不想折腾本地模型部署、希望用 API 完成情感倾向判断的人以及想把爬虫数据和大模型能力串起来做小工具的人。核心检索词先明确小红书评论抓取用 Python 怎么做、评论数据怎么清洗、情感分析怎么通过统一 API 完成、config.toml配置骨架长什么样。下面逐个拆。2. 前置准备TaoToken 统一 API 与项目结构在写代码之前先把「分析」这一环的底座搭好。TaoToken 提供的是统一 API 入口也就是说你不需要为不同模型分别对接不同的 SDK改一个model字段就能切换。官网地址是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 根地址是 https://taotoken.net/api 。你需要先拿到一个 API Key。进入控制台创建即可https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 然后在 API Keys 页面生成密钥https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。密钥只显示一次复制到本地环境变量里别硬编码进代码。项目结构建议这样组织后面每一步都能对上号xiaohongshu_sentiment/ ├── config.toml # 统一配置API 地址、模型、爬虫参数 ├── crawler.py # 评论抓取与解析 ├── clean.py # 数据清洗 ├── analyze.py # 调用 TaoToken 做情感分析 ├── verify.py # 结果验证与抽样核对 └── data/ ├── raw_comments.csv └── analyzed_comments.csvconfig.toml是整个流程的配置中心爬虫的 cookie、笔记 ID、API 的 base_url 和 model 都放这里改配置不用动代码。下面给出可直接复制的骨架# config.toml [api] base_url https://taotoken.net/api api_key sk-你的密钥 # 建议用环境变量覆盖 model claude-3-5-sonnet # 可换成其他支持的模型 timeout 60 max_retries 3 [crawler] note_id 你的笔记ID xsec_token 你的xsec_token cookie 你的cookie字符串 page_size 10 save_path data/raw_comments.csv [clean] min_length 2 # 过滤掉过短评论 drop_duplicates true output_path data/clean_comments.csv [analyze] batch_size 20 # 每批送多少条评论给模型 output_path data/analyzed_comments.csv注意cookie 和 xsec_token 属于账号敏感信息只放在本地config.toml不要提交到 Git。生产环境建议用环境变量注入。读取配置用 Python 3.11 自带的tomllib就行不用额外装包import tomllib def load_config(pathconfig.toml): with open(path, rb) as f: return tomllib.load(f) cfg load_config() print(cfg[api][base_url])到这里前置就绪一个 Key、一份配置、一个目录结构。接下来进入抓取环节。3. 可复制配置评论抓取、清洗与 API 调用3.1 一级评论抓取与解析小红书的评论接口一次只返回 10 条靠cursor翻页。核心逻辑就是「请求 → 解析 → 更新 cursor → 判断 has_more」。下面这段是抓取一级评论的骨架重点看翻页循环import requests import pandas as pd import time def fetch_comments(note_id, xsec_token, cookie, max_pages50): url https://edith.xiaohongshu.com/api/sns/web/v2/comment/page headers { accept: application/json, text/plain, */*, origin: https://www.xiaohongshu.com, referer: https://www.xiaohongshu.com/, user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36, cookie: cookie, } all_comments [] cursor for page in range(max_pages): params { note_id: note_id, cursor: cursor, top_comment_id: , image_formats: jpg,webp,avif, xsec_token: xsec_token, } resp requests.get(url, headersheaders, paramsparams, timeout15) data resp.json() comments data.get(data, {}).get(comments, []) if not comments: break all_comments.extend(parse_comments(comments)) cursor data[data].get(cursor, ) if not data[data].get(has_more): break time.sleep(1.5) # 控制频率别把接口打爆 return all_comments解析函数把 JSON 拍平成字典字段包括评论 ID、时间、内容、点赞数、昵称、IP 归属地、子评论数等import datetime def parse_comments(comments): rows [] for c in comments: user c.get(user_info, {}) ts c.get(create_time) rows.append({ comment_id: c.get(id, ), note_id: c.get(note_id, ), comment_time: datetime.datetime.fromtimestamp(ts / 1000).strftime( %Y-%m-%d %H:%M:%S) if ts else , comment_content: c.get(content, ), like_count: c.get(like_count, 0), commenter_nickname: user.get(nickname, ), commenter_ip: c.get(ip_location, ), sub_comment_count: c.get(sub_comment_count, 0), comment_level: 1, }) return rows二级评论的抓取思路一样只是接口换成sub_comments参数多传一个root_comment_id。判断某条一级评论有没有子评论看它的sub_comment_count是否大于 0 即可没必要对每条都发请求。3.2 数据清洗抓下来的评论里混着表情符号、换行、纯符号、重复内容直接送模型会浪费 token 还影响判断。清洗做三件事去重、去噪、截断超长文本。import re import pandas as pd def clean_comments(df, min_length2): df df.drop_duplicates(subset[comment_id]) df[comment_content] df[comment_content].astype(str) # 去掉换行和多余空格 df[comment_content] df[comment_content].str.replace( r\s, , regexTrue).str.strip() # 过滤过短和纯符号 df df[df[comment_content].str.len() min_length] df df[~df[comment_content].str.fullmatch(r[\W_])] # 超长截断避免单条吃掉太多 token df[comment_content] df[comment_content].str[:500] return df.reset_index(dropTrue)清洗完存成clean_comments.csv这一步的产出就是情感分析的输入。3.3 调用 TaoToken 做情感分析这是整条链路的关键。TaoToken 的接口兼容 OpenAI 风格的chat/completions所以用requests直接 POST 就行。核心是设计好 prompt让模型输出结构化结果方便后续解析。import requests import json def analyze_batch(comments, cfg): url f{cfg[api][base_url]}/v1/chat/completions headers { Authorization: fBearer {cfg[api][api_key]}, Content-Type: application/json, } prompt ( 你是评论情感分析助手。对下面每条评论判断情感倾向 只输出 JSON 数组每项包含 index、sentimentpositive/neutral/negative、 confidence0-1、reason不超过20字。\n\n ) for i, text in enumerate(comments): prompt f{i}. {text}\n payload { model: cfg[api][model], messages: [{role: user, content: prompt}], temperature: 0.2, } resp requests.post(url, headersheaders, jsonpayload, timeoutcfg[api][timeout]) resp.raise_for_status() content resp.json()[choices][0][message][content] return json.loads(content)批量送的时候注意batch_size别太大20 条一批比较稳既省请求次数又不会让单次响应过长导致解析失败。如果模型返回带了 markdown 代码块标记解析前先剥掉def strip_code_fence(text): text text.strip() if text.startswith(): text text.split(\n, 1)[1] text text.rsplit(, 1)[0] return text.strip()把每批结果按index映射回原始评论写入analyzed_comments.csv字段加上sentiment、confidence、reason。到这里抓取 → 清洗 → 分析三步就跑通了。4. 验证请求跑通一次完整分析并核对结果配置写完了得实际发一次请求确认链路是通的。先写个最小验证脚本只分析 3 条评论确认 API 能返回、格式能解析cfg load_config() test_comments [ 这个色号也太好看了吧已经回购三次, 收到货有点失望和图片差距挺大, 请问这个适合干皮吗, ] result analyze_batch(test_comments, cfg) for item in result: print(item[index], item[sentiment], item[confidence], item[reason])正常返回类似0 positive 0.95 明确表达喜爱和回购 1 negative 0.88 表达失望与预期不符 2 neutral 0.90 询问适用肤质无情感倾向看到这个输出说明 API 调用、prompt 设计、JSON 解析三环都没问题。接着跑全量df pd.read_csv(data/clean_comments.csv) all_results [] for i in range(0, len(df), cfg[analyze][batch_size]): batch df[comment_content].iloc[i:i cfg[analyze][batch_size]].tolist() res analyze_batch(batch, cfg) for item in res: row df.iloc[i item[index]].to_dict() row.update({ sentiment: item[sentiment], confidence: item[confidence], reason: item[reason], }) all_results.append(row) time.sleep(1) pd.DataFrame(all_results).to_csv(cfg[analyze][output_path], indexFalse)跑完做一次分布统计看看情感比例是否合理out pd.read_csv(data/analyzed_comments.csv) print(out[sentiment].value_counts(normalizeTrue)) print(out.groupby(sentiment)[confidence].mean())如果 positive 占比 90% 以上先别高兴很可能是 prompt 引导性太强或者样本本身偏正面。这时候抽 20 条人工核对重点看confidence低于 0.6 的那些模型自己都没把握的往往就是边界样本。验证环节还有一个实用技巧把reason字段按情感分组看一遍如果 negative 组的 reason 全是「表达不满」这种废话说明 prompt 需要加约束要求模型引用评论里的具体词。这样输出的理由才有可解释性也方便你回头排查误判。5. 本篇常见错排查跑这条链路时报错基本集中在几个地方对照着查能省不少时间。401 / 403 报错先确认Authorization头是不是Bearer开头中间有空格。再检查 Key 有没有多余换行。如果 Key 没问题看base_url是不是写成了https://taotoken.net/api注意不要漏掉/v1/chat/completions这段路径。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 路径和参数以文档为准。返回内容不是合法 JSON模型有时会在 JSON 外面包一层说明文字或代码块。解析前统一用strip_code_fence处理并且用try/except兜底解析失败就把原始文本存下来单独排查别让整批任务挂掉。评论抓取返回空数组大概率是 cookie 过期或xsec_token不匹配。小红书对请求签名有校验x-s、x-t这些头如果缺失或过期接口会返回空数据而不是报错这点特别坑。建议每次抓取前先用一条已知存在的笔记做连通性测试。cursor 翻页死循环如果某次返回的cursor和上一次相同说明已经到底了加个判断if cursor last_cursor: break否则会一直请求同一页。情感分析结果全是一个类别检查 prompt 里有没有无意中给出倾向性示例。另外temperature设太高会让结果不稳定情感分类这种任务设 0.1 到 0.3 比较合适。token 超限单批评论太多或单条太长都会触发。把batch_size降到 10单条截断到 300 字基本能解决。如果还超就按字符数动态分批而不是按条数。CSV 中文乱码写文件时用encodingutf-8-sigExcel 打开才不会乱码。这个坑在 Windows 上尤其常见。排查顺序建议先验证 API 单条能通再验证爬虫单页能通最后跑全量。哪一步断了就停在哪一步查别一次性全跑然后对着报错发呆。6. 后续怎么用从单次分析到长期流程跑通一次之后你手里就有了一份带情感标签的评论数据。接下来能做的事不少按时间维度看情感趋势找出负面评论集中的时间段按 IP 归属地看地域差异把 negative 且高置信度的评论单独导出作为产品改进的输入。如果想把这件事做成长期跑的流程建议把分析部分独立成一个可复用的模块模型选择上按需切换。日常轻量分析用响应快的模型就够遇到需要深度理解长评论、做多轮推理的场景可以在模型对话页面直接试不同模型的效果https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 。如果你还要把评论分析接进更大的自动化流程比如定时抓取加定时分析那用 Coding Plan 来管理调用配额会更省心https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。最后提醒一句抓取评论要遵守平台规则和相关法律法规控制请求频率别对接口造成压力数据只用于自己学习分析别拿去商用或传播。技术本身是中性的怎么用取决于人。