@geoly-ai/social-hub-cli 0.3.43 → 0.3.44
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/CHANGELOG.md +17 -0
- package/dist/cmd-manifest.json +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/integrity.json +18 -17
- package/skills/manifest.json +2 -2
- package/skills/reddit-answers-research/SKILL.md +1 -1
- package/skills/reddit-matrix.lock.json +5 -5
- package/skills/reddit-phase-brief/references/phase4-brief-workflow.md +2 -2
- package/skills/reddit-phase-research/references/phase01-research.md +2 -2
- package/skills/reddit-strategy-shared/references/version-manifest.md +2 -2
- package/skills/reddit-voc-volume/SKILL.md +35 -28
- package/skills/reddit-voc-volume/matrix-contract.md +1 -1
- package/skills/reddit-voc-volume/references/arctic-shift.md +8 -7
- package/skills/reddit-voc-volume/references/browser-fallback.md +9 -4
- package/skills/reddit-voc-volume/requirements.txt +3 -1
- package/skills/reddit-voc-volume/scripts/reddit_voc_volume.py +3 -20
- package/skills/reddit-voc-volume/tests/test_links_fallback.py +87 -0
- package/skills/social-hub-content-classification/SKILL.md +4 -0
- package/skills/social-hub-intelligence/SKILL.md +24 -1
- package/skills/social-hub-openclaw-context/SKILL.md +21 -16
- package/skills/social-hub-scrape/SKILL.md +18 -16
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import contextlib
|
|
4
|
+
import importlib.util
|
|
5
|
+
import io
|
|
6
|
+
import json
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import sys
|
|
9
|
+
import unittest
|
|
10
|
+
from unittest import mock
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
SCRIPT = Path(__file__).resolve().parents[1] / "scripts" / "reddit_voc_volume.py"
|
|
14
|
+
SPEC = importlib.util.spec_from_file_location("reddit_voc_links_under_test", SCRIPT)
|
|
15
|
+
assert SPEC is not None and SPEC.loader is not None
|
|
16
|
+
voc = importlib.util.module_from_spec(SPEC)
|
|
17
|
+
SPEC.loader.exec_module(voc)
|
|
18
|
+
POST_URL = "https://www.reddit.com/r/example/comments/abc123/slug/"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class LinksFallbackTests(unittest.TestCase):
|
|
22
|
+
def run_main(self, *args: str) -> tuple[int, dict]:
|
|
23
|
+
stdout = io.StringIO()
|
|
24
|
+
with mock.patch.object(sys, "argv", [str(SCRIPT), *args]), contextlib.redirect_stdout(stdout):
|
|
25
|
+
code = voc.main()
|
|
26
|
+
return code, json.loads(stdout.getvalue())
|
|
27
|
+
|
|
28
|
+
def test_arctic_miss_uses_rendered_page_without_loading_browser_cookies(self) -> None:
|
|
29
|
+
with (
|
|
30
|
+
mock.patch.object(voc.CookieStore, "get", side_effect=AssertionError("browser cookies loaded")) as cookies,
|
|
31
|
+
mock.patch.object(voc, "arctic_post_by_permalink", return_value=None) as archive,
|
|
32
|
+
mock.patch.object(voc, "fetch_page_scrape", return_value={"markdown": "Public page"}) as page,
|
|
33
|
+
mock.patch.object(voc, "collect_subreddit_metrics", return_value=[]),
|
|
34
|
+
mock.patch.object(voc, "hub_writeback_items", return_value={"status": "pending"}),
|
|
35
|
+
mock.patch.object(voc, "sleep_with_jitter", return_value=0.0),
|
|
36
|
+
):
|
|
37
|
+
code, out = self.run_main("links", POST_URL, "--fallback", "auto", "--sleep-sec", "0")
|
|
38
|
+
|
|
39
|
+
self.assertEqual(code, 0)
|
|
40
|
+
self.assertTrue(out["ok"])
|
|
41
|
+
self.assertEqual(out["items"][0]["item_type"], "firecrawl_page")
|
|
42
|
+
self.assertEqual(out["errors"], [])
|
|
43
|
+
archive.assert_called_once()
|
|
44
|
+
self.assertEqual(page.call_args.kwargs["provider"], "social-hub")
|
|
45
|
+
cookies.assert_not_called()
|
|
46
|
+
|
|
47
|
+
def test_arctic_miss_without_fallback_reports_archive_not_json(self) -> None:
|
|
48
|
+
with (
|
|
49
|
+
mock.patch.object(voc.CookieStore, "get", side_effect=AssertionError("browser cookies loaded")) as cookies,
|
|
50
|
+
mock.patch.object(voc, "arctic_post_by_permalink", side_effect=RuntimeError("archive unavailable")),
|
|
51
|
+
mock.patch.object(voc, "collect_subreddit_metrics", return_value=[]),
|
|
52
|
+
mock.patch.object(voc, "hub_writeback_items", return_value={"status": "not_applicable"}),
|
|
53
|
+
mock.patch.object(voc, "sleep_with_jitter", return_value=0.0),
|
|
54
|
+
):
|
|
55
|
+
code, out = self.run_main("links", POST_URL, "--fallback", "none", "--sleep-sec", "0")
|
|
56
|
+
|
|
57
|
+
self.assertEqual(code, 1)
|
|
58
|
+
self.assertFalse(out["ok"])
|
|
59
|
+
self.assertTrue(any("archive unavailable" in error for error in out["errors"]))
|
|
60
|
+
self.assertTrue(any("Arctic lookup failed" in note for note in out["notes"]))
|
|
61
|
+
cookies.assert_not_called()
|
|
62
|
+
|
|
63
|
+
def test_auto_fallback_tries_firecrawl_after_social_hub_failure(self) -> None:
|
|
64
|
+
def page_result(_url: str, *, provider: str, **_kwargs: object) -> dict:
|
|
65
|
+
if provider == "social-hub":
|
|
66
|
+
raise RuntimeError("render unavailable")
|
|
67
|
+
return {"markdown": "Public page"}
|
|
68
|
+
|
|
69
|
+
with (
|
|
70
|
+
mock.patch.object(voc.CookieStore, "get", side_effect=AssertionError("browser cookies loaded")) as cookies,
|
|
71
|
+
mock.patch.object(voc, "arctic_post_by_permalink", return_value=None),
|
|
72
|
+
mock.patch.object(voc, "fetch_page_scrape", side_effect=page_result) as page,
|
|
73
|
+
mock.patch.object(voc, "social_hub_unavailable_hint", return_value="Hub unavailable"),
|
|
74
|
+
mock.patch.object(voc, "collect_subreddit_metrics", return_value=[]),
|
|
75
|
+
mock.patch.object(voc, "hub_writeback_items", return_value={"status": "pending"}),
|
|
76
|
+
mock.patch.object(voc, "sleep_with_jitter", return_value=0.0),
|
|
77
|
+
):
|
|
78
|
+
code, out = self.run_main("links", POST_URL, "--fallback", "auto", "--sleep-sec", "0")
|
|
79
|
+
|
|
80
|
+
self.assertEqual(code, 0)
|
|
81
|
+
self.assertEqual(out["items"][0]["page_provider"], "firecrawl")
|
|
82
|
+
self.assertEqual([call.kwargs["provider"] for call in page.call_args_list], ["social-hub", "firecrawl"])
|
|
83
|
+
cookies.assert_not_called()
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
if __name__ == "__main__":
|
|
87
|
+
unittest.main()
|
|
@@ -42,6 +42,10 @@ worker(单实例 cron)按 active spec 对缺当前 revision 成功 run 的
|
|
|
42
42
|
这些 run 可能对应帖子的**旧 revision**。要判断某帖「当前」被分成什么,只能用
|
|
43
43
|
`get-subject`(精确唯一键查询,proposed / missing 二态)。**不要拿 `recent` 的
|
|
44
44
|
第一条当 latest 用。**
|
|
45
|
+
- **`recent` 没有分页,返回的是截断后的一页**:`count` / `returned` 是**本页条数,不是总数**;
|
|
46
|
+
`truncated: true` 表示还有更多(服务端多取一条判定,精确),`selectionLimit` 是本次生效上限。
|
|
47
|
+
`--limit` 未到 200 时可以调大(上限 200);**已是 200 仍 `truncated: true`,说明这个 recent 视图枚举不完余项**,
|
|
48
|
+
**不要自己拼 offset**——这个端点根本不接受分页参数。
|
|
45
49
|
|
|
46
50
|
## 读面
|
|
47
51
|
|
|
@@ -259,11 +259,32 @@ watchlist 须显式传 `brandIds`。
|
|
|
259
259
|
```bash
|
|
260
260
|
social-hub intelligence monitors-list [--enabled true|false] [--watchlist <uuid>]
|
|
261
261
|
social-hub intelligence monitors-enable --watchlist <uuid> [--fetch-limit 100]
|
|
262
|
+
social-hub intelligence monitors-update --watchlist <uuid> --fetch-limit 50 # 只改配置,不碰启停
|
|
262
263
|
social-hub intelligence monitors-disable --watchlist <uuid> [--purge]
|
|
263
264
|
social-hub intelligence monitors-run # 手工触发一轮(异步入队)
|
|
264
265
|
social-hub intelligence monitor-runs-list [--status degraded] [-n 20]
|
|
265
266
|
```
|
|
266
267
|
|
|
268
|
+
### 🔴 改抓取上限用 `monitors-update`,⛔ 不要用 `monitors-enable`
|
|
269
|
+
|
|
270
|
+
`monitors-enable` 是个 **upsert**,它的 `DO UPDATE` 里写死了 `enabled = true` ——
|
|
271
|
+
所以拿它去改一个**已停止**监听的 `--fetch-limit`,会把那个监听**静默重新启用**,
|
|
272
|
+
该板块的新帖会立刻重新推给负责人。
|
|
273
|
+
|
|
274
|
+
`monitors-update` 背后的 SQL 里**根本不出现 `enabled` 列**,所以它没有能力改启停:
|
|
275
|
+
停止状态下改完仍是停止,启用状态下改完仍是启用。
|
|
276
|
+
|
|
277
|
+
| 我要做的事 | 用哪个命令 |
|
|
278
|
+
| ------------------- | ----------------------------------- |
|
|
279
|
+
| 加入监听 / 重新启用 | `monitors-enable` |
|
|
280
|
+
| **只改抓取上限** | `monitors-update`(启停状态原样不动) |
|
|
281
|
+
| 停止监听(软停) | `monitors-disable` |
|
|
282
|
+
|
|
283
|
+
⚠️ `monitors-update` **不会创建**监听:该 watchlist 从没配置过监听时返回 404
|
|
284
|
+
(要新建请显式用 `monitors-enable`)。
|
|
285
|
+
⚠️ `--fetch-limit` 两个命令同一套边界:整数 1–100。
|
|
286
|
+
⚠️ `sort` **没有任何写入面**,两个命令都不收 —— DDL 的 CHECK 只允许 `'new'` 一个值。
|
|
287
|
+
|
|
267
288
|
**停止监听 = 软停**(`enabled=false`),保留观察 ledger,重启后已报告过的帖子不会重复报告。
|
|
268
289
|
`--purge` 是另一个动作:**连历史观察一起硬删**,只有要删掉整个 watchlist 时才需要
|
|
269
290
|
(monitor 配置对 watchlist 的外键是 RESTRICT,不先 purge 就删不掉)——purge 后重新监听
|
|
@@ -604,4 +625,6 @@ WebSocket `runs/ws` 为 watch 能力,不适合普通 CLI 批处理。
|
|
|
604
625
|
|
|
605
626
|
## 通用网页抓取
|
|
606
627
|
|
|
607
|
-
本 skill 只覆盖**系统内部**的 Reddit 情报采集(落库 +
|
|
628
|
+
本 skill 只覆盖**系统内部**的 Reddit 情报采集(落库 + 聚合)。worker 侧仍先试 `.json`(curl_cffi + 住宅代理),**但 `.json` 已被 Reddit 全面拦截**(2026-09-18 实测住宅代理同样 403)——真正出数的是封禁熔断器之后的 **Firecrawl 渲染兜底**(需 worker 配 `FIRECRAWL_BASE_URL`,`REDDIT_FIRECRAWL_FALLBACK_ENABLED` 不显式关即生效)。
|
|
629
|
+
|
|
630
|
+
若要**按任意 URL 临时抓取非 Reddit 页面**(HTML/普通网页走 Firecrawl),用独立的 **`social-hub-scrape`** skill(`social-hub scrape url …`)。⛔ **不要用它抓 Reddit**:该端点的 `.json` 直连与 Firecrawl 渲染两条路都被拦(见该 skill 顶部红字)。
|
|
@@ -129,22 +129,27 @@ social-hub query subreddit-candidates -t <team-id> --account A1 --action browse
|
|
|
129
129
|
|
|
130
130
|
**响应要点**(`subreddit-candidates`):
|
|
131
131
|
|
|
132
|
-
| 字段 | 说明
|
|
133
|
-
| ----------------------------------------- |
|
|
134
|
-
| `items[].source` | `system_pool`、`persona.*`、`brand.binding`;`fallback_team_pool` 仅表示是否用系统池补足
|
|
135
|
-
| `items[].sourceScope` | `system`(全局 catalog)或 `account`(人设/品牌展开)
|
|
136
|
-
| `items[].poolKind` | `pet` / `common` / `industry` / `brand` / `direct` / `other`
|
|
137
|
-
| `systemPools.expandedByKind` | 系统级 pet/common/industry/brand 汇总(选板前先看)
|
|
138
|
-
| `items[].subreddit` | 可直接消费的 `r/Subreddit` 格式
|
|
139
|
-
| `items[].selectionWeight` | 加权随机用;**禁止直接取 `items[0]`**
|
|
140
|
-
| `items[].cooldownUntil` | 未过期时禁止 `comment`/`reply`
|
|
141
|
-
| `items[].allowedActions` | 当前动作允许的操作
|
|
142
|
-
| `personaPools.expandedByKind` | 按来源汇总:`direct` / `common` / `industry` / `pet` / `brand` 的 `r/...` 列表
|
|
143
|
-
| `personaPools.expandedPools[].subreddits` | 每个 pool ref 展开后的具体板块(可多条)
|
|
144
|
-
| `personaPools.brandPools` | 账号品牌绑定及对应 `brand_pools.*` ref 展开结果
|
|
145
|
-
| `personaPools.unresolvedRefs` | alias 表无法解析的 ref;正常 seed 后 P01-P57 的 `starter/advanced/pet_pool` 不应出现
|
|
146
|
-
| `fallback.reason` | 如 `candidate_pool_exhausted` 须在 Agent 回执说明
|
|
147
|
-
| `exhaustion.reason` | **`items` 为空时是哪一类空** —— 用它判「要不要惊动人」,见下
|
|
132
|
+
| 字段 | 说明 |
|
|
133
|
+
| ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
134
|
+
| `items[].source` | `system_pool`、`persona.*`、`brand.binding`;`fallback_team_pool` 仅表示是否用系统池补足 |
|
|
135
|
+
| `items[].sourceScope` | `system`(全局 catalog)或 `account`(人设/品牌展开) |
|
|
136
|
+
| `items[].poolKind` | `pet` / `common` / `industry` / `brand` / `direct` / `other` |
|
|
137
|
+
| `systemPools.expandedByKind` | 系统级 pet/common/industry/brand 汇总(选板前先看) |
|
|
138
|
+
| `items[].subreddit` | 可直接消费的 `r/Subreddit` 格式 |
|
|
139
|
+
| `items[].selectionWeight` | 加权随机用;**禁止直接取 `items[0]`** |
|
|
140
|
+
| `items[].cooldownUntil` | 未过期时禁止 `comment`/`reply` |
|
|
141
|
+
| `items[].allowedActions` | 当前动作允许的操作 |
|
|
142
|
+
| `personaPools.expandedByKind` | 按来源汇总:`direct` / `common` / `industry` / `pet` / `brand` 的 `r/...` 列表 |
|
|
143
|
+
| `personaPools.expandedPools[].subreddits` | 每个 pool ref 展开后的具体板块(可多条) |
|
|
144
|
+
| `personaPools.brandPools` | 账号品牌绑定及对应 `brand_pools.*` ref 展开结果 |
|
|
145
|
+
| `personaPools.unresolvedRefs` | alias 表无法解析的 ref;正常 seed 后 P01-P57 的 `starter/advanced/pet_pool` 不应出现 |
|
|
146
|
+
| `fallback.reason` | 如 `candidate_pool_exhausted` 须在 Agent 回执说明 |
|
|
147
|
+
| `exhaustion.reason` | **`items` 为空时是哪一类空** —— 用它判「要不要惊动人」,见下 |
|
|
148
|
+
| `selectionLimit` / `returned` | 本次生效的 limit(含默认值)与实际返回条数(= `items.length`) |
|
|
149
|
+
| `truncated` | **有候选被截掉了**。分档配额 / limit 造成的:调大 `--limit` 或收窄 `--industry`/`--tier`;单 key 行数上限(500)造成的:上游固定帽,调 limit 补不全,只能收窄 industry 或找人处理。评论动作下配额阶段尚未过冷却/affinity,可能偏多报 |
|
|
150
|
+
|
|
151
|
+
`account-pools` 同样返回 `selectionLimit` / `returned` / `truncated`(口径一致,不带 `exhaustion`)。
|
|
152
|
+
⛔ `truncated: false` 只说明「没被截断」,**不**说明池子够用。
|
|
148
153
|
|
|
149
154
|
### `items: []` 到底是哪一类空(`exhaustion`)
|
|
150
155
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: social-hub-scrape
|
|
3
3
|
description: >-
|
|
4
|
-
通用网页抓取:HTML/普通网页经自托管 Firecrawl 返回 markdown/html/rawHtml/links
|
|
5
|
-
Reddit
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
通用网页抓取:HTML/普通网页经自托管 Firecrawl 返回 markdown/html/rawHtml/links。
|
|
5
|
+
⛔ Reddit 不要用本端点:.json 直连已被 Reddit 全面拦截(2026-09-18 实测住宅代理同样 403),
|
|
6
|
+
本端点对 Reddit 的两条路径(.json 直连、Firecrawl 渲染)都取不到数据,要 Reddit 数据走
|
|
7
|
+
social-hub-intelligence 的采集链路。用户要抓某个非 Reddit 网页、取证、拉页面正文/链接,
|
|
8
|
+
或说 social-hub scrape 时使用;不用于绕过登录墙/验证码,也不做全站爬取。
|
|
9
9
|
metadata:
|
|
10
10
|
cliVersion: ">=0.0.12"
|
|
11
11
|
---
|
|
@@ -14,7 +14,11 @@ metadata:
|
|
|
14
14
|
|
|
15
15
|
> **前置条件:** 先读 [`../social-hub-shared/SKILL.md`](../social-hub-shared/SKILL.md),完成 `auth login`。
|
|
16
16
|
|
|
17
|
-
**按你给的 URL 抓**,不套系统内部的 Reddit
|
|
17
|
+
**按你给的 URL 抓**,不套系统内部的 Reddit 采集逻辑。适合临时取证、抓任意公开页面正文/链接。端点分流:**Reddit 主机 + `.json`(或 `--reddit-json`)→ API 内直连 curl_cffi**,**其余 HTML/普通网页 → Firecrawl**。
|
|
18
|
+
|
|
19
|
+
> 🔴 **Reddit 这条路现在是死的,别用本端点核实 Reddit 内容。** 2026-09-01 与 2026-09-18 两次实测:`.json` + 住宅代理 → `outcome: blocked`(403);Firecrawl `html`/`rawHtml` 渲染单帖页与板块列表页 → `outcome: captcha`;`old.reddit.com` 同样 captcha。API 侧只有这两条路,都被拦着。
|
|
20
|
+
> **要 Reddit 数据走 worker 侧采集**(`social-hub-intelligence`):那边另有 Reddit OAuth、`REDDIT_FIRECRAWL_FALLBACK_ENABLED` 渲染兜底与 `.json` 封禁熔断器,实测仍有产量。
|
|
21
|
+
> ⚠️ 「这次抓到了」不能推出「这个通道可用」——成败取决于出口 IP 当时的状态。
|
|
18
22
|
|
|
19
23
|
## 命令
|
|
20
24
|
|
|
@@ -25,13 +29,9 @@ social-hub scrape url "https://example.com/article"
|
|
|
25
29
|
# 指定输出格式(markdown|html|rawHtml|links,逗号分隔)
|
|
26
30
|
social-hub scrape url "https://example.com" -f markdown,links
|
|
27
31
|
|
|
28
|
-
# Reddit
|
|
29
|
-
social-hub scrape url "https://www.reddit.com/r/espresso/about.json" --reddit-json
|
|
30
|
-
social-hub scrape url "https://www.reddit.com/r/espresso
|
|
31
|
-
social-hub scrape url "https://www.reddit.com/search.json?q=plaud&sort=new"
|
|
32
|
-
|
|
33
|
-
# 抓 Reddit 页面正文(HTML,经 Firecrawl 渲染)
|
|
34
|
-
social-hub scrape url "https://www.reddit.com/r/espresso"
|
|
32
|
+
# ⛔ Reddit(.json 直连与 HTML 渲染都已被拦,实测取不到,保留仅为说明分流规则)
|
|
33
|
+
# social-hub scrape url "https://www.reddit.com/r/espresso/about.json" --reddit-json
|
|
34
|
+
# social-hub scrape url "https://www.reddit.com/r/espresso"
|
|
35
35
|
|
|
36
36
|
# 超时(毫秒,1000-60000)
|
|
37
37
|
social-hub scrape url "https://example.com" --timeout 20000
|
|
@@ -42,8 +42,9 @@ social-hub scrape status
|
|
|
42
42
|
|
|
43
43
|
## 边界与约束
|
|
44
44
|
|
|
45
|
-
- **HTML/普通网页需先部署 Firecrawl**(`docker/docker-compose.firecrawl.yml`)并给 API 配 `FIRECRAWL_BASE_URL`;未配时抓 HTML
|
|
46
|
-
-
|
|
45
|
+
- **HTML/普通网页需先部署 Firecrawl**(`docker/docker-compose.firecrawl.yml`)并给 API 配 `FIRECRAWL_BASE_URL`;未配时抓 HTML 报「未配置」。
|
|
46
|
+
- **Reddit:两条路径当前都不可用**(见上方红字)。`.json` 直连不依赖 Firecrawl、靠 API 内 curl_cffi + `REDDIT_HTTP_PROXY`,但住宅代理也已被 403;换代理后是否恢复要实测,不要假设。
|
|
47
|
+
- **分流规则**:Reddit 主机 + (`--reddit-json` 或 URL 以 `.json` 结尾)→ 直连 curl_cffi;其余 → Firecrawl。⚠️ 分流规则照旧,但 Reddit 这一侧当前取不到数据(见上方红字)。
|
|
47
48
|
- **权限 `scrape.create`**(运维/合规向;client 角色无权)。
|
|
48
49
|
- **SSRF 防护**:仅 http/https,拒绝私网/环回/云 metadata 地址(含 IPv6 私网段、DNS rebinding、重定向落地复检);可配 `SCRAPE_ALLOWED_HOSTS` 域名白名单收窄可抓范围。
|
|
49
50
|
- **并发受控**:API 侧非阻塞信号量(`SCRAPE_MAX_CONCURRENCY`,默认 4),满载返回 **429**,需重试。
|
|
@@ -52,4 +53,5 @@ social-hub scrape status
|
|
|
52
53
|
## 与其它 skill 的分工
|
|
53
54
|
|
|
54
55
|
- 要跑**系统内部**的 Reddit 情报/VOC 采集(watchlists、hot-posts、insights、fetch-by-\*,落库 + 聚合)→ `social-hub-intelligence`。
|
|
55
|
-
- 本 skill 是**通用/临时抓取**:给什么 URL 抓什么(
|
|
56
|
+
- 本 skill 是**通用/临时抓取**:给什么 URL 抓什么(HTML + 普通网页),输出原始内容而非系统结构化情报。
|
|
57
|
+
- **Reddit 取证不要用本 skill**:`reddit-voc-volume` 自 v2.12.28 起已不打 reddit.com `.json`,历史帖子/评论走 Arctic Shift 归档,渲染页证据走 Hub / Firecrawl。
|