Python 处理抖音评论数据:DrissionPage 监听、评论去重与 CSV 导出
浏览器加载评论时页面收到的数据可以用于学习结构化解析。整个流程要分清监听到的响应、已经提取的评论以及平台实际存在的评论。它们的数量通常不同。本文先说明监听方式再给出可独立运行的 JSON 转 CSV 示例。仅处理允许访问和使用的数据接口路径与字段须按实际页面响应确认不承诺抓取全部评论。1. 先监听再触发加载DrissionPage 的监听器需要先启动才能获取后续产生的数据包。本文监听示例依据官网标注的 4.1.1.4 文档使用其他版本时先查对应接口避免混用。监听器文档fromDrissionPageimportChromium tabChromium().latest_tab target_fragmentinput(输入已确认的评论请求 URL 特征).strip()ifnottarget_fragment:raiseValueError(需要明确监听目标)tab.listen.start(target_fragment)print(请在浏览器中打开目标内容并加载评论)packettab.listen.wait(timeout30,raise_errFalse)ifpacketisFalse:print(未在限定时间内取得匹配响应)elifpacket.is_failed:print(请求失败请检查当前页面状态)else:print(HTTP 状态,packet.response.status)print(响应体类型,type(packet.response.body).__name__)使用listen.wait()取监听队列中的包不是listen.get(路径)。响应体可能已经是字典也可能是字符串或字节不能一律再调用json.loads()。多个包可逐个处理并限制等待时间和最大数量。整页滚动不一定滚动了评论面板固定滚动三次也不证明评论加载完整。是否还有下一批需要结合页面状态、响应分页字段和实际新增 ID 判断。2. 主评论与回复分别建模建议字段包括video_id、comment_id、parent_id、正文、发表时间和采集时间。回复指向对应主评论主评论的parent_id留空。某条主评论里的reply_comments可能只是预览不保证包含全部回复。回复总数应与已经提取的回复条数分别记录。评论 ID 用字符串保存防止电子表格把长编号转换为科学计数或丢失精度。3. JSON 转 CSV一个可运行的本地示例下面输入采用常见的comments、cid、text、create_time与reply_comments结构并用虚构数据演示。真实字段不同应修改适配函数而不是把缺失字段全部填成空值后继续。importcsvimportjsonfromdatetimeimportdatetime,timezonefrompathlibimportPathdefas_object(body):ifisinstance(body,bytes):bodybody.decode(utf-8)ifisinstance(body,str):bodyjson.loads(body)ifnotisinstance(body,dict):raiseValueError(响应体不是 JSON 对象)returnbodydefiso_time(seconds):ifsecondsisNone:returnreturndatetime.fromtimestamp(int(seconds),tztimezone.utc).isoformat()defextract_rows(payload,video_id,collected_at):commentsas_object(payload).get(comments)ifnotisinstance(comments,list):raiseValueError(缺少 comments 列表请核对数据结构)rows,seen[],set()defadd(item,parent_id):ifnotisinstance(item,dict)oritem.get(cid)isNone:raiseValueError(评论缺少有效 cid)cidstr(item[cid])ifnotcid:raiseValueError(cid 不能为空)key(str(video_id),cid)ifkeyinseen:returntextitem.get(text)ifnotisinstance(text,str):raiseValueError(评论正文不是字符串)rows.append({video_id:str(video_id),comment_id:cid,parent_id:parent_id,text_raw:text,created_at_utc:iso_time(item.get(create_time)),collected_at_utc:collected_at,})seen.add(key)forcommentincomments:add(comment,)repliescomment.get(reply_comments)or[]ifnotisinstance(replies,list):raiseValueError(回复字段不是列表)forreplyinreplies:add(reply,str(comment[cid]))returnrowsif__name____main__:demo{comments:[{cid:demo-001,text:示例主评论,create_time:1767225600,reply_comments:[{cid:demo-002,text:示例回复,create_time:1767225660}],}]}# 使用已有文件时可替换为# payload json.loads(Path(comments.json).read_text(encodingutf-8))payloaddemo nowdatetime.now(timezone.utc).isoformat()rowsextract_rows(payload,demo-video,now)fields[video_id,comment_id,parent_id,text_raw,created_at_utc,collected_at_utc]withopen(comments_demo.csv,w,encodingutf-8-sig,newline)asf:writercsv.DictWriter(f,fieldnamesfields)writer.writeheader()writer.writerows(rows)print(f写入{len(rows)}条示例记录)程序将正文、编号与明确时区的时间保存下来没有收集昵称等分析不需要的个人标识。多响应合并时还需要在批次层按(video_id, comment_id)去重函数内部的去重只覆盖单次输入。4. 两个常见导出问题CSV 使用newline避免某些环境下多出空行utf-8-sig便于常见中文电子表格识别。长 ID 在导入时指定文本类型。评论正文属于用户输入。用电子表格打开时以、等字符开头的文本可能被解释成公式。保留原始分析文件同时为展示文件按目标软件要求处理公式触发字符不要悄悄修改原始文本。5. 怎样报告采集范围每次记录视频标识、采集时间、排序方式、主评论数、回复数、去重数量、失败原因与终止条件。接口没有新包可能是未触发加载、请求失败或目标特征不匹配不能直接写成“全部采集完毕”。评论删除、折叠、排序和访问条件都会影响样本。最终更准确的描述是“在指定时间与排序条件下取得的评论样本”。只有确认分页、范围和可见性才有依据讨论覆盖程度。参考DrissionPage 监听网络数据、Python CSV 文档。