应使用带深度限制的递归函数解析评论树形结构,避免recursionerror;优先用jsonpath-ng提取字段,但字段名不一致时需回退python函数;切忌mutable默认参数和硬编码class名。

评论数据是字典嵌套列表,直接 for 循环会漏掉子评论
很多 API 返回的评论结构类似:comment 对象里有 replies 字段,它是个列表,每个元素又是带 replies 的评论——典型的树形结构。用两层 for 硬写,第三层就断了;用 while + 队列又容易绕晕。
真正该做的是递归函数,但别手写栈或搞深拷贝——Python 默认递归深度是 1000,而某些热门帖子的评论嵌套可能超 200 层,一不小心就 RecursionError: maximum recursion depth exceeded。
- 定义函数时加个
depth参数,主动控制递归层数(比如限制 ≤5) - 把
replies列表传进去前先判空:if not comment.get("replies"):,避免键不存在时报KeyError - 别在递归里拼接字符串或构造新字典,改用生成器
yield,内存更稳
用 jsonpath-ng 解析 JSON 嵌套字段比手写递归更省事
如果你只是想“提取所有评论内容”,根本不用自己遍历树。jsonpath-ng 支持递归下降操作符 $..content,能一次捞出所有层级的 content 字段值。
但它不是标准库,得 pip install jsonpath-ng;而且对字段名不一致的场景(比如有的叫 text,有的叫 body)无能为力,这时候还是得切回 Python 函数。
- 安装后用
parse("$..replies")可以定位所有回复数组,再逐个处理 - 注意
jsonpath-ng返回的是匹配对象列表,要调用.find(data)才真正执行解析 - 如果原始数据里有
None或缺失字段,.find()不报错但结果为空,得提前过滤
requests + BeautifulSoup 解析 HTML 嵌套评论时,class 名动态变化是最大坑
网页评论区常靠 JS 渲染,class 名带哈希(如 comment_abc123)、或用 CSS-in-JS 生成随机类名。用 soup.select(".comment-item") 可能一开始有效,换一页就失效。
更稳的方式是找结构锚点:比如每个评论块都包裹在 <div data-comment-id> 里,或者父容器固定 ID 是 <code>comments-list。不要依赖视觉 class,依赖语义属性。
- 优先用
soup.find_all(attrs={"data-comment-id": True})抓节点 - 子评论往往缩进更深,可结合
find_next_siblings()+ 深度判断,而不是死磕 class - 遇到
BeautifulSoup解析失败(比如乱码或标签闭合异常),先试response.content而非response.text,编码更准
递归函数里别用 mutable 默认参数存结果
写成 def parse_comments(comments, result=[]): 看似方便,但第二次调用时 result 不是空的——它是上一次调用留下的引用。这是 Python 新手最常踩的隐形坑。
尤其在爬虫里,你可能反复调用这个函数解析不同帖子的评论,结果发现第二条数据里混着第一条的数据。
- 默认参数必须是
None:def parse_comments(comments, result=None): - 开头补一句:
if result is None: result = [] - 如果函数要返回完整列表,就别边递归边 append,统一最后 return,逻辑更干净
树形结构没那么可怕,可怕的是把递归当成黑箱,不设退出条件、不验输入、不控引用。真遇到 10 层以上嵌套,先看是不是上游数据本身就有环(比如 A 回复 B,B 又回复 A),那就得加已访问 ID 缓存,否则无限循环。











