惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园_首页
T
Threat Research - Cisco Blogs
GbyAI
GbyAI
Y
Y Combinator Blog
美团技术团队
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
博客园 - 【当耐特】
S
SegmentFault 最新的问题
IT之家
IT之家
Recent Announcements
Recent Announcements
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
阮一峰的网络日志
阮一峰的网络日志
T
The Blog of Author Tim Ferriss
Martin Fowler
Martin Fowler
Microsoft Azure Blog
Microsoft Azure Blog
V
Visual Studio Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
U
Unit 42
WordPress大学
WordPress大学
博客园 - Franky
L
LangChain Blog
人人都是产品经理
人人都是产品经理
小众软件
小众软件
博客园 - 叶小钗
罗磊的独立博客
酷 壳 – CoolShell
酷 壳 – CoolShell
大猫的无限游戏
大猫的无限游戏
云风的 BLOG
云风的 BLOG
Vercel News
Vercel News
雷峰网
雷峰网
腾讯CDC
Google DeepMind News
Google DeepMind News
博客园 - 三生石上(FineUI控件)
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Help Net Security
Help Net Security
C
Check Point Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
N
News and Events Feed by Topic
V2EX - 技术
V2EX - 技术
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Schneier on Security
Schneier on Security
博客园 - 聂微东
A
Arctic Wolf
H
Heimdal Security Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Recent Commits to openclaw:main
Recent Commits to openclaw:main
T
The Exploit Database - CXSecurity.com
C
Cyber Attacks, Cyber Crime and Cyber Security
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Google DeepMind News
Google DeepMind News

博客园 - 唯一客服系统开发笔记

