WeChat Article to Markdown
Overview
Convert a WeChat public account article (mp.weixin.qq.com) into a Markdown file. Preserve all original text verbatim — no rewriting, no summarization, no polishing. Include every image from the article with its original CDN URL. Also detect and mark videos, audio, and other embedded media so no content is silently lost.
Workflow
Step 0: Validate URL & Preflight Check
Before fetching anything, validate the input:
-
Check URL format: Must contain
mp.weixin.qq.com. If not, tell the user:
> ⚠️ 这个链接不是微信公众号文章地址。请确认链接以https://mp.weixin.qq.com/开头。 -
Clean the URL: Strip tracking parameters (
?mpshare=1&scene=1&srcid=...&sharer_shareinfo=...etc.). Keep only the base URL up to and including the article ID. This avoids anti-scraping triggers. -
Proceed only if URL is valid.
Step 1: Fetch Article Text
Use WebFetch to retrieve the article content:
WebFetch with prompt: "Extract the complete article: title, author, publish date, all body text exactly as written (no summarization). Preserve headings, paragraphs, lists, quotes. Note where images appear."
Error handling — if WebFetch returns empty or fails:
-
If the response contains "环境异常" / "访问过于频繁" / "verify" → the article is behind an anti-scraping wall. Tell the user:
> ⚠️ 微信反爬机制触发,暂时无法抓取。请稍后重试,或在手机上打开文章确认链接是否有效。 -
If the response is empty or garbled → try the raw HTML fetch in Step 2 directly. If that also fails:
> ⚠️ 无法获取文章内容。可能原因:① 链接已失效/文章被删除 ② 网络连接问题 ③ 需要微信登录验证。请确认链接在浏览器中能正常打开后重试。 -
If the response looks like a login/verification page → tell the user:
> ⚠️ 该文章可能需要微信登录才能查看,暂不支持转换。
Step 2: Fetch Raw HTML & Extract All Media
WeChat articles lazy-load images via data-src attributes. WebFetch alone cannot capture these — the raw HTML must be fetched with curl. This single fetch also extracts videos and audio.
curl -sL --max-time 30 -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" "<article-url>" 2>/dev/null > /tmp/wx_raw.html
Error handling — check curl result:
- If curl exits with code 6 (DNS failure) →
⚠️ 网络连接失败,请检查网络后重试。 - If curl exits with code 28 (timeout) →
⚠️ 请求超时,微信服务器响应过慢,请稍后重试。 - If curl exits with code 35/51 (SSL) →
⚠️ SSL 证书问题,请检查系统时间是否正确。 - If the output file is empty or very small (<500 bytes) → likely behind anti-scraping wall, see Step 1 error handling.
- If the output contains
"msg": "need verify"or"environment"→ anti-scraping triggered. Tell user to retry later.
Then extract all media types from the saved HTML in one pass:
python3 -c "
import sys, re, html
with open('/tmp/wx_raw.html', 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
# --- Images (data-src on img tags) ---
img_pattern = r'<img[^>]*data-src=\"(https://mmbiz\.qpic\.cn/[^\"]+)\"[^>]*/?>'
for i, m in enumerate(re.finditer(img_pattern, content)):
start = max(0, m.start() - 800)
end = min(len(content), m.end() + 200)
context = content[start:end]
clean = re.sub(r'<[^>]+>', ' ', context)
clean = html.unescape(clean)
clean = re.sub(r'\s+', ' ', clean).strip()[-400:]
url = m.group(1).replace('&amp;', '&')
print(f'=== Image {i+1} ===')
print(f'Type: image')
print(f'URL: {url}')
print(f'Context: ...{clean}...')
print()
# --- Videos (mp-commonvideosnap, iframe video player, video tags) ---
video_patterns = [
(r'<mp-commonvideosnap[^>]*>(.*?),"(https://[^"]+)"', 'WeChat video'),
(r'<iframe[^>]*data-src=\"(https://mp\.weixin\.qq\.com/mp/readtemplate[^\"]*mpvideo[^\"]*)\"', 'Embedded video'),
(r'<video[^>]*poster=\"([^\"]+)\"[^>]*src=\"([^\"]+)\"', 'HTML5 video'),
]
vid_idx = 0
for pattern, label in video_patterns:
for m in re.finditer(pattern, content, re.DOTALL):
vid_idx += 1
start = max(0, m.start() - 800)
end = min(len(content), m.end() + 200)
context = content[start:end]
clean = re.sub(r'<[^>]+>', ' ', context)
clean = html.unescape(clean)
clean = re.sub(r'\s+', ' ', clean).strip()[-400:]
url = m.group(m.lastindex).replace('&amp;', '&')
poster = m.group(1).replace('&amp;', '&') if m.lastindex >= 2 else ''
print(f'=== Video {vid_idx} ===')
print(f'Type: video ({label})')
print(f'URL: {url}')
if poster:
print(f'Poster: {poster}')
print(f'Context: ...{clean}...')
print()
# --- Audio (mpvoice, mpmusic) ---
audio_patterns = [
(r'<mpvoice[^>]*voice_encode_fileid=\"([^\"]+)\"[^>]*>', 'WeChat voice'),
(r'<mpmusic[^>]*src=\"([^\"]+)\"[^>]*>', 'Music card'),
]
aud_idx = 0
for pattern, label in audio_patterns:
for m in re.finditer(pattern, content):
aud_idx += 1
start = max(0, m.start() - 800)
end = min(len(content), m.end() + 200)
context = content[start:end]
clean = re.sub(r'<[^>]+>', ' ', context)
clean = html.unescape(clean)
clean = re.sub(r'\s+', ' ', clean).strip()[-400:]
print(f'=== Audio {aud_idx} ===')
print(f'Type: audio ({label})')
print(f'ID: {m.group(1)}')
print(f'Context: ...{clean}...')
print()
"
This single script outputs all images, videos, and audio in order of appearance, each with surrounding context for position mapping.
Step 3: Map All Media to Article Positions
Use the context text before each media element to determine where it belongs in the article flow:
- Images → insert as
 - Videos → insert as
\n\n> 📹 视频({label}):[点击查看]({video-url}) - If no direct video URL is available (common for WeChat native videos), insert:
> 📹 视频({label}):请前往原文查看 - If a poster/cover image is available, include it above the note
- Audio → insert as
> 🎵 音频({label}):请前往原文收听 - WeChat audio cannot be directly linked in markdown, so always note the position and direct users to the original article
Step 4: Write the Markdown File
Create a Markdown file with:
- Article title as
#heading - Author and source as metadata
- All body text preserved exactly as-is
- Images inserted at correct positions:
 - Videos marked at correct positions with cover image (if available) + video note
- Audio marked at correct positions with audio note
- Code blocks, bold, lists, and other formatting preserved from the original
▸captions (image captions in WeChat articles) kept verbatim, placed near their corresponding images
Step 5: Verify
After writing, run two checks:
# Check no empty image placeholders remain
grep -c '!\[\]()' <output-file.md>
Should return 0.
# List all media markers to confirm videos/audio were captured
grep -n '📹\|🎵' <output-file.md>
If the article was known to contain videos/audio but none are found, warn the user:
> ⚠️ 原文可能包含视频/音频内容,但未能自动提取。请前往原文确认。
Step 6: Report to User
After presenting the file, include a brief summary:
- Total images extracted and inserted
- Total videos found (and whether URLs were available or just marked)
- Total audio clips found
- Any warnings (missing media, anti-scraping issues, etc.)
Key Rules
- Never alter the original text. No summarization, no polishing, no rewording. Keep every character.
- Never skip images. Every image in the article must have its CDN URL in the markdown.
- Never silently drop media. Videos and audio must be marked at their positions even if a direct URL cannot be extracted — the user must know content exists there.
- Validate before fetching. Check URL format, clean tracking params, and handle network errors with clear Chinese messages.
- Map all media by context. Use the raw HTML context around each element to correctly place it. Ignore decorative/minor images only if they are not part of the article content (e.g., WeChat UI elements).
- Use the article title as filename. Save as
{article-title}.mdin the current workspace directory. - Present the file to the user when done, with a summary of what was extracted.
FAQ — 常见问题与排查
Q1: 提示"微信反爬机制触发"怎么办?
微信对同一 IP 的频繁请求会触发反爬。解决办法:
- 等待 5-10 分钟后重试
- 确认链接在手机微信中能正常打开
- 如果多次重试仍失败,可能是该文章被限制了访问频率,建议换个时间段再试
Q2: 图片地址在 Markdown 中显示裂图?
微信图片 CDN(mmbiz.qpic.cn)的 URL 会设置防盗链和过期时间。可能原因:
- 防盗链:部分图片限制了 Referer,直接在 Markdown 编辑器中打开会裂图。解决:在浏览器中打开图片 URL 确认是否可访问;如果图片已过期,需重新从原文获取
- URL 过期:微信图片 URL 并非永久有效,建议转换后尽快下载图片到本地归档
- HTML 实体未解码:检查 URL 中是否残留 &amp;,应为 &
Q3: 文章里的视频为什么只有标记没有播放链接?
微信原生视频使用 mp-commonvideosnap 自定义标签,视频流地址是动态生成的,无法直接提取为静态 URL。技能会在对应位置插入 > 📹 视频 标记,并保留封面图(如果有),用户可前往原文查看视频内容。第三方嵌入视频(如腾讯视频 iframe)可以提取到链接。
Q4: 转换后的文章内容不完整?
可能原因及排查:
- 文章过长被截断:超长文章(万字以上)的 HTML 可能被分页或延迟加载。检查 curl 抓取的 HTML 是否包含完整 </div> 闭合标签
- 正文在 JS 中动态渲染:部分文章正文内容由 JavaScript 渲染,curl 只能拿到静态 HTML。这种情况下 WebFetch 提取的文本可能更完整,以 WebFetch 的文本为准,图片位置参考 HTML 上下文
- 付费/会员文章:需要付费才能查看完整内容的文章,免费部分可以正常转换
Q5: curl 命令在 Windows 上报错?
- 确认使用的是 Git Bash 环境(WorkBuddy 默认使用 bash)
- 如果提示
curl: command not found,尝试用完整路径或改用 PowerShell 的Invoke-WebRequest - Python 命令在不同系统上可能不同:Windows 上可能需要用
python而非python3
Q6: 文章包含小程序卡片、投票、红包等特殊元素怎么办?
这些微信特有元素无法转为标准 Markdown,技能会在对应位置插入提示标记:
- 小程序卡片 → > 🔗 小程序卡片:请前往原文查看
- 投票 → > 📊 投票:请前往原文参与
- 红包/赞赏 → > 💰 红包/赞赏:请前往原文查看
Q7: 提示"该文章可能需要微信登录才能查看"?
部分文章设置了仅粉丝可见或需要关注公众号才能阅读。这类文章无法直接抓取,建议:
- 在手机微信中打开文章,完成关注/登录后复制完整内容
- 或截图后用 OCR 工具转换
Q8: 转换速度很慢怎么办?
- 图片较多的文章(20张以上)需要多次 Edit 操作来填入图片 URL,属正常现象
- 可在转换完成后提示用户"图片较多,转换耗时稍长,请耐心等待"
- 如果 curl 超过 30 秒未响应,会自动超时并提示重试
Q9: 如何保存图片到本地而不是引用 CDN 链接?
本技能默认保留 CDN 原始链接。如果需要将图片下载到本地,转换完成后可追加一步:
# 批量下载 Markdown 文件中引用的图片
grep -oP '!\[.*?\]\(\K[^)]+' <output-file.md> | while read url; do
filename=$(echo "$url" | grep -oP '[^/]+\.(jpg|png|gif|webp)' | head -1)
curl -sL "$url" -o "images/$filename"
done
然后批量替换 Markdown 中的 CDN 链接为本地路径。