wechat-article-parser 0.0.2__tar.gz → 0.0.3__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wechat-article-parser
3
- Version: 0.0.2
3
+ Version: 0.0.3
4
4
  Summary: WeChat MP article parser - extract metadata and content from WeChat public account articles
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "wechat-article-parser"
7
- version = "0.0.2"
7
+ version = "0.0.3"
8
8
  description = "WeChat MP article parser - extract metadata and content from WeChat public account articles"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -0,0 +1,4 @@
1
+ from .models import AccountType, ArticleResult, WeChatVerifyError
2
+ from .parser import parse, parse_async
3
+
4
+ __all__ = ["parse", "parse_async", "ArticleResult", "AccountType", "WeChatVerifyError"]
@@ -1,12 +1,27 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass, field
4
+ from enum import Enum
4
5
 
5
6
 
6
7
  class WeChatVerifyError(Exception):
7
8
  """微信返回了验证码/人机验证页面,而非文章内容时抛出此异常。"""
8
9
 
9
10
 
11
+ class AccountType(str, Enum):
12
+ """公众号账号类型。继承自 str,既可按枚举比较也可按字符串比较。"""
13
+
14
+ UNKNOWN = ""
15
+ SUBSCRIPTION = "订阅号"
16
+ SERVICE = "服务号"
17
+
18
+ def __str__(self) -> str:
19
+ return self.value
20
+
21
+ def __format__(self, format_spec: str) -> str:
22
+ return format(self.value, format_spec)
23
+
24
+
10
25
  @dataclass
11
26
  class ArticleResult:
12
27
  """微信公众号文章的解析结果。"""
@@ -18,6 +33,7 @@ class ArticleResult:
18
33
  mp_alias: str = ""
19
34
  mp_image: str = ""
20
35
  mp_description: str = ""
36
+ mp_account_type: AccountType = AccountType.UNKNOWN
21
37
 
22
38
  # 文章信息
23
39
  article_id: str = ""
@@ -11,7 +11,7 @@ import httpx
11
11
  from bs4 import BeautifulSoup, Tag
12
12
  from markdownify import MarkdownConverter
13
13
 
14
- from .models import ArticleResult, WeChatVerifyError
14
+ from .models import AccountType, ArticleResult, WeChatVerifyError
15
15
 
16
16
  _USER_AGENT = (
17
17
  "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
@@ -76,6 +76,15 @@ def _normalize_image_url(url: str) -> str:
76
76
  return url
77
77
 
78
78
 
79
+ def _service_type_to_account_type(value: str) -> AccountType:
80
+ """映射 WeChat new_service_type 到账号类型:0/1 → 订阅号,2 → 服务号。"""
81
+ if value in ("0", "1"):
82
+ return AccountType.SUBSCRIPTION
83
+ if value == "2":
84
+ return AccountType.SERVICE
85
+ return AccountType.UNKNOWN
86
+
87
+
79
88
  def _extract_picture_cdn_urls(script_text: str) -> list[str]:
80
89
  """从 picture_page_info_list 中只提取正文图片的 cdn_url,排除 watermark_info 和 share_cover 中的。"""
81
90
  urls: list[str] = []
@@ -120,8 +129,21 @@ def _extract_meta(soup: BeautifulSoup, result: ArticleResult) -> None:
120
129
  setattr(result, attr, value)
121
130
 
122
131
 
132
+ def _extract_account_type(script_text: str, result: ArticleResult) -> None:
133
+ """从 new_service_type 中提取账号类型:0/1 → 订阅号,2 → 服务号。"""
134
+ if result.mp_account_type:
135
+ return
136
+ m = re.search(r"new_service_type:\s*'(\d+)'", script_text)
137
+ if m:
138
+ account_type = _service_type_to_account_type(m.group(1))
139
+ if account_type != AccountType.UNKNOWN:
140
+ result.mp_account_type = account_type
141
+
142
+
123
143
  def _extract_rich_text_meta(script_text: str, result: ArticleResult) -> None:
124
144
  """从富文本文章的 script 标签中提取元数据。"""
145
+ _extract_account_type(script_text, result)
146
+
125
147
  if "var hd_head_img" in script_text:
126
148
  m = re.search(r'var hd_head_img = "([^"]+)"', script_text)
127
149
  if m:
@@ -164,6 +186,8 @@ def _extract_rich_text_meta(script_text: str, result: ArticleResult) -> None:
164
186
 
165
187
  def _extract_swiper_meta(script_text: str, result: ArticleResult) -> None:
166
188
  """从图片轮播 / 纯文本 / 视频分享页面的 script 标签中提取元数据。"""
189
+ _extract_account_type(script_text, result)
190
+
167
191
  if "window.__initCgiDataConfig =" in script_text:
168
192
  m = re.search(r"d\.hd_head_img.*?:\s*'([^']+)'", script_text)
169
193
  if m:
@@ -12,6 +12,8 @@ TEST_URLS = [
12
12
  "https://mp.weixin.qq.com/s/DmZXjgIzq5gBo3H-YtcjVw",
13
13
  "https://mp.weixin.qq.com/s/80bysSCadvy9VbaovXBv2g",
14
14
  "https://mp.weixin.qq.com/s/sfwGsafriO9sm6PfbQVXhw",
15
+ "https://mp.weixin.qq.com/s/h8E6riExCaH2Znmnprj-WQ",
16
+ "https://mp.weixin.qq.com/s/ySQdtsRlRmAl_skdc5HQ-A",
15
17
  ]
16
18
 
17
19
 
@@ -43,6 +45,7 @@ def _assert_result(result: ArticleResult, url: str) -> None:
43
45
  print(f"公众号ID: {result.mp_id}")
44
46
  print(f"公众号名称: {result.mp_name}")
45
47
  print(f"公众号别名: {result.mp_alias}")
48
+ print(f"账号类型: {result.mp_account_type}")
46
49
  print(
47
50
  f"公众号图片: {result.mp_image[:80]}..."
48
51
  if result.mp_image
@@ -98,6 +101,7 @@ def test_fetch_all(url: str) -> None:
98
101
  print(f"公众号ID: {result.mp_id}")
99
102
  print(f"公众号名称: {result.mp_name}")
100
103
  print(f"公众号别名: {result.mp_alias}")
104
+ print(f"账号类型: {result.mp_account_type}")
101
105
  print(f"公众号图片: {result.mp_image}")
102
106
  print(f"公众号简介: {result.mp_description}")
103
107
  print(f"文章ID: {result.article_id}")
@@ -1,4 +0,0 @@
1
- from .models import ArticleResult, WeChatVerifyError
2
- from .parser import parse, parse_async
3
-
4
- __all__ = ["parse", "parse_async", "ArticleResult", "WeChatVerifyError"]