docs: 更新 README 和安装文档,新增 yt-studio-groupid-lookup 技能说明
This commit is contained in:
124
skills/yt-studio-groupid-lookup/SKILL.md
Normal file
124
skills/yt-studio-groupid-lookup/SKILL.md
Normal file
@@ -0,0 +1,124 @@
|
||||
---
|
||||
name: "yt-studio-groupid-lookup"
|
||||
description: "把一批群组名批量解析为 entity_id(即 groupId),并标注其归属的内容所有者。围绕 scripts/lookup_groups.py,用「套件回放」search_groups 接口:在线捕获鉴权套件后并发查询,或离线用已存套件回放。当用户给出群组名清单、要查找/映射/匹配群组的 groupId 或 entity_id、确认某群组属于哪个所有者/内容管理器、排查群组 ID 查不到或回放 401 时使用。"
|
||||
---
|
||||
|
||||
# 群组名解析为 entity_id(脚本版)
|
||||
|
||||
把群组名 --> `groupId`(即 explore URL 里的 `entity_id`),并标注归属的内容所有者。底层执行 `scripts/lookup_groups.py`。核心是「套件回放」:**套件**(捕获页面的完整鉴权头 + 请求体模板),**回放**(重发 `search_groups`,只改 `query`),因手写请求必 401。
|
||||
|
||||
## 何时使用
|
||||
|
||||
- **批量解析**:用户有一批群组名,要拿到每个名字对应的 groupId / entity_id。
|
||||
- **归属确认**:同名群组可能多个所有者都有,需要确认某群组属于哪个内容管理器(ownerId + 显示名)。
|
||||
- **建 URL 前置**:要生成 explore 报告 URL,但清单里只有群组名、没有 entity_id。
|
||||
- **排查失败**:回放遇到 401 / 查不到 / 大小写差异,需要定位修复。
|
||||
|
||||
## 领域词汇
|
||||
|
||||
- **套件(Bundle)**:从页面捕获的一次 `search_groups` 请求 = 完整请求头(`Authorization: SAPISIDHASH...`、`Cookie`、`X-YouTube-Delegation-Context` 等)+ 请求体模板(含 `context.user.delegationContext`)。一个内容所有者一份。手写必 401,唯一可靠来源。
|
||||
- **回放(Replay)**:用套件重发 `search_groups`,**只改 body 的 `query` 字段**,其余(尤其 delegation 语境)一律不动。
|
||||
- **实体ID / groupId**:响应 `groupDatas[].groupId`,即 explore URL 的 `entity_id`(如 `NCy9C2QPQ1E`)。
|
||||
- **精确命中**:响应当中存在 `displayName === 查询名` 的项,直接取它的 groupId;命中即停,不再往下查。
|
||||
- **变体候选**:无精确命中时,接口仍可能返回前 5 个相近名,写入结果「备注」列供人工复核。
|
||||
|
||||
## 脚本与依赖
|
||||
|
||||
- 脚本:`scripts/lookup_groups.py`(本技能目录下;若缺失,用 SearchCodebase 按 `lookup_groups.py` 定位)。
|
||||
- 依赖:playwright、requests、openpyxl 已统一在根 `pyproject.toml` 声明(`uv sync` 自动装),环境准备见 `uv-env-setup` 技能。
|
||||
- 登录态必须已存在:脚本不登录,只复用已登录 YouTube Studio 会话,否则跳 Google 登录页。
|
||||
|
||||
## 选择运行分支
|
||||
|
||||
| 你的情况 | 分支 | 关键参数 |
|
||||
|---|---|---|
|
||||
| 要在线拿新套件并立即查询 | 分支 A:捕获+回放 | `--url` + `--names` |
|
||||
| 套件已存盘(`--save-bundles` 产物) | 分支 B:离线回放 | `--bundles` + `--names` |
|
||||
| 只想捕获/校验套件,先不查 | 分支 C:仅捕获 | `--url`(不带 `--names`) |
|
||||
| 无浏览器/网络,验脚本逻辑 | 分支 D:自测 | `--selftest` |
|
||||
|
||||
多所有者的分三种做法(三者都对应分支 A):`--url` 重复传多次、或 `--url ... --save-bundles bundles.json` 先存盘、或下次直接用 `--bundles bundles.json`(分支 B)。
|
||||
|
||||
## 工作流
|
||||
|
||||
### 步骤 0:环境与自测(分支 D)
|
||||
|
||||
```bash
|
||||
uv run python scripts\lookup_groups.py --selftest
|
||||
```
|
||||
|
||||
**完成判据**:打印 `selftest OK`,退出码 0(无需浏览器/网络)。
|
||||
|
||||
### 步骤 1:确认登录态来源
|
||||
|
||||
复用已登录 YouTube Studio 的浏览器会话,否则跳 Google 登录页。与 `youtube-studio-csv-download` 同一套约定:
|
||||
|
||||
- 方式 A(能关浏览器):`--user-data-dir <目录> --channel chrome|msedge`,二者必须同品牌。
|
||||
- 方式 B(浏览器不能关):先 `chrome.exe --remote-debugging-port=9222` 或 `msedge.exe --remote-debugging-port=9222`,再加 `--connect http://localhost:9222`。
|
||||
|
||||
**完成判据**:得到任意一种可复用的登录态(用户数据目录路径,或调试端口号)。
|
||||
|
||||
### 步骤 2:拿到所有者 URL 与群组名单
|
||||
|
||||
- 所有者 URL:`studio.youtube.com/owner/<ownerId>/analytics?...`;脚本自动从 URL 路径 `owner/<id>` 或 `?o=<id>` 提取 ownerId,不需手工填。
|
||||
- 群组名单:json / txt / csv / xlsx 均可,支持 `群组名称`、`group_name`、`实体名称`、`名称` 等列名,自动去重。(格式细节见 [references/input-guide.md](references/input-guide.md))
|
||||
|
||||
**完成判据**:`--names` 指向的文件能被脚本解析出非空名单(列名可识别)。
|
||||
|
||||
### 步骤 3:运行脚本
|
||||
|
||||
分支 A(在线捕获 + 回放):
|
||||
|
||||
```powershell
|
||||
# 单所有者
|
||||
uv run python scripts\lookup_groups.py --url "<owner URL>" --names 名单.xlsx --channel chrome --user-data-dir "$env:LOCALAPPDATA\Google\Chrome\User Data"
|
||||
# 多所有者:--url 重复;浏览器不能关就改用 --connect
|
||||
uv run python scripts\lookup_groups.py --url "<owner1 URL>" --url "<owner2 URL>" --names 名单.csv --connect http://localhost:9222 --save-bundles bundles.json
|
||||
```
|
||||
|
||||
分支 B(离线回放,套件已存盘,无需浏览器):
|
||||
|
||||
```powershell
|
||||
uv run python scripts\lookup_groups.py --bundles bundles.json --names 名单.json --out result.xlsx
|
||||
```
|
||||
|
||||
分支 C(仅捕获/校验套件,暂不查询):
|
||||
|
||||
```powershell
|
||||
uv run python scripts\lookup_groups.py --url "<owner URL>" --save-bundles bundles.json
|
||||
```
|
||||
|
||||
**关键行为**:
|
||||
- 打开页面后先**自动触发一次群组搜索**(猜搜索框);猜不中会提示「在浏览器顶部搜索框输入任意词并回车」,只需触发一次 `search_groups` 请求即可。
|
||||
- 捕到请求后,脚本校验 `delegationContext.externalOwnerId` 与 URL ownerId 是否一致、鉴权头是否齐全(缺 Authorization/Cookie/delegation 是 401 根因,会在捕获时直接打警告)。
|
||||
- 回放用线程本地 Session,并发(默认 8 线程)逐个名字 × 逐个所有者,**首个精确命中即停**。
|
||||
|
||||
**完成判据**:终端打印 `[输出] 精确命中 N/M -> <路径>`,退出码 0,且生成结果文件。
|
||||
|
||||
### 步骤 4:校验产物与处理未命中
|
||||
|
||||
- 结果列:`group_name / ownerid / owner_display / groupid / 备注`;`ownerid + owner_display` 标注群组归属的所有者。
|
||||
- 未命中的名字会列在末尾 `[复核] 未命中 N 个`,其候选/线索在「备注」列。
|
||||
- 处理变体:若备注里只有大小写/空格差异的候选,直接改用候选 groupId;若跨所有者都命中,以**精确 displayName** 为准核对归属;若名字疑似错字,用更短关键词(如品牌名)重跑一次。
|
||||
|
||||
**完成判据**:无 `HTTPxxx` / `ERR` 残留;每个群组要么有确定 groupId,要么在备注列明确记录了候选或原因。
|
||||
|
||||
## 常用参数速查
|
||||
|
||||
| 参数 | 说明 |
|
||||
|---|---|
|
||||
| `--url` | 所有者分析页 URL,可重复(多所有者) |
|
||||
| `--names` | 群组名单:json / txt / csv / xlsx |
|
||||
| `--bundles` | 已有套件 json(离线回放;或与 `--url` 捕获结果合并) |
|
||||
| `--save-bundles` | 把套件写盘,供下次离线回放 |
|
||||
| `--out` | 结果文件,默认 `group_entity_id_result.xlsx`,缺 openpyxl 自动回退 csv |
|
||||
| `--max-workers` | 并发线程数,默认 8 |
|
||||
| `--connect` | CDP 附加到已打开浏览器 |
|
||||
| `--user-data-dir` / `--channel` | 用已登录用户数据目录启动 |
|
||||
| `--owner-display` | 所有者显示名(可选,单 `--url` 时用于结果标注) |
|
||||
| `--wait-seconds` | 等待手动触发搜索的最长秒数,默认 180 |
|
||||
|
||||
## 参考
|
||||
|
||||
- [名单与套件制作指南](references/input-guide.md):名单文件格式/列别名、套件 bundles.json 结构、多所有者、产物核对。
|
||||
- [常见问题排查](references/troubleshooting.md):按 现象 → 原因 → 解决 逐条排查(401、未登录跳转、捕获不到请求、查不到、CDP 连不上、channel 不匹配等)。
|
||||
87
skills/yt-studio-groupid-lookup/references/input-guide.md
Normal file
87
skills/yt-studio-groupid-lookup/references/input-guide.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# 名单与套件制作指南
|
||||
|
||||
`lookup_groups.py` 的两个输入:群组**名单**(`--names`)和鉴权**套件**(`--bundles` / `--save-bundles`)。本指南说明怎么组织。
|
||||
|
||||
## 1. 群组名单(`--names`)
|
||||
|
||||
### 1.1 文件格式
|
||||
|
||||
- **json**:三种结构都支持。
|
||||
- 纯数组:`["名字1", "名字2"]`
|
||||
- 带表头的二维数组:`[["GROUP_NAME"], ["名字1"], ["名字2"]]`
|
||||
- 对象:`{"names": ["名字1", "名字2"]}`(也认 `group_names` / `groups` / `group_name`)
|
||||
- **txt**:每行一个名字,空行自动忽略。
|
||||
- **csv / xlsx**:读首个工作表,自动识别名字列(去空白转小写比对)。
|
||||
|
||||
### 1.2 自动识别的列名(别名)
|
||||
|
||||
`群组名称` / `群组` / `group_name` / `groupname` / `名称` / `name` / `实体名称` / `group name`。
|
||||
|
||||
- 有表头:首行表头命别名列,取该列后续所有行;首行不是表头且**单列**时整列当名字。
|
||||
- 多列且首行无别名列:脚本报错「无法识别名字列」,改用别名列名或改成单列文件。
|
||||
- 空值 / `nan` / `none` 自动跳过,名字按顺序去重。
|
||||
|
||||
### 1.3 示例
|
||||
|
||||
```csv
|
||||
群组名称,备注
|
||||
X 漫剧-1,
|
||||
X 漫剧-2,重点
|
||||
靓舟桃,
|
||||
```
|
||||
|
||||
### 1.4 核对清单
|
||||
|
||||
- [ ] 明确要查的群组名,含必要的同名区分(如品牌名 + 序号)。
|
||||
- [ ] 名字与页面上的 displayName 完全一致,否则只能进变体候选。
|
||||
- [ ] csv/xlsx 用了别名列名或为单列文件。
|
||||
|
||||
## 2. 鉴权套件(`--bundles` / `--save-bundles`)
|
||||
|
||||
一个内容所有者对应一个**套件**,`bundles.json` 是套件数组。在线捕获(`--url` + `--save-bundles`)自动生成;也可手工维护。
|
||||
|
||||
### 2.1 结构
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"ownerId": "bqSUnNpU67xJ51TxH4PKpQ",
|
||||
"ownerDisplay": "FUTURE TV Co,Ltd",
|
||||
"url": "https://studio.youtube.com/youtubei/v1/yta_web/search_groups?alt=json",
|
||||
"headers": {
|
||||
"Authorization": "SAPISIDHASH ...",
|
||||
"Cookie": "...",
|
||||
"X-YouTube-Delegation-Context": "...",
|
||||
"...": "..."
|
||||
},
|
||||
"bodyTemplate": { "...": "...", "query": "" }
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### 2.2 关键字段
|
||||
|
||||
- `ownerId`:内容管理器的 id(约 18 字符 base62)。
|
||||
- `headers`:约 12+ 个;**必须原样回放**,缺失任一(尤其 `Authorization`、`Cookie`、`X-YouTube-Delegation-Context`)都会 401。不要重造。
|
||||
- 回放时脚本会剥离长度/传输类头(`content-length`、`host`、`connection`、`transfer-encoding`、`accept-encoding` 等),由 requests 自算。
|
||||
- `bodyTemplate`:请求体模板,含 `context.user.delegationContext.externalOwnerId`(锁定查询在哪个所有者名下)与 `serializedDelegationContext`。**回放只改 `query`**,其余一律不动。
|
||||
|
||||
### 2.3 多所有者
|
||||
|
||||
- 逐个所有者页面各捕获一份套件,合并进同一个 `bundles.json`。
|
||||
- 同名 ownerId 以最新捕获为准(`--save-bundles` 会与 `--bundles` 读入的合并后写盘)。
|
||||
- 查询时按 `bundles.json` 中的顺序逐个所有者尝试,**首个精确命中即停**。
|
||||
|
||||
### 2.4 核对清单
|
||||
|
||||
- [ ] `headers` 含 Authorization / Cookie / X-YouTube-Delegation-Context。
|
||||
- [ ] `bodyTemplate` 的 `externalOwnerId` 与 URL ownerId 一致(不一致以请求体为准,脚本会打警告)。
|
||||
- [ ] 每个所有者都有独立套件,不混用。
|
||||
- [ ] 套件有效期:鉴权头是页面逐请求计算的,过期需重新 `--url` 捕获。
|
||||
|
||||
## 3. 产物核对
|
||||
|
||||
- 结果列:`group_name / ownerid / owner_display / groupid / 备注`。
|
||||
- `groupid` 非空 = 精确命中;其 `ownerid + owner_display` 即归属所有者。
|
||||
- `groupid` 为空时看「备注」:候选(`名=ID`)或各所有者线索(HTTP/网络错/无结果)。
|
||||
- 行数应与名单一致(含重复项已去重后的名字数)。
|
||||
@@ -0,0 +1,96 @@
|
||||
# 常见问题排查
|
||||
|
||||
先按输出判断类型:`[捕获]`/`[套件]` 前缀的警告是**提示**(不中断);`SystemExit` / 致命错误则中断。核心是先分清是「没登录 / 套件没捕到 / 回放 401 / 查不到」。
|
||||
|
||||
## 登录态与浏览器
|
||||
|
||||
### 跳转到 Google 登录页
|
||||
- 现象:脚本报「当前会话未登录,已跳转到 Google 登录页」,或捕获时停在 accounts.google。
|
||||
- 原因:没复用已登录 YouTube Studio 的会话,开了全新会话。
|
||||
- 解决:改用 `--user-data-dir + --channel`(方式 A,需先关闭对应浏览器),或 `--connect http://localhost:9222`(方式 B,浏览器开在调试端口)。
|
||||
|
||||
### 启动/连接浏览器失败
|
||||
- 现象:`[!] 启动/连接浏览器失败: ...`。
|
||||
- 原因:方式 A 时浏览器未关闭(用户数据目录被占用);方式 B 时调试端口没起。
|
||||
- 解决:方式 A 关闭 Chrome/Edge 后重试;方式 B 先 `chrome.exe --remote-debugging-port=9222`(或 msedge)再运行。
|
||||
|
||||
### channel 不匹配
|
||||
- 现象:启动后用 --user-data-dir 报告浏览器品牌不符。
|
||||
- 原因:`--channel` 与用户数据目录指向的浏览器不是同一品牌(chrome / msedge)。
|
||||
- 解决:`--channel` 必须与 `--user-data-dir` 指向的浏览器一致。
|
||||
|
||||
### CDP 附加后页面是空白/不是目标所有者
|
||||
- 现象:连上 9222 但页面停在别的标签或未登录。
|
||||
- 原因:`connect_over_cdp` 取的是第一个 context 的新标签,可能与已有登录标签不同步。
|
||||
- 解决:确保浏览器已登录目标所有者;必要时先用浏览器手动打开所有者 URL 确认登录态。
|
||||
|
||||
## 套件捕获(`_capture_bundle`)
|
||||
|
||||
### 捕获不到 search_groups 请求
|
||||
- 现象:`[捕获] ... 未捕获到 search_groups 请求` / `TimeoutError`。
|
||||
- 原因:没触发搜索,或页面不是高级模式分析页,或搜索框选择器没猜中。
|
||||
- 解决:在浏览器里于该所有者分析页**顶部搜索/筛选框输入任意词并回车**(只需一次)。脚本会提示并等待(默认 180 秒,可 `--wait-seconds` 调大)。若选择器每次都猜不中,可先手动搜索确认页面是高级模式。
|
||||
|
||||
### 警告:externalOwnerId 与 URL ownerId 不一致
|
||||
- 现象:`[捕获] 警告:请求体 externalOwnerId=... 与 URL ownerId=... 不一致,以请求体为准`。
|
||||
- 原因:URL 指向的 owner 与当前页面 delegation 语境不同(切换所有者后会残留)。
|
||||
- 解决:确认 URL 是目标所有者;页面确实停在目标所有者高级模式页再捕获。
|
||||
|
||||
### 警告:缺 Authorization/Cookie/delegation
|
||||
- 现象:`[捕获] 警告:缺少 Authorization(SAPISIDHASH)头 / ...`。
|
||||
- 原因:捕获到的请求头不全,或请求体缺 `context.user.delegationContext`。
|
||||
- 解决:重新捕获一次,或确认页面是在已登录的目标所有者分析页发起的群组搜索。
|
||||
|
||||
## 回放查询
|
||||
|
||||
### 回放全量 401
|
||||
- 现象:结果备注大量 `HTTP401(鉴权失败:套件缺 Authorization/Cookie 或已过期,请重新捕获)`。
|
||||
- 原因:套件过期(鉴权头是逐请求计算的),或头不全。
|
||||
- 解决:重新用 `--url` 在线捕获套件(更新到 `--save-bundles`),再回放。查 `references/input-guide.md` 第 2 节核对 headers。
|
||||
|
||||
### HTTP403 / 无权限
|
||||
- 现象:`HTTP403(无权限或 delegation 语境不符)`。
|
||||
- 原因:delegation 语境不属于当前登录账号,或该所有者无授权。
|
||||
- 解决:确认登录账号对该内容管理器有权限,重新捕获正确所有者的套件。
|
||||
|
||||
### HTTP429 / 限流
|
||||
- 现象:`HTTP429(限流:调低 --max-workers 或稍后重试)`。
|
||||
- 原因:并发太高或请求过频繁。
|
||||
- 解决:降低 `--max-workers`,稍后重试。
|
||||
|
||||
### 网络错(ERR)
|
||||
- 现象:备注 `网络错: ...`。
|
||||
- 原因:网络不通 / 域名被拦 / 代理导致请求失败。
|
||||
- 解决:检查网络与代理,重试。
|
||||
|
||||
## 匹配结果
|
||||
|
||||
### 某名字显示「无结果」
|
||||
- 现象:备注含 `无结果`。
|
||||
- 原因:该所有者下没有匹配的群组,或名字与 displayName 不完全一致。
|
||||
- 解决:换所有者再试,或用更短关键词重跑;若名字疑似错字,用品牌名做短词触发变体候选。
|
||||
|
||||
### 名字只在候选里(大小写/空格差异)
|
||||
- 现象:备注含候选 `名=ID`,但没有精确命中。
|
||||
- 原因:列表里的 displayName 与输入存在大小写/空格差异。
|
||||
- 解决:直接采用候选 groupId,或把名单里的名字改成与 displayName 完全一致后重跑。
|
||||
|
||||
### 同名群组跨所有者都有
|
||||
- 现象:多个所有者都返回候选,或都命中但归属不同。
|
||||
- 原因:不同内容管理器可各有一个「X 漫剧-N」。
|
||||
- 解决:以**精确 displayName** 命中为准核对实体归属;跨所有者同名时确认要的是哪个 ownerId 下的 groupId。
|
||||
|
||||
### 名单不识别 / 空名单
|
||||
- 现象:`[!] 名单为空或无法解析:...`,或 `无法识别名字列`。
|
||||
- 原因:列名不在别名表,或文件结构不对。
|
||||
- 解决:按 `references/input-guide.md` 第 1 节整理:用别名列名(群组名称/group_name/实体名称/名称),或改为单列文件。
|
||||
|
||||
## 输出问题
|
||||
|
||||
### 输出行数为 0 / 找不到输出文件
|
||||
- 现象:`[输出] 精确命中 0/N`,或默认路径没找到。
|
||||
- 解决:确认 `--names` 能解析出名单;`--out` 显式指定路径。缺 openpyxl 时 `.xlsx` 自动回退为 `.csv`(脚本会提示)。
|
||||
|
||||
### 缺依赖报 ModuleNotFoundError
|
||||
- 现象:`ModuleNotFoundError: No module named 'requests'` 或 `'playwright'` 或 `'openpyxl'`。
|
||||
- 解决:项目根 `uv sync` 后统一用 `uv run` 前缀执行;或单独 `pip install requests playwright openpyxl`。
|
||||
Reference in New Issue
Block a user