gzh-subscribe
微信公众号文章订阅 — 每天 6 点,盯梢竞对、同类、关注账号,一份你订阅的公众号文章推送。
Install
npx skills add https://github.com/redfox-data/redfox-community/tree/main/skills/gzh-subscribe
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install redfox-data-redfox-community@llmmart
git clone https://github.com/redfox-data/redfox-community.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole redfox-data/redfox-community collection as a plugin from our marketplace. Git is the plain clone.
README
公众号订阅追踪 / gzh-subscribe
简介
你的公众号内容雷达。订阅竞对、同类和关注账号,每天早上 6 点自动获取订阅账号的最新发文,以清晰的表格展示发文日期、作者、标题、简介、阅读数、点赞数与原文链接,一键生成可视化日报。
核心价值
- 收件箱式订阅:像订阅 Newsletter 一样订阅公众号,最多 20 个,按「竞对 / 同类 / 关注」三类分组管理。
- 每日准时推送:一键开启定时任务,每天 06:00 自动生成精排日报并打开浏览器,像收邮件一样看公众号。
- 关键数据一屏尽览:发文日期、标题、简介、阅读数、点赞数一表呈现,原文链接一键直达。
- 双模式阅读:终端实时查表,可视化日报适合分享与存档。
适用对象
- 📊 品牌 / 市场运营 — 盯梢竞对公众号动态,第一时间掌握对手选题与动作。
- ✍️ 内容创作者 — 追踪同类账号与行业大号,持续获取选题灵感与写作参照。
- 🔍 行业研究者 — 把关注账号的每日发文沉淀为日报,便于归档与回顾。
功能特性
核心功能
- 订阅管理:添加、取消、查看订阅,最多 20 个公众号;账号 ID 支持 account / wxId / bizInfo 三种格式,提供任意一种即可。
- 三类分组:「竞对账号」盯对手、「同类账号」找灵感、「关注账号」追大神,日报按分组展示。
- 每日发文获取:一次拉取所有订阅账号的最新发文,包含标题、作者、简介、阅读数、点赞数、发布时间与原文链接。
- 可视化日报:自动生成精美日报页面,按分组排版并自动打开浏览器,便于分享与存档。
- 每日自动推送:一键开启每天 06:00 定时推送,自动生成当日日报;也可随时取消。
密钥获取与安全说明
- 本技能需要使用环境变量:
REDFOX_API_KEY。 REDFOX_API_KEY由 红狐 hub (https://redfox.hk)提供。- 请前往 红狐 hub 注册账号,获取
REDFOX_API_KEY。 - 配置设备环境变量
REDFOX_API_KEY后使用本技能。 - 在提供密钥前,请先确认密钥来源、可用范围、有效期及是否支持重置/撤销。
- 禁止在代码、提示词、日志或输出文件中硬编码/明文暴露密钥。
使用指南
直接用自然语言描述需求,无需记忆命令。
常用说法速查
| 意图 | 示例话术 | 效果 |
|---|---|---|
| 订阅公众号 | 「帮我订阅红狐数据这个公众号,ID 是 redfoxdata1,归到关注账号」 | 加入订阅列表,后续可随时拉取发文 |
| 查看订阅 | 「看看我现在订阅了哪些公众号」 | 按分组列出全部订阅账号 |
| 拉取发文 | 「拉一下我订阅的公众号最新发文」 | 终端表格展示各账号最新文章与数据 |
| 生成日报 | 「帮我生成今天的公众号日报」 | 生成可视化日报并自动打开浏览器 |
| 每日自动推送 | 「开启每天早上 6 点的公众号推送」 | 安装定时任务,每天自动生成并打开日报 |
| 取消订阅 | 「取消订阅红狐数据」 | 从订阅列表中移除该账号 |
输出示例
拉取发文后,终端表格按账号依次展示每篇文章的发文日期、作者、标题、简介、阅读数、点赞数与原文链接;同时生成一份可视化日报,按「竞对 / 同类 / 关注」分组排版,适合分享与存档。
使用场景
| 场景 | 角色 | 示例问法 | 收益 |
|---|---|---|---|
| 每日晨报 | 内容运营 | 「开启每天早上 6 点的公众号自动推送」 | 像收邮件一样,每天收到订阅账号的发文日报 |
| 竞对监控 | 品牌 / 市场 | 「把竞品公众号加进竞对账号,看看他们昨天发了什么」 | 一屏看完竞对最新发文与阅读、点赞数据 |
| 特别关注 | 创作者 | 「把行业大号加到关注账号,有新发文第一时间告诉我」 | 行业大号动态优先展示,新发文及时掌握 |
| 内容加工 | 深度创作者 | 「把今天拉取的文章数据给我,我要做摘要改写」 | 拉取结果可配合 AI 做摘要、仿写与数据沉淀 |
重要数据说明
- 数据来自红狐广域库,覆盖范围广;采用 T+1 更新,每日拉取前一天及近 7 天的发文。
- 订阅上限为 20 个公众号;每次拉取按订阅数消耗数据额度,建议开启每日自动推送,而非频繁手动拉取。
- 如确认账号 ID 正确但广域库中仍查不到目标账号,可联系红狐数据获取定制支持:redfoxdata@proton.me。
Skill manifest
公众号订阅追踪
你的公众号内容雷达。订阅竞对、同类和关注账号,自动抓取每日发文,以清晰的表格形式展示:发文日期、作者、标题、简介、阅读数、点赞数、发文链接,一键生成 HTML 日报。
首次使用
首次使用需先配置 API Key
前往 redfox.hk 注册获取 API Key,三种配置方式任选其一:
| 配置方式 | 说明 | 命令 |
|---|---|---|
| 环境变量(推荐) | 设置一次,全局生效 | export REDFOX_API_KEY=ak_你的密钥 |
| 命令行参数 | 临时使用 | python3 "$SKILL_PATH/assets/subscribe.py" fetch --api-key ak_你的密钥 |
| 配置文件 | 持久化存储 | echo '{"api_key":"ak_你的密钥"}' > ~/.qoder/apis/redfox.json |
配置完成后,即可使用:
# 1. 添加订阅(需提供账号 ID,支持三种格式)
# 以「红狐数据」公众号为例:
python3 "$SKILL_PATH/assets/subscribe.py" add "红狐数据" --id "redfoxdata1" --category "关注账号"
# 也可用 wxId 或 bizInfo 格式
python3 "$SKILL_PATH/assets/subscribe.py" add "红狐数据" --id "gh_53301f7745f3"
python3 "$SKILL_PATH/assets/subscribe.py" add "红狐数据" --id "MzY4ODI4ODc2MA=="
# 2. 拉取发文
python3 "$SKILL_PATH/assets/subscribe.py" fetch
# 3. 生成并打开日报
python3 "$SKILL_PATH/assets/subscribe.py" report
HTML 日报保存在 ~/Downloads/QoderGzhReports/ 目录,自动在浏览器中打开。
后续使用
| 配置方式 | 说明 | 命令 |
|---|---|---|
| 环境变量(推荐) | 设置一次,全局生效 | export REDFOX_API_KEY=ak_你的密钥 |
| 命令行参数 | 临时使用 | python3 "$SKILL_PATH/assets/subscribe.py" fetch --api-key ak_你的密钥 |
| 配置文件 | 持久化存储 | echo '{"api_key":"ak_你的密钥"}' > ~/.qoder/apis/redfox.json |
功能特点
- 收件箱式订阅:像订阅 Newsletter 一样订阅公众号,最多 20 个,需提供账号 ID(支持 account / wxId / bizInfo 三种格式)
- 每日 6 点准时推送:一键安装定时任务,每天早上一份精排日报,自动打开浏览器(广域库 T+1,拉取昨天及近 7 天发文)
- 三类分组管理:「竞对账号」盯对手、「同类账号」找灵感、「关注账号」追大神
- 关键数据一屏尽览:发文日期、标题、简介、阅读数、点赞数,原文链接一键直达
- 终端 + 日报双模式:命令行实时查表,HTML 日报适合分享存档
数据覆盖说明:本 Skill 基于红狐广域库,覆盖范围更广。 如在广域库中仍搜不到目标账号,可联系红狐数据获取定制支持: redfoxdata@proton.me
适用场景
每日晨报
开启 --subscribe,每天 06:00 自动拉取所有订阅公众号的发文,
生成 HTML 日报并自动打开浏览器。像收邮件一样,每天早上收到一份
专属的公众号文章推送。
竞对监控
把竞品公众号加入「竞对账号」分类,随时 fetch 一屏看完他们的
最新发文——标题、简介、阅读数、点赞数、文章链接,表格一目了然。
特别关注
把行业大号、灵感来源加入「关注账号」分类,日报中优先展示, 有新发文第一时间掌握动态。
内容加工
拉取到的文章数据可以配合 LLM 进一步使用:
- 摘要改写:喂给 LLM 生成自定义摘要或分析观点
- 风格仿写:模仿目标公众号的文风输出学习笔记
- 数据沉淀:日报 HTML 可导出为 PDF / Markdown 存档
使用方式
管理订阅
账号 ID 说明:新接口基于红狐广域库,必须提供账号 ID 才能查询。 支持以下三种格式(三选一即可),以「红狐数据」公众号为例:
- account — 公众号标识,最容易获取,如
redfoxdata1- wxId — 微信号,格式
gh_开头,仅限自己的公众号后台查看,如gh_53301f7745f3- bizInfo — 文章链接中的 biz 编码,通过手机端默认浏览器打开任意文章,从链接地址中提取,如
MzY4ODI4ODc2MA==三种格式提供任意一种即可,推荐优先使用 account 格式。
# 添加订阅(账号名称 + 账号 ID 均为必填)
python3 "$SKILL_PATH/assets/subscribe.py" add "公众号名称" --id "redfoxdata1" --category "竞对账号"
# 支持三种账号 ID 格式
python3 "$SKILL_PATH/assets/subscribe.py" add "公众号名称" --id "gh_53301f7745f3"
python3 "$SKILL_PATH/assets/subscribe.py" add "公众号名称" --id "MzY4ODI4ODc2MA=="
# 取消订阅(支持用名称或 ID)
python3 "$SKILL_PATH/assets/subscribe.py" remove "公众号名称"
python3 "$SKILL_PATH/assets/subscribe.py" remove "WebNotes"
# 查看所有订阅
python3 "$SKILL_PATH/assets/subscribe.py" list
拉取发文
# 拉取所有订阅公众号的最新发文
python3 "$SKILL_PATH/assets/subscribe.py" fetch
# 指定日期
python3 "$SKILL_PATH/assets/subscribe.py" fetch --date 2026-05-26
# 仅查看终端表格(不生成日报)
python3 "$SKILL_PATH/assets/subscribe.py" fetch --no-report
生成日报
# 生成今日 HTML 日报
python3 "$SKILL_PATH/assets/subscribe.py" report
# 指定日期和输出目录
python3 "$SKILL_PATH/assets/subscribe.py" report --date 2026-05-26 --output-dir ~/Desktop
每日自动推送
# 开启每日 06:00 自动推送
python3 "$SKILL_PATH/assets/subscribe.py" --subscribe
# 取消每日自动推送
python3 "$SKILL_PATH/assets/subscribe.py" --unsubscribe
参数说明
| 命令 | 参数 | 说明 |
|---|---|---|
add |
accountName |
公众号名称(必填) |
--id |
账号 ID(必填),支持 account / wxId / bizInfo | |
--category |
分类标签:竞对账号 / 同类账号 / 关注账号 | |
remove |
identifier |
公众号名称 或 公众号 ID |
list |
— | 列出所有订阅 |
fetch |
--date |
指定日期 YYYY-MM-DD(默认今天) |
--no-report |
仅终端展示,不生成日报 | |
report |
--date |
指定日期 YYYY-MM-DD(默认今天) |
--output-dir |
输出目录(默认 ~/Downloads/QoderGzhReports) | |
| 全局 | --api-key |
指定 API Key |
--subscribe |
安装每日定时任务(06:00) | |
--unsubscribe |
卸载定时任务 |
依赖安装
| 依赖 | 安装命令 |
|---|---|
requests |
pip3 install requests |
常见问题
Q:账号 ID 是什么?支持哪些格式? A:本 Skill 使用红狐广域库,必须提供账号 ID 才能查询。支持三种格式(三选一即可):
- account:公众号标识,最容易获取(如
redfoxdata1) - wxId:微信号,格式
gh_开头,仅限自己的公众号后台查看(如gh_53301f7745f3) - bizInfo:文章链接中的 biz 编码,通过手机端默认浏览器打开任意文章,从链接地址中提取(如
MzY4ODI4ODc2MA==)
提供其中任意一种即可,推荐优先使用 account 格式。
Q:最多能订阅多少个公众号? A:最多 20 个。这是保证 API 调用效率和合理使用的上限。
Q:拉取频率有限制吗? A:每次 fetch 按订阅数消耗 API 额度。建议开启每日自动推送,而非频繁手动拉取。
Q:日报存在哪?
A:默认 ~/Downloads/QoderGzhReports/,文件名格式 公众号日报_2026-05-27.html。
Q:怎么自定义日报样式?
A:修改 $SKILL_PATH/assets/report_template.html 中的 CSS 变量即可换色、换字体。
Q:分类标签有什么用? A:日报中会按分类分组展示(竞对账号、同类账号、关注账号),便于快速区分不同目的的订阅。不指定分类则统一归入「关注账号」。
Q:和 RSS 阅读器有什么区别? A:专为微信公众号设计。微信没有 RSS,本 Skill 直接获取公众号发文数据,还能看到阅读数、点赞数等微信独有的互动指标。
Q:文章内容可以改写或仿写吗? A:日报展示的是标题+简介+数据。如需对文章做摘要改写或风格仿写,可将拉取到的数据配合 LLM 进一步处理。
Q:额度用完了怎么办? A:前往 redfox.hk 注册获取 API Token。
Q:为什么有些公众号查不到? A:本 Skill 使用红狐广域库,覆盖范围较广。如确认账号 ID 正确但仍查不到,请联系红狐数据获取定制支持:redfoxdata@proton.me。
获取方式
本 Skill 可在以下平台找到:
Files (redfox-community)
-
assets
-
report_template.html 9.4 KB · in bundle
-
subscribe.py 36.3 KB
#!/usr/bin/env python3 """ 微信公众号文章订阅 — 每天 6 点,盯梢竞对、同类和关注账号,推送最新发文 ======================================================== 订阅你关注的微信公众号,自动抓取每日发文内容,表格化展示。 Usage: python3 subscribe.py add "公众号名称" --id "redfoxdata1" --category "竞对账号" python3 subscribe.py remove "公众号名称" python3 subscribe.py list python3 subscribe.py fetch python3 subscribe.py fetch --date 2026-05-26 python3 subscribe.py report python3 subscribe.py --subscribe python3 subscribe.py --unsubscribe """ import argparse import json import os import subprocess import sys import time from collections import defaultdict from datetime import datetime, timedelta from pathlib import Path try: import requests HAS_REQUESTS = True except ImportError: HAS_REQUESTS = False # ─── 配置 ───────────────────────────────────────────────────────────────────────── API_URL = "https://redfox.hk/story/api/gzh/data/queryWorkList" CONFIG_DIR = Path.home() / ".qoder" / "apis" CONFIG_FILE = CONFIG_DIR / "redfox.json" ENV_KEY = "REDFOX_API_KEY" SOURCE = "公众号账号订阅-GitHub" SUBSCRIPTIONS_FILE = Path.home() / ".qoder" / "gzh_subscriptions.json" MAX_SUBSCRIPTIONS = 20 DEFAULT_OUTPUT_DIR = Path.home() / "Downloads" / "QoderGzhReports" PLIST_LABEL = "com.qoder.gzh-subscribe" PLIST_DIR = Path.home() / "Library" / "LaunchAgents" DEFAULT_CATEGORIES = ["竞对账号", "同类账号", "关注账号"] CATEGORY_DOT_CLASS = { "竞对账号": "competitor", "同类账号": "similar", "关注账号": "following", } # ─── 终端颜色 ────────────────────────────────────────────────────────────────────── GREEN = "\033[92m" YELLOW = "\033[93m" RED = "\033[91m" CYAN = "\033[96m" BOLD = "\033[1m" RESET = "\033[0m" def info(msg): print(f"{GREEN}[✓]{RESET} {msg}") def warn(msg): print(f"{YELLOW}[!]{RESET} {msg}") def error(msg): print(f"{RED}[✗]{RESET} {msg}") def step(msg): print(f"{CYAN}[→]{RESET} {msg}") # ─── API Key 管理 ────────────────────────────────────────────────────────────────── def get_api_key(cli_key=None): """Get API key: CLI arg > env var > config file.""" if cli_key: return cli_key env_key = os.environ.get(ENV_KEY) if env_key: return env_key if CONFIG_FILE.exists(): try: data = json.loads(CONFIG_FILE.read_text()) key = data.get("api_key") if key: return key except (json.JSONDecodeError, OSError): pass return None # ─── 订阅数据管理 ────────────────────────────────────────────────────────────────── def load_subscriptions(): """加载订阅列表""" if not SUBSCRIPTIONS_FILE.exists(): return [] try: data = json.loads(SUBSCRIPTIONS_FILE.read_text(encoding="utf-8")) return data.get("subscriptions", []) except (json.JSONDecodeError, OSError): return [] def save_subscriptions(subscriptions): """保存订阅列表""" SUBSCRIPTIONS_FILE.parent.mkdir(parents=True, exist_ok=True) data = {"subscriptions": subscriptions, "updatedAt": datetime.now().isoformat()} SUBSCRIPTIONS_FILE.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8") def find_subscription(identifier): """查找某个订阅 — 支持按 accountName 或 accountId 查找""" subs = load_subscriptions() for sub in subs: if sub["accountName"] == identifier or sub["accountId"] == identifier: return sub return None def add_subscription(account_name, account_id="", category="关注账号"): """添加订阅""" subs = load_subscriptions() if len(subs) >= MAX_SUBSCRIPTIONS: error(f"已达订阅上限({MAX_SUBSCRIPTIONS} 个),请先取消一些订阅后再添加") return False # 按名称查重 existing = find_subscription(account_name) if existing: id_suffix = f" (ID: {existing.get('accountId')})" if existing.get("accountId") else "" warn(f"已订阅过「{existing['accountName']}」{id_suffix},无需重复添加") return False subs.append({ "accountId": account_id, "accountName": account_name, "category": category, "subscribedAt": datetime.now().isoformat(), }) save_subscriptions(subs) id_info = f" (ID: {account_id})" if account_id else "" info(f"已订阅「{account_name}」{id_info} — 分类: {category}") info(f"当前共订阅 {len(subs)}/{MAX_SUBSCRIPTIONS} 个公众号") return True def remove_subscription(identifier): """移除订阅 — 支持按 accountName 或 accountId 移除""" subs = load_subscriptions() target = None for i, sub in enumerate(subs): if sub["accountName"] == identifier or sub["accountId"] == identifier: target = subs.pop(i) break if not target: warn(f"未找到「{identifier}」的订阅") return False save_subscriptions(subs) id_info = f" ({target['accountId']})" if target.get("accountId") else "" info(f"已取消订阅「{target['accountName']}」{id_info}") info(f"当前共订阅 {len(subs)}/{MAX_SUBSCRIPTIONS} 个公众号") return True def list_subscriptions(): """列出所有订阅""" subs = load_subscriptions() if not subs: warn("当前没有订阅任何公众号") print(f"\n 使用 '{CYAN}add{RESET}' 命令添加订阅:") print(f" python3 subscribe.py add \"<公众号名称>\" --id \"<账号ID>\" --category \"竞对账号\"") print(f" 账号 ID 支持三种格式:account / wxId / bizInfo") return # 按分类分组 groups = defaultdict(list) for sub in subs: groups[sub.get("category", "关注账号")].append(sub) print(f"\n{BOLD}当前订阅 ({len(subs)}/{MAX_SUBSCRIPTIONS}):{RESET}\n") for cat in DEFAULT_CATEGORIES + ["其他"]: items = groups.pop(cat, []) if not items: if cat in DEFAULT_CATEGORIES: groups.pop(cat, None) continue # 分类颜色 cat_color = {"竞对账号": RED, "同类账号": YELLOW, "关注账号": CYAN}.get(cat, CYAN) print(f" {cat_color}{BOLD}▸ {cat}{RESET}") for item in items: subscribed_at = item.get("subscribedAt", "")[:10] if item.get("subscribedAt") else "" id_part = item.get("accountId", "") display_line = f" {item['accountName']}" if id_part: display_line += f" (ID: {id_part})" if subscribed_at: display_line += f" 订阅于 {subscribed_at}" print(display_line) # 剩余未定义分类 for cat, items in groups.items(): if not items: continue print(f" {CYAN}▸ {cat}{RESET}") for item in items: id_part = item.get("accountId", "") display = f" {item['accountName']}" if id_part: display += f" (ID: {id_part})" print(display) print() # ─── 数据获取 ────────────────────────────────────────────────────────────────────── def fetch_account_articles(session, account_id, account_name, date_str): """获取单个公众号文章列表(广域库 T+1,查昨天及近 7 天),需提供账号 ID,最多 5 次请求(每次 20 条,共 100 条)""" if not account_id: warn(f"「{account_name}」缺少账号 ID,无法查询。请使用 add 命令重新订阅并提供账号 ID") return [] id_label = f" (ID: {account_id})" all_articles = [] # T+1: 广域库查不到当天数据,取昨天往前 7 天 target_date = datetime.strptime(date_str, "%Y-%m-%d") end_date = target_date - timedelta(days=1) start_date = end_date - timedelta(days=7) end_str = end_date.strftime("%Y-%m-%d") start_str = start_date.strftime("%Y-%m-%d") for page in range(5): # 5 页,每页 20 条 offset = page * 20 payload = { "account": account_id, "offset": offset, "sortType": "0", "publishTimeStart": f"{start_str} 00:00:00", "publishTimeEnd": f"{end_str} 23:59:59", "source": SOURCE, } try: resp = session.post(API_URL, json=payload, timeout=20) result = resp.json() except requests.exceptions.Timeout: warn(f"请求超时: {account_name}{id_label}") return all_articles if all_articles else [] except Exception as e: warn(f"请求失败: {account_name}{id_label}: {e}") return all_articles if all_articles else [] code = result.get("code") if code == 3108: warn("触发频率限制,等待 5s...") time.sleep(5) try: resp = session.post(API_URL, json=payload, timeout=20) result = resp.json() code = result.get("code") except Exception: return all_articles if all_articles else [] if code not in (200, 2000): if code in (3106, 3107): error(f"API Key 错误 (code {code}): {result.get('msg', '')}") elif code == 3203: warn(f"「{account_name}」不在广域库中,暂未收录") print(f" 💡 如需查询此账号,请联系红狐数据获取定制支持:redfoxdata@proton.me") elif code: warn(f"API 返回错误 (code {code}): {result.get('msg', '')} — {account_name}") return all_articles if all_articles else [] data_raw = result.get("data", {}) if not data_raw: break # 无更多数据 # 兼容多种响应结构 if isinstance(data_raw, list): articles = data_raw elif isinstance(data_raw, dict): articles = data_raw.get("list") or data_raw.get("articles") or data_raw.get("records") or [] else: articles = [] if not articles: break # 空页,停止翻页 # 为每篇文章附加公众号信息 for article in articles: article["_accountId"] = account_id article["_accountName"] = account_name article["_url"] = article.get("workUrl") or article.get("url") or "#" all_articles.extend(articles) # 返回不足 20 条说明已是最后一页 if len(articles) < 20: break return all_articles def fetch_all_articles(session, subscriptions, date_str): """拉取所有订阅公众号在指定日期的文章""" all_articles = [] total = len(subscriptions) for i, sub in enumerate(subscriptions, 1): aid = sub.get("accountId", "") name = sub["accountName"] print(f"\r {CYAN}[→]{RESET} 拉取: {name} ({i}/{total})", end="", flush=True) articles = fetch_account_articles(session, aid, name, date_str) if articles: for article in articles: article["_category"] = sub.get("category", "关注账号") all_articles.extend(articles) # 避免请求过快 if i < total: time.sleep(0.3) print() return all_articles # ─── 数字格式化 ──────────────────────────────────────────────────────────────────── def format_number(n): """格式化数字: 1234 -> 1.2k, 12345 -> 1.2w""" if n is None: return "0" n = int(n) if n >= 10000: return f"{n/10000:.1f}w" if n >= 1000: return f"{n/1000:.1f}k" return str(n) # ─── 终端表格展示 ────────────────────────────────────────────────────────────────── def print_terminal_table(articles): """在终端打印文章表格""" if not articles: warn("没有获取到任何文章") return # 按分类分组 groups = defaultdict(list) for article in articles: cat = article.get("_category", "关注账号") if cat not in DEFAULT_CATEGORIES: cat = "关注账号" groups[cat].append(article) # 分类颜色 cat_colors = {"竞对账号": RED, "同类账号": YELLOW, "关注账号": CYAN} cat_icons = {"竞对账号": "⚔", "同类账号": "◎", "关注账号": "★"} total_shown = 0 for cat in DEFAULT_CATEGORIES: arts = groups.get(cat, []) if not arts: continue # 按时间降序排序(最新的在前) arts.sort(key=lambda a: a.get("publicTime") or a.get("publishDate") or a.get("publishTime") or "", reverse=True) cat_color = cat_colors.get(cat, CYAN) cat_icon = cat_icons.get(cat, "●") print(f"\n {cat_color}{BOLD}{cat_icon} {cat}{RESET} — {len(arts)} 篇文章") print(f" {YELLOW}{'─'*100}{RESET}") # 表头 header = (f" {'发文日期':<12}{'作者':<16}{'标题':<30}" f"{'简介':<22}{'阅读':>8}{'点赞':>8}") print(f" {YELLOW}{header}{RESET}") print(f" {YELLOW}{'─'*100}{RESET}") for article in arts[:10]: # 每类最多展示 10 篇 pub_date = (article.get("publicTime") or article.get("publishDate") or article.get("publishTime") or article.get("date") or "-")[:10] author = (article.get("_accountName") or article.get("author") or article.get("userName") or "-") title = article.get("title") or article.get("name") or "无标题" summary = (article.get("summary") or article.get("description") or article.get("digest") or article.get("abstract") or "") reads = format_number(article.get("readCount") or article.get("clicksCount") or article.get("reads")) likes = format_number(article.get("likeCount") or article.get("likes")) url = article.get("_url") or "#" # 截断 author_d = author[:14] + ".." if len(author) > 16 else author title_d = title[:27] + ".." if len(title) > 30 else title summary_d = summary[:19] + ".." if len(summary) > 22 else summary # 每行 print(f" {pub_date:<12}{author_d:<16}{title_d:<30}" f"{summary_d:<22}{reads:>8}{likes:>8}") # 链接行 if url != "#": print(f" {'':>12}{'':>16}{CYAN} ↳ {url}{RESET}") total_shown += 1 # 如果有超过 10 篇的,提示截断 remaining = len(articles) - total_shown if remaining > 0: print(f"\n {YELLOW}... 还有 {remaining} 篇文章,请在 HTML 日报中查看完整列表{RESET}") print() # ─── HTML 报告生成 ────────────────────────────────────────────────────────────────── def compute_stats(articles): """计算统计数据""" total = len(articles) if total == 0: return {"article_count": 0, "account_count": 0, "avg_reads": "0", "total_likes": "0"} reads = [a.get("readCount") or a.get("clicksCount") or a.get("reads") or 0 for a in articles] avg_reads = sum(int(r) for r in reads) // total if total > 0 else 0 total_likes = sum(int(a.get("likeCount") or a.get("likes") or 0) for a in articles) accounts = set(a.get("_accountId") or a.get("uid") or a.get("_accountName") for a in articles) return { "article_count": total, "account_count": len(accounts), "avg_reads": format_number(avg_reads), "total_likes": format_number(total_likes), } def generate_category_sections(articles): """生成分类区块 HTML""" groups = defaultdict(list) for article in articles: cat = article.get("_category", "关注账号") if cat not in DEFAULT_CATEGORIES: cat = "关注账号" groups[cat].append(article) sections_html = "" cat_order = ["竞对账号", "同类账号", "关注账号"] for cat in cat_order: arts = groups.get(cat, []) if not arts: continue # 按时间降序 arts.sort(key=lambda a: a.get("publicTime") or a.get("publishDate") or a.get("publishTime") or "", reverse=True) dot_class = CATEGORY_DOT_CLASS.get(cat, "other") rows_html = "" for article in arts: pub_date = (article.get("publicTime") or article.get("publishDate") or article.get("publishTime") or article.get("date") or "-")[:10] author = (article.get("_accountName") or article.get("author") or article.get("userName") or "-") title = article.get("title") or article.get("name") or "无标题" summary = (article.get("summary") or article.get("description") or article.get("digest") or article.get("abstract") or "") reads = format_number(article.get("readCount") or article.get("clicksCount") or article.get("reads")) likes = format_number(article.get("likeCount") or article.get("likes")) url = article.get("_url") or "#" rows_html += f""" <tr> <td class="date">{pub_date}</td> <td class="author">{author}</td> <td class="title"><a href="{url}" target="_blank" rel="noopener">{title}</a></td> <td class="summary" title="{summary}">{summary}</td> <td class="reads">{reads}</td> <td class="likes">{likes}</td> </tr>""" sections_html += f""" <div class="category-section"> <div class="category-header"> <span class="category-dot {dot_class}"></span> <span class="category-name">{cat}</span> <span class="category-count">{len(arts)} 篇文章</span> </div> <div class="table-wrapper"> <table> <thead> <tr> <th>发文日期</th> <th>公众号</th> <th>标题</th> <th>简介</th> <th>阅读</th> <th>点赞</th> </tr> </thead> <tbody>{rows_html} </tbody> </table> </div> </div>""" return sections_html def generate_report(articles, date_str): """生成完整 HTML 报告""" stats = compute_stats(articles) # 尝试从模板文件读取 template_path = Path(__file__).parent / "report_template.html" if template_path.exists(): template = template_path.read_text(encoding="utf-8") else: warn("模板文件未找到,使用内置模板") template = get_fallback_template() # 日期中文显示 try: dt = datetime.strptime(date_str, "%Y-%m-%d") weekdays = ["一", "二", "三", "四", "五", "六", "日"] date_cn = f"{dt.year}年{dt.month}月{dt.day}日 星期{weekdays[dt.weekday()]}" except ValueError: date_cn = date_str category_sections = generate_category_sections(articles) timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") html = template html = html.replace("{{DATE}}", date_str) html = html.replace("{{DATE_CN}}", date_cn) html = html.replace("{{ACCOUNT_COUNT}}", str(stats["account_count"])) html = html.replace("{{ARTICLE_COUNT}}", str(stats["article_count"])) html = html.replace("{{AVG_READS}}", stats["avg_reads"]) html = html.replace("{{TOTAL_LIKES}}", stats["total_likes"]) html = html.replace("{{CATEGORY_SECTIONS}}", category_sections) html = html.replace("{{TIMESTAMP}}", timestamp) return html def get_fallback_template(): """内置最小 HTML 模板(当模板文件缺失时使用)""" return '''<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>公众号订阅日报 - {{DATE}}</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, sans-serif; background: #0f0f0f; color: #f0ece6; padding: 2rem; } .header { text-align: center; padding: 2rem 0; } .header h1 { font-size: 2rem; color: #4FC3F7; } .header p { color: #9a9590; margin-top: 0.5rem; } .stats { display: flex; justify-content: center; gap: 2rem; padding: 1rem; margin: 1rem 0; } .stat-item { text-align: center; } .stat-value { font-size: 1.5rem; font-weight: bold; color: #4FC3F7; } .stat-label { font-size: 0.8rem; color: #666; } .category-section { margin: 2rem 0; } .category-header { display: flex; align-items: center; gap: 0.6rem; margin-bottom: 0.8rem; } .category-dot { width: 10px; height: 10px; border-radius: 50%; } .category-dot.competitor { background: #F06292; } .category-dot.similar { background: #FFB74D; } .category-dot.following { background: #4FC3F7; } .category-name { font-size: 1.2rem; font-weight: bold; } .category-count { font-size: 0.85rem; color: #9a9590; } .table-wrapper { overflow-x: auto; border: 1px solid rgba(255,255,255,0.06); border-radius: 10px; background: #1e1e1e; } table { width: 100%; border-collapse: collapse; font-size: 0.85rem; } thead th { background: #1a1a1a; color: #9a9590; text-align: left; padding: 0.6rem 0.8rem; border-bottom: 1px solid rgba(255,255,255,0.06); text-transform: uppercase; font-size: 0.72rem; } tbody td { padding: 0.6rem 0.8rem; border-bottom: 1px solid rgba(255,255,255,0.06); } tbody tr:hover { background: #252525; } td.date { white-space: nowrap; color: #9a9590; } td.title a { color: #f0ece6; text-decoration: none; } td.title a:hover { color: #4FC3F7; } td.summary { color: #9a9590; max-width: 250px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } td.reads { color: #4FC3F7; text-align: right; } td.likes { color: #F06292; text-align: right; } .footer { text-align: center; padding: 2rem; color: #666; font-size: 0.8rem; margin-top: 2rem; border-top: 1px solid rgba(255,255,255,0.06); } </style> </head> <body> <div class="header"> <h1>公众号订阅日报</h1> <p>{{DATE_CN}} · {{ACCOUNT_COUNT}} 个公众号 · {{ARTICLE_COUNT}} 篇文章</p> </div> <div class="stats"> <div class="stat-item"><div class="stat-value">{{ACCOUNT_COUNT}}</div><div class="stat-label">订阅公众号</div></div> <div class="stat-item"><div class="stat-value">{{ARTICLE_COUNT}}</div><div class="stat-label">今日发文</div></div> <div class="stat-item"><div class="stat-value">{{AVG_READS}}</div><div class="stat-label">平均阅读</div></div> <div class="stat-item"><div class="stat-value">{{TOTAL_LIKES}}</div><div class="stat-label">总点赞</div></div> </div> <div class="container">{{CATEGORY_SECTIONS}}</div> <div class="footer">由微信公众号文章订阅 Skill 生成 · {{TIMESTAMP}}</div> </body> </html>''' # ─── 订阅定时任务 ────────────────────────────────────────────────────────────────── def install_subscription(): """安装定时任务,每天自动拉取并生成日报""" if sys.platform == "darwin": PLIST_DIR.mkdir(parents=True, exist_ok=True) plist_path = PLIST_DIR / f"{PLIST_LABEL}.plist" script_path = os.path.abspath(__file__) log_path = str(Path.home() / "Library" / "Logs" / "qoder-gzh-subscribe.log") # 传递 API Key 环境变量 env_section = "" api_key = os.environ.get(ENV_KEY) if api_key: env_section = f""" <key>EnvironmentVariables</key> <dict> <key>{ENV_KEY}</key> <string>{api_key}</string> </dict>""" plist_content = f'''<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>{PLIST_LABEL}</string> <key>ProgramArguments</key> <array> <string>/usr/bin/python3</string> <string>{script_path}</string> <string>fetch</string> </array> <key>StartCalendarInterval</key> <dict> <key>Hour</key> <integer>6</integer> <key>Minute</key> <integer>0</integer> </dict> <key>StandardOutPath</key> <string>{log_path}</string> <key>StandardErrorPath</key> <string>{log_path}</string> <key>RunAtLoad</key> <false/>{env_section} </dict> </plist>''' plist_path.write_text(plist_content, encoding="utf-8") try: subprocess.run(["launchctl", "load", str(plist_path)], check=True, capture_output=True) info("订阅成功! 每天 06:00 自动拉取所有订阅公众号的发文并生成日报") info(f"日报目录: ~/Downloads/QoderGzhReports/") info(f"日志: {log_path}") return True except subprocess.CalledProcessError as e: error(f"订阅安装失败: {e.stderr.decode()}") return False else: # Linux / Windows: 使用 crontab script_path = os.path.abspath(__file__) cron_line = f"0 6 * * * /usr/bin/python3 {script_path} fetch" try: subprocess.run( f'(crontab -l 2>/dev/null; echo "{cron_line}") | crontab -', shell=True, check=True, capture_output=True ) info("订阅成功! 每天 06:00 自动拉取并生成日报 (crontab)") info(f"日报目录: ~/Downloads/QoderGzhReports/") return True except subprocess.CalledProcessError: warn("自动配置 crontab 失败,请手动添加:") print(f" {cron_line}") return False def remove_subscription_task(): """卸载定时任务""" if sys.platform == "darwin": plist_path = PLIST_DIR / f"{PLIST_LABEL}.plist" if not plist_path.exists(): warn("未找到订阅配置,无需取消") return False try: subprocess.run(["launchctl", "unload", str(plist_path)], check=True, capture_output=True) except subprocess.CalledProcessError: pass plist_path.unlink(missing_ok=True) info("已取消订阅,定时任务已移除") return True else: script_path = os.path.abspath(__file__) try: subprocess.run( f'crontab -l 2>/dev/null | grep -v "{script_path}" | crontab -', shell=True, check=True, capture_output=True ) info("已取消订阅,crontab 任务已移除") return True except subprocess.CalledProcessError: warn("自动移除 crontab 失败,请手动执行: crontab -e") return False # ─── 主流程 ──────────────────────────────────────────────────────────────────────── def main(): parser = argparse.ArgumentParser( description="微信公众号文章订阅 — 订阅公众号,追踪每日发文", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: python3 subscribe.py add "公众号名称" --id "redfoxdata1" --category "竞对账号" python3 subscribe.py add "公众号名称" --id "gh_53301f7745f3" python3 subscribe.py add "公众号名称" --id "MzY4ODI4ODc2MA==" python3 subscribe.py remove "公众号名称" python3 subscribe.py remove "WebNotes" python3 subscribe.py list python3 subscribe.py fetch python3 subscribe.py report --date 2026-05-26 python3 subscribe.py --subscribe python3 subscribe.py --unsubscribe 账号 ID 支持三种格式(三选一即可): account — 公众号标识,最容易获取(如 redfoxdata1) wxId — 微信号,仅限自己的公众号后台查看(如 gh_53301f7745f3) bizInfo — 文章链接中的 biz 编码,通过手机端浏览器提取(如 MzY4ODI4ODc2MA==) """, ) subparsers = parser.add_subparsers(dest="command", help="可用命令") # ── add 子命令 ── add_parser = subparsers.add_parser("add", help="添加订阅") add_parser.add_argument("account_name", help="公众号名称(必填)") add_parser.add_argument("--id", dest="account_id", required=True, help="账号 ID — 支持三种格式(必填):account(公众号标识)/ wxId(微信号)/ bizInfo(文章链接编码)") add_parser.add_argument("--category", default="关注账号", choices=DEFAULT_CATEGORIES + ["其他"], help="分类标签(默认: 关注账号)") # ── remove 子命令 ── remove_parser = subparsers.add_parser("remove", help="取消订阅") remove_parser.add_argument("identifier", help="公众号名称 或 公众号 ID") # ── list 子命令 ── subparsers.add_parser("list", help="列出所有订阅") # ── fetch 子命令 ── fetch_parser = subparsers.add_parser("fetch", help="拉取今日发文") fetch_parser.add_argument("--date", default=datetime.now().strftime("%Y-%m-%d"), help="指定日期 YYYY-MM-DD(默认: 今天)") fetch_parser.add_argument("--no-report", action="store_true", help="仅终端展示,不生成日报") # ── report 子命令 ── report_parser = subparsers.add_parser("report", help="生成 HTML 日报") report_parser.add_argument("--date", default=datetime.now().strftime("%Y-%m-%d"), help="指定日期 YYYY-MM-DD(默认: 今天)") report_parser.add_argument("--output-dir", help="输出目录") # ── 全局参数 ── parser.add_argument("--api-key", help="API Key") parser.add_argument("--subscribe", action="store_true", help="安装每日定时任务(06:00 自动拉取)") parser.add_argument("--unsubscribe", action="store_true", help="卸载定时任务") args = parser.parse_args() # ── Banner ── banner = f"""{CYAN}{BOLD} ╔══════════════════════════════════════╗ ║ 公众号订阅追踪 · 发文监控 ║ ║ 竞对 · 同类 · 关注 · 一网打尽 ║ ╚══════════════════════════════════════╝{RESET} """ print(banner) # ── 订阅/取消 ── if args.subscribe: install_subscription() return if args.unsubscribe: remove_subscription_task() return # ── 检查依赖 ── if not HAS_REQUESTS: error("缺少 requests 库,请安装: pip3 install requests") sys.exit(1) # ── 分发命令 ── if args.command == "add": add_subscription(args.account_name, args.account_id, args.category) return if args.command == "remove": remove_subscription(args.identifier) return if args.command == "list": list_subscriptions() return if args.command in ("fetch", "report"): # 检查是否有订阅 subscriptions = load_subscriptions() if not subscriptions: error("尚未订阅任何公众号,请先用 'add' 命令添加订阅") print(f"\n 示例: {CYAN}python3 subscribe.py add \"公众号名称\" --id \"WebNotes\"{RESET}") sys.exit(1) # API Key api_key = get_api_key(cli_key=args.api_key) if not api_key: print(f"{RED}╔══════════════════════════════════════════════════╗{RESET}") print(f"{RED}║ 未配置 API Key,请通过以下方式之一配置: ║{RESET}") print(f"{RED}║ ║{RESET}") print(f"{RED}║ export REDFOX_API_KEY=ak_你的密钥 ║{RESET}") print(f"{RED}║ python3 subscribe.py --api-key ak_你的密钥 ║{RESET}") print(RED + "║ echo '{\"api_key\":\"ak_你的密钥\"}' > ~/.qoder/apis/redfox.json ║" + RESET) print(f"{RED}║ ║{RESET}") print(f"{RED}║ 注册获取 Key: https://redfox.hk/settings/api-keys ║{RESET}") print(f"{RED}╚══════════════════════════════════════════════════╝{RESET}") sys.exit(1) # Session session = requests.Session() session.verify = True session.headers.update({ "Content-Type": "application/json", "X-API-KEY": api_key, }) # 拉取文章 step(f"从 {len(subscriptions)} 个公众号拉取发文(日期: {args.date})...") print() articles = fetch_all_articles(session, subscriptions, args.date) if not articles: warn("未获取到任何文章") print(f" 💡 当前 Skill 基于红狐广域库。如确认账号 ID 正确但仍查不到,") print(f" 请联系红狐数据获取定制支持:redfoxdata@proton.me") sys.exit(1) info(f"拉取完成: 共 {len(articles)} 篇文章") # 终端表格始终展示 print_terminal_table(articles) # 生成 HTML 日报(fetch --no-report 时跳过) if args.command == "report" or (args.command == "fetch" and not args.no_report): step("生成 HTML 日报...") html_content = generate_report(articles, args.date) output_dir = Path(args.output_dir) if hasattr(args, 'output_dir') and args.output_dir else DEFAULT_OUTPUT_DIR output_dir.mkdir(parents=True, exist_ok=True) filename = f"公众号日报_{args.date}.html" output_path = output_dir / filename output_path.write_text(html_content, encoding="utf-8") info(f"日报已生成: {output_path}") # 打开浏览器 if sys.platform == "darwin": subprocess.run(["open", str(output_path)], check=False) elif sys.platform == "linux": subprocess.run(["xdg-open", str(output_path)], check=False) print(f"\n{GREEN}{BOLD}✓ 完成!{RESET}") print(f" 订阅公众号: {len(subscriptions)} 个") print(f" 文章总数: {len(articles)} 篇") return # ── 无命令 ── parser.print_help() print(f"\n{CYAN}快速开始:{RESET}") print(f" add <名称> — 添加订阅") print(f" remove <名称/ID> — 取消订阅") print(f" list — 查看订阅") print(f" fetch — 拉取发文") print(f" report — 生成日报") if __name__ == "__main__": main()
-
-
README.en.md 6.8 KB
# WeChat Official Account Subscription Tracker / gzh-subscribe --- ## Introduction Your content radar for WeChat Official Accounts. Subscribe to competitor, peer, and followed accounts, and automatically fetch their latest articles every morning at 6 AM. Each article is presented in a clean table showing publish date, author, title, summary, read count, like count, and the original link — with a one-click visual daily report. **Core Value** - **Newsletter-style subscription**: Subscribe to Official Accounts just like a newsletter, up to 20 accounts, organized into three groups: Competitors / Peers / Followed. - **Punctual daily delivery**: Enable the scheduled task once, and a curated daily report is generated and opened in your browser at 06:00 every morning — reading Official Accounts like checking email. - **Key metrics at a glance**: Publish date, title, summary, read count, and like count in one table, with one-click access to the original article. - **Dual reading modes**: Real-time tables in the terminal, plus a visual daily report ideal for sharing and archiving. **Who It's For** - 📊 **Brand / Marketing Operators** — Keep an eye on competitor accounts and stay on top of their topics and moves. - ✍️ **Content Creators** — Track peer accounts and industry leaders for continuous topic inspiration and writing references. - 🔍 **Industry Researchers** — Archive daily articles from followed accounts as daily reports for easy review. --- ## Features ### Core Capabilities - **Subscription management**: Add, remove, and list subscriptions for up to 20 Official Accounts; account IDs accept three formats (account / wxId / bizInfo) — any one of them works. - **Three-group organization**: "Competitors" to watch rivals, "Peers" to find inspiration, "Followed" to track industry leaders; the daily report is grouped accordingly. - **Daily article fetching**: Pull the latest articles from all subscribed accounts in one go, including title, author, summary, read count, like count, publish time, and original link. - **Visual daily report**: Automatically generates a polished report page, organized by group and opened in your browser — ready for sharing and archiving. - **Automatic daily delivery**: Enable the 06:00 daily push with one command to generate the report automatically every day; cancel anytime. --- ## API Key Acquisition & Security - This skill requires the environment variable: `REDFOX_API_KEY`. - `REDFOX_API_KEY` is provided by [RedFoxHub](https://redfox.hk/settings/api-keys?source=github) (`https://redfox.hk`). - Please visit [RedFoxHub](https://redfox.hk?source=github) to register an account and obtain your `REDFOX_API_KEY`. - Configure the `REDFOX_API_KEY` environment variable on your device before using this skill. - Before providing your API key, please confirm its source, scope of access, validity period, and whether it supports reset/revocation. - Do not hardcode or expose the API key in plain text in code, prompts, logs, or output files. --- ## Usage Guide Just describe what you need in natural language — no commands to memorize. ### Common Phrases Cheat Sheet | Intent | Example Phrase | Result | | ---------------------- | ------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- | | Subscribe to an account | "Subscribe me to the RedFox Data Official Account, ID redfoxdata1, under Followed accounts" | Added to your subscription list, ready for fetching anytime | | View subscriptions | "Show me which Official Accounts I've subscribed to" | Lists all subscribed accounts by group | | Fetch articles | "Pull the latest articles from my subscribed accounts" | Shows the latest articles and metrics in a terminal table | | Generate daily report | "Generate today's Official Account daily report for me" | Generates the visual daily report and opens it in your browser | | Automatic daily push | "Turn on the 6 AM daily Official Account push" | Installs a scheduled task that generates and opens the report daily | | Unsubscribe | "Unsubscribe RedFox Data" | Removes the account from your subscription list | ### Output Example After fetching, the terminal table lists each article by account with publish date, author, title, summary, read count, like count, and original link. A visual daily report is also generated, organized into Competitors / Peers / Followed groups — ideal for sharing and archiving. --- ## Use Cases | Scenario | Role | Example Question | Benefit | | --------------------- | ----------------------- | --------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | | Morning briefing | Content operator | "Turn on the 6 AM automatic Official Account push" | Receive a daily article report from subscribed accounts, like checking email | | Competitor monitoring | Brand / Marketing | "Add competitor accounts to the Competitors group and show what they posted yesterday" | See all competitor articles with read and like counts on one screen | | Close following | Creator | "Add top industry accounts to Followed and tell me as soon as they publish" | Industry leaders' updates are shown first, so you never miss a new article | | Content repurposing | Advanced creator | "Give me today's fetched article data so I can summarize and rewrite it" | Fetched results can be fed to AI for summarizing, rewriting, and archiving | --- ## Important Data Notes - Data comes from the RedFox wide-coverage database with broad account coverage; it is updated on a T+1 basis, fetching articles from the previous day and the last 7 days. - The subscription limit is 20 Official Accounts; each fetch consumes data quota based on the number of subscriptions, so enabling the automatic daily push is recommended over frequent manual fetches. - If the account ID is confirmed correct but still cannot be found in the wide-coverage database, contact RedFox Data for customized support: redfoxdata@proton.me. --- -
README.md 5.4 KB
# 公众号订阅追踪 / gzh-subscribe --- ## 简介 你的公众号内容雷达。订阅竞对、同类和关注账号,每天早上 6 点自动获取订阅账号的最新发文,以清晰的表格展示发文日期、作者、标题、简介、阅读数、点赞数与原文链接,一键生成可视化日报。 **核心价值** - **收件箱式订阅**:像订阅 Newsletter 一样订阅公众号,最多 20 个,按「竞对 / 同类 / 关注」三类分组管理。 - **每日准时推送**:一键开启定时任务,每天 06:00 自动生成精排日报并打开浏览器,像收邮件一样看公众号。 - **关键数据一屏尽览**:发文日期、标题、简介、阅读数、点赞数一表呈现,原文链接一键直达。 - **双模式阅读**:终端实时查表,可视化日报适合分享与存档。 **适用对象** - 📊 **品牌 / 市场运营** — 盯梢竞对公众号动态,第一时间掌握对手选题与动作。 - ✍️ **内容创作者** — 追踪同类账号与行业大号,持续获取选题灵感与写作参照。 - 🔍 **行业研究者** — 把关注账号的每日发文沉淀为日报,便于归档与回顾。 --- ## 功能特性 ### 核心功能 - **订阅管理**:添加、取消、查看订阅,最多 20 个公众号;账号 ID 支持 account / wxId / bizInfo 三种格式,提供任意一种即可。 - **三类分组**:「竞对账号」盯对手、「同类账号」找灵感、「关注账号」追大神,日报按分组展示。 - **每日发文获取**:一次拉取所有订阅账号的最新发文,包含标题、作者、简介、阅读数、点赞数、发布时间与原文链接。 - **可视化日报**:自动生成精美日报页面,按分组排版并自动打开浏览器,便于分享与存档。 - **每日自动推送**:一键开启每天 06:00 定时推送,自动生成当日日报;也可随时取消。 --- ## 密钥获取与安全说明 - 本技能需要使用环境变量:`REDFOX_API_KEY`。 - `REDFOX_API_KEY` 由 [红狐 hub](https://redfox.hk/settings/api-keys?source=github) (`https://redfox.hk`)提供。 - 请前往 [红狐 hub](https://redfox.hk?source=github) 注册账号,获取 `REDFOX_API_KEY`。 - 配置设备环境变量 `REDFOX_API_KEY` 后使用本技能。 - 在提供密钥前,请先确认密钥来源、可用范围、有效期及是否支持重置/撤销。 - 禁止在代码、提示词、日志或输出文件中硬编码/明文暴露密钥。 --- ## 使用指南 直接用自然语言描述需求,无需记忆命令。 ### 常用说法速查 | 意图 | 示例话术 | 效果 | | ------------ | ------------------------------------------------------------ | -------------------------------------- | | 订阅公众号 | 「帮我订阅红狐数据这个公众号,ID 是 redfoxdata1,归到关注账号」 | 加入订阅列表,后续可随时拉取发文 | | 查看订阅 | 「看看我现在订阅了哪些公众号」 | 按分组列出全部订阅账号 | | 拉取发文 | 「拉一下我订阅的公众号最新发文」 | 终端表格展示各账号最新文章与数据 | | 生成日报 | 「帮我生成今天的公众号日报」 | 生成可视化日报并自动打开浏览器 | | 每日自动推送 | 「开启每天早上 6 点的公众号推送」 | 安装定时任务,每天自动生成并打开日报 | | 取消订阅 | 「取消订阅红狐数据」 | 从订阅列表中移除该账号 | ### 输出示例 拉取发文后,终端表格按账号依次展示每篇文章的发文日期、作者、标题、简介、阅读数、点赞数与原文链接;同时生成一份可视化日报,按「竞对 / 同类 / 关注」分组排版,适合分享与存档。 --- ## 使用场景 | 场景 | 角色 | 示例问法 | 收益 | | -------- | ------------- | ---------------------------------------------------- | ------------------------------------------ | | 每日晨报 | 内容运营 | 「开启每天早上 6 点的公众号自动推送」 | 像收邮件一样,每天收到订阅账号的发文日报 | | 竞对监控 | 品牌 / 市场 | 「把竞品公众号加进竞对账号,看看他们昨天发了什么」 | 一屏看完竞对最新发文与阅读、点赞数据 | | 特别关注 | 创作者 | 「把行业大号加到关注账号,有新发文第一时间告诉我」 | 行业大号动态优先展示,新发文及时掌握 | | 内容加工 | 深度创作者 | 「把今天拉取的文章数据给我,我要做摘要改写」 | 拉取结果可配合 AI 做摘要、仿写与数据沉淀 | --- ## 重要数据说明 - 数据来自红狐广域库,覆盖范围广;采用 T+1 更新,每日拉取前一天及近 7 天的发文。 - 订阅上限为 20 个公众号;每次拉取按订阅数消耗数据额度,建议开启每日自动推送,而非频繁手动拉取。 - 如确认账号 ID 正确但广域库中仍查不到目标账号,可联系红狐数据获取定制支持:redfoxdata@proton.me。 --- -
SKILL.md 8.9 KB
--- name: gzh-subscribe description: 微信公众号文章订阅 — 每天 6 点,盯梢竞对、同类、关注账号,一份你订阅的公众号文章推送。 --- # 公众号订阅追踪 你的公众号内容雷达。订阅竞对、同类和关注账号,自动抓取每日发文,以清晰的表格形式展示:发文日期、作者、标题、简介、阅读数、点赞数、发文链接,一键生成 HTML 日报。 --- ## 首次使用 **首次使用需先配置 API Key** 前往 [redfox.hk](https://redfox.hk/settings/api-keys?source=github) 注册获取 API Key,三种配置方式任选其一: | 配置方式 | 说明 | 命令 | |----------|------|------| | **环境变量**(推荐) | 设置一次,全局生效 | `export REDFOX_API_KEY=ak_你的密钥` | | **命令行参数** | 临时使用 | `python3 "$SKILL_PATH/assets/subscribe.py" fetch --api-key ak_你的密钥` | | **配置文件** | 持久化存储 | `echo '{"api_key":"ak_你的密钥"}' > ~/.qoder/apis/redfox.json` | 配置完成后,即可使用: ```bash # 1. 添加订阅(需提供账号 ID,支持三种格式) # 以「红狐数据」公众号为例: python3 "$SKILL_PATH/assets/subscribe.py" add "红狐数据" --id "redfoxdata1" --category "关注账号" # 也可用 wxId 或 bizInfo 格式 python3 "$SKILL_PATH/assets/subscribe.py" add "红狐数据" --id "gh_53301f7745f3" python3 "$SKILL_PATH/assets/subscribe.py" add "红狐数据" --id "MzY4ODI4ODc2MA==" # 2. 拉取发文 python3 "$SKILL_PATH/assets/subscribe.py" fetch # 3. 生成并打开日报 python3 "$SKILL_PATH/assets/subscribe.py" report ``` HTML 日报保存在 `~/Downloads/QoderGzhReports/` 目录,自动在浏览器中打开。 --- ## 后续使用 | 配置方式 | 说明 | 命令 | |----------|------|------| | **环境变量**(推荐) | 设置一次,全局生效 | `export REDFOX_API_KEY=ak_你的密钥` | | **命令行参数** | 临时使用 | `python3 "$SKILL_PATH/assets/subscribe.py" fetch --api-key ak_你的密钥` | | **配置文件** | 持久化存储 | `echo '{"api_key":"ak_你的密钥"}' > ~/.qoder/apis/redfox.json` | --- ## 功能特点 - **收件箱式订阅**:像订阅 Newsletter 一样订阅公众号,最多 20 个,需提供账号 ID(支持 account / wxId / bizInfo 三种格式) - **每日 6 点准时推送**:一键安装定时任务,每天早上一份精排日报,自动打开浏览器(广域库 T+1,拉取昨天及近 7 天发文) - **三类分组管理**:「竞对账号」盯对手、「同类账号」找灵感、「关注账号」追大神 - **关键数据一屏尽览**:发文日期、标题、简介、阅读数、点赞数,原文链接一键直达 - **终端 + 日报双模式**:命令行实时查表,HTML 日报适合分享存档 > **数据覆盖说明**:本 Skill 基于红狐广域库,覆盖范围更广。 > 如在广域库中仍搜不到目标账号,可联系红狐数据获取定制支持: > **redfoxdata@proton.me** --- ## 适用场景 ### 每日晨报 开启 `--subscribe`,每天 06:00 自动拉取所有订阅公众号的发文, 生成 HTML 日报并自动打开浏览器。像收邮件一样,每天早上收到一份 专属的公众号文章推送。 ### 竞对监控 把竞品公众号加入「竞对账号」分类,随时 `fetch` 一屏看完他们的 最新发文——标题、简介、阅读数、点赞数、文章链接,表格一目了然。 ### 特别关注 把行业大号、灵感来源加入「关注账号」分类,日报中优先展示, 有新发文第一时间掌握动态。 ### 内容加工 拉取到的文章数据可以配合 LLM 进一步使用: - **摘要改写**:喂给 LLM 生成自定义摘要或分析观点 - **风格仿写**:模仿目标公众号的文风输出学习笔记 - **数据沉淀**:日报 HTML 可导出为 PDF / Markdown 存档 --- ## 使用方式 ### 管理订阅 > **账号 ID 说明**:新接口基于红狐广域库,必须提供账号 ID 才能查询。 > 支持以下三种格式(三选一即可),以「红狐数据」公众号为例: > > - **account** — 公众号标识,最容易获取,如 `redfoxdata1` > - **wxId** — 微信号,格式 `gh_` 开头,仅限**自己的公众号后台**查看,如 `gh_53301f7745f3` > - **bizInfo** — 文章链接中的 biz 编码,通过**手机端默认浏览器**打开任意文章,从链接地址中提取,如 `MzY4ODI4ODc2MA==` > > 三种格式提供任意一种即可,推荐优先使用 account 格式。 ```bash # 添加订阅(账号名称 + 账号 ID 均为必填) python3 "$SKILL_PATH/assets/subscribe.py" add "公众号名称" --id "redfoxdata1" --category "竞对账号" # 支持三种账号 ID 格式 python3 "$SKILL_PATH/assets/subscribe.py" add "公众号名称" --id "gh_53301f7745f3" python3 "$SKILL_PATH/assets/subscribe.py" add "公众号名称" --id "MzY4ODI4ODc2MA==" # 取消订阅(支持用名称或 ID) python3 "$SKILL_PATH/assets/subscribe.py" remove "公众号名称" python3 "$SKILL_PATH/assets/subscribe.py" remove "WebNotes" # 查看所有订阅 python3 "$SKILL_PATH/assets/subscribe.py" list ``` ### 拉取发文 ```bash # 拉取所有订阅公众号的最新发文 python3 "$SKILL_PATH/assets/subscribe.py" fetch # 指定日期 python3 "$SKILL_PATH/assets/subscribe.py" fetch --date 2026-05-26 # 仅查看终端表格(不生成日报) python3 "$SKILL_PATH/assets/subscribe.py" fetch --no-report ``` ### 生成日报 ```bash # 生成今日 HTML 日报 python3 "$SKILL_PATH/assets/subscribe.py" report # 指定日期和输出目录 python3 "$SKILL_PATH/assets/subscribe.py" report --date 2026-05-26 --output-dir ~/Desktop ``` ### 每日自动推送 ```bash # 开启每日 06:00 自动推送 python3 "$SKILL_PATH/assets/subscribe.py" --subscribe # 取消每日自动推送 python3 "$SKILL_PATH/assets/subscribe.py" --unsubscribe ``` ### 参数说明 | 命令 | 参数 | 说明 | |------|------|------| | `add` | `accountName` | 公众号名称(必填) | | | `--id` | 账号 ID(必填),支持 account / wxId / bizInfo | | | `--category` | 分类标签:竞对账号 / 同类账号 / 关注账号 | | `remove` | `identifier` | 公众号名称 或 公众号 ID | | `list` | — | 列出所有订阅 | | `fetch` | `--date` | 指定日期 YYYY-MM-DD(默认今天) | | | `--no-report` | 仅终端展示,不生成日报 | | `report` | `--date` | 指定日期 YYYY-MM-DD(默认今天) | | | `--output-dir` | 输出目录(默认 ~/Downloads/QoderGzhReports) | | 全局 | `--api-key` | 指定 API Key | | | `--subscribe` | 安装每日定时任务(06:00) | | | `--unsubscribe` | 卸载定时任务 | ### 依赖安装 | 依赖 | 安装命令 | |------|----------| | `requests` | `pip3 install requests` | --- ## 常见问题 **Q:账号 ID 是什么?支持哪些格式?** A:本 Skill 使用红狐广域库,**必须提供账号 ID** 才能查询。支持三种格式(三选一即可): - **account**:公众号标识,最容易获取(如 `redfoxdata1`) - **wxId**:微信号,格式 `gh_` 开头,仅限**自己的公众号后台**查看(如 `gh_53301f7745f3`) - **bizInfo**:文章链接中的 biz 编码,通过**手机端默认浏览器**打开任意文章,从链接地址中提取(如 `MzY4ODI4ODc2MA==`) 提供其中任意一种即可,推荐优先使用 account 格式。 **Q:最多能订阅多少个公众号?** A:最多 20 个。这是保证 API 调用效率和合理使用的上限。 **Q:拉取频率有限制吗?** A:每次 fetch 按订阅数消耗 API 额度。建议开启每日自动推送,而非频繁手动拉取。 **Q:日报存在哪?** A:默认 `~/Downloads/QoderGzhReports/`,文件名格式 `公众号日报_2026-05-27.html`。 **Q:怎么自定义日报样式?** A:修改 `$SKILL_PATH/assets/report_template.html` 中的 CSS 变量即可换色、换字体。 **Q:分类标签有什么用?** A:日报中会按分类分组展示(竞对账号、同类账号、关注账号),便于快速区分不同目的的订阅。不指定分类则统一归入「关注账号」。 **Q:和 RSS 阅读器有什么区别?** A:专为微信公众号设计。微信没有 RSS,本 Skill 直接获取公众号发文数据,还能看到阅读数、点赞数等微信独有的互动指标。 **Q:文章内容可以改写或仿写吗?** A:日报展示的是标题+简介+数据。如需对文章做摘要改写或风格仿写,可将拉取到的数据配合 LLM 进一步处理。 **Q:额度用完了怎么办?** A:前往 [redfox.hk](https://redfox.hk/settings/api-keys?source=github) 注册获取 API Token。 **Q:为什么有些公众号查不到?** A:本 Skill 使用红狐**广域库**,覆盖范围较广。如确认账号 ID 正确但仍查不到,请联系红狐数据获取定制支持:**redfoxdata@proton.me**。 --- ## 获取方式 本 Skill 可在以下平台找到: - [SkillHub](https://skillhub.cn) - [ClawHub](https://clawhub.com) - [GitHub](https://github.com) - [GitHub](https://github.com)
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.