parsehub 2.2.0__tar.gz → 2.2.1__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.
Files changed (75) hide show
  1. {parsehub-2.2.0/src/parsehub.egg-info → parsehub-2.2.1}/PKG-INFO +2 -2
  2. {parsehub-2.2.0 → parsehub-2.2.1}/pyproject.toml +2 -2
  3. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/parsers/parser/douban.py +20 -6
  4. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/provider_api/douban.py +4 -2
  5. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/provider_api/xhs.py +13 -6
  6. {parsehub-2.2.0 → parsehub-2.2.1/src/parsehub.egg-info}/PKG-INFO +2 -2
  7. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub.egg-info/requires.txt +1 -1
  8. {parsehub-2.2.0 → parsehub-2.2.1}/LICENSE +0 -0
  9. {parsehub-2.2.0 → parsehub-2.2.1}/README.md +0 -0
  10. {parsehub-2.2.0 → parsehub-2.2.1}/setup.cfg +0 -0
  11. {parsehub-2.2.0 → parsehub-2.2.1}/src/__init__.py +0 -0
  12. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/__init__.py +0 -0
  13. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/cli.py +0 -0
  14. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/cli_config.py +0 -0
  15. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/config/__init__.py +0 -0
  16. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/config/config.py +0 -0
  17. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/errors.py +0 -0
  18. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/parsers/__init__.py +0 -0
  19. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/parsers/base/__init__.py +0 -0
  20. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/parsers/base/base.py +0 -0
  21. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/parsers/base/ytdlp.py +0 -0
  22. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/parsers/parser/__init__.py +0 -0
  23. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/parsers/parser/bilibili.py +0 -0
  24. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/parsers/parser/coolapk.py +0 -0
  25. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/parsers/parser/douyin.py +0 -0
  26. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/parsers/parser/facebook.py +0 -0
  27. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/parsers/parser/instagram.py +0 -0
  28. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/parsers/parser/kuaishou.py +0 -0
  29. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/parsers/parser/pipix.py +0 -0
  30. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/parsers/parser/snapchat.py +0 -0
  31. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/parsers/parser/threads.py +0 -0
  32. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/parsers/parser/tieba.py +0 -0
  33. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/parsers/parser/tiktok.py +0 -0
  34. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/parsers/parser/twitter.py +0 -0
  35. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/parsers/parser/weibo.py +0 -0
  36. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/parsers/parser/weixin.py +0 -0
  37. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/parsers/parser/xhs.py +0 -0
  38. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/parsers/parser/xiaoheihe.py +0 -0
  39. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/parsers/parser/youtube.py +0 -0
  40. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/parsers/parser/zhihu.py +0 -0
  41. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/parsers/parser/zuiyou.py +0 -0
  42. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/provider_api/__init__.py +0 -0
  43. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/provider_api/bilibili.py +0 -0
  44. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/provider_api/coolapk.py +0 -0
  45. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/provider_api/douyin.py +0 -0
  46. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/provider_api/instagram.py +0 -0
  47. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/provider_api/kuaishou.py +0 -0
  48. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/provider_api/pipix.py +0 -0
  49. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/provider_api/threads.py +0 -0
  50. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/provider_api/tieba.py +0 -0
  51. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/provider_api/tiktok.py +0 -0
  52. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/provider_api/twitter.py +0 -0
  53. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/provider_api/weibo.py +0 -0
  54. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/provider_api/weixin.py +0 -0
  55. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/provider_api/xiaoheihe.py +0 -0
  56. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/provider_api/zhihu.py +0 -0
  57. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/provider_api/zuiyou.py +0 -0
  58. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/types/__init__.py +0 -0
  59. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/types/callback.py +0 -0
  60. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/types/media_file.py +0 -0
  61. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/types/media_ref.py +0 -0
  62. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/types/platform.py +0 -0
  63. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/types/post.py +0 -0
  64. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/types/result.py +0 -0
  65. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/utils/downloader.py +0 -0
  66. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/utils/helpers.py +0 -0
  67. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub/utils/media_info.py +0 -0
  68. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub.egg-info/SOURCES.txt +0 -0
  69. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub.egg-info/dependency_links.txt +0 -0
  70. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub.egg-info/entry_points.txt +0 -0
  71. {parsehub-2.2.0 → parsehub-2.2.1}/src/parsehub.egg-info/top_level.txt +0 -0
  72. {parsehub-2.2.0 → parsehub-2.2.1}/test/test_cli.py +0 -0
  73. {parsehub-2.2.0 → parsehub-2.2.1}/test/test_cli_config.py +0 -0
  74. {parsehub-2.2.0 → parsehub-2.2.1}/test/test_core_offline.py +0 -0
  75. {parsehub-2.2.0 → parsehub-2.2.1}/test/test_downloader.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: parsehub
3
- Version: 2.2.0
3
+ Version: 2.2.1
4
4
  Summary: 轻量、异步、开箱即用的社交媒体聚合解析库
