baijiahao.py 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. # -*- coding: utf-8 -*-
  2. """
  3. 百家号视频发布器
  4. """
  5. import asyncio
  6. import json
  7. from typing import List
  8. from datetime import datetime
  9. from .base import (
  10. BasePublisher, PublishParams, PublishResult,
  11. WorkItem, WorksResult, CommentItem, CommentsResult
  12. )
  13. class BaijiahaoPublisher(BasePublisher):
  14. """
  15. 百家号视频发布器
  16. 使用 Playwright 自动化操作百家号创作者中心
  17. """
  18. platform_name = "baijiahao"
  19. login_url = "https://baijiahao.baidu.com/"
  20. publish_url = "https://baijiahao.baidu.com/builder/rc/edit?type=video"
  21. cookie_domain = ".baidu.com"
  22. # 登录检测配置
  23. login_check_url = "https://baijiahao.baidu.com/builder/rc/home"
  24. login_indicators = ["passport.baidu.com", "/login", "wappass.baidu.com"]
  25. login_selectors = ['text="登录"', 'text="请登录"', '[class*="login-btn"]']
  26. async def get_account_info(self, cookies: str) -> dict:
  27. """
  28. 获取百家号账号信息
  29. 通过调用 settingInfo API 获取用户信息
  30. """
  31. print(f"\n{'='*60}")
  32. print(f"[{self.platform_name}] 获取账号信息")
  33. print(f"{'='*60}")
  34. try:
  35. await self.init_browser()
  36. cookie_list = self.parse_cookies(cookies)
  37. await self.set_cookies(cookie_list)
  38. if not self.page:
  39. raise Exception("Page not initialized")
  40. # 访问百家号后台首页
  41. print(f"[{self.platform_name}] 访问后台首页...")
  42. await self.page.goto(self.login_check_url, wait_until="domcontentloaded", timeout=30000)
  43. await asyncio.sleep(3)
  44. # 检查登录状态
  45. current_url = self.page.url
  46. print(f"[{self.platform_name}] 当前 URL: {current_url}")
  47. for indicator in self.login_indicators:
  48. if indicator in current_url:
  49. print(f"[{self.platform_name}] 检测到登录页面,Cookie 已失效")
  50. return {
  51. "success": False,
  52. "error": "Cookie 已失效,需要重新登录",
  53. "need_login": True
  54. }
  55. # 调用 settingInfo API 获取用户信息
  56. print(f"[{self.platform_name}] 调用 settingInfo API...")
  57. api_result = await self.page.evaluate('''
  58. async () => {
  59. try {
  60. const response = await fetch('https://baijiahao.baidu.com/user-ui/cms/settingInfo', {
  61. method: 'GET',
  62. credentials: 'include',
  63. headers: {
  64. 'Accept': 'application/json, text/plain, */*'
  65. }
  66. });
  67. return await response.json();
  68. } catch (e) {
  69. return { error: e.message };
  70. }
  71. }
  72. ''')
  73. print(f"[{self.platform_name}] API 响应: errno={api_result.get('errno')}")
  74. if api_result.get('error'):
  75. return {
  76. "success": False,
  77. "error": api_result.get('error')
  78. }
  79. if api_result.get('errno') == 0 and api_result.get('data'):
  80. data = api_result['data']
  81. account_info = {
  82. "success": True,
  83. "account_id": str(data.get('new_uc_id', '')) or f"baijiahao_{int(datetime.now().timestamp() * 1000)}",
  84. "account_name": data.get('name', '') or '百家号账号',
  85. "avatar_url": data.get('avatar', ''),
  86. "fans_count": 0, # 百家号 API 不直接返回粉丝数
  87. "works_count": 0,
  88. }
  89. print(f"[{self.platform_name}] 获取成功: {account_info['account_name']}")
  90. return account_info
  91. else:
  92. error_msg = api_result.get('errmsg', '未知错误')
  93. print(f"[{self.platform_name}] API 返回错误: {error_msg}")
  94. # 如果是登录相关错误,标记需要重新登录
  95. if api_result.get('errno') in [10000010, 10001401]:
  96. return {
  97. "success": False,
  98. "error": error_msg,
  99. "need_login": True
  100. }
  101. return {
  102. "success": False,
  103. "error": error_msg
  104. }
  105. except Exception as e:
  106. import traceback
  107. traceback.print_exc()
  108. return {
  109. "success": False,
  110. "error": str(e)
  111. }
  112. finally:
  113. await self.close_browser()
  114. async def publish(self, cookies: str, params: PublishParams) -> PublishResult:
  115. """发布视频到百家号"""
  116. print(f"\n{'='*60}")
  117. print(f"[{self.platform_name}] 开始发布视频")
  118. print(f"[{self.platform_name}] 视频路径: {params.video_path}")
  119. print(f"[{self.platform_name}] 标题: {params.title}")
  120. print(f"{'='*60}")
  121. # TODO: 实现百家号视频发布逻辑
  122. return PublishResult(
  123. success=False,
  124. platform=self.platform_name,
  125. error="百家号发布功能暂未实现"
  126. )
  127. async def get_works(self, cookies: str, page: int = 0, page_size: int = 20) -> WorksResult:
  128. """获取百家号作品列表"""
  129. print(f"\n{'='*60}")
  130. print(f"[{self.platform_name}] 获取作品列表")
  131. print(f"[{self.platform_name}] page={page}, page_size={page_size}")
  132. print(f"{'='*60}")
  133. works: List[WorkItem] = []
  134. total = 0
  135. has_more = False
  136. try:
  137. await self.init_browser()
  138. cookie_list = self.parse_cookies(cookies)
  139. await self.set_cookies(cookie_list)
  140. if not self.page:
  141. raise Exception("Page not initialized")
  142. # 访问内容管理页面
  143. await self.page.goto("https://baijiahao.baidu.com/builder/rc/content", wait_until="domcontentloaded", timeout=30000)
  144. await asyncio.sleep(3)
  145. # 检查登录状态
  146. current_url = self.page.url
  147. for indicator in self.login_indicators:
  148. if indicator in current_url:
  149. raise Exception("Cookie 已过期,请重新登录")
  150. # 调用作品列表 API
  151. cursor = page * page_size
  152. api_result = await self.page.evaluate(f'''
  153. async () => {{
  154. try {{
  155. const response = await fetch('https://baijiahao.baidu.com/pcui/article/lists?start={cursor}&count={page_size}&article_type=video', {{
  156. method: 'GET',
  157. credentials: 'include',
  158. headers: {{
  159. 'Accept': 'application/json'
  160. }}
  161. }});
  162. return await response.json();
  163. }} catch (e) {{
  164. return {{ error: e.message }};
  165. }}
  166. }}
  167. ''')
  168. print(f"[{self.platform_name}] API 响应: {json.dumps(api_result, ensure_ascii=False)[:200]}")
  169. if api_result.get('errno') == 0:
  170. article_list = api_result.get('data', {}).get('article_list', [])
  171. has_more = api_result.get('data', {}).get('has_more', False)
  172. for article in article_list:
  173. work_id = str(article.get('article_id', ''))
  174. if not work_id:
  175. continue
  176. works.append(WorkItem(
  177. work_id=work_id,
  178. title=article.get('title', ''),
  179. cover_url=article.get('cover_images', [''])[0] if article.get('cover_images') else '',
  180. duration=0,
  181. status='published',
  182. publish_time=article.get('publish_time', ''),
  183. play_count=int(article.get('read_count', 0)),
  184. like_count=int(article.get('like_count', 0)),
  185. comment_count=int(article.get('comment_count', 0)),
  186. share_count=int(article.get('share_count', 0)),
  187. ))
  188. total = len(works)
  189. print(f"[{self.platform_name}] 获取到 {total} 个作品")
  190. except Exception as e:
  191. import traceback
  192. traceback.print_exc()
  193. return WorksResult(
  194. success=False,
  195. platform=self.platform_name,
  196. error=str(e)
  197. )
  198. finally:
  199. await self.close_browser()
  200. return WorksResult(
  201. success=True,
  202. platform=self.platform_name,
  203. works=works,
  204. total=total,
  205. has_more=has_more
  206. )
  207. async def get_comments(self, cookies: str, work_id: str, cursor: str = "") -> CommentsResult:
  208. """获取百家号作品评论"""
  209. # TODO: 实现评论获取逻辑
  210. return CommentsResult(
  211. success=False,
  212. platform=self.platform_name,
  213. work_id=work_id,
  214. error="百家号评论功能暂未实现"
  215. )