wechat-article-parser 0.0.15__tar.gz → 0.0.17__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- wechat_article_parser-0.0.17/PKG-INFO +138 -0
- wechat_article_parser-0.0.17/README.md +123 -0
- wechat_article_parser-0.0.17/docs/api.md +226 -0
- wechat_article_parser-0.0.17/docs/content-rules.md +95 -0
- wechat_article_parser-0.0.17/docs/development.md +100 -0
- {wechat_article_parser-0.0.15 → wechat_article_parser-0.0.17}/pyproject.toml +1 -1
- {wechat_article_parser-0.0.15 → wechat_article_parser-0.0.17}/src/wechat_article_parser/models.py +1 -2
- {wechat_article_parser-0.0.15 → wechat_article_parser-0.0.17}/src/wechat_article_parser/parser.py +364 -60
- {wechat_article_parser-0.0.15 → wechat_article_parser-0.0.17}/tests/test_article_urls.py +11 -0
- wechat_article_parser-0.0.17/tests/test_fullscreen_content.py +124 -0
- {wechat_article_parser-0.0.15 → wechat_article_parser-0.0.17}/tests/test_models.py +7 -4
- {wechat_article_parser-0.0.15 → wechat_article_parser-0.0.17}/tests/test_parser.py +23 -1
- wechat_article_parser-0.0.17/tests/test_redpacket_content.py +87 -0
- wechat_article_parser-0.0.17/tests/test_svg_content.py +453 -0
- {wechat_article_parser-0.0.15 → wechat_article_parser-0.0.17}/tests/test_type_routing.py +53 -0
- wechat_article_parser-0.0.17/tests/test_vote_content.py +148 -0
- wechat_article_parser-0.0.15/PKG-INFO +0 -313
- wechat_article_parser-0.0.15/README.md +0 -298
- wechat_article_parser-0.0.15/tests/test_svg_content.py +0 -98
- {wechat_article_parser-0.0.15 → wechat_article_parser-0.0.17}/.gitignore +0 -0
- {wechat_article_parser-0.0.15 → wechat_article_parser-0.0.17}/LICENSE +0 -0
- {wechat_article_parser-0.0.15 → wechat_article_parser-0.0.17}/src/wechat_article_parser/__init__.py +0 -0
- {wechat_article_parser-0.0.15 → wechat_article_parser-0.0.17}/tests/conftest.py +0 -0
- {wechat_article_parser-0.0.15 → wechat_article_parser-0.0.17}/tests/test_markdown.py +0 -0
- {wechat_article_parser-0.0.15 → wechat_article_parser-0.0.17}/tests/test_miniprogram_content.py +0 -0
- {wechat_article_parser-0.0.15 → wechat_article_parser-0.0.17}/tests/test_paid_content.py +0 -0
- {wechat_article_parser-0.0.15 → wechat_article_parser-0.0.17}/tests/test_product_content.py +0 -0
- {wechat_article_parser-0.0.15 → wechat_article_parser-0.0.17}/tests/test_profile_content.py +0 -0
- {wechat_article_parser-0.0.15 → wechat_article_parser-0.0.17}/tests/test_repost_content.py +0 -0
- {wechat_article_parser-0.0.15 → wechat_article_parser-0.0.17}/tests/test_swiper_content.py +0 -0
- {wechat_article_parser-0.0.15 → wechat_article_parser-0.0.17}/tests/test_user_name.py +0 -0
- {wechat_article_parser-0.0.15 → wechat_article_parser-0.0.17}/tests/test_video_content.py +0 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: wechat-article-parser
|
|
3
|
+
Version: 0.0.17
|
|
4
|
+
Summary: WeChat MP article parser - extract metadata and content from WeChat public account articles
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Requires-Dist: beautifulsoup4>=4.12
|
|
9
|
+
Requires-Dist: httpx>=0.27
|
|
10
|
+
Requires-Dist: markdownify>=0.13
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
|
|
13
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# wechat-article-parser
|
|
17
|
+
|
|
18
|
+
微信公众号文章解析器。传入文章链接,提取公众号信息、文章元数据和 Markdown 正文,支持同步与异步调用。
|
|
19
|
+
|
|
20
|
+
## 功能
|
|
21
|
+
|
|
22
|
+
- 支持微信文章短链接和长链接。
|
|
23
|
+
- 支持标准图文、转载式文章、纯文本、视频与音频分享、图片轮播及全屏布局短文本。
|
|
24
|
+
- 提取标题、作者公众号、摘要、封面、发布时间、付费标记等信息,返回结构化数据。
|
|
25
|
+
- 将正文转换为 Markdown,提取正文图片链接,支持 SVG 内嵌图片和文字。
|
|
26
|
+
- 将投票、公众号推荐、小程序、红包封面等组件转换为可读文字或查看提示。
|
|
27
|
+
- 支持请求超时、自定义 User-Agent、代理和按需返回原始 HTML。
|
|
28
|
+
|
|
29
|
+
## 安装
|
|
30
|
+
|
|
31
|
+
需要 **Python 3.10+**。
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
python -m pip install wechat-article-parser
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`httpx`、`beautifulsoup4` 和 `markdownify` 会随包自动安装。
|
|
38
|
+
|
|
39
|
+
## 快速开始
|
|
40
|
+
|
|
41
|
+
将以下示例中的 URL 替换为实际文章链接。
|
|
42
|
+
|
|
43
|
+
### 同步调用
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from wechat_article_parser import parse
|
|
47
|
+
|
|
48
|
+
result = parse("https://mp.weixin.qq.com/s/xxxxx")
|
|
49
|
+
|
|
50
|
+
if result.is_valid:
|
|
51
|
+
print(result.article_title)
|
|
52
|
+
print(result.mp_name)
|
|
53
|
+
print(result.article_markdown)
|
|
54
|
+
print(result.images)
|
|
55
|
+
else:
|
|
56
|
+
print("部分关键字段未能解析,请检查返回结果")
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 异步调用
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
import asyncio
|
|
63
|
+
|
|
64
|
+
from wechat_article_parser import parse_async
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
async def main():
|
|
68
|
+
result = await parse_async("https://mp.weixin.qq.com/s/xxxxx")
|
|
69
|
+
if result.is_valid:
|
|
70
|
+
print(result.article_title)
|
|
71
|
+
print(result.article_markdown)
|
|
72
|
+
else:
|
|
73
|
+
print("部分关键字段未能解析,请检查返回结果")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
asyncio.run(main())
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 请求选项
|
|
80
|
+
|
|
81
|
+
两个接口均支持以下关键字参数:
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
from wechat_article_parser import parse
|
|
85
|
+
|
|
86
|
+
result = parse(
|
|
87
|
+
"https://mp.weixin.qq.com/s/xxxxx",
|
|
88
|
+
timeout=30, # 默认 15 秒
|
|
89
|
+
user_agent="MyBot/1.0", # 默认使用内置 User-Agent
|
|
90
|
+
proxy="http://127.0.0.1:7890", # 可选,显式指定代理
|
|
91
|
+
include_raw_html=True, # 默认 False
|
|
92
|
+
)
|
|
93
|
+
print(result.raw_html)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## 返回结果
|
|
97
|
+
|
|
98
|
+
两个接口均返回 `ArticleResult` 数据类,常用字段如下:
|
|
99
|
+
|
|
100
|
+
| 字段 | 说明 |
|
|
101
|
+
|---|---|
|
|
102
|
+
| `article_title` / `article_description` | 文章标题 / 摘要 |
|
|
103
|
+
| `article_markdown` | Markdown 正文 |
|
|
104
|
+
| `article_publish_time` | 发布时间,Unix 时间戳 |
|
|
105
|
+
| `article_cover_image` / `images` | 封面链接 / 最终保留的正文图片链接列表 |
|
|
106
|
+
| `mp_name` / `mp_user_name` | 公众号名称 / 用户名 |
|
|
107
|
+
| `article_display_type` | `article`、`video`、`audio`、`image`、`text` 或 `unknown` |
|
|
108
|
+
| `article_url_type` | 输入链接类型:`short`、`long` 或 `unknown` |
|
|
109
|
+
| `article_is_paid` | 是否检测到付费标记 |
|
|
110
|
+
| `is_valid` | 关键字段是否完整的计算属性 |
|
|
111
|
+
|
|
112
|
+
完整字段、枚举、长链接识别条件及 JSON 序列化示例见 [API 参考](docs/api.md)。
|
|
113
|
+
|
|
114
|
+
## 使用边界与错误处理
|
|
115
|
+
|
|
116
|
+
- 基于页面 HTML 和已知脚本数据提取,不执行 JavaScript,不还原交互与视觉布局。图片返回链接,不下载媒体文件。
|
|
117
|
+
- 明确识别的 SVG 快速逐帧动画仅保留首帧;轮播及无法确认的帧序列保留可提取图片。具体规则见[正文转换规则](docs/content-rules.md)。
|
|
118
|
+
- 视频、音频分享可以没有文字附言。付费预览及部分卡片可能只返回文字提示;`is_valid=True` 不代表已获取付费全文、媒体文件或小程序内部内容。
|
|
119
|
+
- 页面结构变化或内容不可访问时,可能返回不完整结果。调用方应检查 `is_valid`;长链接的 `article_id` 为空属于正常情况。
|
|
120
|
+
- **自 2026-11-02 起,微信短链接文章页不再提供文章签名 `sn`,返回的 `article_sn` 为空字符串,不影响 `is_valid` 判断。**
|
|
121
|
+
- 检测到微信人机验证时抛出 `WeChatVerifyError`;HTTP 状态异常和网络错误通过 `httpx` 异常向外传播。接口不自动重试,处理示例见 [API 参考](docs/api.md#异常与不完整结果)。
|
|
122
|
+
|
|
123
|
+
## 文档
|
|
124
|
+
|
|
125
|
+
- [API 参考](docs/api.md):参数、全部返回字段、枚举、有效性判断和异常处理。
|
|
126
|
+
- [正文转换规则](docs/content-rules.md):图片与动画、微信组件、页面模板与回退规则。
|
|
127
|
+
- [开发指南](docs/development.md):本地安装、项目结构、离线测试和真实链接调试。
|
|
128
|
+
- [版本记录](https://github.com/huanjuedadehen/wechat-article-parser/releases):各版本的发布说明。
|
|
129
|
+
|
|
130
|
+
## 参与贡献
|
|
131
|
+
|
|
132
|
+
欢迎通过 [Issues](https://github.com/huanjuedadehen/wechat-article-parser/issues) 报告问题,或提交 Pull Request。
|
|
133
|
+
请附上可复现的文章链接、版本信息和预期结果;修改解析行为时补充离线回归用例。
|
|
134
|
+
开发环境和测试方法见[开发指南](docs/development.md)。
|
|
135
|
+
|
|
136
|
+
## 许可证
|
|
137
|
+
|
|
138
|
+
本项目使用 [MIT License](LICENSE)。
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# wechat-article-parser
|
|
2
|
+
|
|
3
|
+
微信公众号文章解析器。传入文章链接,提取公众号信息、文章元数据和 Markdown 正文,支持同步与异步调用。
|
|
4
|
+
|
|
5
|
+
## 功能
|
|
6
|
+
|
|
7
|
+
- 支持微信文章短链接和长链接。
|
|
8
|
+
- 支持标准图文、转载式文章、纯文本、视频与音频分享、图片轮播及全屏布局短文本。
|
|
9
|
+
- 提取标题、作者公众号、摘要、封面、发布时间、付费标记等信息,返回结构化数据。
|
|
10
|
+
- 将正文转换为 Markdown,提取正文图片链接,支持 SVG 内嵌图片和文字。
|
|
11
|
+
- 将投票、公众号推荐、小程序、红包封面等组件转换为可读文字或查看提示。
|
|
12
|
+
- 支持请求超时、自定义 User-Agent、代理和按需返回原始 HTML。
|
|
13
|
+
|
|
14
|
+
## 安装
|
|
15
|
+
|
|
16
|
+
需要 **Python 3.10+**。
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
python -m pip install wechat-article-parser
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`httpx`、`beautifulsoup4` 和 `markdownify` 会随包自动安装。
|
|
23
|
+
|
|
24
|
+
## 快速开始
|
|
25
|
+
|
|
26
|
+
将以下示例中的 URL 替换为实际文章链接。
|
|
27
|
+
|
|
28
|
+
### 同步调用
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
from wechat_article_parser import parse
|
|
32
|
+
|
|
33
|
+
result = parse("https://mp.weixin.qq.com/s/xxxxx")
|
|
34
|
+
|
|
35
|
+
if result.is_valid:
|
|
36
|
+
print(result.article_title)
|
|
37
|
+
print(result.mp_name)
|
|
38
|
+
print(result.article_markdown)
|
|
39
|
+
print(result.images)
|
|
40
|
+
else:
|
|
41
|
+
print("部分关键字段未能解析,请检查返回结果")
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### 异步调用
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
import asyncio
|
|
48
|
+
|
|
49
|
+
from wechat_article_parser import parse_async
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
async def main():
|
|
53
|
+
result = await parse_async("https://mp.weixin.qq.com/s/xxxxx")
|
|
54
|
+
if result.is_valid:
|
|
55
|
+
print(result.article_title)
|
|
56
|
+
print(result.article_markdown)
|
|
57
|
+
else:
|
|
58
|
+
print("部分关键字段未能解析,请检查返回结果")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
asyncio.run(main())
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 请求选项
|
|
65
|
+
|
|
66
|
+
两个接口均支持以下关键字参数:
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from wechat_article_parser import parse
|
|
70
|
+
|
|
71
|
+
result = parse(
|
|
72
|
+
"https://mp.weixin.qq.com/s/xxxxx",
|
|
73
|
+
timeout=30, # 默认 15 秒
|
|
74
|
+
user_agent="MyBot/1.0", # 默认使用内置 User-Agent
|
|
75
|
+
proxy="http://127.0.0.1:7890", # 可选,显式指定代理
|
|
76
|
+
include_raw_html=True, # 默认 False
|
|
77
|
+
)
|
|
78
|
+
print(result.raw_html)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## 返回结果
|
|
82
|
+
|
|
83
|
+
两个接口均返回 `ArticleResult` 数据类,常用字段如下:
|
|
84
|
+
|
|
85
|
+
| 字段 | 说明 |
|
|
86
|
+
|---|---|
|
|
87
|
+
| `article_title` / `article_description` | 文章标题 / 摘要 |
|
|
88
|
+
| `article_markdown` | Markdown 正文 |
|
|
89
|
+
| `article_publish_time` | 发布时间,Unix 时间戳 |
|
|
90
|
+
| `article_cover_image` / `images` | 封面链接 / 最终保留的正文图片链接列表 |
|
|
91
|
+
| `mp_name` / `mp_user_name` | 公众号名称 / 用户名 |
|
|
92
|
+
| `article_display_type` | `article`、`video`、`audio`、`image`、`text` 或 `unknown` |
|
|
93
|
+
| `article_url_type` | 输入链接类型:`short`、`long` 或 `unknown` |
|
|
94
|
+
| `article_is_paid` | 是否检测到付费标记 |
|
|
95
|
+
| `is_valid` | 关键字段是否完整的计算属性 |
|
|
96
|
+
|
|
97
|
+
完整字段、枚举、长链接识别条件及 JSON 序列化示例见 [API 参考](docs/api.md)。
|
|
98
|
+
|
|
99
|
+
## 使用边界与错误处理
|
|
100
|
+
|
|
101
|
+
- 基于页面 HTML 和已知脚本数据提取,不执行 JavaScript,不还原交互与视觉布局。图片返回链接,不下载媒体文件。
|
|
102
|
+
- 明确识别的 SVG 快速逐帧动画仅保留首帧;轮播及无法确认的帧序列保留可提取图片。具体规则见[正文转换规则](docs/content-rules.md)。
|
|
103
|
+
- 视频、音频分享可以没有文字附言。付费预览及部分卡片可能只返回文字提示;`is_valid=True` 不代表已获取付费全文、媒体文件或小程序内部内容。
|
|
104
|
+
- 页面结构变化或内容不可访问时,可能返回不完整结果。调用方应检查 `is_valid`;长链接的 `article_id` 为空属于正常情况。
|
|
105
|
+
- **自 2026-11-02 起,微信短链接文章页不再提供文章签名 `sn`,返回的 `article_sn` 为空字符串,不影响 `is_valid` 判断。**
|
|
106
|
+
- 检测到微信人机验证时抛出 `WeChatVerifyError`;HTTP 状态异常和网络错误通过 `httpx` 异常向外传播。接口不自动重试,处理示例见 [API 参考](docs/api.md#异常与不完整结果)。
|
|
107
|
+
|
|
108
|
+
## 文档
|
|
109
|
+
|
|
110
|
+
- [API 参考](docs/api.md):参数、全部返回字段、枚举、有效性判断和异常处理。
|
|
111
|
+
- [正文转换规则](docs/content-rules.md):图片与动画、微信组件、页面模板与回退规则。
|
|
112
|
+
- [开发指南](docs/development.md):本地安装、项目结构、离线测试和真实链接调试。
|
|
113
|
+
- [版本记录](https://github.com/huanjuedadehen/wechat-article-parser/releases):各版本的发布说明。
|
|
114
|
+
|
|
115
|
+
## 参与贡献
|
|
116
|
+
|
|
117
|
+
欢迎通过 [Issues](https://github.com/huanjuedadehen/wechat-article-parser/issues) 报告问题,或提交 Pull Request。
|
|
118
|
+
请附上可复现的文章链接、版本信息和预期结果;修改解析行为时补充离线回归用例。
|
|
119
|
+
开发环境和测试方法见[开发指南](docs/development.md)。
|
|
120
|
+
|
|
121
|
+
## 许可证
|
|
122
|
+
|
|
123
|
+
本项目使用 [MIT License](LICENSE)。
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# API 参考
|
|
2
|
+
|
|
3
|
+
[返回 README](../README.md) · [正文转换规则](content-rules.md)
|
|
4
|
+
|
|
5
|
+
## 解析接口
|
|
6
|
+
|
|
7
|
+
以下接口和类型均可从 `wechat_article_parser` 顶层导入。
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
from wechat_article_parser import ArticleResult
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def parse(
|
|
14
|
+
url: str,
|
|
15
|
+
*,
|
|
16
|
+
timeout: int = 15,
|
|
17
|
+
user_agent: str | None = None,
|
|
18
|
+
proxy: str | None = None,
|
|
19
|
+
include_raw_html: bool = False,
|
|
20
|
+
) -> ArticleResult: ...
|
|
21
|
+
|
|
22
|
+
async def parse_async(
|
|
23
|
+
url: str,
|
|
24
|
+
*,
|
|
25
|
+
timeout: int = 15,
|
|
26
|
+
user_agent: str | None = None,
|
|
27
|
+
proxy: str | None = None,
|
|
28
|
+
include_raw_html: bool = False,
|
|
29
|
+
) -> ArticleResult: ...
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
两个接口使用相同的解析逻辑,均返回 `ArticleResult`;异步接口需要 `await`。
|
|
33
|
+
每次调用创建一个 HTTP 客户端,请求时跟随重定向,结束后关闭客户端。
|
|
34
|
+
|
|
35
|
+
| 参数 | 说明 |
|
|
36
|
+
|---|---|
|
|
37
|
+
| `url` | 微信公众号文章链接,支持短链接和长链接 |
|
|
38
|
+
| `timeout` | 传给 HTTP 客户端的超时时间,单位秒,默认 `15` |
|
|
39
|
+
| `user_agent` | 自定义 User-Agent,默认使用内置浏览器标识 |
|
|
40
|
+
| `proxy` | 显式指定代理地址,例如 `http://127.0.0.1:7890`;默认 `None` |
|
|
41
|
+
| `include_raw_html` | 默认 `False`;为 `True` 时填充 `result.raw_html` |
|
|
42
|
+
|
|
43
|
+
`url` 之后的参数必须以关键字传入。`proxy=None` 时不显式设置代理;HTTP 客户端仍使用默认环境配置。
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from wechat_article_parser import parse
|
|
47
|
+
|
|
48
|
+
result = parse(
|
|
49
|
+
"https://mp.weixin.qq.com/s/xxxxx", # 替换为实际文章链接
|
|
50
|
+
timeout=30,
|
|
51
|
+
user_agent="MyBot/1.0",
|
|
52
|
+
proxy="http://127.0.0.1:7890",
|
|
53
|
+
include_raw_html=True,
|
|
54
|
+
)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## 链接识别
|
|
58
|
+
|
|
59
|
+
同步和异步接口均支持短链接 `/s/<22位文章ID>`(ID 由英文字母、数字、`_` 或 `-` 组成)和长链接 `/s?__biz=...&mid=...&idx=...&sn=...&chksm=...`。
|
|
60
|
+
根据原始输入 URL 识别链接类型:协议须为 HTTP/HTTPS,域名须为 `mp.weixin.qq.com`,且不能包含 URL 用户名或密码。
|
|
61
|
+
长链接路径须为 `/s`,且 `__biz`、`mid`、`idx`、`sn`、`chksm` 五个查询参数必须都存在且有值,
|
|
62
|
+
空字符串或纯空白不算有效值;重复参数中的每个值也须非空白。额外查询参数和片段不影响识别。
|
|
63
|
+
未满足识别条件时,`article_url_type` 为 `unknown`,仍尝试解析页面,不额外抛出链接校验异常。
|
|
64
|
+
|
|
65
|
+
长链接返回的 `article_id` 为空,其他字段继续从页面提取。
|
|
66
|
+
只有识别为 `long` 时,`is_valid` 才允许缺少 `article_id`。
|
|
67
|
+
|
|
68
|
+
`ArticleURLType` 的成员为 `SHORT = "short"`、`LONG = "long"`、`UNKNOWN = "unknown"`。
|
|
69
|
+
支持枚举比较、字符串比较和 JSON 序列化。链接类型按原始输入判断,不按重定向后的地址判断。
|
|
70
|
+
|
|
71
|
+
## 返回结果
|
|
72
|
+
|
|
73
|
+
`parse` 和 `parse_async` 均返回 `ArticleResult` 数据类,包含以下字段:
|
|
74
|
+
|
|
75
|
+
| 字段 | 类型 | 说明 |
|
|
76
|
+
|---|---|---|
|
|
77
|
+
| `mp_id_b64` | `str` | 公众号 ID(Base64 编码原始值) |
|
|
78
|
+
| `mp_id` | `int` | 公众号 ID(解码后的整数) |
|
|
79
|
+
| `mp_name` | `str` | 公众号名称 |
|
|
80
|
+
| `mp_alias` | `str` | 公众号别名 |
|
|
81
|
+
| `mp_user_name` | `str` | 公众号用户名(如 `aifaner383402` 或 `gh_9a1919baf888`),未获取到时为空字符串 |
|
|
82
|
+
| `mp_image` | `str` | 公众号头像链接 |
|
|
83
|
+
| `mp_description` | `str` | 公众号简介 |
|
|
84
|
+
| `mp_account_type` | `AccountType` | 账号类型:`AccountType.SUBSCRIPTION`(订阅号)/ `AccountType.SERVICE`(服务号)/ `AccountType.UNKNOWN`(未识别) |
|
|
85
|
+
| `article_id` | `str` | 短链接中的文章 ID;长链接为空字符串 |
|
|
86
|
+
| `article_url_type` | `ArticleURLType` | 输入链接类型:`short`、`long` 或 `unknown`(默认);可从包顶层导入枚举,支持字符串比较和 JSON 序列化 |
|
|
87
|
+
| `article_msg_id` | `int` | 文章所在的群发消息 ID |
|
|
88
|
+
| `article_idx` | `int` | 群发图文中的位置(从 1 开始) |
|
|
89
|
+
| `article_sn` | `str` | 文章签名(防伪校验);页面未提供时为空字符串,不影响 `is_valid` |
|
|
90
|
+
| `article_title` | `str` | 文章标题 |
|
|
91
|
+
| `article_cover_image` | `str` | 文章封面图链接 |
|
|
92
|
+
| `article_description` | `str` | 文章摘要 |
|
|
93
|
+
| `article_markdown` | `str` | 文章正文的 Markdown 内容;视频或音频分享没有文字附言时可为空 |
|
|
94
|
+
| `article_publish_time` | `int` | 发布时间(Unix 时间戳) |
|
|
95
|
+
| `article_is_paid` | `bool` | 是否检测到当前文章的付费标记,默认 `False`;不表示是否已获取付费全文 |
|
|
96
|
+
| `article_display_type` | `ArticleDisplayType` | 文章展示类型:`article`、`video`、`audio`、`image`、`text` 或 `unknown` |
|
|
97
|
+
| `article_item_show_type` | `int \| None` | 微信页面中的原始展示类型:`0` 图文、`5` 视频、`7` 音频、`8` 图片轮播、`10` 文字;其他已提取的数值原样保留,缺失或无法解析时为 `None` |
|
|
98
|
+
| `raw_html` | `str` | 原始 HTML 网页源代码(仅在 `include_raw_html=True` 时填充,否则为空字符串) |
|
|
99
|
+
| `images` | `list[str]` | 最终保留的正文图片链接,按顺序去重;不包含被省略的动画帧、小程序封面和头像、红包封面预览图 |
|
|
100
|
+
| `is_valid` | `bool` | 关键字段是否全部解析成功(属性) |
|
|
101
|
+
|
|
102
|
+
`is_valid` 为 `True` 的条件:`mp_id`、`mp_name`、`article_msg_id`、`article_idx`、`article_title`、`article_publish_time` 均不为空/零,且 `article_markdown` 非空或 `article_display_type in ("video", "audio")`。
|
|
103
|
+
`article_sn` 为非必需字段,页面未提供签名时仍可返回有效结果;这不改变输入长链接的参数要求,详见[链接识别](#链接识别)。
|
|
104
|
+
此外,`article_url_type == "long"` 时允许 `article_id` 为空;短链接和未识别链接仍要求 `article_id` 非空。
|
|
105
|
+
视频和音频分享允许没有文字正文,仍须满足其他关键字段要求;有效性不表示已获取视频或音频文件。
|
|
106
|
+
|
|
107
|
+
付费受限页面返回提示且关键元数据完整时,`is_valid` 也为 `True`,这不表示已获取付费正文。
|
|
108
|
+
|
|
109
|
+
未提取到的字符串默认为 `""`,整数默认为 `0`,图片列表默认为空列表;
|
|
110
|
+
展示类型、链接类型和账号类型默认为各自的 `UNKNOWN`。
|
|
111
|
+
`article_is_paid` 默认为 `False`,`article_item_show_type` 默认为 `None`。
|
|
112
|
+
|
|
113
|
+
## JSON 序列化
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
import json
|
|
117
|
+
from dataclasses import asdict
|
|
118
|
+
|
|
119
|
+
from wechat_article_parser import parse
|
|
120
|
+
|
|
121
|
+
result = parse("https://mp.weixin.qq.com/s/xxxxx")
|
|
122
|
+
data = asdict(result)
|
|
123
|
+
data["is_valid"] = result.is_valid
|
|
124
|
+
print(json.dumps(data, ensure_ascii=False, indent=2))
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
`is_valid` 是计算属性,**不包含在 `asdict(result)` 中**,需要时自行加入。
|
|
128
|
+
三个枚举均继承自 `str`,JSON 中直接输出字符串值。
|
|
129
|
+
|
|
130
|
+
## 判断是否为付费文章
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
from wechat_article_parser import parse
|
|
134
|
+
|
|
135
|
+
result = parse("https://mp.weixin.qq.com/s/xxxxx")
|
|
136
|
+
print(result.article_is_paid) # True:识别为付费文章
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
未检测到付费标记时返回 `False`;有可见预览或可访问正文的付费文章仍返回 `True`。
|
|
140
|
+
该字段包含在 `dataclasses.asdict(result)` 中,JSON 序列化为布尔值。
|
|
141
|
+
标记来源及预览处理方式见[正文转换规则](content-rules.md#微信组件与占位提示)。
|
|
142
|
+
|
|
143
|
+
## 判断文章展示类型
|
|
144
|
+
|
|
145
|
+
`ArticleDisplayType` 是字符串枚举,既支持枚举比较,也支持语义字符串比较:
|
|
146
|
+
|
|
147
|
+
| 枚举 | 字符串值 | 含义 |
|
|
148
|
+
|---|---|---|
|
|
149
|
+
| `ArticleDisplayType.ARTICLE` | `article` | 图文,包括转载式 |
|
|
150
|
+
| `ArticleDisplayType.VIDEO` | `video` | 视频分享 |
|
|
151
|
+
| `ArticleDisplayType.AUDIO` | `audio` | 音频分享 |
|
|
152
|
+
| `ArticleDisplayType.IMAGE` | `image` | 图片轮播 |
|
|
153
|
+
| `ArticleDisplayType.TEXT` | `text` | 文字消息 |
|
|
154
|
+
| `ArticleDisplayType.UNKNOWN` | `unknown` | 页面类型缺失或未支持 |
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
from wechat_article_parser import parse, ArticleDisplayType
|
|
158
|
+
|
|
159
|
+
result = parse("https://mp.weixin.qq.com/s/xxxxx")
|
|
160
|
+
if result.article_display_type == ArticleDisplayType.VIDEO:
|
|
161
|
+
print("这是视频分享")
|
|
162
|
+
|
|
163
|
+
if result.article_display_type == "video":
|
|
164
|
+
print("视频分享可以没有文字正文")
|
|
165
|
+
|
|
166
|
+
print(result.article_display_type) # 例如 video
|
|
167
|
+
print(result.article_item_show_type) # 例如 5
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
`article_display_type` 是返回结果中的数据字段,包含在 `dataclasses.asdict(result)` 中,JSON 序列化时输出语义字符串。
|
|
171
|
+
`article_item_show_type` 返回微信页面中 `item_show_type` 的原始数值,也包含在 `dataclasses.asdict(result)` 中;JSON 序列化时输出数字或 `null`。
|
|
172
|
+
|
|
173
|
+
## 判断账号类型
|
|
174
|
+
|
|
175
|
+
`AccountType` 继承自 `str` 枚举,既支持枚举比较,也支持与中文字符串直接比较:
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
from wechat_article_parser import parse, AccountType
|
|
179
|
+
|
|
180
|
+
result = parse("https://mp.weixin.qq.com/s/xxxxx")
|
|
181
|
+
|
|
182
|
+
# 推荐:枚举比较(有类型提示与 IDE 补全)
|
|
183
|
+
if result.mp_account_type == AccountType.SERVICE:
|
|
184
|
+
print("这是服务号")
|
|
185
|
+
|
|
186
|
+
# 也支持:字符串字面量比较
|
|
187
|
+
if result.mp_account_type == "服务号":
|
|
188
|
+
print("这是服务号")
|
|
189
|
+
|
|
190
|
+
# 打印直接输出中文值
|
|
191
|
+
print(f"账号类型: {result.mp_account_type}") # 账号类型: 订阅号
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
`AccountType.UNKNOWN` 的字符串值为空字符串 `""`。
|
|
195
|
+
|
|
196
|
+
## 异常与不完整结果
|
|
197
|
+
|
|
198
|
+
| 情况 | 行为 |
|
|
199
|
+
|---|---|
|
|
200
|
+
| 检测到微信验证码或人机验证页面 | 抛出 `WeChatVerifyError` |
|
|
201
|
+
| HTTP 状态异常 | `response.raise_for_status()` 抛出的 `httpx.HTTPStatusError` 直接向外传播 |
|
|
202
|
+
| 请求超时、连接失败等网络问题 | `httpx.RequestError` 及其子类直接向外传播,例如 `httpx.TimeoutException` |
|
|
203
|
+
| 页面可获取,但关键内容未提取完整 | 返回 `ArticleResult`,由调用方检查 `is_valid` |
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
import httpx
|
|
207
|
+
|
|
208
|
+
from wechat_article_parser import WeChatVerifyError, parse
|
|
209
|
+
|
|
210
|
+
try:
|
|
211
|
+
result = parse("https://mp.weixin.qq.com/s/xxxxx")
|
|
212
|
+
except WeChatVerifyError:
|
|
213
|
+
print("微信要求人机验证,请稍后重试")
|
|
214
|
+
except httpx.HTTPStatusError as exc:
|
|
215
|
+
print(f"HTTP 请求失败:{exc.response.status_code}")
|
|
216
|
+
except httpx.RequestError as exc:
|
|
217
|
+
print(f"网络请求失败:{exc}")
|
|
218
|
+
else:
|
|
219
|
+
if result.is_valid:
|
|
220
|
+
print(result.article_markdown)
|
|
221
|
+
else:
|
|
222
|
+
print("部分关键字段未能解析,请检查返回结果")
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
接口不自动重试。封面、摘要等非必填字段为空不影响有效性;
|
|
226
|
+
`is_valid` 不用于判断付费全文、视频文件或组件内部内容是否已获取。
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# 正文转换规则
|
|
2
|
+
|
|
3
|
+
[返回 README](../README.md) · [API 参考](api.md) · [开发指南](development.md)
|
|
4
|
+
|
|
5
|
+
本文说明 Markdown 和图片列表的保留规则,以及页面模板的识别方式。
|
|
6
|
+
解析器从页面 HTML 和已知脚本数据结构提取内容,不执行 JavaScript,也不渲染页面截图。
|
|
7
|
+
|
|
8
|
+
## 文字与排版
|
|
9
|
+
|
|
10
|
+
- 保留可提取的正文文字、段落、图片和普通正文链接,转换为 Markdown;不还原原网页的样式和交互布局。
|
|
11
|
+
- 富文本中的代码保留缩进与换行,代码中的不换行空格转为普通空格;表格中的管道符进行转义。
|
|
12
|
+
- 展开无格式作用的 `span`,合并重复嵌套的加粗、斜体和删除线,避免编辑器生成的冗余标签导致递归超限。
|
|
13
|
+
- 纯文本和分享附言保留换行。纯文本、全屏布局分支的标题超过 50 字符时,优先取不超过 50 字符的首句,否则截取前 30 字符。
|
|
14
|
+
|
|
15
|
+
## 图片与 SVG
|
|
16
|
+
|
|
17
|
+
| 内容 | 输出规则 |
|
|
18
|
+
|---|---|
|
|
19
|
+
| 静态正文图片 | 保留图片链接 |
|
|
20
|
+
| 轮播图片 | 保留各页图片 |
|
|
21
|
+
| SVG 缩放动画中的图片 | 保留图片,去掉缩放效果 |
|
|
22
|
+
| 明确识别的 SVG 快速逐帧序列 | 仅保留首帧 |
|
|
23
|
+
| 无法确认的帧序列、CSS / JavaScript 动画 | 保留可提取的图片,不执行动画 |
|
|
24
|
+
| SVG 延迟显示、点击展开的内容 | 直接输出可提取的文字和图片 |
|
|
25
|
+
| SVG 跳转链接 | 去掉链接,保留其中的文字或图片 |
|
|
26
|
+
| SVG 遮罩、空热区、动画控制和无来源空图片 | 不生成正文 |
|
|
27
|
+
|
|
28
|
+
富文本图片支持 `src`、`data-src`;SVG 图片支持 `background`、`background-image` 中的
|
|
29
|
+
HTTP(S) 地址(有引号或无引号)、`foreignObject` 内的图片,以及 `<image href="…">` / `<image xlink:href="…">`。
|
|
30
|
+
微信 CDN 图片地址会进行规格标准化,提取结果不保证与原始 URL 完全相同。
|
|
31
|
+
这里只返回链接,不下载图片文件,也不对 GIF 等图片文件解码、截取首帧。
|
|
32
|
+
|
|
33
|
+
`images` 收集最终保留的正文图片并按顺序去重;除已识别的帧序列缩减外,富文本 Markdown 保留原文重复图片。
|
|
34
|
+
小程序封面、头像和红包封面预览图按下文规则排除。
|
|
35
|
+
|
|
36
|
+
SVG `<text>` / `<tspan>` 和 `foreignObject` 中的 HTML 正文按原顺序保留。
|
|
37
|
+
定义、裁剪、遮罩、辅助标题/说明及路径等几何元素不作为正文输出。
|
|
38
|
+
这些规则按标签和结构处理,**不会根据画面语义判断一张图片是否为纯装饰**,也不把 SVG 矢量图形栅格化为图片。
|
|
39
|
+
|
|
40
|
+
### 快速逐帧序列的识别条件
|
|
41
|
+
|
|
42
|
+
当前仅对可确认的结构缩减图片,规则包括:
|
|
43
|
+
|
|
44
|
+
- 图片尺寸相同,按横向或纵向等距排列;循环的 `discrete` 平移动画按对应坐标依次切换。
|
|
45
|
+
- 合并连续重复坐标后至少有 3 个位置,总时长除以位置数大于 0 且不超过 0.2 秒。
|
|
46
|
+
- 支持动画序列末尾分号、重复坐标停留,以及通过最近 SVG 的 `viewBox` 或明确尺寸解析的百分比宽高。
|
|
47
|
+
- 背景 SVG 未设置自身位置和尺寸时,可读取直接外层 `foreignObject` 的几何信息。
|
|
48
|
+
- 帧间距大于图片尺寸时,须确认视口能容纳当前帧且不会展示相邻帧;未播放的尾图仅在视口恰好容纳一帧时省略。
|
|
49
|
+
- 存在不支持的额外变换、关键时间点或无法确认的几何结构时,不缩减帧序列。
|
|
50
|
+
|
|
51
|
+
## 微信组件与占位提示
|
|
52
|
+
|
|
53
|
+
以下规则用于富文本正文中的相应组件。组件文字按其正文位置输出;
|
|
54
|
+
仅有组件提示且关键元数据完整时,`is_valid` 也可能为 `True`。
|
|
55
|
+
|
|
56
|
+
| 内容 | Markdown 输出 | 范围说明 |
|
|
57
|
+
|---|---|---|
|
|
58
|
+
| 仅有视频号视频卡片或微信视频 iframe | 这是一篇纯视频内容。 | 不提取视频文件 |
|
|
59
|
+
| 仅有付费预览占位 | 这是一篇付费内容,正文需付费后查看。 | 保留公开元数据;可见预览正文正常提取 |
|
|
60
|
+
| 仅有商品广告卡片(`mp-common-cpsad`) | 这是一篇商品推荐内容。 | 已有文字和图片仍正常提取,不表示获取了商品详情 |
|
|
61
|
+
| 公众号推荐卡片(`mp-common-profile`) | `**公众号名称**:简介` | 缺少名称或简介时,仅输出已有字段 |
|
|
62
|
+
| 小程序卡片(`mp-common-miniprogram`) | 加粗标题、`小程序:名称`、请在微信中打开小程序查看内容。 | 缺少标题或名称时省略对应段落;不保留封面、头像和内部路径链接 |
|
|
63
|
+
| 红包封面(`mp-common-redpacket`) | 红包封面,请在微信中查看。 | 不展示名称、预览图或领取链接,不判断是否仍可领取 |
|
|
64
|
+
| 投票(`mp-common-vote`) | 加粗题目及选项列表 | 支持多题和多个组件;不输出票数,不推断单选或多选 |
|
|
65
|
+
|
|
66
|
+
投票数据缺失或异常时,保留组件已有文字;没有文字则输出“投票内容,请在微信中查看。”。
|
|
67
|
+
解析结果不代表完整投票内容或实时结果。
|
|
68
|
+
|
|
69
|
+
`article_is_paid` 独立于正文内容:有可见预览或可访问正文的付费文章仍会标记为 `True`。
|
|
70
|
+
识别来源为页面全局或 `window.cgiDataNew` 直接属性中的 `is_pay_subscribe`,以及正文中的 `mp-pay-preview-filter`。
|
|
71
|
+
|
|
72
|
+
## 页面模板与回退
|
|
73
|
+
|
|
74
|
+
解析优先读取全局 `item_show_type`:`0` 图文、`5` 视频、`7` 音频、`8` 图片轮播、`10` 文字。
|
|
75
|
+
音频与视频共用文字附言提取逻辑。展示类型的原始值和语义值分别写入
|
|
76
|
+
`article_item_show_type` 和 `article_display_type`。
|
|
77
|
+
|
|
78
|
+
字段缺失、未知或优先分支未提取到正文时,依次尝试:
|
|
79
|
+
|
|
80
|
+
| 顺序 | 模板 | HTML 特征 |
|
|
81
|
+
|---|---|---|
|
|
82
|
+
| 1 | 标准富文本 | `div.rich_media_content` |
|
|
83
|
+
| 2 | 转载式 | `div.original_page` |
|
|
84
|
+
| 3 | 纯文本 | `p#js_text_desc` |
|
|
85
|
+
| 4 | 视频分享 | `div#js_common_share_desc_wrap` |
|
|
86
|
+
| 5 | 图片轮播 | `div.share_media_swiper_content` |
|
|
87
|
+
| 6 | 全屏布局 | `div#js_fullscreen_layout_padding` |
|
|
88
|
+
|
|
89
|
+
每个分支使用独立候选结果,只有提取到正文才采用;失败分支不影响后续分支的标题、图片和元数据。
|
|
90
|
+
展示类型由 `item_show_type` 映射,不根据回退模板重新推断,因此未知类型也可能提取到正文。
|
|
91
|
+
视频、音频无附言时仍可通过最终元数据提取返回有效结果。
|
|
92
|
+
|
|
93
|
+
全屏文字只读取 `cgiDataNew.text_page_info` 的直接属性:优先使用 `content_noencode`,为空或没有可见正文时尝试 `content`。
|
|
94
|
+
外层或嵌套卡片中的同名字段不作为正文。图片和文字可位于不同脚本中,转义引号和换行会解码。
|
|
95
|
+
图文通过全屏脚本回退提取正文时,也会读取富文本元数据。
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# 开发指南
|
|
2
|
+
|
|
3
|
+
[返回 README](../README.md) · [正文转换规则](content-rules.md)
|
|
4
|
+
|
|
5
|
+
## 本地安装
|
|
6
|
+
|
|
7
|
+
需要 Python 3.10 或更高版本。建议使用虚拟环境:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
git clone https://github.com/huanjuedadehen/wechat-article-parser.git
|
|
11
|
+
cd wechat-article-parser
|
|
12
|
+
python3 -m venv .venv
|
|
13
|
+
source .venv/bin/activate
|
|
14
|
+
python -m pip install -e ".[dev]"
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Windows PowerShell 下使用 `.venv\Scripts\Activate.ps1` 激活虚拟环境。
|
|
18
|
+
构建系统为 hatchling,版本号、依赖及 pytest 配置集中在 `pyproject.toml`。
|
|
19
|
+
|
|
20
|
+
## 项目结构
|
|
21
|
+
|
|
22
|
+
```text
|
|
23
|
+
src/wechat_article_parser/
|
|
24
|
+
├── __init__.py # 公开接口和类型导出
|
|
25
|
+
├── models.py # ArticleResult、枚举和异常
|
|
26
|
+
└── parser.py # HTTP 抓取、页面提取与 Markdown 转换
|
|
27
|
+
tests/ # 离线回归用例与真实链接集成测试
|
|
28
|
+
docs/ # API、转换规则和开发说明
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`parse()` 和 `parse_async()` 共用 `_parse_html()`。
|
|
32
|
+
修改模板或组件行为前,可先阅读[正文转换规则](content-rules.md);公开字段约定见 [API 参考](api.md)。
|
|
33
|
+
|
|
34
|
+
## 离线回归测试
|
|
35
|
+
|
|
36
|
+
日常修改优先运行离线测试,不请求微信:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
python -m pytest tests --ignore=tests/test_parser.py -q
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
功能用例分布在 `tests/test_*_content.py`、`test_markdown.py`、`test_type_routing.py`、
|
|
43
|
+
`test_models.py`、`test_user_name.py` 等文件中。异步测试使用 `@pytest.mark.asyncio`,
|
|
44
|
+
项目已配置 `asyncio_mode = "auto"`。
|
|
45
|
+
|
|
46
|
+
## 真实链接集成测试
|
|
47
|
+
|
|
48
|
+
以下命令会访问微信,请按需要运行:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# 全部测试:包含离线回归与真实链接请求
|
|
52
|
+
python -m pytest tests -v
|
|
53
|
+
|
|
54
|
+
# 仅运行真实链接测试
|
|
55
|
+
python -m pytest tests/test_parser.py -v -s
|
|
56
|
+
|
|
57
|
+
# 仅运行同步测试
|
|
58
|
+
python -m pytest tests/test_parser.py -v -s -k "test_parse_sync"
|
|
59
|
+
|
|
60
|
+
# 仅运行异步测试
|
|
61
|
+
python -m pytest tests/test_parser.py -v -s -k "test_parse_async"
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
频繁请求可能触发验证码或 IP 限流。参数化同步/异步测试遇到 `WeChatVerifyError` 时会跳过,
|
|
65
|
+
**跳过不代表解析通过**。其他网络错误仍会导致测试失败。
|
|
66
|
+
|
|
67
|
+
## 调试单个链接
|
|
68
|
+
|
|
69
|
+
将示例 URL 替换为实际文章链接:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# 打印所有字段
|
|
73
|
+
python -m pytest tests/test_parser.py::test_fetch_all -s --url "https://mp.weixin.qq.com/s/xxxxx"
|
|
74
|
+
|
|
75
|
+
# 打印 Markdown
|
|
76
|
+
python -m pytest tests/test_parser.py::test_fetch_markdown -s --url "https://mp.weixin.qq.com/s/xxxxx"
|
|
77
|
+
|
|
78
|
+
# 打印原始 HTML
|
|
79
|
+
python -m pytest tests/test_parser.py::test_fetch_raw_html -s --url "https://mp.weixin.qq.com/s/xxxxx"
|
|
80
|
+
|
|
81
|
+
# 通过代理请求
|
|
82
|
+
python -m pytest tests/test_parser.py::test_fetch_all -s \
|
|
83
|
+
--url "https://mp.weixin.qq.com/s/xxxxx" \
|
|
84
|
+
--proxy "http://127.0.0.1:7890"
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`--url` 只供三个 `test_fetch_*` 调试入口使用,不替换参数化集成测试中的链接列表。
|
|
88
|
+
未传 `--url` 时,这三个入口会跳过;遇到验证码时则直接抛出异常。
|
|
89
|
+
`--proxy` 供真实链接测试使用。
|
|
90
|
+
|
|
91
|
+
`test_fetch_all` 和 `test_fetch_markdown` 只打印结果,不断言解析有效;
|
|
92
|
+
`test_fetch_raw_html` 只检查原始 HTML 非空。验证修复时应同时检查关键字段、`is_valid` 和实际正文内容。
|
|
93
|
+
|
|
94
|
+
## 提交问题与贡献代码
|
|
95
|
+
|
|
96
|
+
- 通过 [Issues](https://github.com/huanjuedadehen/wechat-article-parser/issues) 报告问题,提供包版本、Python 版本、可复现的文章链接、预期结果和实际结果或异常。
|
|
97
|
+
- 分享 HTML、日志或代理配置前,移除凭据及不适合公开的信息。
|
|
98
|
+
- 修改解析行为时添加对应的离线 HTML 或脚本样例,断言正文、图片或元数据的预期结果,再按需要验证真实链接。
|
|
99
|
+
- 保持同步和异步接口行为一致;修改公开接口或转换规则时同步更新文档。
|
|
100
|
+
- 注释与文档使用中文。提交 PR 前运行离线回归测试,并在 PR 中说明改动目的与验证方式。
|