5
5
  Author-email: 梓澪 <zilingmio@gmail.com>
6
6
  License: MIT
@@ -28,7 +28,7 @@ Requires-Dist: pydantic>=1.10.19
28
28
  Requires-Dist: markdownify>=1.1.0
29
29
  Requires-Dist: markdown>=3.7
30
30
  Requires-Dist: requests
31
- Requires-Dist: httpx>=0.24.1
31
+ Requires-Dist: httpx[socks]>=0.24.1
32
32
  Requires-Dist: pillow>=12.1.0
33
33
  Requires-Dist: python-slugify[unidecode]>=8.0.4
34
34
  Requires-Dist: opencv-python-headless>=4.13.0.92
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "parsehub"
3
- version = "2.2.0"
3
+ version = "2.2.1"
4
4
  description = "轻量、异步、开箱即用的社交媒体聚合解析库"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12.0"
@@ -31,7 +31,7 @@ dependencies = [
31
31
  "markdownify>=1.1.0",
32
32
  "markdown>=3.7",
33
33
  "requests",
34
- "httpx>=0.24.1",
34
+ "httpx[socks]>=0.24.1",
35
35
  "pillow>=12.1.0",
36
36
  "python-slugify[unidecode]>=8.0.4",
37
37
  "opencv-python-headless>=4.13.0.92",
@@ -1,12 +1,15 @@
1
1
  from pathlib import Path
2
+ from typing import Union
2
3
 
3
4
  from ...provider_api.douban import IMAGE_REFERER, Douban, DoubanError, DoubanPhoto, DoubanVideo
4
5
  from ...types import (
5
6
  AniRef,
6
7
  AnyMediaRef,
7
8
  DownloadResult,
9
+ ImageParseResult,
8
10
  ImageRef,
9
11
  ParseError,
12
+ ParseResult,
10
13
  Platform,
11
14
  ProgressCallback,
12
15
  RichTextParseResult,
@@ -22,7 +25,7 @@ class DoubanParser(BaseParser):
22
25
  __match__ = r"^(http(s)?://)?(((www|m)\.)?douban\.com/((group/)?topic/\d+|doubanapp/dispatch)|douc\.cc/.+)"
23
26
  __redirect_keywords__ = ["douc.cc", "doubanapp/dispatch"]
24
27
 
25
- async def _do_parse(self, raw_url: str) -> "DoubanRichTextParseResult":
28
+ async def _do_parse(self, raw_url: str) -> Union["DoubanRichTextParseResult", "DoubanImageParseResult"]:
26
29
  try:
27
30
  topic = await Douban(proxy=self.proxy, cookie=self.cookie.get_value()).parse(raw_url)
28
31
  except DoubanError as e:
@@ -30,11 +33,16 @@ class DoubanParser(BaseParser):
30
33
  except Exception as e:
31
34
  raise ParseError("豆瓣解析失败: 未知错误") from e
32
35
 
36
+ photos = [self.to_media_ref(p) for p in topic.photos]
37
+
38
+ if topic.image_layout == "horizontal":
39
+ return DoubanImageParseResult(title=topic.title, photo=photos, content=topic.text_content)
40
+
33
41
  # 图片和视频在正文里有位置关系, 交给 RichText 由 markdown 保留顺序
34
- media: list[AnyMediaRef] = [
35
- *([self.to_video_ref(topic.video)] if topic.video else []),
36
- *(self.to_media_ref(p) for p in topic.photos),
37
- ]
42
+ media: list[AnyMediaRef] = []
43
+ if topic.video:
44
+ media.append(self.to_video_ref(topic.video))
45
+ media.extend(photos)
38
46
  return DoubanRichTextParseResult(title=topic.title, media=media, markdown_content=topic.markdown_content)
39
47
 
40
48
  @staticmethod
@@ -59,7 +67,7 @@ class DoubanParser(BaseParser):
59
67
  )
60
68
 
61
69
 
62
- class DoubanRichTextParseResult(RichTextParseResult):
70
+ class DoubanParseResult(ParseResult):
63
71
  async def _do_download(
64
72
  self,
65
73
  *,
@@ -83,4 +91,10 @@ class DoubanRichTextParseResult(RichTextParseResult):
83
91
  )
84
92
 
85
93
 
94
+ class DoubanImageParseResult(ImageParseResult, DoubanParseResult): ...
95
+
96
+
97
+ class DoubanRichTextParseResult(RichTextParseResult, DoubanParseResult): ...
98
+
99
+
86
100
  __all__ = ["DoubanParser", "DoubanRichTextParseResult"]
@@ -1,6 +1,6 @@
1
1
  import re
2
2
  from dataclasses import dataclass, field
3
- from typing import Any, cast
3
+ from typing import Any, Literal, cast
4
4
 
5
5
  import httpx
6
6
  from bs4 import BeautifulSoup
@@ -80,6 +80,7 @@ class DoubanTopic:
80
80
  title: str
81
81
  markdown_content: str
82
82
  text_content: str
83
+ image_layout: Literal["horizontal", "vertical"] # vertical 图文混排, horizontal 图集
83
84
  video: DoubanVideo | None = None
84
85
  photos: list[DoubanPhoto] = field(default_factory=list)
85
86
 
@@ -95,7 +96,8 @@ class DoubanTopic:
95
96
  markdown_content=markdown_content,
96
97
  text_content=text_content,
97
98
  video=parse_video(video_info) if video_info else None,
98
- photos=[p for photo in data.get("photos") or [] if (p := parse_photo(photo))],
99
+ photos=[p for photo in data.get("photos", []) if (p := parse_photo(photo))],
100
+ image_layout=data.get("image_layout", "horizontal"),
99
101
  )
