{"record":{"id":"edfc14d0f1bad027","repo":"xpzouying/xiaohongshu-mcp","slug":"commentid-s-userid-s","errorCode":null,"errorMessage":"未找到评论 (commentID: %s, userID: %s)","messagePattern":"未找到评论 \\(commentID: (.+?), userID: (.+?)\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"xiaohongshu/comment_feed.go","lineNumber":270,"sourceCode":"\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\t// 5. 滚到最后一条评论再继续下滚，触发懒加载\n\t\tif currentCount > 0 {\n\t\t\tif elements, err := page.Timeout(2 * time.Second).Elements(\".comment-item\"); err == nil && len(elements) > 0 {\n\t\t\t\tif err := elements[len(elements)-1].ScrollIntoView(); err != nil {\n\t\t\t\t\tlogrus.Debugf(\"滚动到最后一条评论失败: %v\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t\thumanize.Delay(ctx, humanize.BetweenScroll)\n\t\t}\n\n\t\thumanScroll(ctx, page, \"normal\", false, 1)\n\t\thumanize.Delay(ctx, humanize.BetweenScroll)\n\t}\n\n\treturn nil, fmt.Errorf(\"未找到评论 (commentID: %s, userID: %s)\", commentID, userID)\n}\n\n// lookupComment 在当前已渲染的评论里查找目标，找不到返回 nil。\nfunc lookupComment(page *rod.Page, commentID, userID string) *rod.Element {\n\tif commentID != \"\" {\n\t\tif el, err := page.Timeout(2 * time.Second).Element(fmt.Sprintf(\"#comment-%s\", commentID)); err == nil && el != nil {\n\t\t\treturn el\n\t\t}\n\t}\n\n\tif userID == \"\" {\n\t\treturn nil\n\t}\n\n\t// 一级和二级评论都带 .comment-item；.parent-comment 会把整个楼层一起匹配上。\n\telements, err := page.Timeout(2 * time.Second).Elements(\".comment-item\")\n\tif err != nil {\n\t\treturn nil","sourceCodeStart":252,"sourceCodeEnd":288,"githubUrl":"https://github.com/xpzouying/xiaohongshu-mcp/blob/332d196854a9eac0d2b8c2c0e3d0cc43139d724c/xiaohongshu/comment_feed.go#L252-L288","documentation":"findCommentElement 完成全部滚动/展开/懒加载流程后仍未匹配到 commentID（或 userID）对应的评论：评论已删除、不可见，或 ID 无效。与超时分支不同，此时评论区已搜索完毕。","triggerScenarios":"ReplyToComment 传入的 commentID 在页面无 #comment-{id} 节点且按 data-user-id 也匹配不到 userID：评论已被删除、commentID 格式与 DOM 不符、或该用户在该 feed 下无可见评论。","commonSituations":"用旧抓取数据里的 commentID 回复（评论已删/被隐藏）；commentID 与 DOM id 约定不符（DOM 是 #comment-xxx 而传入 id 带其他前缀）；userID 传错或目标评论被折叠。","solutions":["核对传入的 commentID 是否与 DOM 的 #comment-{commentID} 格式一致（去掉多余前缀/后缀）","确认目标评论仍存在：手动打开 feed 页检查该评论是否可见","确认 userID 正确且该用户在该 feed 下确有可见评论","刷新抓取数据，用最新的评论列表重新定位评论 ID"],"exampleFix":"// before\ncommentID := \"665f1a2b000000001234abcd\" // 来自旧数据\nerr := action.ReplyToComment(ctx, feedID, token, commentID, userID, content)\n\n// after：先做格式与新鲜度校验\nif !isValidCommentID(commentID) {\n    return fmt.Errorf(\"commentID 格式非法: %q\", commentID)\n}\nif time.Since(commentFetchedAt) > 24*time.Hour {\n    return errors.New(\"评论数据过期，请先刷新评论列表再回复\")\n}\nerr := action.ReplyToComment(ctx, feedID, token, commentID, userID, content)","handlingStrategy":"validation","validationCode":"func isValidCommentID(id string) bool {\n    // 24 位十六进制（小红书评论 ID 形态），按实际格式调整\n    if len(id) != 24 { return false }\n    for _, c := range id {\n        if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')) { return false }\n    }\n    return true\n}\n// 调用前\nif !isValidCommentID(commentID) || userID == \"\" {\n    return errors.New(\"非法 commentID 或缺失 userID，拒绝回复\")\n}","typeGuard":"func isCommentNotFoundErr(err error) bool {\n    return err != nil && (strings.Contains(err.Error(), \"未找到评论\") ||\n        strings.Contains(err.Error(), \"无法找到评论\"))\n}","tryCatchPattern":"err := action.ReplyToComment(ctx, feedID, token, commentID, userID, content)\nif isCommentNotFoundErr(err) {\n    // 评论已删除/不可见：刷新评论列表数据，跳过该条并记录\n    invalidateCachedComment(commentID)\n    log.Infof(\"评论 %s 已不存在，跳过\", commentID)\n    return nil\n}\nreturn err","preventionTips":["回复前校验 commentID 格式，确保能拼出合法的 #comment-{id} 选择器","使用最近一次抓取的评论数据，不要拿超过一天的旧 ID 回复","同时提供 commentID 和 userID（双保险），提高查找命中率","对\"未找到评论\"分类：数据过期 vs 评论被删，分别处理缓存与重试"],"tags":["comment-lookup","selector","stale-data","dom"],"backgroundTag":"comment-not-found-in-feed","analyzedSha":"332d196854a9eac0d2b8c2c0e3d0cc43139d724c","analyzedAt":"2026-09-05T22:22:55.988Z","contentChangedAt":"2026-09-05T22:22:55.988Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}