
Gzh Subscribe
- 242 installs
- 316 repo stars
- Updated August 4, 2026
- redfox-data/redfox-community
Subscribes to competitor, peer, and followed WeChat accounts and pushes a daily 9am digest of their new articles with engagement data as an HTML report.
About
A WeChat account subscription tracker that fetches daily new posts from chosen accounts and generates a morning HTML digest. A content creator uses it to monitor competitors and peers with a daily push of their articles.
- Daily 9am push of new articles from subscribed accounts
- Table with date, author, title, reads, likes, and links plus HTML digest
Gzh Subscribe by the numbers
- 242 all-time installs (skills.sh)
- +17 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #909 of 1,879 Marketing & SEO skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/redfox-data/redfox-community --skill gzh-subscribeAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 242 |
|---|---|
| repo stars | ★ 316 |
| Last updated | August 4, 2026 |
| Repository | redfox-data/redfox-community ↗ |
What it does
Subscribes to competitor, peer, and followed WeChat accounts and pushes a daily 9am digest of their new articles with engagement data as an HTML report.
Files
公众号订阅追踪
你的公众号内容雷达。订阅竞对、同类和关注账号,自动抓取每日发文,以清晰的表格形式展示:发文日期、作者、标题、简介、阅读数、点赞数、发文链接,一键生成 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. 添加订阅(只需公众号名称即可)
python3 "$SKILL_PATH/assets/subscribe.py" add "示例公众号" --category "竞对账号"
# 如果有公众号微信号(如 WebNotes),可以一并提供以精确定位
python3 "$SKILL_PATH/assets/subscribe.py" add "示例公众号" --id "WebNotes"
# 2. 拉取今日发文
python3 "$SKILL_PATH/assets/subscribe.py" fetch
# 3. 生成并打开日报
python3 "$SKILL_PATH/assets/subscribe.py" reportHTML 日报保存在 ~/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 个,名称即可,微信号可选
- 每日 9 点准时推送:一键安装定时任务,每天早上一份精排日报,自动打开浏览器
- 三类分组管理:「竞对账号」盯对手、「同类账号」找灵感、「关注账号」追大神
- 关键数据一屏尽览:发文日期、标题、简介、阅读数、点赞数,原文链接一键直达
- 终端 + 日报双模式:命令行实时查表,HTML 日报适合分享存档
---
适用场景
每日晨报
开启 --subscribe,每天 09:00 自动拉取所有订阅公众号的发文, 生成 HTML 日报并自动打开浏览器。像收邮件一样,每天早上收到一份 专属的公众号文章推送。
竞对监控
把竞品公众号加入「竞对账号」分类,随时 fetch 一屏看完他们的 最新发文——标题、简介、阅读数、点赞数、文章链接,表格一目了然。
特别关注
把行业大号、灵感来源加入「关注账号」分类,日报中优先展示, 有新发文第一时间掌握动态。
内容加工
拉取到的文章数据可以配合 LLM 进一步使用:
- 摘要改写:喂给 LLM 生成自定义摘要或分析观点
- 风格仿写:模仿目标公众号的文风输出学习笔记
- 数据沉淀:日报 HTML 可导出为 PDF / Markdown 存档
---
使用方式
管理订阅
# 添加订阅(只需公众号名称即可)
python3 "$SKILL_PATH/assets/subscribe.py" add "公众号名称" --category "竞对账号"
# 添加订阅时附带公众号微信号
python3 "$SKILL_PATH/assets/subscribe.py" add "公众号名称" --id "WebNotes"
# 取消订阅(支持用名称或微信号)
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每日自动推送
# 开启每日 09:00 自动推送
python3 "$SKILL_PATH/assets/subscribe.py" --subscribe
# 取消每日自动推送
python3 "$SKILL_PATH/assets/subscribe.py" --unsubscribe参数说明
| 命令 | 参数 | 说明 |
|---|---|---|
add | accountName | 公众号名称(必填) |
--id | 公众号 ID(可选) | |
--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 | 安装每日定时任务(09:00) | |
--unsubscribe | 卸载定时任务 |
依赖安装
| 依赖 | 安装命令 |
|---|---|
requests | pip3 install requests |
---
常见问题
Q:公众号 ID(微信号)是什么?必须提供吗? A:公众号的微信号(如 WebNotes),可在公众号主页 → 基础信息中查看。非必填,仅用公众号名称也能完成订阅和拉取发文。提供微信号可以让 API 更精确地定位到目标公众号。
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。
---
获取方式
本 Skill 可在以下平台找到:
<!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>
<!-- Fonts: Archivo Black (display) + Space Grotesk (body) -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Archivo+Black&family=Space+Grotesk:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
/* ═══════════════════════════════════════════════
CSS CUSTOM PROPERTIES — DARK TABLE PRESET
═══════════════════════════════════════════════ */
:root {
--bg-primary: #0f0f0f;
--bg-secondary: #1a1a1a;
--bg-card: #1e1e1e;
--bg-card-hover: #252525;
--text-primary: #f0ece6;
--text-secondary: #9a9590;
--text-muted: #666;
--accent: #4FC3F7;
--accent-light: #81D4FA;
--accent-glow: rgba(79, 195, 247, 0.12);
--accent-border: rgba(79, 195, 247, 0.25);
--accent-green: #66BB6A;
--accent-orange: #FFB74D;
--accent-pink: #F06292;
--border: rgba(255, 255, 255, 0.06);
--font-display: 'Archivo Black', Impact, sans-serif;
--font-body: 'Space Grotesk', -apple-system, sans-serif;
--title-size: clamp(1.8rem, 4vw, 3rem);
--h2-size: clamp(1.2rem, 2.5vw, 1.6rem);
--h3-size: clamp(1rem, 1.8vw, 1.2rem);
--body-size: clamp(0.78rem, 1.2vw, 0.9rem);
--small-size: clamp(0.68rem, 0.9vw, 0.78rem);
--radius: 10px;
--radius-sm: 6px;
}
/* ═══════════════════════════════════════════════
RESET & BASE
═══════════════════════════════════════════════ */
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: var(--font-body);
background: var(--bg-primary);
color: var(--text-primary);
line-height: 1.5;
min-height: 100vh;
}
/* ═══════════════════════════════════════════════
HEADER
═══════════════════════════════════════════════ */
.header {
text-align: center;
padding: 3rem 1.5rem 2rem;
position: relative;
}
.header::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 80px;
height: 2px;
background: var(--accent);
border-radius: 1px;
}
.header h1 {
font-family: var(--font-display);
font-size: var(--title-size);
color: var(--accent);
letter-spacing: -0.02em;
}
.header .subtitle {
color: var(--text-secondary);
margin-top: 0.5rem;
font-size: var(--body-size);
}
/* ═══════════════════════════════════════════════
STATS
═══════════════════════════════════════════════ */
.stats {
display: flex;
justify-content: center;
gap: 2.5rem;
padding: 1.5rem 1rem;
margin: 0 auto;
max-width: 600px;
flex-wrap: wrap;
}
.stat-item {
text-align: center;
min-width: 70px;
}
.stat-value {
font-family: var(--font-display);
font-size: 1.6rem;
color: var(--accent);
}
.stat-label {
font-size: var(--small-size);
color: var(--text-muted);
margin-top: 0.2rem;
text-transform: uppercase;
letter-spacing: 0.05em;
}
/* ═══════════════════════════════════════════════
CATEGORY SECTION
═══════════════════════════════════════════════ */
.container {
max-width: 1200px;
margin: 0 auto;
padding: 1rem 1.5rem 2rem;
}
.category-section {
margin-bottom: 2rem;
}
.category-header {
display: flex;
align-items: center;
gap: 0.8rem;
margin-bottom: 1rem;
padding-bottom: 0.6rem;
border-bottom: 1px solid var(--border);
}
.category-dot {
width: 10px;
height: 10px;
border-radius: 50%;
flex-shrink: 0;
}
.category-dot.competitor { background: var(--accent-pink); }
.category-dot.similar { background: var(--accent-orange); }
.category-dot.following { background: var(--accent); }
.category-dot.other { background: var(--accent-green); }
.category-name {
font-family: var(--font-display);
font-size: var(--h3-size);
letter-spacing: -0.01em;
}
.category-count {
font-size: var(--small-size);
color: var(--text-muted);
}
/* ═══════════════════════════════════════════════
TABLE
═══════════════════════════════════════════════ */
.table-wrapper {
overflow-x: auto;
border-radius: var(--radius);
border: 1px solid var(--border);
background: var(--bg-card);
}
table {
width: 100%;
border-collapse: collapse;
font-size: var(--small-size);
}
thead th {
background: var(--bg-secondary);
color: var(--text-secondary);
font-weight: 600;
text-align: left;
padding: 0.7rem 0.8rem;
white-space: nowrap;
border-bottom: 1px solid var(--border);
font-size: 0.72rem;
text-transform: uppercase;
letter-spacing: 0.04em;
}
tbody td {
padding: 0.65rem 0.8rem;
border-bottom: 1px solid var(--border);
vertical-align: middle;
}
tbody tr:last-child td {
border-bottom: none;
}
tbody tr:hover {
background: var(--bg-card-hover);
}
td.date {
white-space: nowrap;
color: var(--text-secondary);
font-variant-numeric: tabular-nums;
}
td.author {
white-space: nowrap;
font-weight: 500;
}
td.title {
min-width: 200px;
}
td.title a {
color: var(--text-primary);
text-decoration: none;
transition: color 0.15s;
}
td.title a:hover {
color: var(--accent);
}
td.summary {
color: var(--text-secondary);
max-width: 280px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
td.reads, td.likes {
text-align: right;
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
td.reads { color: var(--accent); }
td.likes { color: var(--accent-pink); }
td.link a {
color: var(--accent);
text-decoration: none;
font-weight: 500;
white-space: nowrap;
transition: color 0.15s;
}
td.link a:hover {
color: var(--accent-light);
text-decoration: underline;
}
/* ═══════════════════════════════════════════════
EMPTY STATE
═══════════════════════════════════════════════ */
.empty-state {
text-align: center;
padding: 3rem 1rem;
color: var(--text-muted);
}
.empty-state .icon {
font-size: 2.5rem;
margin-bottom: 0.8rem;
opacity: 0.5;
}
.empty-state p {
font-size: var(--body-size);
}
/* ═══════════════════════════════════════════════
FOOTER
═══════════════════════════════════════════════ */
.footer {
text-align: center;
padding: 2rem;
color: var(--text-muted);
font-size: var(--small-size);
border-top: 1px solid var(--border);
margin-top: 2rem;
}
/* ═══════════════════════════════════════════════
RESPONSIVE
═══════════════════════════════════════════════ */
@media (max-width: 768px) {
.header { padding: 2rem 1rem 1.5rem; }
.stats { gap: 1.5rem; }
.container { padding: 0.5rem 0.8rem 1.5rem; }
td.summary { max-width: 150px; }
}
</style>
</head>
<body>
<div class="header">
<h1>微信公众号文章订阅</h1>
<p class="subtitle">{{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>
#!/usr/bin/env python3
"""
微信公众号文章订阅 — 每天 9 点,盯梢竞对、同类和关注账号,推送最新发文
========================================================
订阅你关注的微信公众号,自动抓取每日发文内容,表格化展示。
Usage:
python3 subscribe.py add "公众号名称" --category "竞对账号"
python3 subscribe.py add "公众号名称" --id "WebNotes"
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
from pathlib import Path
try:
import requests
HAS_REQUESTS = True
except ImportError:
HAS_REQUESTS = False
# ─── 配置 ─────────────────────────────────────────────────────────────────────────
API_URL = "https://redfox.hk/story/api/gzhData/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 \"<公众号名称>\" --category \"竞对账号\"")
print(f" python3 subscribe.py add \"<公众号名称>\" --id \"<微信号>\" # 微信号可选")
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):
"""获取单个公众号在指定日期的文章列表,最多 5 次请求(每次 20 条,共 100 条)"""
id_label = f" (ID: {account_id})" if account_id else ""
all_articles = []
for page in range(5): # 5 页,每页 20 条
offset = page * 20
payload = {
"uid": account_id or "",
"accountName": account_name,
"offset": offset,
"sortType": "default",
"publishTimeStart": f"{date_str} 00:00:00",
"publishTimeEnd": f"{date_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:
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 or str(article.get("uid", ""))
article["_accountName"] = account_name
article["_url"] = 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("clicksCount") or
article.get("readCount") 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("clicksCount") or a.get("readCount") 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("clicksCount") or
article.get("readCount") 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>9</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("订阅成功! 每天 09: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 9 * * * /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("订阅成功! 每天 09: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 "公众号名称" --category "竞对账号"
python3 subscribe.py add "公众号名称" --id "WebNotes"
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
""",
)
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", default="",
help="公众号微信号(可选,如 WebNotes,可在公众号主页基础信息中查看)")
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="安装每日定时任务(09: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 \"公众号名称\"{RESET}")
print(f" {CYAN}python3 subscribe.py add \"公众号名称\" --id \"微信号\"{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("未获取到任何文章,可能是今天还没有发文,或 API 暂时不可用")
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()
WeChat Subscription Tracker / gzh-subscribe
---
Overview
Your public account content radar. Subscribe to competitors, peers, and followed accounts — auto-fetch daily posts with clear table display and HTML daily report.
Core Value
- Inbox-Style Subscription: Subscribe to public accounts like newsletters, up to 20, name-only
- Daily 9 AM Delivery: One-click cron setup for automated curated daily reports
- Three-Category Grouping: Competitors / Peers / Favorites, organized for easy management
- Key Metrics at a Glance: Publish date, title, reads, likes — article link one-click away
Target Users
- 🏢 Competitive Monitors — Track competitor posts and stay informed
- 📝 Content Creators — Follow industry leaders for creative inspiration
- 🔍 Researchers — Subscribe to focused accounts for daily aggregation
---
Features
Core Features
- Public Account Subscription: Subscribe by account name (WeChat ID optional), up to 20
- Three Categories: Competitors / Peers / Favorites, grouped in reports
- Daily Auto Push: 09:00 scheduled fetch, auto-generates HTML report and opens browser
- Terminal Table Display: Publish date, title, summary, reads, likes in clear table format
- Date Backtrack: Fetch posts for a specific date to review history
- LLM-Ready Data: Fetched data can be used with LLMs for summary rewriting and style imitation
---
API Key Acquisition & Security
- The skill uses the environment variable:
REDFOX_API_KEY. REDFOX_API_KEYis provided by RedFoxHub (https://redfox.hk).- Visit RedFoxHub to register and obtain your
REDFOX_API_KEY. - Configure the device environment variable
REDFOX_API_KEYbefore using this skill. - Before providing your key, verify its origin, scope, validity period, and whether reset/revocation is supported.
- Never hardcode or expose the key in code, prompts, logs, or output files.
---
Usage
Manage your subscriptions in natural language.
Quick Reference
| Intent | Example | Result |
|---|---|---|
| Add subscription | "Subscribe to QbitAI public account" | Adds account to watchlist |
| View posts | "Fetch today's posts from my subscriptions" | Retrieves all subscribed accounts' daily posts |
| Competitor tracking | "Add XX account to competitor monitoring" | Categorized as competitor, prioritized in reports |
| View report | "Generate today's subscription daily report" | Fetches posts and generates HTML report |
| Enable push | "Push public account daily report every morning" | Installs cron job for 09:00 auto-push |
---
Use Cases
| Scenario | Role | Example Query | Benefit |
|---|---|---|---|
| Morning briefing | Product manager | "What did my subscribed accounts post today?" | One-screen overview of all subscriptions |
| Competitor monitoring | Marketing ops | "What are competitor accounts posting lately?" | Track strategies, adjust responses timely |
| Inspiration gathering | Content creator | "Any new articles from industry leaders today?" | Follow top accounts for topic ideas |
| Content processing | Researcher | "Export this batch of article data for analysis" | Combine with LLMs for rewriting and style imitation |
公众号订阅追踪 / gzh-subscribe
---
简介
你的公众号内容雷达。订阅竞对、同类和关注账号,自动抓取每日发文,以清晰表格展示关键数据并生成 HTML 日报。
核心价值
- 收件箱式订阅:像订阅 Newsletter 一样订阅公众号,最多 20 个,名称即可
- 每日 9 点推送:一键安装定时任务,每天早上自动产出精排日报
- 三类分组管理:竞对账号、同类账号、关注账号,分类清晰便于管理
- 关键数据一览:发文日期、标题、阅读数、点赞数,原文链接一键直达
适用对象
- 🏢 竞品监控者 — 盯对手公众号最新发文,掌握动态
- 📝 内容创作者 — 追踪行业大号,获取创作灵感
- 🔍 研究人员 — 订阅关注账号,每日汇总分析
---
功能特性
核心功能
- 公众号订阅:通过公众号名称即可订阅(微信号可选),最多 20 个
- 三类分组:竞对账号 / 同类账号 / 关注账号,按类别分开展示
- 每日自动推送:09:00 定时拉取,自动生成 HTML 日报并打开浏览器
- 终端表格展示:发文日期、标题、简介、阅读数、点赞数,表格清晰
- 日期回溯:支持指定日期拉取,查看历史发文
- 数据加工友好:拉取数据可配合 LLM 做摘要改写、风格仿写
---
密钥获取与安全说明
- 本技能需要使用环境变量:
REDFOX_API_KEY。 REDFOX_API_KEY由 红狐 hub (https://redfox.hk)提供。- 请前往 红狐 hub 注册账号,获取
REDFOX_API_KEY。 - 配置设备环境变量
REDFOX_API_KEY后使用本技能。 - 在提供密钥前,请先确认密钥来源、可用范围、有效期及是否支持重置/撤销。
- 禁止在代码、提示词、日志或输出文件中硬编码/明文暴露密钥。
---
使用指南
直接用自然语言管理你的公众号订阅。
常用说法速查
| 意图 | 示例话术 | 效果 |
|---|---|---|
| 添加订阅 | 「帮我订阅量子位公众号」 | 添加公众号到关注列表 |
| 查看发文 | 「拉取我订阅的公众号今天发了什么」 | 抓取所有订阅账号今日发文 |
| 竞对监控 | 「把 XX 公众号加入竞对监控」 | 分类为竞对账号,日报中优先展示 |
| 查看日报 | 「生成今天的公众号订阅日报」 | 拉取发文并生成 HTML 日报 |
| 开启推送 | 「每天早上推送公众号日报」 | 安装定时任务,09:00 自动推送 |
---
使用场景
| 场景 | 角色 | 示例问法 | 收益 |
|---|---|---|---|
| 每日晨报 | 产品经理 | 「看看我关注的公众号今天发了什么」 | 一屏看完所有订阅,快速掌握动态 |
| 竞对监控 | 市场运营 | 「竞品公众号最近发了什么内容」 | 盯对手发文策略,及时调整应对 |
| 灵感搜集 | 内容创作者 | 「行业大号今天有什么新文章」 | 追踪头部账号,获取选题灵感 |
| 内容加工 | 研究者 | 「把这批文章数据导出来分析」 | 配合 LLM 做摘要改写、风格仿写 |