100
102
 
101
103
 
@@ -10,6 +10,8 @@ from typing import Any, cast
10
10
  import httpx
11
11
  from bs4 import BeautifulSoup
12
12
 
13
+ from ..errors import ParseError
14
+
13
15
 
14
16
  class XHSAPI:
15
17
  def __init__(self, proxy: str | None = None, cookie: dict | None = None):
@@ -17,8 +19,13 @@ class XHSAPI:
17
19
  self.cookie = cookie
18
20
 
19
21
  async def __fetch_html(self, url: str) -> str:
20
- async with httpx.AsyncClient(proxy=self.proxy, cookies=self.cookie) as client:
21
- return (await client.get(url, timeout=30)).text
22
+ async with httpx.AsyncClient(proxy=self.proxy, cookies=self.cookie, follow_redirects=True) as client:
23
+ result = await client.get(url, timeout=30)
24
+ if "/login" in str(result.url):
25
+ raise ParseError("该帖子需要登录后查看")
26
+ elif "/404" in str(result.url):
27
+ raise ParseError("帖子不存在")
28
+ return result.text
22
29
 
23
30
  @staticmethod
24
31
  async def __extract_data(html: str) -> dict[str, Any]:
@@ -27,7 +34,7 @@ class XHSAPI:
27
34
  script for script in soup.find_all("script") if script.text.lstrip().startswith("window.__INITIAL_STATE__")
28
35
  ]
29
36
  if not scripts:
30
- raise ValueError("No data found")
37
+ raise ParseError("No data found")
31
38
  script = scripts[0].text
32
39
  json_data = script.replace("window.__INITIAL_STATE__=", "")
33
40
  json_data = re.sub(r"\bundefined\b", "null", json_data) # 清理js对象
@@ -36,11 +43,11 @@ class XHSAPI:
36
43
 
37
44
  def __parse(self, data: dict[str, Any]) -> XHSPost:
38
45
  if not data.get("note"):
39
- raise ValueError("该帖子需要登录后查看")
46
+ raise ParseError("该帖子需要登录后查看")
40
47
  first_note_id = data["note"]["firstNoteId"]
41
48
  note = data["note"]["noteDetailMap"][first_note_id]["note"]
42
49
  if not note:
43
- raise ValueError("未获取到内容, 该帖子可能需要登录后查看")
50
+ raise ParseError("未获取到内容, 该帖子可能需要登录后查看")
44
51
 
45
52
  title = note["title"]
46
53
  desc = note["desc"]
@@ -79,7 +86,7 @@ class XHSAPI:
79
86
  stream = media["stream"]
80
87
  selected_stream = self.__select_stream(stream)
81
88
  if not selected_stream:
82
- raise ValueError("未获取到视频流")
89
+ raise ParseError("未获取到视频流")
83
90
  stream = selected_stream[0]
84
91
  media_list.append(
85
92
  XHSMedia(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: parsehub
3
- Version: 2.2.0
3
+ Version: 2.2.1
4
4
  Summary: 轻量、异步、开箱即用的社交媒体聚合解析库
5
5
  Author-email: 梓澪 <zilingmio@gmail.com>
6
6
  License: MIT
@@ -28,7 +28,7 @@ Requires-Dist: pydantic>=1.10.19
28
28
  Requires-Dist: markdownify>=1.1.0
29
29
  Requires-Dist: markdown>=3.7
30
30
  Requires-Dist: requests
31
- Requires-Dist: httpx>=0.24.1
31
+ Requires-Dist: httpx[socks]>=0.24.1
32
32
  Requires-Dist: pillow>=12.1.0
33
33
  Requires-Dist: python-slugify[unidecode]>=8.0.4
34
34
  Requires-Dist: opencv-python-headless>=4.13.0.92
@@ -11,7 +11,7 @@ pydantic>=1.10.19
11
11
  markdownify>=1.1.0
12
12
  markdown>=3.7
13
13
  requests
14
- httpx>=0.24.1
14
+ httpx[socks]>=0.24.1
15
15
  pillow>=12.1.0
16
16
  python-slugify[unidecode]>=8.0.4
17
17
  opencv-python-headless>=4.13.0.92
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes