244 lines
11 KiB
Markdown
244 lines
11 KiB
Markdown
# 浏览器搜索抖音账号操作规范
|
||||
|
|
|
|||
|
|
> 通过 browser-harness 搜索抖音账号 ID,获取账号主页信息与视频列表。
|
|||
|
|
> 核心原则:**适度访问网站**,控制访问频率与行为节奏,不对目标站点造成负担。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 一、适用场景
|
|||
|
|
|
|||
|
|
- 已知抖音号或账号 ID,需要获取该账号的主页信息(昵称、粉丝数、简介等)
|
|||
|
|
- 需要获取账号下的视频列表(视频 ID、标题、点赞数等)
|
|||
|
|
- 三方数据 API 与 MCP 平台均无法覆盖的账号
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 二、执行流程
|
|||
|
|
|
|||
|
|
### 步骤 0:适度访问 — 首页浏览(50% 概率执行)
|
|||
|
|
|
|||
|
|
| 子步骤 | 操作 | 说明 |
|
|||
|
|
|--------|------|------|
|
|||
|
|
| 0a | 随机决定是否执行(50% 概率) | 不每次都走,适度访问 |
|
|||
|
|
| 0b | 打开抖音首页 `new_tab("https://www.douyin.com")` | — |
|
|||
|
|
| 0c | 检测登录弹窗,有则点击关闭按钮(X / "暂不登录") | 首页阶段自动关闭,不需登录 |
|
|||
|
|
| 0d | 随机停留 3~8 秒 | 适度访问,不快速跳转 |
|
|||
|
|
| 0e | 随机滚动 1~2 次页面 | 适度浏览行为 |
|
|||
|
|
|
|||
|
|
### 步骤 1:搜索账号 ID
|
|||
|
|
|
|||
|
|
| 子步骤 | 操作 | 说明 |
|
|||
|
|
|--------|------|------|
|
|||
|
|
| 1a | 导航到搜索页 `https://www.douyin.com/search/{账号ID}?type=user` 或在搜索框输入 | — |
|
|||
|
|
| 1b | 检测登录弹窗 | **如弹出登录窗口,停止操作,提示用户手动登录** |
|
|||
|
|
| 1c | 等待搜索结果加载 | `wait_for_load()` |
|
|||
|
|
|
|||
|
|
### 步骤 2:进入达人主页
|
|||
|
|
|
|||
|
|
| 子步骤 | 操作 | 说明 |
|
|||
|
|
|--------|------|------|
|
|||
|
|
| 2a | 从搜索结果中找到对应账号 | 人工确认匹配(抖音号可能与搜索词不同) |
|
|||
|
|
| 2b | 点击进入主页 | — |
|
|||
|
|
| 2c | 检测登录弹窗 | **如弹出登录窗口,停止操作,提示用户手动登录** |
|
|||
|
|
| 2d | `wait_for_load()` 等待主页加载完成 | — |
|
|||
|
|
| 2e | 随机停留 2~5 秒 | 适度访问 |
|
|||
|
|
|
|||
|
|
### 步骤 3:提取账号基础信息
|
|||
|
|
|
|||
|
|
通过 `js(...)` 提取以下字段:
|
|||
|
|
|
|||
|
|
| 字段 | 说明 |
|
|||
|
|
|------|------|
|
|||
|
|
| 昵称 | 账号显示名称 |
|
|||
|
|
| 抖音号 | 账号 short id(可搜索短号) |
|
|||
|
|
| 抖音ID(sec_uid) | 账号唯一标识,主页 URL 中 `/user/{sec_uid}` 那串 |
|
|||
|
|
| 抖音地址 | 主页链接 `https://www.douyin.com/user/{sec_uid}` |
|
|||
|
|
| 粉丝数 | 关注者数量 |
|
|||
|
|
| 关注数 | 关注数量 |
|
|||
|
|
| 获赞数 | 总获赞 |
|
|||
|
|
| 作品数 | 视频作品总数 |
|
|||
|
|
| 简介 | 个人介绍 |
|
|||
|
|
| IP属地 | 账号所在地区 |
|
|||
|
|
| 头像 URL | 头像图片地址 |
|
|||
|
|
|
|||
|
|
### 步骤 4:提取视频列表(数量按 SKILL.md「视频获取数量」参数)
|
|||
|
|
|
|||
|
|
| 子步骤 | 操作 | 说明 |
|
|||
|
|
|--------|------|------|
|
|||
|
|
| 4a | 滚动到视频列表区域 | — |
|
|||
|
|
| 4b | 每次滚动后等待 1~3 秒(随机) | 适度访问,不连续快速滚动 |
|
|||
|
|
| 4c | 滚动 2~3 次即可;停止条件:收集数量达到 SKILL.md「视频获取数量」参数,**或滚动到底部后加载不出更多视频**(先到者为准) | 达到目标数或无法加载更多即停,禁止无限滚动 |
|
|||
|
|
| 4d | 通过 `js(...)` 提取每条视频数据 | 从 React Fiber 中提取 awemeInfo,包含完整 stats/video/textExtra |
|
|||
|
|
|
|||
|
|
**滚动容器与加载技巧(实测踩坑记录,2026-08):**
|
|||
|
|
|
|||
|
|
1. **抖音达人主页的滚动容器不是 `window`**,是 `.route-sc` 元素(`overflow-y:auto`)。直接滚动 window 无效,必须滚动真实容器:
|
|||
|
|
```js
|
|||
|
|
// 定位真实滚动容器(含视频链接的 overflow 祖先),显式滚到底触发懒加载
|
|||
|
|
const container = document.querySelector('.route-sc') ||
|
|||
|
|
Array.from(document.querySelectorAll('a[href*="/video/"]')).map(a => {
|
|||
|
|
let el = a.parentElement;
|
|||
|
|
while (el && el.scrollHeight <= el.clientHeight) el = el.parentElement;
|
|||
|
|
return el;
|
|||
|
|
}).find(Boolean);
|
|||
|
|
if (container) container.scrollTop = container.scrollHeight;
|
|||
|
|
```
|
|||
|
|
2. 滚动后等待 1~3 秒再检查:**若滚动到底(scrollTop 不再增长)或链接数不再增加,即"加载不出更多"**,立即停止(不无限滚动)。
|
|||
|
|
3. 备选:用 harness 的滚轮助手 `scroll(x, y, dy)`(派发 mouseWheel 事件,滚动光标下元素),适用于容器定位困难时。
|
|||
|
|
4. **提取兜底**:Fiber 提取(awemeInfo)个别卡片可能取不到(虚拟化卡片差异),用 `a[href*="/video/"]` 的 href 提取 aweme_id 兜底,保证数量完整。
|
|||
|
|
|
|||
|
|
**数据提取方式:** 通过遍历页面上 `a[href*="/video/"]` 元素的 React Fiber,向上查找 `awemeInfo` 对象,获取完整的视频数据(含 stats、video、textExtra 等字段),而非仅从 DOM 文本解析。
|
|||
|
|
|
|||
|
|
**提取的视频字段(13列标准):**
|
|||
|
|
|
|||
|
|
| 列序 | 字段 | 说明 | 数据来源 |
|
|||
|
|
|------|------|------|----------|
|
|||
|
|
| 1 | 序号 | 自增序号 | 脚本生成 |
|
|||
|
|
| 2 | 视频ID | aweme_id | 页面数据 |
|
|||
|
|
| 3 | 视频标题 | 视频描述文案 | 页面数据 |
|
|||
|
|
| 4 | 视频地址 | `https://www.douyin.com/video/{aweme_id}` | 拼接生成 |
|
|||
|
|
| 5 | 点赞数 | digg_count(数字) | 页面数据 |
|
|||
|
|
| 6 | 点赞(显示) | 如 "2.1万"、"10万+" | 页面显示文本 |
|
|||
|
|
| 7 | 评论数 | comment_count | 页面数据 |
|
|||
|
|
| 8 | 分享数 | share_count | 页面数据 |
|
|||
|
|
| 9 | 收藏数 | collect_count | 页面数据 |
|
|||
|
|
| 10 | 播放量 | play_count(如页面可见) | 页面数据 |
|
|||
|
|
| 11 | 视频时长 | duration(秒) | 页面数据 |
|
|||
|
|
| 12 | 发布时间 | create_time(格式化为 YYYY-MM-DD) | 页面数据 |
|
|||
|
|
| 13 | 标签 | 从标题中提取的 # 标签,逗号分隔 | 标题解析 |
|
|||
|
|
|
|||
|
|
### 步骤 5:整理输出
|
|||
|
|
|
|||
|
|
- 账号基础信息 → 结构化保存
|
|||
|
|
- 视频列表 → 保存为 Excel 到 `{产出根目录}/{达人昵称}/短视频表格.xlsx`
|
|||
|
|
|
|||
|
|
> **注意**:浏览器抓取的数据保存为 **Excel 文件**(`短视频表格.xlsx`),与三方数据 API 保存的 JSON 格式(`视频数据.json`)不同。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 三、登录弹窗处理逻辑
|
|||
|
|
|
|||
|
|
| 场景 | 处理方式 |
|
|||
|
|
|------|---------|
|
|||
|
|
| 首页浏览阶段(步骤0) | 自动关闭弹窗,不需要登录 |
|
|||
|
|
| 搜索阶段(步骤1) | **停止操作,提示用户登录**,用户登录后继续 |
|
|||
|
|
| 进入主页阶段(步骤2) | **停止操作,提示用户登录**,用户登录后继续 |
|
|||
|
|
|
|||
|
|
**提示语示例:**
|
|||
|
|
> 检测到登录弹窗,请在浏览器中手动登录抖音账号,登录完成后告诉我继续。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 四、适度访问原则
|
|||
|
|
|
|||
|
|
1. **随机停留时间**:所有等待时间使用随机区间(如 3~8 秒、1~3 秒),不使用固定值
|
|||
|
|
2. **随机滚动节奏**:滚动间隔随机化,不连续快速滚动
|
|||
|
|
3. **不每次都走首页浏览**:50% 概率跳过,避免每次行为完全一致
|
|||
|
|
4. **控制请求量**:视频列表收集数量达到 SKILL.md「视频获取数量」参数即停止滚动,不无限加载
|
|||
|
|
5. **单次任务单账号**:一次只处理一个账号,不批量并发
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 五、browser-harness 调用方式(环境 SOP)
|
|||
|
|
|
|||
|
|
> **环境说明(2026-08 实测)**:browser-harness 由 uv 工具安装,可执行文件在 `~/.local/bin/browser-harness.exe`;技能本体在 `~/.dsh/skills/browser-harness/`(随 dsh_MCNProject 仓库分发,换机器自带)。
|
|||
|
|
|
|||
|
|
### 5.1 环境准备(每次浏览器任务前【必做】)
|
|||
|
|
|
|||
|
|
**① 清除代理环境变量(最重要,漏掉必踩坑)**:本机 Shadowsocks 的 Privoxy 组件监听 10800,会劫持 127.0.0.1 的 CDP 请求(报 "Internal Privoxy Error" 或连接失败)。
|
|||
|
|
|
|||
|
|
```powershell
|
|||
|
|
Remove-Item Env:HTTP_PROXY, Env:HTTPS_PROXY, Env:http_proxy, Env:https_proxy -ErrorAction SilentlyContinue
|
|||
|
|
$env:NO_PROXY = "127.0.0.1,localhost,::1"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**② 连接方式(按顺序尝试)**:
|
|||
|
|
|
|||
|
|
| 方式 | 操作 | 说明 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| A. 自动(推荐) | 直接运行 browser-harness(daemon 自动启动并连接浏览器) | 最简单;可能触发 Chrome 授权弹窗(见 5.2) |
|
|||
|
|
| B. 定向连接 | `$env:BU_CDP_URL="http://127.0.0.1:<端口>"` 后运行 | 端口:先探测 9222(用户 Chrome 隐式调试端口)/ 9223(独立实例),取有效者 |
|
|||
|
|
| C. 独立实例 | `chrome.exe --remote-debugging-port=9223 --user-data-dir=<固定目录> about:blank` | 与用户浏览器隔离;Chrome 151+ 首次连接同样可能弹授权 |
|
|||
|
|
|
|||
|
|
### 5.2 Chrome 151+ 远程调试授权(关键机制)
|
|||
|
|
|
|||
|
|
- Chrome 151+ 无论隐式(9222)还是显式(--remote-debugging-port)启动,**CDP 端点默认 404,首次连接会弹「Allow remote debugging?」授权框**;点击「允许」后端点才可用(Chrome 会记住,后续不再弹)
|
|||
|
|
- 弹窗可能出现在**后台窗口**:看不到时打开 `chrome://inspect/#remote-debugging` 页面启用(勾选后同样生效)
|
|||
|
|
- 连接前探测授权状态:`Invoke-WebRequest http://127.0.0.1:<端口>/json/version` —— **返回含 "Browser" 的 JSON = 可用**;404/超时 = 未授权或非 CDP
|
|||
|
|
|
|||
|
|
### 5.3 失败排查顺序(按此执行,不跳步)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
1. doctor:browser-harness doctor(确认工具/daemon/Chrome 三要素)
|
|||
|
|
2. 端口探测:9222 / 9223 / 9224 的 /json/version(IPv4 + IPv6 [::1] 都试)
|
|||
|
|
- 返回 Browser JSON → 用它(BU_CDP_URL 定向)
|
|||
|
|
- 404 但端口有监听 → 非 CDP 或未授权(走 5.2 授权)
|
|||
|
|
- 无监听 → 启动独立实例或等 daemon 自动连接
|
|||
|
|
3. 代理检查:确认 HTTP_PROXY/HTTPS_PROXY 已清(5.1 ①)
|
|||
|
|
4. daemon 异常:browser-harness --reload(重启 daemon 后再试)
|
|||
|
|
5. 仍失败 → 提示用户:Chrome 窗口点「允许」+ chrome://inspect 检查
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5.4 调用示例(准备就绪后)
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
BH="$HOME/.local/bin/browser-harness.exe"
|
|||
|
|
|
|||
|
|
# 适度访问 — 打开首页
|
|||
|
|
"$BH" <<'PY'
|
|||
|
|
new_tab("https://www.douyin.com")
|
|||
|
|
wait_for_load()
|
|||
|
|
PY
|
|||
|
|
|
|||
|
|
# 搜索账号
|
|||
|
|
"$BH" <<'PY'
|
|||
|
|
goto_url("https://www.douyin.com/search/{账号ID}?type=user")
|
|||
|
|
wait_for_load()
|
|||
|
|
PY
|
|||
|
|
|
|||
|
|
# 提取页面数据
|
|||
|
|
"$BH" <<'PY'
|
|||
|
|
result = js("""
|
|||
|
|
return {
|
|||
|
|
title: document.title,
|
|||
|
|
html: document.body.innerHTML.substring(0, 5000)
|
|||
|
|
}
|
|||
|
|
""")
|
|||
|
|
print(result)
|
|||
|
|
PY
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5.5 标签页复用规则(重要,避免窗口/标签页堆积)
|
|||
|
|
|
|||
|
|
**操作 Chrome 前先检查已打开的窗口/标签页:有则直接复用,没有才新建。**
|
|||
|
|
|
|||
|
|
1. **检查已打开标签页**:用 browser-harness 列出当前标签页(`cdp("Target.getTargets", flatten=True)` 或 `page_info()` 查看当前页),确认目标窗口是否已有抖音标签页
|
|||
|
|
2. **有已打开标签页 → 直接复用**:在现有标签页上导航即可——`goto_url("https://www.douyin.com/search/{账号ID}?type=user")` 或先 `goto_url("https://www.douyin.com")` 再搜索;**不要每次 `new_tab` 新建标签页**(多任务串行共用同一 Chrome 时,标签页会无限堆积)
|
|||
|
|
3. **没有标签页/窗口 → 才新建**:`new_tab("https://www.douyin.com")`
|
|||
|
|
4. **用完不关闭**:任务完成后保留标签页(留给后续任务复用),下次任务直接导航到目标地址即可
|
|||
|
|
5. **多任务串行**:多个刷新/导入任务共用同一 9223 Chrome 实例,前一个任务的抖音标签页直接导航复用,无需重新打开
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 检查已打开标签页
|
|||
|
|
"$BH" <<'PY'
|
|||
|
|
targets = cdp("Target.getTargets", flatten=True)["targetInfos"]
|
|||
|
|
for t in targets:
|
|||
|
|
if t.get("type") == "page":
|
|||
|
|
print(t.get("url", "")[:80])
|
|||
|
|
PY
|
|||
|
|
|
|||
|
|
# 有抖音标签页 → 直接导航复用(不新建)
|
|||
|
|
"$BH" <<'PY'
|
|||
|
|
goto_url("https://www.douyin.com/search/{账号ID}?type=user")
|
|||
|
|
wait_for_load()
|
|||
|
|
PY
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 六、已知问题
|
|||
|
|
|
|||
|
|
- 抖音网页版需要 JS 渲染,且有限流/验证码机制
|
|||
|
|
- 视频列表懒加载,滚动 2~3 次收集到 SKILL.md「视频获取数量」参数数量即可停止
|
|||
|
|
- 未登录状态下搜索可能被限制,部分功能需要登录
|
|||
|
|
- Chrome 需开启 remote debugging(`chrome://inspect/#remote-debugging`)
|