#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
微信公众号文章抓取脚本
用途:抓取微信公众号文章原始 HTML,提取文字内容与图片 URL 列表。
使用方法:
python3 extract_wechat.py <微信文章URL> [输出目录]
示例:
python3 extract_wechat.py "https://mp.weixin.qq.com/s/xxxx" ./output
输出文件:
- raw_article.html 原始 HTML 源码
- extracted_content.txt 提取的图片列表与纯文本摘要
"""
import sys
import os
import re
import urllib.request
import html as html_lib
from pathlib import Path
def fetch_wechat_html(url: str) -> str:
"""
使用桌面微信 User-Agent 抓取微信公众号文章原始 HTML。
:param url: 微信公众号文章 URL
:return: 原始 HTML 字符串
:raises: urllib.error.URLError 如果请求失败
"""
headers = {
'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 '
'MicroMessenger/8.0.0'
),
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
}
req = urllib.request.Request(url, headers=headers)
with urllib.request.urlopen(req, timeout=30) as resp:
return resp.read().decode('utf-8', errors='replace')
def extractTitle(htmlContent: str) -> str:
"""从 HTML 中提取文章标题。"""
match = re.search(r'
]*>(.*?)', htmlContent, re.DOTALL | re.IGNORECASE)
if match:
title = re.sub(r'<[^>]+>', '', match.group(1)).strip()
return html_lib.unescape(title)
return "未知标题"
def extractImages(htmlContent: str) -> list:
"""
从微信文章 HTML 中提取所有图片 URL。
优先提取 data-src 属性(微信懒加载),其次 src 属性。
:param htmlContent: 原始 HTML 字符串
:return: 去重后的图片 URL 列表
"""
imgUrls = []
seen = set()
# 优先匹配 data-src(微信懒加载图片)
for match in re.finditer(r'data-src=["\']([^"\']+mmbiz[^"\']+)["\']', htmlContent):
url = html_lib.unescape(match.group(1))
if url not in seen:
imgUrls.append(url)
seen.add(url)
# 补充匹配 src(部分图片无 data-src)
for match in re.finditer(r'
]+src=["\']([^"\']+mmbiz[^"\']+)["\']', htmlContent):
url = html_lib.unescape(match.group(1))
if url not in seen:
imgUrls.append(url)
seen.add(url)
return imgUrls
def extractText(htmlContent: str) -> str:
"""
提取文章正文区域的纯文本(js_content 内部)。
:param htmlContent: 原始 HTML 字符串
:return: 清理后的纯文本
"""
contentMatch = re.search(
r'id=["\']js_content["\'][^>]*>(.*?)(?=]+>', ' ', text)
# 解码 HTML 实体
text = html_lib.unescape(text)
# 清理多余空白
text = re.sub(r'\s+', ' ', text).strip()
return text
def main():
"""主入口函数。"""
if len(sys.argv) < 2:
print("用法: python3 extract_wechat.py <微信文章URL> [输出目录]")
print("示例: python3 extract_wechat.py 'https://mp.weixin.qq.com/s/xxxx' ./output")
sys.exit(1)
url = sys.argv[1]
outputDir = Path(sys.argv[2]) if len(sys.argv) > 2 else Path("./output")
outputDir.mkdir(parents=True, exist_ok=True)
print(f"📡 正在抓取: {url}")
htmlContent = fetch_wechat_html(url)
# 保存原始 HTML
rawPath = outputDir / "raw_article.html"
rawPath.write_text(htmlContent, encoding='utf-8')
print(f"💾 原始 HTML 已保存: {rawPath} ({len(htmlContent):,} 字节)")
# 提取标题
title = extractTitle(htmlContent)
print(f"📰 标题: {title}")
# 提取图片
imgUrls = extractImages(htmlContent)
print(f"🖼️ 发现 {len(imgUrls)} 张图片")
# 提取文本
textContent = extractText(htmlContent)
# 写入提取结果
extractPath = outputDir / "extracted_content.txt"
with open(extractPath, 'w', encoding='utf-8') as f:
f.write(f"Title: {title}\n\n")
f.write(f"Images ({len(imgUrls)}):\n")
for i, imgUrl in enumerate(imgUrls, 1):
f.write(f"[{i}] {imgUrl}\n")
f.write(f"\n--- 正文摘要 (前 2000 字) ---\n\n")
f.write(textContent[:2000])
print(f"📄 提取结果已保存: {extractPath}")
print(f"\n✅ 完成!共提取 {len(imgUrls)} 张图片,正文约 {len(textContent)} 字。")
if __name__ == "__main__":
main()