企业微信怎么设置投诉页面 汽修行业AI知识库应用到抖店智能咨询 快手直播间弹幕评论抓取 知识库 AI Excel表格数据上传处理 扣子知识库 AI 智能体完整创建、对接实操教程 客服私有化部署 + 抖音私信接入说明 抖店飞鸽客服自动回复机器人插件使用 网站客服系统与电商客服自动化工具 唯一客服浏览器插件自动回复抖音评论私信 拼多多客服如何实现24小时全自动值守、大幅降本增效?这款自动化插件帮你轻松破局! 扣子 AI 智能体对接企业微信客服 抖音私信和评论网页端浏览器自动化插件 功能详解 批量扒完抖音评论还能发私信 | 本地开源工具 唯一客服企业微信侧边栏客户画像:让客服在聊天时直接查看客户详情、进群记录和跟进轨迹 自定义微信卡片链接生成工具:自由设置标题、描述与封面图 唯一客服系统接入自定义大模型:一行代码都不用改,写个 HTTP 接口就够了 唯一客服系统多智能体架构深度解析:一个后台,四种 AI 引擎随心切换 GoLiveChat:Golang独立部署海外英文在线客服系统全解析 抖音无痕引流神器!评论自动回复藏微表情,规避系统检测,自动引私域 抖音评论区全自动表情回复插件-引流推广小技巧,有人评论自动化回表情 企业微信官方机器人无法对接外部群/个人微信的解决方案 Why We Built GoLiveChat — A Self-Hosted Live Chat Platform That Puts You in Control 抖音智能客服自动化:评论与私信自动化回复功能 微信个人号群发:用 UI 自动化模拟人工操作,实现零封号批量发送 从零搭建企业微信 AI 客服:Hook 注入 + 扣子知识库实战 抖音私信接入扣子知识库机器人教程|企业号/个人号全自动AI回复落地指南 抖音私信自动化回复-抖音企业号聚合回复-抖音私信AI智能体对接 Gin + 本地 CDN Vue + SQLite:绝配!真正开箱即用的本地系统三驾马车 轴承行业官网必备:一键接入在线客服,牢牢锁住搜索流量 PC 微信 4.1.9.62 UIA 抓取消息方案总结 唯一客服系统二次开发基础:Golang + Gin + Gorm + WebSocket 快速上手教程 Golang客服系统为什么比PHP客服系统更稳定?高并发、长连接、私有化部署全解析 聊城变量网络科技有限公司_专注企业客服系统与自动化解决方案 90%的人区分不清网站客服系统和电商客服平台 Python小程序二手房源界面抓取方案 聊城企业建站别踩坑!我是变量网络创始人程序员老狼,给聊城企业做真正能获客的官网 私域临时任务众包管理工具-基于微信号的创建任务,领取任务,反馈任务完整实现 聊城变量网络科技:扎根鲁西,用技术赋能临清本土企业数字化转型 电竞护航小程序完整开发方案 电竞护航UniApp源码项目-聚焦核心下单业务 在线客服系统源码 唯一客服 SCRM源码 微信群活码功能 企业微信 SCRM 服务商代开发实战:参数配置全打通,源码可直接交付 深耕PHP十余年,做聊城本地靠谱的网站建设公司——聊城变量网络科技有限公司 企业微信群活码开发-唯一客服企微xscrm源码开发 聊城本地企业微信服务商|变量网络科技,专注企微代开发 & 自研 SCRM 定制 从零搭建 UniApp 电竞护航项目源码(无NPM依赖、纯净版架构 2026电竞护航系统完整开发方案(行业分析+数据库+UniApp前端架构) 聊城临清本土互联网科技企业-聊城变量网络科技有限公司 Dify报错not_chat_app?一文解决工作流与对话流的核心混淆 唯一客服企业微信 SCRM:客户备注搜索功能 企业微信电竞护航运营体系 企业微信+电竞护航系统落地方案(解决漏单、私域流失等核心痛点) 从SCRM表结构底层逻辑,看唯一客服如何破解私域运营痛点 唯一客服 SCRM:独立部署的Golang企业微信SCRM源码 基于 UI 自动化的 QQ 群消息自动转发工具 自研企业微信SCRM系统源码独立部署(Golang+Vue.js) 程序员老狼|个人简介 企业微信SCRM应用开发:回调事件数据获取 基于企业微信 API 实现渠道活码 企业微信渠道活码(联系我)创建指南 企业微信开放平台:第三方系统通讯录管理 企业微信客服 × Dify 独立对接 AI企业实操落地方案服务商|不玩虚的,聚焦技术落地、解决企业实际问题 程序员老狼 · OPC超级个体介绍 私域引流:企业微信客服分流个人微信 扣子API使用场景及核心操作指南(非流式响应重点,记住上下文历史记录) 想让AI搜到你?自建内容站才是长久办法 拼多多客服自动回复工具|告别手动值守,轻松应对海量咨询 美团点评客服自动回复神器|告别手动回复,轻松达标平台考核 为什么企业需要专业的在线客服系统 Websocket高性能即时通讯在线客服系统 微信消息定时自动发送:用Python脚本与cron任务实现办公自动化 在线客服系统源码实现 企业客服系统解决方案:一站式解决客户服务难题 降低运营成本:智能客服系统ROI分析 电商客服系统:提升店铺转化率的必备利器 智能客服机器人:7×24小时永不离线的AI服务专家 在线客服系统功能全解析:企业选型必看指南 客服系统多少钱?在线客服系统价格全对比 在线客服系统:企业数字化转型的必备工具 独立站网页即时聊天在线客服系统 选择独立开发的Golang在线客服系统,安全稳定,性能领先 赋能跨境与独立站——您的专属智能在线客服系统 同城跑腿派送系统源码 聊城临清网络科技公司哪家强? 聊城网络科技公司哪家强? 实测!我们公众号的客服系统,消息提醒到底有多“狠”? 在线客服系统实现复杂统计需求,直接SQL操作查询数据库,统计超过一分钟未回复消息数 我开发的客服系统,自动欢迎语以及对接AI与转人工模式思考
抖音私信自动化回复-多版本 DOM 适配-抖音对不同账号推了不同的私信页面版本
唯一客服系统开发笔记 · 2026-06-03 · via 博客园 - 唯一客服系统开发笔记

同一个”私信”按钮,换了账号就找不到了。这不是 bug,是抖音的灰度发布。我是如何用弹性选择器让自动化脚本在多个页面版本中都活下来的。

联系微信:llike620

引用原文:抖音私信自动化回复-多版本 DOM 适配

1. 当你以为定位对了,换个账号就崩了

事情的起点很简单——我写了一个浏览器扩展,用来监听抖音私信并自动回复。

最先定位到昵称的 XPath 是这样的:

let nickname = getTextNodeContent(document,
  '//div[@data-mask="conversaton-detail-content"]/div[1]//span'
);

在 A 账号上跑得稳稳当当。换了 B 账号,直接返回 null

加调试日志一看,B 账号的私信页面 DOM 结构完全不一样——没有 data-mask 属性,用的是一个叫 RightPanelHeadertitle 的 class。

同一个网址,同一个功能,不同的 DOM。

2. 罪魁祸首:A/B 实验下的多版本页面

这不是 bug,是抖音在做 A/B 实验(也叫灰度发布)。

策略表现
灰度发布 新版本只推给部分用户,逐步放量
A/B 测试 多个版本同时跑,对比数据后决定用哪个
用户分桶 按 user_id hash 分配到不同实验组
地域 / 设备差异 某些版本仅特定地区或设备可见

换句话说,你在浏览器里看到的抖音私信页面,和隔壁同事看到的可能源码都不相同。对于普通用户这是无感的,对于要做 DOM 自动化的我们来说,这就是噩梦。

这套私信页面的消息列表区域,我最终发现了 至少 4 种不同的 DOM 结构:

版本条件XPath
新版聊天列表 messageMessageListlist //div[@class="messageMessageListlist"]//div[@data-index="0"]//div[@data-e2e="msg-item-content"]
当前会话高亮 conversationConversationItemcurConversation //div[contains(@class,"conversationConversationItemcurConversation")]//pre
旧版消息面板 messageContent + flex //div[@id="messageContent"]/div[1]/div[3]/div[contains(@style, "justify-content: space-between;")]//pre
单 class 会话条目 仅一个 class 名 //div[@data-e2e="conversation-item" and contains(@class, " ") and not(contains(substring-after(@class, " "), " "))]//pre

四种结构,指向同一个数据——最新的那条私信内容。

3. 解决方案:弹性选择器

我采用的核心策略叫 弹性选择器(Resilient Selectors),核心思想就一句话:

不指望一个选择器能通吃所有版本,用优先级降级的方式逐个尝试。

3.1 昵称提取:两级降级

let nickname = getTextNodeContent(document,
  '//div[@data-mask="conversaton-detail-content"]/div[1]//span'
);
if (!nickname) {
  nickname = getTextNodeContent(document,
    '//div[@class="RightPanelHeadertitle"]'
  );
}

先用最可靠的结构属性定位,失败后降级到 class 属性的备选。

3.2 消息内容提取:双级联降级

let content =
  getTextNodeContent(document,
    '//div[@class="messageMessageListlist"]//div[@data-index="0"]//div[@data-e2e="msg-item-content"]'
  )
  || getTextNodeContent(document,
    '//div[contains(@class,"conversationConversationItemcurConversation")]//pre'
  );

if (!content) {
  content =
    getTextNodeContent(document,
      '//div[@id="messageContent"]/div[1]/div[3]/div[contains(@style, "justify-content: space-between;")]//pre'
    )
    || getTextNodeContent(document,
      '//div[@data-e2e="conversation-item" and contains(@class, " ") and not(contains(substring-after(@class, " "), " "))]//pre'
    );
}

第一层用 || 短路尝试两个常用版本;第二层用 if (!content) 兜底两个历史版本。四个选择器覆盖了所有已知的页面变体。

3.3 输入框:两路径适配

let textarea = getNode(document, '//div[@contenteditable="true"]/div/div');
if (textarea) {
  simulateEditableInput('//div[@contenteditable="true"]/div/div', replyContent);
} else {
  simulateEditableInput('//div[@contenteditable="true"]/div/span', replyContent);
}

抖音的 contenteditable 输入框在不同版本下结尾元素分别是 <div><span>,按顺序先试一个。

3.4 发送按钮:两套 class 体系

let btn = getNode(document, '//span[contains(@class,"e2e-send-msg-btn")]');
if (!btn) {
  btn = getNode(document, '//div[contains(@class,"messageMsgInputinputAction")]/*[3]');
}

一个用 e2e- 前缀的测试定位属性,一个用业务 class 名——两种命名体系,只能同时兼容。

6. 完整架构图

┌─────────────────────────────────────────────────────────┐
│                   URL Gating Layer                       │
│         location.href.startsWith("douyin/user/self")     │
│                         ↓ true                           │
├─────────────────────────────────────────────────────────┤
│              Resilient Selector Layer                    │
│                                                         │
│  Nickname:   Selector A → Selector B (fallback)          │
│  Content:    Selector A || Selector B → C || D           │
│  Textarea:   Selector A → Selector B                     │
│  Send Btn:   Selector A → Selector B                     │
│                                                         │
│              ↑ 4 components × N versions                 │
├─────────────────────────────────────────────────────────┤
│                   DOM Access Layer                       │
│            document.evaluate (XPath, main doc)           │
│            shadowRoot.firstElementChild (wujie)          │
├─────────────────────────────────────────────────────────┤
│                   Action Layer                           │
│         simulateClick2 / simulateEditableInput           │
└─────────────────────────────────────────────────────────┘

7. 总结:几个值得带走的原则

#原则说明
1 别相信单一选择器 同一条 XPath 在不同账号下可能返回 null
2 || 做短路降级 比层层 if 更清晰,优先用最常见的版本
3 if (!result) 做二级兜底 用于历史遗留版本,与常用版本分离
4 URL 前置拦截 不在无关页面上做 DOM 查询,减少开销
5 了解目标平台的架构 wujie / shadow DOM / iframe,直接影响脚本能碰到哪些元素
6 弹性选择器不是万能的 页面底层大改版时仍需手动更新,但它把维护成本从”崩了就修”降到了”加一条”