@iducky/media-agent 1.1.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/SHA256SUMS +34 -30
- package/bin/media-agent.mjs +19 -7
- package/docs/guides/capabilities.md +6 -2
- package/docs/guides/installation.md +42 -6
- package/manifest.json +74 -58
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/resources/capabilities.json +43 -1
- package/skills/douyin-creator-index/SKILL.md +2 -0
- package/skills/douyin-creator-publish/SKILL.md +3 -1
- package/skills/douyin-enterprise-short-video-export/SKILL.md +2 -0
- package/skills/douyin-enterprise-video-rankings/SKILL.md +2 -0
- package/skills/xiaohongshu-creator-publish/SKILL.md +3 -1
- package/src/media_agent/cli.py +5 -0
- package/src/media_agent/commands.sh +20 -20
- package/src/media_agent/platforms/douyin/check_login.py +8 -11
- package/src/media_agent/platforms/douyin/collect_industry_taxonomy.py +10 -1
- package/src/media_agent/platforms/douyin/collect_video_rankings.py +14 -5
- package/src/media_agent/platforms/douyin/douyin_hotspot_v2.py +12 -3
- package/src/media_agent/platforms/douyin/douyin_publish.py +65 -218
- package/src/media_agent/platforms/douyin/enterprise_login.py +6 -11
- package/src/media_agent/platforms/douyin/export_short_video.py +10 -1
- package/src/media_agent/platforms/douyin/login_controller.py +4 -15
- package/src/media_agent/platforms/toutiao/toutiao_login_controller.py +4 -22
- package/src/media_agent/platforms/xiaohongshu/xiaohongshu_check_login.py +8 -11
- package/src/media_agent/platforms/xiaohongshu/xiaohongshu_login_controller.py +5 -17
- package/src/media_agent/platforms/xiaohongshu/xiaohongshu_publish.py +63 -182
- package/src/media_agent/runtime/account_manager.py +16 -88
- package/src/media_agent/runtime/arguments.py +69 -0
- package/src/media_agent/runtime/diagnostics.py +186 -0
- package/src/media_agent/runtime/locking.py +74 -0
- package/src/media_agent/runtime/publish_control.py +98 -0
- package/src/node/skills.mjs +2 -1
- package/tools/artifacts.py +1 -0
- package/tools/install_runtime.py +5 -0
|
@@ -30,5 +30,47 @@
|
|
|
30
30
|
"agents/openai.yaml": "cfe112ae911831c7f9947b80bea7fa5adbaf44d8461bbe23c9d987aecae99f6e"
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
-
]
|
|
33
|
+
],
|
|
34
|
+
"commands": {
|
|
35
|
+
"collect-douyin-index": {
|
|
36
|
+
"status": "IMPLEMENTATION_REQUIRED",
|
|
37
|
+
"execution": "skill",
|
|
38
|
+
"reason": "Legacy CLI does not verify all supplied arguments"
|
|
39
|
+
},
|
|
40
|
+
"collect-video-rankings": {
|
|
41
|
+
"status": "IMPLEMENTATION_REQUIRED",
|
|
42
|
+
"execution": "skill",
|
|
43
|
+
"reason": "Legacy CLI does not verify all supplied arguments"
|
|
44
|
+
},
|
|
45
|
+
"collect-industry-taxonomy": {
|
|
46
|
+
"status": "IMPLEMENTATION_REQUIRED",
|
|
47
|
+
"execution": "skill",
|
|
48
|
+
"reason": "Legacy CLI does not verify all supplied arguments"
|
|
49
|
+
},
|
|
50
|
+
"export-short-video": {
|
|
51
|
+
"status": "IMPLEMENTATION_REQUIRED",
|
|
52
|
+
"execution": "skill",
|
|
53
|
+
"reason": "Legacy CLI does not verify all supplied arguments"
|
|
54
|
+
},
|
|
55
|
+
"publish-prepare": {
|
|
56
|
+
"status": "OFFLINE_VERIFIED",
|
|
57
|
+
"execution": "cli"
|
|
58
|
+
},
|
|
59
|
+
"xhs-publish-prepare": {
|
|
60
|
+
"status": "IMPLEMENTED_UNVERIFIED",
|
|
61
|
+
"execution": "cli"
|
|
62
|
+
},
|
|
63
|
+
"douyin-creator-image-text-publish": {
|
|
64
|
+
"status": "ADAPTED_UNVERIFIED",
|
|
65
|
+
"execution": "skill"
|
|
66
|
+
},
|
|
67
|
+
"xiaohongshu-creator-image-text-publish": {
|
|
68
|
+
"status": "ADAPTED_UNVERIFIED",
|
|
69
|
+
"execution": "skill"
|
|
70
|
+
},
|
|
71
|
+
"toutiao-creator-article-draft": {
|
|
72
|
+
"status": "AGENT_WORKFLOW",
|
|
73
|
+
"execution": "skill"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
34
76
|
}
|
|
@@ -30,3 +30,5 @@ media-agent collect-douyin-index <profile_id> [--lists realtime,rising] [--pages
|
|
|
30
30
|
v1.0.3 的 `douyin_hotspot_v2.py` 硬编码测试 Profile,`media-agent` 未把 `<profile_id>` 传入采集脚本。对其他 Profile 执行前必须确认安装版本已参数化,否则返回 `IMPLEMENTATION_REQUIRED`,不得声称使用了请求的账号。
|
|
31
31
|
|
|
32
32
|
检查上述参数化限制时,必须读取实现 `src/media_agent/platforms/douyin/douyin_hotspot_v2.py` 和命令路由 `src/media_agent/commands.sh`;已安装环境对应 `<MEDIA_AGENT_HOME>/.app/current/` 下的这些路径。命令存在不代表参数化限制已消除。
|
|
33
|
+
|
|
34
|
+
1.1.1 安全修订:旧采集/导出 CLI 的真实执行已在浏览器启动前拦截并返回 IMPLEMENTATION_REQUIRED(退出码 6)。不要尝试绕过门禁直接运行历史脚本;需要业务操作时按本 Skill 的可见浏览器工作流执行并核验输入条件。dry-run 成功不表示真实采集已支持。
|
|
@@ -10,7 +10,7 @@ description: 在抖音创作者中心准备、确认、提交和核验视频作
|
|
|
10
10
|
```bash
|
|
11
11
|
media-agent publish-dry-run <profile_id> --media-path <path> [--scheduled-at <ISO>]
|
|
12
12
|
media-agent publish-prepare <profile_id> --media-path <path> [--scheduled-at <ISO>]
|
|
13
|
-
media-agent publish-confirm <profile_id>
|
|
13
|
+
media-agent publish-confirm <profile_id> --task-id <task_id>
|
|
14
14
|
media-agent publish-status <profile_id> --task-id <task_id>
|
|
15
15
|
media-agent publish-close <profile_id>
|
|
16
16
|
```
|
|
@@ -54,3 +54,5 @@ URL 跳转、点击计数、按钮消失或本地账本均不是成功证据。C
|
|
|
54
54
|
## 收尾
|
|
55
55
|
|
|
56
56
|
成功或明确失败后正常关闭并释放 lock。`indeterminate` 默认保持现场。禁止 `kill`、`pkill`、覆盖账本原记录或自动重试。
|
|
57
|
+
|
|
58
|
+
发布确认必须带 prepare 返回的 task_id;没有对应的存活准备进程时拒绝执行,不重新打开页面补点发布。确认信号不等于发布成功。结果 indeterminate 的素材禁止直接重试,应先人工核验。
|
|
@@ -27,3 +27,5 @@ media-agent export-short-video <profile_id> [--date YYYY-MM-DD]
|
|
|
27
27
|
## 返回
|
|
28
28
|
|
|
29
29
|
返回 `profile_id`、日期范围、`file_name`、`file_size`、`sheet_names`、`data_rows`、下载状态、附件投递状态、浏览器关闭和 lock 释放状态。不得把榜单抓取结果冒充平台原始导出。
|
|
30
|
+
|
|
31
|
+
1.1.1 安全修订:旧采集/导出 CLI 的真实执行已在浏览器启动前拦截并返回 IMPLEMENTATION_REQUIRED(退出码 6)。不要尝试绕过门禁直接运行历史脚本;需要业务操作时按本 Skill 的可见浏览器工作流执行并核验输入条件。dry-run 成功不表示真实采集已支持。
|
|
@@ -49,3 +49,5 @@ media-agent validate-rankings <task_dir>
|
|
|
49
49
|
正常关闭浏览器并释放 lock;本地文件必须作为真实附件发送后才算完成。
|
|
50
50
|
|
|
51
51
|
检查上述参数化限制时,必须读取实现 `src/media_agent/platforms/douyin/collect_video_rankings.py` 和命令路由 `src/media_agent/commands.sh`;已安装环境对应 `<MEDIA_AGENT_HOME>/.app/current/` 下的这些路径。命令存在不代表参数化限制已消除。
|
|
52
|
+
|
|
53
|
+
1.1.1 安全修订:旧采集/导出 CLI 的真实执行已在浏览器启动前拦截并返回 IMPLEMENTATION_REQUIRED(退出码 6)。不要尝试绕过门禁直接运行历史脚本;需要业务操作时按本 Skill 的可见浏览器工作流执行并核验输入条件。dry-run 成功不表示真实采集已支持。
|
|
@@ -23,7 +23,7 @@ description: 在小红书创作服务平台上传、填写标题、发布并核
|
|
|
23
23
|
```bash
|
|
24
24
|
media-agent xhs-publish-dry-run <profile_id> --media-path <path> # 素材校验 + SHA-256 账本去重,不开浏览器
|
|
25
25
|
media-agent xhs-publish-prepare <profile_id> --media-path <path> --title <标题> # 上传、填标题并读回、写账本、保持浏览器
|
|
26
|
-
media-agent xhs-publish-confirm <profile_id> # 单次真实点击“发布”
|
|
26
|
+
media-agent xhs-publish-confirm <profile_id> --task-id <task_id> # 单次真实点击“发布”
|
|
27
27
|
media-agent xhs-publish-status <profile_id> [--task-id <id>] # 查询发布/审核状态
|
|
28
28
|
media-agent xhs-publish-close <profile_id> # 正常关闭浏览器并释放 lock
|
|
29
29
|
```
|
|
@@ -151,3 +151,5 @@ current_url=<移除敏感查询参数后的 URL>
|
|
|
151
151
|
## 收尾
|
|
152
152
|
|
|
153
153
|
成功、拒绝或只读查询结束后正常关闭 page/context/browser 并释放 lock,让 Profile 正常写盘。`PUBLISH_INDETERMINATE` 默认保留现场供人工检查。禁止使用 `kill`、`pkill`、清空 `browser_data`、删除作品或自动重复提交。
|
|
154
|
+
|
|
155
|
+
发布确认必须带 prepare 返回的 task_id;没有对应的存活准备进程时拒绝执行,不重新打开页面补点发布。确认信号不等于发布成功。结果 indeterminate 的素材禁止直接重试,应先人工核验。
|
package/src/media_agent/cli.py
CHANGED
|
@@ -10,6 +10,11 @@ import sys
|
|
|
10
10
|
def main(argv=None):
|
|
11
11
|
args = list(sys.argv[1:] if argv is None else argv)
|
|
12
12
|
package = Path(__file__).resolve().parent
|
|
13
|
+
if args and (args[0] in ('tasks', 'upgrade') or args[:2] == ['config', 'validate']):
|
|
14
|
+
from media_agent.runtime.diagnostics import main as diagnose
|
|
15
|
+
return diagnose(args)
|
|
16
|
+
from media_agent.runtime.arguments import validate
|
|
17
|
+
args = validate(args)
|
|
13
18
|
if args and args[0] == 'run-script':
|
|
14
19
|
mapping = json.loads((package / 'script_map.json').read_text())
|
|
15
20
|
name = args[1].removesuffix('.py') if len(args) > 1 else ''
|
|
@@ -87,7 +87,6 @@ case "$CMD" in
|
|
|
87
87
|
PROFILE_ID="$1"; require_profile "$PROFILE_ID"
|
|
88
88
|
run_script login_controller.py "$PROFILE_ID" --send '{"action":"close"}'
|
|
89
89
|
sleep 3
|
|
90
|
-
rm -f "$SCRIPT_DIR/locks/${PROFILE_ID}.lock"
|
|
91
90
|
;;
|
|
92
91
|
|
|
93
92
|
login-refresh-qr)
|
|
@@ -163,18 +162,8 @@ case "$CMD" in
|
|
|
163
162
|
;;
|
|
164
163
|
|
|
165
164
|
close)
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
touch "$SCRIPT_DIR/locks/${PROFILE_ID}.close"
|
|
169
|
-
# Wait for browser to actually close (check with lsof)
|
|
170
|
-
for i in $(seq 1 30); do
|
|
171
|
-
if ! lsof -n 2>/dev/null | grep -q "$PROFILE_ID/browser_data"; then
|
|
172
|
-
break
|
|
173
|
-
fi
|
|
174
|
-
sleep 2
|
|
175
|
-
done
|
|
176
|
-
rm -f "$SCRIPT_DIR/locks/${PROFILE_ID}.lock" "$SCRIPT_DIR/locks/${PROFILE_ID}.close"
|
|
177
|
-
echo "已关闭并释放锁"
|
|
165
|
+
echo "Use the owning controller's login-close, xhs-login-close, toutiao-login-close or publish-close command; no lock was removed."
|
|
166
|
+
exit 1
|
|
178
167
|
;;
|
|
179
168
|
|
|
180
169
|
collect-douyin-index)
|
|
@@ -252,26 +241,32 @@ case "$CMD" in
|
|
|
252
241
|
|
|
253
242
|
publish-prepare)
|
|
254
243
|
PROFILE_ID="$1"; require_profile "$PROFILE_ID"; check_capability "$PROFILE_ID" "creator_publish"
|
|
255
|
-
shift; MEDIA_PATH=""; SCHEDULED=""
|
|
244
|
+
shift; MEDIA_PATH=""; SCHEDULED=""; TEXT_ARGS=()
|
|
256
245
|
while [[ $# -gt 0 ]]; do
|
|
257
246
|
case "$1" in
|
|
258
247
|
--help) echo "publish-prepare <profile_id> --media-path <path> [--scheduled-at <ISO>]"; exit 0 ;;
|
|
259
248
|
--media-path) MEDIA_PATH="$2"; shift 2 ;;
|
|
260
249
|
--scheduled-at) SCHEDULED="$2"; shift 2 ;;
|
|
250
|
+
--title|--description) TEXT_ARGS+=("$1" "$2"); shift 2 ;;
|
|
261
251
|
*) shift ;;
|
|
262
252
|
esac
|
|
263
253
|
done
|
|
264
254
|
[ -z "$MEDIA_PATH" ] && { echo "错误: 缺少 --media-path"; exit 1; }
|
|
265
255
|
if [ -n "$SCHEDULED" ]; then
|
|
266
|
-
run_script douyin_publish.py --prepare --profile-id "$PROFILE_ID" --media-path "$MEDIA_PATH" --scheduled-at "$SCHEDULED"
|
|
256
|
+
run_script douyin_publish.py --prepare --profile-id "$PROFILE_ID" --media-path "$MEDIA_PATH" "${TEXT_ARGS[@]}" --scheduled-at "$SCHEDULED"
|
|
267
257
|
else
|
|
268
|
-
run_script douyin_publish.py --prepare --profile-id "$PROFILE_ID" --media-path "$MEDIA_PATH"
|
|
258
|
+
run_script douyin_publish.py --prepare --profile-id "$PROFILE_ID" --media-path "$MEDIA_PATH" "${TEXT_ARGS[@]}"
|
|
269
259
|
fi
|
|
270
260
|
;;
|
|
271
261
|
|
|
272
262
|
publish-confirm)
|
|
273
263
|
PROFILE_ID="$1"; require_profile "$PROFILE_ID"; check_capability "$PROFILE_ID" "creator_publish"
|
|
274
|
-
|
|
264
|
+
shift; TASK_ID=""
|
|
265
|
+
while [[ $# -gt 0 ]]; do
|
|
266
|
+
case "$1" in --task-id) TASK_ID="$2"; shift 2 ;; *) echo "Unknown option: $1"; exit 1 ;; esac
|
|
267
|
+
done
|
|
268
|
+
[ -n "$TASK_ID" ] || { echo "TASK_ID_REQUIRED"; exit 1; }
|
|
269
|
+
run_script douyin_publish.py --confirm-submit --profile-id "$PROFILE_ID" --task-id "$TASK_ID"
|
|
275
270
|
;;
|
|
276
271
|
|
|
277
272
|
publish-status)
|
|
@@ -363,7 +358,12 @@ case "$CMD" in
|
|
|
363
358
|
|
|
364
359
|
xhs-publish-confirm)
|
|
365
360
|
PROFILE_ID="$1"; require_profile "$PROFILE_ID"; check_capability "$PROFILE_ID" "creator_publish"
|
|
366
|
-
|
|
361
|
+
shift; TASK_ID=""
|
|
362
|
+
while [[ $# -gt 0 ]]; do
|
|
363
|
+
case "$1" in --task-id) TASK_ID="$2"; shift 2 ;; *) echo "Unknown option: $1"; exit 1 ;; esac
|
|
364
|
+
done
|
|
365
|
+
[ -n "$TASK_ID" ] || { echo "TASK_ID_REQUIRED"; exit 1; }
|
|
366
|
+
run_script xiaohongshu_publish.py --confirm-submit --profile-id "$PROFILE_ID" --task-id "$TASK_ID"
|
|
367
367
|
;;
|
|
368
368
|
|
|
369
369
|
xhs-publish-status)
|
|
@@ -408,7 +408,7 @@ case "$CMD" in
|
|
|
408
408
|
echo " collect-industry-taxonomy <id>"
|
|
409
409
|
echo " publish-dry-run <id> --media-path <path>"
|
|
410
410
|
echo " publish-prepare <id> --media-path <path> [--scheduled-at <ISO>]"
|
|
411
|
-
echo " publish-confirm <id>"
|
|
411
|
+
echo " publish-confirm <id> --task-id <id>"
|
|
412
412
|
echo " publish-status <id> [--task-id <id>]"
|
|
413
413
|
echo " publish-close <id>"
|
|
414
414
|
echo " toutiao-login-start <id> 启动今日头条持久化登录控制器"
|
|
@@ -426,7 +426,7 @@ case "$CMD" in
|
|
|
426
426
|
echo " xhs-check-login <id> 小红书登录状态检查"
|
|
427
427
|
echo " xhs-publish-dry-run <id> --media-path <path>"
|
|
428
428
|
echo " xhs-publish-prepare <id> --media-path <path> --title <标题>"
|
|
429
|
-
echo " xhs-publish-confirm <id>"
|
|
429
|
+
echo " xhs-publish-confirm <id> --task-id <id>"
|
|
430
430
|
echo " xhs-publish-status <id> [--task-id <id>]"
|
|
431
431
|
echo " xhs-publish-close <id>"
|
|
432
432
|
echo "Exit: 0=OK 1=err 2=LOGIN_REQUIRED 3=PROFILE_LOCKED 4=NO_CAPABILITY 5=NOT_FOUND 6=WAIT_INDUSTRY 7=indeterminate 8=DUPLICATE_BLOCKED"
|
|
@@ -42,15 +42,12 @@ def main():
|
|
|
42
42
|
if not sites_to_check:
|
|
43
43
|
sites_to_check = sites_cfg # backward compat
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
except:
|
|
52
|
-
LOCK_FILE.unlink()
|
|
53
|
-
LOCK_FILE.write_text(json.dumps({'task_id': 'check-login', 'pid': os.getpid(), 'time': datetime.now().isoformat(), 'site': SITE}, indent=2))
|
|
45
|
+
TASK_ID = 'check-login'
|
|
46
|
+
from media_agent.runtime.locking import acquire, release
|
|
47
|
+
if not acquire(LOCK_FILE, TASK_ID):
|
|
48
|
+
print('PROFILE_LOCKED')
|
|
49
|
+
raise SystemExit(3)
|
|
50
|
+
|
|
54
51
|
|
|
55
52
|
try:
|
|
56
53
|
ctx = launch_persistent_context(
|
|
@@ -154,7 +151,7 @@ def main():
|
|
|
154
151
|
}
|
|
155
152
|
|
|
156
153
|
ctx.close()
|
|
157
|
-
LOCK_FILE
|
|
154
|
+
release(LOCK_FILE, TASK_ID)
|
|
158
155
|
|
|
159
156
|
# Update state
|
|
160
157
|
for site_name, r in results.items():
|
|
@@ -188,7 +185,7 @@ def main():
|
|
|
188
185
|
print(json.dumps(output, indent=2, ensure_ascii=False))
|
|
189
186
|
sys.exit(exit_code)
|
|
190
187
|
except Exception as e:
|
|
191
|
-
LOCK_FILE
|
|
188
|
+
release(LOCK_FILE, TASK_ID)
|
|
192
189
|
print(json.dumps({'profile_id': ALIAS, 'login_status': 'check_failed', 'error': str(e)[:100]}, indent=2))
|
|
193
190
|
sys.exit(1)
|
|
194
191
|
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
"""Legacy operation, executed only through main()."""
|
|
2
2
|
|
|
3
3
|
def main():
|
|
4
|
+
import json
|
|
5
|
+
import sys
|
|
6
|
+
print(json.dumps({'state': 'IMPLEMENTATION_REQUIRED',
|
|
7
|
+
'operation': 'collect_industry_taxonomy',
|
|
8
|
+
'reason': 'Legacy browser flow cannot yet verify all requested parameters; use the documented Skill workflow.'}))
|
|
9
|
+
raise SystemExit(6)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _legacy_reference():
|
|
4
13
|
#!/usr/bin/env python3
|
|
5
14
|
"""Collect industry taxonomy v2 — fixed readback and proper cascader interaction."""
|
|
6
15
|
import yaml
|
|
@@ -22,7 +31,7 @@ def main():
|
|
|
22
31
|
LOCK_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
23
32
|
if LOCK_FILE.exists():
|
|
24
33
|
try: os.kill(json.loads(LOCK_FILE.read_text()).get('pid',0), 0); print('LOCKED'); sys.exit(1)
|
|
25
|
-
except: LOCK_FILE.unlink()
|
|
34
|
+
except Exception: LOCK_FILE.unlink()
|
|
26
35
|
LOCK_FILE.write_text(json.dumps({'task_id':f'taxonomy_v2_{ts}','pid':os.getpid()}, indent=2))
|
|
27
36
|
print(f'TASK|taxonomy_v2_{ts}', flush=True)
|
|
28
37
|
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
"""Legacy operation, executed only through main()."""
|
|
2
2
|
|
|
3
3
|
def main():
|
|
4
|
+
import json
|
|
5
|
+
import sys
|
|
6
|
+
print(json.dumps({'state': 'IMPLEMENTATION_REQUIRED',
|
|
7
|
+
'operation': 'collect_video_rankings',
|
|
8
|
+
'reason': 'Legacy browser flow cannot yet verify all requested parameters; use the documented Skill workflow.'}))
|
|
9
|
+
raise SystemExit(6)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _legacy_reference():
|
|
4
13
|
#!/usr/bin/env python3
|
|
5
14
|
"""Collect video rankings - DOM-based extraction, verified locators."""
|
|
6
15
|
from media_agent.runtime.paths import runtime_home
|
|
@@ -28,7 +37,7 @@ def main():
|
|
|
28
37
|
LOCK_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
29
38
|
if LOCK_FILE.exists():
|
|
30
39
|
try: os.kill(json.loads(LOCK_FILE.read_text()).get('pid',0), 0); sys.exit(1)
|
|
31
|
-
except: LOCK_FILE.unlink()
|
|
40
|
+
except Exception: LOCK_FILE.unlink()
|
|
32
41
|
LOCK_FILE.write_text(json.dumps({'task_id':TASK_ID,'pid':os.getpid()}, indent=2))
|
|
33
42
|
print(f'TASK|{TASK_ID}', flush=True)
|
|
34
43
|
|
|
@@ -119,21 +128,21 @@ def main():
|
|
|
119
128
|
|
|
120
129
|
def click_tab(page, name):
|
|
121
130
|
try: page.locator(f'text={name}').first.click(); time.sleep(5); return True
|
|
122
|
-
except: return False
|
|
131
|
+
except Exception: return False
|
|
123
132
|
|
|
124
133
|
def parse_numeric(val):
|
|
125
134
|
if not val: return {'display': val, 'numeric': None}
|
|
126
135
|
t = val.strip()
|
|
127
136
|
if t.endswith('%'):
|
|
128
137
|
try: return {'display': t, 'numeric': float(t[:-1]) / 100}
|
|
129
|
-
except: return {'display': t, 'numeric': None}
|
|
138
|
+
except Exception: return {'display': t, 'numeric': None}
|
|
130
139
|
if t.endswith('万'):
|
|
131
140
|
try: return {'display': t, 'numeric': round(float(t[:-1]) * 10000)}
|
|
132
|
-
except: return {'display': t, 'numeric': None}
|
|
141
|
+
except Exception: return {'display': t, 'numeric': None}
|
|
133
142
|
try:
|
|
134
143
|
n = float(t.replace(',', ''))
|
|
135
144
|
return {'display': t, 'numeric': int(n) if n == int(n) else n}
|
|
136
|
-
except:
|
|
145
|
+
except Exception:
|
|
137
146
|
return {'display': t, 'numeric': None}
|
|
138
147
|
|
|
139
148
|
# ===== Collect =====
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
"""Legacy operation, executed only through main()."""
|
|
2
2
|
|
|
3
3
|
def main():
|
|
4
|
+
import json
|
|
5
|
+
import sys
|
|
6
|
+
print(json.dumps({'state': 'IMPLEMENTATION_REQUIRED',
|
|
7
|
+
'operation': 'douyin_hotspot_v2',
|
|
8
|
+
'reason': 'Legacy browser flow cannot yet verify all requested parameters; use the documented Skill workflow.'}))
|
|
9
|
+
raise SystemExit(6)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _legacy_reference():
|
|
4
13
|
#!/usr/bin/env python3
|
|
5
14
|
"""Douyin热点数据完整采集 v2 - 独立容器分页 + 趋势修复"""
|
|
6
15
|
from media_agent.runtime.paths import runtime_home
|
|
@@ -27,7 +36,7 @@ def main():
|
|
|
27
36
|
if pid:
|
|
28
37
|
try: os.kill(pid, 0); print('ERROR|Lock active', flush=True); sys.exit(1)
|
|
29
38
|
except OSError: LOCK_FILE.unlink()
|
|
30
|
-
except: LOCK_FILE.unlink()
|
|
39
|
+
except Exception: LOCK_FILE.unlink()
|
|
31
40
|
LOCK_FILE.write_text(json.dumps({'task_id':TASK_ID,'pid':os.getpid()}, indent=2))
|
|
32
41
|
print(f'LOCK|{TASK_ID}', flush=True)
|
|
33
42
|
|
|
@@ -49,7 +58,7 @@ def main():
|
|
|
49
58
|
try:
|
|
50
59
|
confirm = page.locator('text=确认').first
|
|
51
60
|
if confirm and confirm.is_visible(timeout=3000): confirm.click(); time.sleep(3)
|
|
52
|
-
except: pass
|
|
61
|
+
except Exception: pass
|
|
53
62
|
|
|
54
63
|
collected_at = datetime.now(timezone(timedelta(hours=8))).isoformat()
|
|
55
64
|
source_url = page.url
|
|
@@ -123,7 +132,7 @@ def main():
|
|
|
123
132
|
if '万' in t:
|
|
124
133
|
return round(float(t.replace('万', '').strip()) * 10000), t
|
|
125
134
|
try: return round(float(t.replace(',', ''))), t
|
|
126
|
-
except: return 0, t
|
|
135
|
+
except Exception: return 0, t
|
|
127
136
|
|
|
128
137
|
def parse_trend_dom(page):
|
|
129
138
|
"""Parse trend from DOM using JS to inspect icon/SVG/class."""
|