소스 정보
- 저장소
- huang1125677925/mybook
- 최근 소스 활동
- 2026년 6월 7일 05:07
- 감지된 SKILL.md 언어
- 중국어
- 스타
- 3
- 포크
- 1
설치 방법
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
소스 파일 검토
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
메뉴
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/huang1125677925/mybook --skill wechat-official-account명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
SOC 직업 분류 기준
SKILL.md 표시 중
| name | wechat-official-account |
| description | 微信公众号运营管理 — 素材管理、文章导出、草稿创建、发布尝试、token维护。覆盖开放平台API的素材操作与发布链路。 |
| author | wangchen |
| version | 1.1.0 |
| credentials | [{"name":"WECHAT_APPID","description":"微信公众号appid(填入你的)"},{"name":"WECHAT_APPSECRET","description":"微信公众号appsecret(填入你的)"}] |
通过微信开放平台官方API操作自己的公众号。适用于文章导出、素材管理、数据分析等场景。
⚠️ 仅限操作自己的公众号 — API绑定了公众号的appid/appsecret,无法抓取别人的号。
⚠️ 优先使用 stable_token 接口(POST JSON),旧接口偶发 40001 无效凭证错误。
# 推荐:stable_token(POST + JSON body)
TOKEN=$(curl -s -X POST "https://api.weixin.qq.com/cgi-bin/stable_token" \
-H "Content-Type: application/json" \
-d "{\"grant_type\":\"client_credential\",\"appid\":\"$APPID\",\"secret\":\"$APPSECRET\"}" \
| python3 -c "import json,sys;print(json.load(sys.stdin).get('access_token',''))")
# 备用:旧 token 接口(偶发40001)
# curl -s "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=$APPID&secret=$APPSECRET"
curl -s -X POST "https://api.weixin.qq.com/cgi-bin/material/batchget_material?access_token=$TOKEN" \
-H "Content-Type: application/json" \
-d '{"type":"news","offset":0,"count":20}'
回包结构:
{
"total_count": 21,
"item_count": 20,
"item": [
{
"media_id": "xxx",
"update_time": 1629034241, <-- ⚠️ 这是正确的发布时间
"content": {
"news_item": [
{
"title": "北漂回忆2",
"update_time": 0, <-- ⚠️ 这里是0!不要用这个!
"create_time": 0, <-- ⚠️ 这里也是0!
"url": "https://..."
}
]
}
}
]
}
curl -s -X POST "https://api.weixin.qq.com/cgi-bin/material/get_material?access_token=$TOKEN" \
-H "Content-Type: application/json" \
-d '{"media_id":"xxx"}'
返回 news_item[0].content 字段包含文章全文HTML。
微信开放平台提供草稿箱API用于创建和发布文章。
# 封面图(永久素材,返回media_id用于thumb_media_id)
curl -s -F "media=@cover.png" \
"https://api.weixin.qq.com/cgi-bin/material/add_material?access_token=$TOKEN&type=image"
# → {"media_id":"xxx", "url":"http://..."}
# 文中插图(返回url嵌入HTML)
curl -s -F "media=@chart.png" \
"https://api.weixin.qq.com/cgi-bin/media/uploadimg?access_token=$TOKEN"
# → {"url":"http://mmbiz.qpic.cn/..."}
curl -s -X POST "https://api.weixin.qq.com/cgi-bin/draft/add?access_token=$TOKEN" \
-H "Content-Type: application/json; charset=utf-8" \
-d '{
"articles": [{
"title": "标题",
"thumb_media_id": "封面media_id",
"author": "作者",
"digest": "摘要",
"show_cover_pic": 1,
"content": "<section>HTML内容</section>",
"content_source_url": "",
"need_open_comment": 1,
"only_fans_can_comment": 0
}]
}'
# → {"media_id":"draft_xxx", "item":[{"index":0, "ad_count":1}]}
⚠️ 45003 / 45004 长度限制陷阱:
title 和 digest 限制按 UTF-8 字节 计算,不是字符数45003 title size out of limit → 标题字节超了,缩减即可45004 description size out of limit → 摘要字节超了curl -s -X POST "https://api.weixin.qq.com/cgi-bin/freepublish/submit?access_token=$TOKEN" \
-H "Content-Type: application/json" \
-d '{"media_id":"draft_xxx"}'
# 成功: {"errcode":0, "publish_id":"xxx"}
# 失败: {"errcode":48001, "errmsg":"api unauthorized"}
48001 权限错误:表示公众号未获得发布API权限。需要:
无设计工具时可用 headless Chrome 截图 HTML 生成封面:
# 1. 写一个 styled HTML(深蓝渐变背景 + 标题文字)
# 2. 截图
google-chrome --headless --disable-gpu --no-sandbox \
--screenshot=cover.png --window-size=900,383 cover.html
# 3. 上传
curl -F "media=@cover.png" \
"https://api.weixin.qq.com/cgi-bin/material/add_material?access_token=$TOKEN&type=image"
封面素材建议:900×383(2.35:1),深蓝/暗色系保持与排版风格一致。
直接调用 scripts/cover_generate.py 一键生成:
python3 scripts/cover_generate.py "主标题" "副标题" [可选小标] [/tmp/cover.png]
模板文件:templates/cover.html(HTML/CSS 模板) + templates/draft_payload.json(完整 draft/add payload 示例)。
生成的文章 HTML 通常是完整文档(含 <html><body>...</body></html>),创建草稿时需要只取 body 内容:
import re
m = re.search(r'<body>(.*?)</body>', html, re.DOTALL)
body = m.group(1).strip() # 去掉外层标签,保留所有 section/p/table
微信图文编辑器只支持有限的HTML标签:
<section> 包裹整体结构<h2>/<h3> + 样式(微信不支持h4)<table> + 内联样式<img> + <section style="text-align:center"> 居中background + border-left 模拟不要用的标签: <div> 在微信中可能渲染异常,统一用 <section>。
⚠️ API 推送 vs 手动粘贴的重大差异:
| 问题 | 手动粘贴到编辑器 | API 创建草稿 |
|---|---|---|
| 深色主题(#1a1a2e底+浅灰字) | 正常显示 | 变成乱码/看不清 |
<tr style="background:..."> 表格表头底色 | 正常 | 被剥离,显示为白底 |
<section> 嵌套背景色 | 正常 | 可能被重置 |
对策——API 推送文章必须用浅色/白底配色方案:
#fff,不设外层深色底<th style="background-color:#1a1a1a;"> 上,不能写在 <tr> 上#333/#555/#34495e 等深色,确保白底上可读<blockquote> + 白字),不影响全局可读性linear-gradient 等复杂 CSS 在 API 推送时不可靠,用纯色import os, re, json, subprocess, requests
APPID = "your_appid_here" # 黄妙然的号
APPSECRET = "your_appsecret_here"
AUTHOR = "王晨"
# Step 1: 拿 token(必须 stable_token,旧接口偶发40001)
r = requests.post(
"https://api.weixin.qq.com/cgi-bin/stable_token",
json={"grant_type": "client_credential", "appid": APPID, "secret": APPSECRET},
timeout=15,
)
token = r.json()["access_token"]
# Step 2: 抽 body(如果是完整 HTML 文档)
with open("/tmp/article.html", "r", encoding="utf-8") as f:
html = f.read()
m = re.search(r"<body[^>]*>(.*?)</body>", html, re.DOTALL)
body = m.group(1).strip() if m else html
# Step 3: 标题/摘要字节检查(**必须** 在调用前 print 出来确认)
title = "标题"
digest = "摘要"
print(len(title.encode("utf-8")), "/ 64 ", len(digest.encode("utf-8")), "/ 120")
# 标题 64 字节 ≈ 21 汉字;摘要 120 字节 ≈ 40 汉字
# Step 4: 上传封面(永久素材)
r = requests.post(
f"https://api.weixin.qq.com/cgi-bin/material/add_material?access_token={token}&type=image",
files={"media": ("cover.png", open("/tmp/cover.png", "rb"), "image/png")},
timeout=30,
)
thumb_media_id = r.json()[]
article = {: [{
: title,
: thumb_media_id,
: AUTHOR,
: digest,
: ,
: body,
: ,
: ,
: ,
}]}
r = requests.post(
,
data=json.dumps(article, ensure_ascii=).encode(),
headers={: },
timeout=,
)
(r.json())
标题/摘要字节限制的实测值(2026-06-06 推送"尾盘买卖逻辑与技巧"实测通过):
| 字段 | 上限 | 实测推送 | 备注 |
|---|---|---|---|
| title | 64 字节 | 63 字节(21 汉字) | 45003 超限 |
| digest | 120 字节 | 120 字节(40 汉字) | 45004 超限 |
| author | 20 字节 | "王晨"(6 字节) | — |
⚠️ 早期记忆曾记错 digest 为 36 字节(实际是 120)。如果你看到 36 一定是错记,以本 skill 为准。
绝不要在封面上擅自加系列名/期数,比如"交易笔记 · 第 12 期"——这不是用户告诉你的具体系列。封面顶部小标只放用户明确指定的字样,没有就只留主副标题。
<!-- ❌ 错误:擅自加 "交易笔记 · 第 12 期" 这种猜测 -->
<div class="tag">交易笔记 · 第 12 期</div>
<!-- ✅ 安全:只放主副标题 -->
<div class="title">尾盘买卖逻辑与技巧</div>
<div class="sub">从理论到实战的系统整理</div>
如果用户给的是多期文章系列(用户自己说过"这是第 X 期"),才写小标;否则一律不写。
必须显式指定 UTF-8 编码和 Content-Type charset:
# ✅ 正确:显式 encode + Content-Type charset
payload = json.dumps(article, ensure_ascii=False).encode("utf-8")
r = requests.post(url, data=payload,
headers={"Content-Type": "application/json; charset=utf-8"})
# ❌ 错误:用 requests 的 json= 参数(charset 可能不完整)
r = requests.post(url, json=article)
原因: 使用 requests.post(json=...) 时 Content-Type 可能不带 charset=utf-8,导致中文在微信端解析为乱码。
在 execute_code 的 Python 字符串中使用中文引号 "" 会导致 SyntaxError,因为 Python 解析器把 " 当成字符串边界:
# ❌ SyntaxError
title = "别再9:30才盯盘了!真正的高手,早在9:15就已看穿一切"
# ^--- Python sees this as end-of-string
# ✅ 用 unicode 转义
title = '别再9:30才盯盘了!真正的高手,早在9:15就已看穿一切'
# 或
title = '\u201c过年加油站\u201d:情绪周期五阶段'
规则:execute_code 中的字符串如果包含中文引号 "",要么用单引号包裹,要么用 \u201c / \u201d 转义。
当标题含中文弯引号 "" 时,在 Python 字符串中会触发 SyntaxError——因为 " 看起来像普通双引号 ",导致 Python 误解析字符串边界:
# ❌ 错误:中文弯引号 "瓶颈理论" 中的 " 被 Python 当成字符串结束符
title = "一年45倍!Serenity的"瓶颈理论",A股怎么用?"
# SyntaxError: invalid syntax
# ✅ 方案1:用 Unicode 转义 \u201c \u201d
title = '一年45倍!Serenity的\u201c瓶颈理论\u201d,A股怎么用?'
# ✅ 方案2:用单引号包裹字符串(如果内部不含单引号)
title = '一年45倍!Serenity的"瓶颈理论",A股怎么用?'
这个坑也适用于
digest、author等任何含中文标点的字符串参数。在execute_code沙箱中写 JSON payload 时尤其要留意,因为沙箱会将代码作为普通 Python 解析。
微信 API 没有"更新草稿"的接口。需要替换一篇文章时只能:
draft/delete 删除旧草稿draft/add 创建新草稿# 删除旧草稿
requests.post(f"https://api.weixin.qq.com/cgi-bin/draft/delete?access_token={token}",
json={"media_id": "draft_xxxxx"})
# 重新创建
requests.post(f"https://api.weixin.qq.com/cgi-bin/draft/add?access_token={token}", ...)
封面图和文中插图走不同的 API:
| 用途 | API | 返回 | 用途 |
|---|---|---|---|
| 封面 | material/add_material?type=image | media_id(用于 thumb_media_id) | 文章封面图 |
| 正文插图 | media/uploadimg | url(mmbiz.qpic.cn CDN 链接) | 嵌入 HTML 的 <img> |
完整流程:
# 1. 上传图片 → 获得 CDN URL
with open("image.png", "rb") as f:
r = requests.post(
f"https://api.weixin.qq.com/cgi-bin/media/uploadimg?access_token={token}",
files={"media": ("image.png", f, "image/png")})
img_url = r.json()["url"] # "http://mmbiz.qpic.cn/..."
# 2. 将 URL 嵌入文章 HTML
article_html = f'''
<section style="text-align:center;margin:20px 0;">
<img src="{img_url}" style="max-width:100%;height:auto;display:block;margin:0 auto;">
</section>
'''
# 3. 创建草稿(含图的 body)
payload = json.dumps({"articles": [{
"title": title, "content": body # body 中已含 img_url
}]}, ensure_ascii=False).encode("utf-8")
症状: API推送草稿后,文章里 <img> 标签在 source 里都有,URL访问也是200,但草稿箱预览看不到图。
根因(按发生概率从高到低):
| # | 原因 | 验证方法 | 修复 |
|---|---|---|---|
| 1 | 草稿箱页面缓存 — 微信草稿箱编辑页有强缓存 | F5/Ctrl+Shift+R 强制刷新草稿箱页面 | 刷新即可 |
| 2 | 图片CDN URL已过期 — media/uploadimg 返回的URL生命周期较短 | curl测试URL状态码,200说明有效 | 重新 media/uploadimg 上传,用新URL重新 draft/add |
| 3 | 图被推到 sz_mmbiz_png/ 子目录(不一定有,但有时URL会被改写) | 在草稿source里检查实际URL | 重新上传并使用新URL |
| 4 | 图片太大未自动展开 — 高图(如>2000px的K线图)默认折叠 | 在草稿编辑器里点开"显示更多图片" | 不需要修复,群发后会自动展开 |
| 5 | 图片URL被微信重写 — 偶尔URL会被改成 __biz 参数形式 | 在草稿编辑器里右键看图实际URL | 重新上传并推送 |
通用排错流程:
# 1. 验证草稿source里是否有 <img> 标签
import re
draft = get_draft(media_id)
imgs = re.findall(r'<img[^>]+src="([^"]+)"', draft["content"])
print(f"找到 {len(imgs)} 张图")
for i, src in enumerate(imgs, 1):
# 2. 验证每张图URL是否可访问
r = requests.get(src)
print(f"图{i}: {r.status_code} | {len(r.content)} bytes")
# 200 = OK;403/404 = URL失效
最稳的做法:重传+重建
# 删除旧草稿
requests.post(f"https://api.weixin.qq.com/cgi-bin/draft/delete?access_token={token}",
json={"media_id": old_media_id})
# 重新上传所有图片(每次都拿新URL)
for img in images:
r = requests.post(
f"https://api.weixin.qq.com/cgi-bin/media/uploadimg?access_token={token}",
files={"media": img})
img_urls.append(r.json()["url"])
# 用新URL重建HTML并推送
html = rebuild_html(content, img_urls)
requests.post(f"https://api.weixin.qq.com/cgi-bin/draft/add?access_token={token}",
data=json.dumps({"articles":[{"content": html, ...}]}, ensure_ascii=False).encode("utf-8"),
headers={"Content-Type": "application/json; charset=utf-8"})
关键教训:
### ⚠️ 列表项之间不能有换行/空白(2026-06-06 实测)
**症状:** 推送的 ul/ol 块在草稿箱显示为"每个 li 后面跟一个空 li"。实测:22 个列表块,原始 74 个 li 在草稿里变成 172 个,其中 97 个是空的。
**根因:** 微信 API 存储时会把 `</li>` 和 `<li>` 之间的换行/空白字符(`\n`、空格、制表符)**自动转成空 `<li>`**。
**验证(实测通过):**
- 原始 HTML(li 间有 `\n`)→ 推送后 172 li,97 空
- 修复 HTML(li 间无空白,全部 `</li><li>` 紧挨着)→ 推送后 74 li,0 空
**修复方案:**
```python
import re
new_body = re.sub(
r'<(ul|ol)([^>]*?)>(.*?)</\1>',
lambda m: f'<{m.group(1)}{m.group(2)}>' + re.sub(r'</li>\s+<li', '</li><li', m.group(3)) + f'</{m.group(1)}>',
body,
flags=re.DOTALL
)
规则:以后写带 ul/ol 的文章 HTML 时,li 之间绝对不能留任何空白/换行。
⚠️ 补充:li 内部也不能有纯空白内容(2026-06-06 第二波修复)
症状补充: 即使 li 之间没有空白,有些 <li> 自身内部只含 \n(如 <li>\n</li>),会被微信 API 解析为可见的空列表项。
修复: 推草稿前统一清洗:
new_body = re.sub(r'<li[^>]*>\s*</li>', '', body)
完整清洗流程(推荐):
# Step 1: 去掉 li 间空白(防止被解析成新的空 li)
body = re.sub(r'</li>\\s+<li', '</li><li', body)
# Step 2: 去掉只有空白的 li(防止显式空项)
body = re.sub(r'<li[^>]*>\\s*</li>', '', body)
从 draft/batchget 拉取的内容如果编码异常(中文字符显示为 æä¹æ 等乱码),是因为内容被以 latin-1 编码存储但实际是 UTF-8 字节。修复方法:
# 从 batchget 拿到原始内容后
with open("draft_content.html", "r", encoding="utf-8") as f:
raw = f.read()
# 用 re-encode 修复
fixed = raw.encode('latin-1').decode('utf-8')
常见原因: 通过 ima 知识库导出或 AI 助手直接粘贴的含中文 HTML 内容,在通过微信 API 存储时发生了编码 mis-match。修复后重新 draft/add 即可。
来源文档→公众号文章的成文规则(2026-06-06 用户明确)
文章内容中不要出现文章标题。 标题只设置在微信草稿的 title 字段中,HTML 内容 body 里不写任何 <h1> 或等效的标题文本。正文直接从一个引导块或引言开始。
尽量不删减原文内容。 从腾讯文档、知识库或任何来源转换文章时,保持原文的完整结构、段落、数据表格和结论。可以调整排版格式(段落划分、标题层级、表格美化),但不能删除、合并、改写核心内容点。
清理AI对话前缀。 腾讯文档的内容通常以 用户:...ima:... 开头,创建文章前要去掉这些对话头尾和免责声明,只保留纯内容部分。
标题/摘要字节校验。 推送前用 len(title.encode("utf-8")) 和 len(digest.encode("utf-8")) 检查字节数。超限时主动缩短,不要依赖API静默截断。
示例:首板战法文章的生成
来源文档: 和ima的对话 → 首板战法内容
处理步骤:
1. 读文档原始内容
2. 去掉"用户:...ima:..."的对话前缀
3. 去掉结尾"(内容由AI生成,仅供参考)"的免责
4. 分成核心逻辑、四大战法、操作细节等章节
5. 套白底HTML模板排版
6. 生成封面(交易系统·标题)
7. 推送草稿(title字段写标题,body不含标题)
详见 scripts/wechat_export.py。
# 列出所有文章
python3 wechat_export.py list
# 下载所有文章为Markdown(文件名 YYYYMMDD_标题.md)
python3 wechat_export.py save
# 下载单篇
python3 wechat_export.py get <media_id>
batchget_material 接口返回的 news_item[].update_time 永远是0。正确发布时间在 item.update_time(素材级别):
# ✅ 正确
item.get("update_time") # Unix时间戳
# ❌ 错误
n.get("update_time") # 永远是0!
而 get_material(获取单篇)接口的 news_item[].create_time/update_time 有正常值,与batchget不同。
references/api-notes.md — 接口字段说明