知识星球精华区每日下载(齐俊杰的粉丝群)
用途
自动下载知识星球「齐俊杰的粉丝群」(group_id 552521181154)【精华】分类下,当日由齐俊杰发布的全部主题。
抓取口径(2026-10-12 调整):作者 == 齐俊杰 且 创建日期 == 目标日期,不再按标题 / 栏目白名单匹配,
也不再限制附件扩展名 —— 齐俊杰当天发什么就抓什么。因此新增栏目(如【资产与风格观察】【重要数据】【学方法】、
无栏目标题的图文贴)会自动纳入,无需改代码。实测旧版白名单会漏掉这些栏目。
每个命中主题产出三类内容:
| 类型 | 来源 | 命名 |
|---|---|---|
| 简介 Word 文档 | 主题正文 talk.text,去掉与标题重复的首行后仍有实质内容时 |
{标题}_简介.docx |
| 主题配图 | talk.images[] 逐张(原图,保留 PNG/JPG 真实格式) |
{标题}_图NN.<ext> |
| 附件 | talk.files[] 全部(不限扩展名:mp3/docx/pdf…) |
原名落盘 |
> 正文只有标题本身的主题(内容全在附件 docx 里,如「看财经」)不会重复产出简介文档。
默认落盘到 ~/Desktop/知识星球/<YYYY-MM-DD>/,并生成 _清单.md(分「简介文档」「图片」「附件」三组)。
何时使用
- 「把今天知识星球的看财经下载下来」「下载齐观晨会的音频」
- 「抓一下齐俊杰今天发的内容」「每日自动获取某某星球精华更新」
- 需要按日期批量补下(
--date/--days)
前置条件
- 本机已安装 Microsoft Edge(脚本以
channel="msedge"调用系统 Edge 内核,不下载 Playwright 自带浏览器)。 - 已完成一次性扫码登录(登录态持久化在
~/.workbuddy/zsxq-edge-profile)。未登录时运行主脚本会返回退出码2并提示先登录。
运行环境
使用专属隔离环境(已装好 playwright):
C:\Users\Administrator\.workbuddy\binaries\python\envs\zsxq\Scripts\python.exe
使用方法
第一步:首次登录(仅一次)
"C:\Users\Administrator\.workbuddy\binaries\python\envs\zsxq\Scripts\python.exe" \
~/.workbuddy/skills/zsxq-daily-downloader/scripts/zsxq_login.py --timeout 520
会弹出一个 Edge 窗口并停在知识星球首页,用微信扫码登录。脚本检测到 zsxq_access_token 后自动关闭窗口并输出 {"logged_in": true}。登录态可维持 1–3 个月,失效后重跑本步即可。
第二步:下载当日内容
"C:\Users\Administrator\.workbuddy\binaries\python\envs\zsxq\Scripts\python.exe" \
~/.workbuddy/skills/zsxq-daily-downloader/scripts/zsxq_daily.py
常用参数:
| 参数 | 说明 |
|---|---|
--dry-run |
只列出将下载的文件,不实际下载(首次排障先用它) |
--date 2026-10-11 |
指定日期下载 |
--days 3 |
含今天向前回溯 3 天 |
--outdir <目录> |
自定义输出目录 |
--headed |
显示浏览器窗口(默认无头,便于自动化;排障时可加) |
--no-text-doc |
不生成简介 Word 文档(默认会生成,见下「简介文档」) |
--no-images |
不下载主题配图(默认下载,见下「主题图片」) |
--fetch-retries 3 |
列表接口失败重试次数(该接口偶发风控 code 1059,默认 3 次、间隔 5 秒) |
--use-proxy |
沿用系统/环境代理(默认直连,见下方「性能」) |
--count 30 |
拉取精华主题条数(默认 30) |
脚本向 stdout 输出 JSON 汇总(downloaded / skipped / missing / errors),向 stderr 输出进度。退出码:0 成功、1 有错误、2 未登录、3 拉取列表失败。
执行流程(脚本内部)
- 以
channel="msedge"+ 持久化user-data-dir启动 Edge(默认无头)。 - 打开群主页,校验
zsxq_access_tokencookie 存在。 - 点击「精华」标签页。
- 在页面上下文内
fetch拉取https://api.zsxq.com/v2/groups/{gid}/topics?scope=digests&count=N,得到主题 JSON;
失败自动重试(该接口偶发返回风控code 1059)。 - 筛选口径:作者 == 齐俊杰 且 创建日期命中目标日期 的全部主题(不做标题/扩展名匹配)。
- 生成简介文档(本地完成,不经浏览器):取
talk.text正文 → 清洗富文本标记 → 去掉与标题重复的首行 →
剩余 ≥10 字符则写{标题}_简介.docx(标题黑体三号居中 / 元信息楷体 / 正文仿宋小四 1.5 倍行距);
已存在且逐段内容一致则跳过。 - 逐个下载附件:滚动定位
div.file-gallery-container div.item→ 页面内原生.click()打开文件预览 → 按文件名定位对应预览面板并点击其中的div.btn.download→ 由 Playwright 捕获download事件并save_as()落盘 → 用接口上报的 size 强校验落盘字节数。 - 下载主题配图(Python 直连图片 CDN,不经浏览器):取
talk.images[]的original.url→ 落盘前用魔数校验是否真是图片 → 命名{标题}_图NN.<ext>。 - 已存在的文件自动跳过(幂等,可重复执行)。
简介文档
对「齐观晨会」这类正文有内容的主题,产出 {标题}_简介.docx(作为下载任务之一):
- 来源:主题
talk.text(即星球页面卡片里显示的简介/问答提纲全文),长文型另有talk.article.text兜底。 - 标题:取正文首行(接口的
topic.title会被截断成…,首行才是完整标题)。 - 判定:去掉标题行后正文 ≥10 字符才生成;不足视为「无简介」(如「看财经」,其正文在附件 docx 里)。
- 命名:固定加
_简介后缀,避免与同名附件 docx 撞名。 - 排版:标题 黑体 16pt 加粗居中;元信息 楷体 10.5pt 居中(作者 · 时间 · 知识星球);正文 仿宋_GB2312 小四、1.5 倍行距。
- 依赖:
python-docx(已装在envs\zsxq)。缺依赖只影响简介文档,附件下载照常。 - 关闭:
--no-text-doc。
主题图片
对每个命中主题,下载其配图(同为下载任务之一):
- 来源:主题
talk.images[],优先取original(原图,quality/100!);无则回退large。 - 命名:
{标题}_图NN.<ext>(NN从 01 起)。标题同样取正文首行,与简介文档保持一致。 - 扩展名按魔数判定,不信任 URL:JPEG / PNG / GIF / WEBP / BMP;非图片一律拒收。
- 链路=Python
urllib直连images.zsxq.com(图片靠 URL 内token=鉴权,实测 HTTP 200)。
⚠️ 不要走页面内 fetch —— 实测那条路被 CORS 拦(TypeError: Failed to fetch)。 - ⚠️ 不能拿接口的
size硬校验:实测original.size报 506,217、实下 517,452(并不相等)。
图片只校验魔数与落盘成功。 - 幂等:同名前缀的图片已存在(大小 > 0)即跳过。
- 关闭:
--no-images。
性能
本机常挂 HTTP_PROXY,经代理下载音频会被限速到约 80 KB/s(33MB 要 7 分钟);
直连可达数 MB/s(30 秒内完成)。 因此脚本默认绕过代理(--no-proxy-server)。
仅当所在网络必须走代理时,才加 --use-proxy。
关键约束(改脚本前必读)
- 禁止用
requests/ PlaywrightAPIRequestContext直连 zsxq 接口。
实测直连会返回 HTTP 200 +{"succeeded":false,"code":1059,"info":"不支持非官方工具访问…"}。
主题列表必须在页面上下文内fetch;附件下载必须由页面自身的下载按钮触发。
另:该列表接口会偶发返回 1059(同一天同一登录态下时通时不通),故脚本内置拉取重试
(--fetch-retries,默认 3 次 × 5 秒)——见到 1059 不要立刻判定登录失效或站点改版。 - 附件不是
<a>标签。文件渲染为div.file-gallery-container > div.item(内含.file-icon与.file-name),
点击div.item只打开内联预览面板,真正的下载动作在div.file-preview-container内的div.btn.download(按钮文本「下载文件」)。 - ⚠️ 预览面板会累积,且 Escape 关不掉。 每浏览一个文件就多一个
div.file-preview-container。
因此绝不能用document.querySelector('div.btn.download')取按钮——那会命中上一个文件残留的面板,
把 A 文件的内容存成 B 文件名(实测踩过:docx 被存成 33MB 的 mp3)。
必须按文件名定位面板:筛出innerText包含目标文件名的div.file-preview-container,再点它内部的下载按钮。
脚本另用「落盘字节数 == 接口上报 size」做最后一道硬校验,不符即删文件并报错。 - 频率极低即是安全策略。
/files/{id}/download_url被官方监控,短时间大量调用可能触发风控。
本技能每天只下当天的几个文件(实测多为 3–6 个),属安全区间;脚本在每次附件下载间sleep(1.5);
不要改写成一次性批量循环抓取历史全部附件。 - 登录态与隐私:cookie 仅存于本机
~/.workbuddy/zsxq-edge-profile,脚本不读取、不输出、不外传任何 cookie 值或下载令牌。 - Playwright 启动 Edge 会额外打开
edge://downloads-hub/附属标签,脚本结束前会自动关闭。 - 收尾要带超时:Edge 开了下载管理器标签后
ctx.close()可能长时间不返回,脚本用后台线程 + 超时兜底,避免下完文件却挂住。 - 图片与附件走的是两条不同链路:附件必须由页面自身按钮触发(必须有浏览器);
图片则用 Python 直连images.zsxq.com即可(页面内 fetch 会被 CORS 拦)。
且图片的接口size≠ 实下字节数,只能按魔数判断是否为有效图片。 - 列表接口
count上限是 30:传count=60会得到{"succeeded":false,"code":14001,"info":null}
(注意info为空,不说明原因)。--count不要往上加。 - 口径范围:默认只抓
scope=digests(精华区)。实测星主存在未加精的主题
(例:2026-10-12 12:41 的一条,只在scope=all/by_owner里),这类不会被下载。
如需全覆盖,把FETCH_TOPICS_JS的 scope 换成by_owner(只看星主),但要注意:
会连带大量随手拍的图(4–7 条/天),且需按owner过滤掉其它星主(如「无肉不欢」),
窗口深度也只有约 3.3 天(30 条)。
抓取口径调整
抓取范围不再用白名单,由 scripts/zsxq_daily.py 的 pick_topics() 实现,逻辑就一条:
if t["create_time"][:10] in dates and t.get("owner") == author:
picked.append(...) # 齐俊杰当天发的主题,全部收
- 想换抓取对象:
--author <用户名>(例如--author 自由之路)。 - 想换星球:
--group <group_id>。 - 想缩小到某几个栏目:不建议再改回白名单;推荐在结果目录里按文件名筛 —— 命名前缀就是栏目名
(如【收盘简评】…、【看财经】…),下游处理更方便。 - 旧版存在过的
RULES白名单(看财经 / 齐观晨会 / 收盘简评 + 扩展名限定)已移除,
它会漏掉【资产与风格观察】【重要数据】【学方法】及无栏目标题的图文贴。
跳过项(清单里「已发现但未下载」)现在只登记作者非齐俊杰的主题,例如「自由之路」发布的晨会提炼问答。
企业微信通知(2026-10-13 新增)
下载完成后把结果推送到企业微信,用独立脚本发送:
"C:\Users\Administrator\.workbuddy\binaries\python\envs\zsxq\Scripts\python.exe" \
~/.workbuddy/skills/zsxq-daily-downloader/scripts/notify_wecom.py <payload.json>
payload.json 格式(UTF-8):
{"chat_id":"woXgx0PQAAV04_C8xIw06_c8D7ixQ5-Q","msg_type":"markdown","markdown":{"content":"<摘要正文>"}}
markdown.content上限 20480 UTF-8 字节,超限要拆分,不要静默截断。- 退出码 0 = 发送成功;1 = 失败。收件人默认为授权人(徐律师)本人。
⚠️ 三个已踩过的坑(改脚本前必读)
- 不要用
wecom-cli.cmd+--json拼参数。中文正文里的引号、括号、表格竖线会被 bash / cmd 层层剥离,
表现为JSON repair error at position 0或unexpected argument '## ...'。
notify_wecom.py直接subprocess.run([node, wecom.js, ...])传 list,不经 shell,一次通过。 .cmd包装器在 Pythonsubprocess下不可靠。cwd=不生效、shell=True会二次转义,
报'目标' 不是内部或外部命令。直接调底层node.exe+node_modules/@wecom/cli/bin/wecom.js。chat_id必须是框架加密值。identity whoami返回的是空对象{},授权人 userid 只能通过
extra_identity_context文本读到;把该 userid 直接当chat_id传会被服务端拒收
(code 40058 'chat_id' 不合法)。给授权人本人发消息用本文档中的固定 userid 即可(已验证 success),
但若要发给群聊或其他成员,必须先message aibot sessions list拿到加密chat_id,
且sessions_count=0时说明机器人无任何会话往来,须先由本人在企业微信里给机器人发一条消息建立会话。
排障
见 references/troubleshooting.md。