dreammis commited on
Commit
60a259e
·
1 Parent(s): 85d07d9

add 百家號上傳實現

Browse files
README.MD CHANGED
@@ -10,7 +10,8 @@ social-auto-upload 该项目旨在自动化发布视频到各个社交媒体平
10
  - [x] bilibili
11
  - [x] 小红书
12
  - [x] 快手
13
- - [ ] 百家号
 
14
 
15
  - 部分国外社交媒体:
16
  - [x] tiktok
 
10
  - [x] bilibili
11
  - [x] 小红书
12
  - [x] 快手
13
+ - [x] 百家号
14
+ - [ ] qq視頻
15
 
16
  - 部分国外社交媒体:
17
  - [x] tiktok
examples/get_baijiahao_cookie.py ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ from pathlib import Path
3
+
4
+ from conf import BASE_DIR
5
+ from uploader.baijiahao_uploader.main import baijiahao_setup
6
+
7
+ if __name__ == '__main__':
8
+ account_file = Path(BASE_DIR / "cookies" / "baijiahao_uploader" / "account.json")
9
+ account_file.parent.mkdir(exist_ok=True)
10
+ cookie_setup = asyncio.run(baijiahao_setup(str(account_file), handle=True))
examples/get_douyin_cookie.py CHANGED
@@ -6,4 +6,5 @@ from uploader.douyin_uploader.main import douyin_setup
6
 
7
  if __name__ == '__main__':
8
  account_file = Path(BASE_DIR / "cookies" / "douyin_uploader" / "account.json")
 
9
  cookie_setup = asyncio.run(douyin_setup(str(account_file), handle=True))
 
6
 
7
  if __name__ == '__main__':
8
  account_file = Path(BASE_DIR / "cookies" / "douyin_uploader" / "account.json")
9
+ account_file.parent.mkdir(exist_ok=True)
10
  cookie_setup = asyncio.run(douyin_setup(str(account_file), handle=True))
examples/get_kuaishou_cookie.py CHANGED
@@ -6,4 +6,5 @@ from uploader.ks_uploader.main import ks_setup
6
 
7
  if __name__ == '__main__':
8
  account_file = Path(BASE_DIR / "cookies" / "ks_uploader" / "account.json")
 
9
  cookie_setup = asyncio.run(ks_setup(str(account_file), handle=True))
 
6
 
7
  if __name__ == '__main__':
8
  account_file = Path(BASE_DIR / "cookies" / "ks_uploader" / "account.json")
9
+ account_file.parent.mkdir(exist_ok=True)
10
  cookie_setup = asyncio.run(ks_setup(str(account_file), handle=True))
examples/get_tencent_cookie.py CHANGED
@@ -6,4 +6,5 @@ from uploader.tencent_uploader.main import weixin_setup
6
 
7
  if __name__ == '__main__':
8
  account_file = Path(BASE_DIR / "cookies" / "tencent_uploader" / "account.json")
 
9
  cookie_setup = asyncio.run(weixin_setup(str(account_file), handle=True))
 
6
 
7
  if __name__ == '__main__':
8
  account_file = Path(BASE_DIR / "cookies" / "tencent_uploader" / "account.json")
9
+ account_file.parent.mkdir(exist_ok=True)
10
  cookie_setup = asyncio.run(weixin_setup(str(account_file), handle=True))
examples/get_tk_cookie.py CHANGED
@@ -6,4 +6,5 @@ from uploader.tk_uploader.main_chrome import tiktok_setup
6
 
7
  if __name__ == '__main__':
8
  account_file = Path(BASE_DIR / "cookies" / "tk_uploader" / "account.json")
 
9
  cookie_setup = asyncio.run(tiktok_setup(str(account_file), handle=True))
 
6
 
7
  if __name__ == '__main__':
8
  account_file = Path(BASE_DIR / "cookies" / "tk_uploader" / "account.json")
9
+ account_file.parent.mkdir(exist_ok=True)
10
  cookie_setup = asyncio.run(tiktok_setup(str(account_file), handle=True))
examples/upload_video_to_baijiahao.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ from pathlib import Path
3
+
4
+ from conf import BASE_DIR
5
+ from uploader.baijiahao_uploader.main import baijiahao_setup, BaiJiaHaoVideo
6
+ from utils.files_times import generate_schedule_time_next_day, get_title_and_hashtags
7
+
8
+
9
+ if __name__ == '__main__':
10
+ filepath = Path(BASE_DIR) / "videos"
11
+ account_file = Path(BASE_DIR / "cookies" / "baijiahao_uploader" / "account.json")
12
+ # 获取视频目录
13
+ folder_path = Path(filepath)
14
+ # 获取文件夹中的所有文件
15
+ files = list(folder_path.glob("*.mp4"))
16
+ file_num = len(files)
17
+ publish_datetimes = generate_schedule_time_next_day(file_num, 1, daily_times=[16])
18
+ cookie_setup = asyncio.run(baijiahao_setup(account_file, handle=False))
19
+ for index, file in enumerate(files):
20
+ title, tags = get_title_and_hashtags(str(file))
21
+ thumbnail_path = file.with_suffix('.png')
22
+ # 打印视频文件名、标题和 hashtag
23
+ print(f"视频文件名:{file}")
24
+ print(f"标题:{title}")
25
+ print(f"Hashtag:{tags}")
26
+ app = BaiJiaHaoVideo(title, file, tags, publish_datetimes[index], account_file)
27
+ asyncio.run(app.main(), debug=False)
uploader/baijiahao_uploader/__init__.py ADDED
File without changes
uploader/baijiahao_uploader/main.py ADDED
@@ -0,0 +1,248 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ import random
3
+ from datetime import datetime
4
+
5
+ from playwright.async_api import Playwright, async_playwright, Page
6
+ import os
7
+ import asyncio
8
+
9
+ from conf import LOCAL_CHROME_PATH
10
+ from utils.base_social_media import set_init_script
11
+ from utils.log import baijiahao_logger
12
+ from utils.network import async_retry
13
+
14
+
15
+ async def baijiahao_cookie_gen(account_file):
16
+ async with async_playwright() as playwright:
17
+ options = {
18
+ 'args': [
19
+ '--lang en-GB'
20
+ ],
21
+ 'headless': False, # Set headless option here
22
+ }
23
+ # Make sure to run headed.
24
+ browser = await playwright.chromium.launch(**options)
25
+ # Setup context however you like.
26
+ context = await browser.new_context() # Pass any options
27
+ context = await set_init_script(context)
28
+ # Pause the page, and start recording manually.
29
+ page = await context.new_page()
30
+ await page.goto("https://baijiahao.baidu.com/builder/theme/bjh/login")
31
+ await page.pause()
32
+ # 点击调试器的继续,保存cookie
33
+ await context.storage_state(path=account_file)
34
+ baijiahao_logger.success("cookie saved")
35
+
36
+
37
+ async def cookie_auth(account_file):
38
+ async with async_playwright() as playwright:
39
+ browser = await playwright.chromium.launch(headless=True)
40
+ context = await browser.new_context(storage_state=account_file)
41
+ context = await set_init_script(context)
42
+ # 创建一个新的页面
43
+ page = await context.new_page()
44
+ # 访问指定的 URL
45
+ await page.goto("https://baijiahao.baidu.com/builder/rc/home")
46
+ await page.wait_for_timeout(timeout=5000)
47
+
48
+ if await page.get_by_text('注册/登录百家号').count():
49
+ baijiahao_logger.error("等待5秒 cookie 失效")
50
+ return False
51
+ else:
52
+ baijiahao_logger.success("[+] cookie 有效")
53
+ return True
54
+
55
+
56
+ async def baijiahao_setup(account_file, handle=False):
57
+ if not os.path.exists(account_file) or not await cookie_auth(account_file):
58
+ if not handle:
59
+ return False
60
+ baijiahao_logger.error("cookie文件不存在或已失效,即将自动打开浏览器,请扫码登录,登陆后会自动生成cookie文件")
61
+ await baijiahao_cookie_gen(account_file)
62
+ return True
63
+
64
+ class BaiJiaHaoVideo(object):
65
+ def __init__(self, title, file_path, tags, publish_date: datetime, account_file, proxy_setting=None):
66
+ self.title = title # 视频标题
67
+ self.file_path = file_path
68
+ self.tags = tags
69
+ self.publish_date = publish_date
70
+ self.account_file = account_file
71
+ self.date_format = '%Y年%m月%d日 %H:%M'
72
+ self.local_executable_path = LOCAL_CHROME_PATH
73
+ self.proxy_setting = proxy_setting
74
+
75
+ async def set_schedule_time(self, page, publish_date):
76
+ """
77
+ todo 时间选择,日后在处理 百家号的时间选择不准确,目前是随机
78
+ """
79
+ publish_date_day = f"{publish_date.month}月{publish_date.day}日" if publish_date.day >9 else f"{publish_date.month}月0{publish_date.day}日"
80
+ publish_date_hour = f"{publish_date.hour}点"
81
+ publish_date_min = f"{publish_date.minute}分"
82
+ await page.wait_for_selector('div.select-wrap', timeout=5000)
83
+ for _ in range(3):
84
+ try:
85
+ await page.locator('div.select-wrap').nth(0).click()
86
+ await page.wait_for_selector('div.rc-virtual-list div.cheetah-select-item', timeout=5000)
87
+ break
88
+ except:
89
+ await page.locator('div.select-wrap').nth(0).click()
90
+ # page.locator(f'div.rc-virtual-list-holder-inner >> text={publish_date_day}').click()
91
+ await page.wait_for_timeout(2000)
92
+ await page.locator(f'div.rc-virtual-list div.cheetah-select-item >> text={publish_date_day}').click()
93
+ await page.wait_for_timeout(2000)
94
+
95
+ # 改为随机点击一个 hour
96
+ for _ in range(3):
97
+ try:
98
+ await page.locator('div.select-wrap').nth(1).click()
99
+ await page.wait_for_selector('div.rc-virtual-list div.rc-virtual-list-holder-inner:visible', timeout=5000)
100
+ break
101
+ except:
102
+ await page.locator('div.select-wrap').nth(1).click()
103
+ await page.wait_for_timeout(2000)
104
+ current_choice_hour = await page.locator('div.rc-virtual-list:visible div.cheetah-select-item-option').count()
105
+ await page.wait_for_timeout(2000)
106
+ await page.locator('div.rc-virtual-list:visible div.cheetah-select-item-option').nth(
107
+ random.randint(1, current_choice_hour-3)).click()
108
+ # 2024.08.05 current_choice_hour的获取可能有问题,页面有7,这里获取了10,暂时硬编码至6
109
+
110
+ await page.wait_for_timeout(2000)
111
+ await page.locator("button >> text=定时发布").click()
112
+
113
+
114
+ async def handle_upload_error(self, page):
115
+ # 日后实现,目前没遇到
116
+ return
117
+ print("视频出错了,重新上传中")
118
+
119
+ async def upload(self, playwright: Playwright) -> None:
120
+ # 使用 Chromium 浏览器启动一个浏览器实例
121
+ browser = await playwright.chromium.launch(headless=False, executable_path=self.local_executable_path, proxy=self.proxy_setting)
122
+ # 创建一个浏览器上下文,使用指定的 cookie 文件
123
+ context = await browser.new_context(storage_state=f"{self.account_file}", user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.4324.150 Safari/537.36')
124
+ # context = await set_init_script(context)
125
+ await context.grant_permissions(['geolocation'])
126
+
127
+ # 创建一个新的页面
128
+ page = await context.new_page()
129
+ # 访问指定的 URL
130
+ await page.goto("https://baijiahao.baidu.com/builder/rc/edit?type=videoV2", timeout=60000)
131
+ baijiahao_logger.info(f"正在上传-------{self.title}.mp4")
132
+ # 等待页面跳转到指定的 URL,没进入,则自动等待到超时
133
+ baijiahao_logger.info('正在打开主页...')
134
+ await page.wait_for_url("https://baijiahao.baidu.com/builder/rc/edit?type=videoV2", timeout=60000)
135
+
136
+ # 点击 "上传视频" 按钮
137
+ await page.locator("div[class^='video-main-container'] input").set_input_files(self.file_path)
138
+
139
+ # 等待页面跳转到指定的 URL
140
+ while True:
141
+ # 判断是是否进入视频发布页面,没进入,则自动等待到超时
142
+ try:
143
+ await page.wait_for_selector("div#formMain:visible")
144
+ break
145
+ except:
146
+ baijiahao_logger.info("正在等待进入视频发布页面...")
147
+ await asyncio.sleep(0.1)
148
+
149
+ # 填充标题和话题
150
+ # 这里为了避免页面变化,故使用相对位置定位:作品标题父级右侧第一个元素的input子元素
151
+ await asyncio.sleep(1)
152
+ baijiahao_logger.info("正在填充标题和话题...")
153
+ await self.add_title_tags(page)
154
+
155
+ upload_status = await self.uploading_video(page)
156
+ if not upload_status:
157
+ baijiahao_logger.error(f"发现上传出错了... 文件:{self.file_path}")
158
+ raise
159
+
160
+ # 判断视频封面图是否生成成功
161
+ while True:
162
+ baijiahao_logger.info("正在确认封面完成, 准备去点击定时/发布...")
163
+ if await page.locator("div.cheetah-spin-container img").count():
164
+ baijiahao_logger.info("封面已完成,点击定时/发布...")
165
+ break
166
+ else:
167
+ baijiahao_logger.info("等待封面生成...")
168
+ await asyncio.sleep(3)
169
+
170
+ await self.publish_video(page, self.publish_date)
171
+ await page.wait_for_timeout(2000)
172
+ if await page.locator('div.passMod_dialog-container >> text=百度安全验证:visible').count():
173
+ baijiahao_logger.error("出现验证,退出")
174
+ raise Exception("出现验证,退出")
175
+ await page.wait_for_url("https://baijiahao.baidu.com/builder/rc/clue**", timeout=5000)
176
+ baijiahao_logger.success("视频发布成功")
177
+
178
+ await context.storage_state(path=self.account_file) # 保存cookie
179
+ baijiahao_logger.info('cookie更新完毕!')
180
+ await asyncio.sleep(2) # 这里延迟是为了方便眼睛直观的观看
181
+ # 关闭浏览器上下文和浏览器实例
182
+ await context.close()
183
+ await browser.close()
184
+
185
+
186
+ @async_retry(timeout=300) # 例如,最多重试3次,超时时间为180秒
187
+ async def uploading_video(self, page):
188
+ while True:
189
+ upload_failed = await page.locator('div .cover-overlay:has-text("上传失败")').count()
190
+ if upload_failed:
191
+ baijiahao_logger.error("发现上传出错了...")
192
+ # await self.handle_upload_error(page) # 假设这是处理上传错误的函数
193
+ return False
194
+
195
+ uploading = await page.locator('div .cover-overlay:has-text("上传中")').count()
196
+ if uploading:
197
+ baijiahao_logger.info("正在上传视频中...")
198
+ await asyncio.sleep(2) # 等待2秒再次检查
199
+ continue
200
+
201
+ # 检查上传是否成功
202
+ if not uploading and not upload_failed:
203
+ baijiahao_logger.success("视频上传完毕")
204
+ return True
205
+
206
+ async def set_schedule_publish(self, page, publish_date):
207
+ while True:
208
+ schedule_element = page.locator("div.op-btn-outter-content >> text=定时发布").locator("..").locator(
209
+ 'button')
210
+ try:
211
+ await schedule_element.click()
212
+ await page.wait_for_selector('div.select-wrap:visible', timeout=3000)
213
+ await page.wait_for_timeout(timeout=2000)
214
+ baijiahao_logger.info("开始点击发布定时...")
215
+ await self.set_schedule_time(page, publish_date)
216
+ break
217
+ except Exception as e:
218
+ baijiahao_logger.error(f"定时发布失败: {e}")
219
+ raise # 重新抛出异常,让重试装饰器捕获
220
+
221
+ @async_retry(timeout=300) # 例如,最多重试3次,超时时间为180秒
222
+ async def publish_video(self, page: Page, publish_date):
223
+ if publish_date != 0:
224
+ # 定时发布
225
+ await self.set_schedule_publish(page, publish_date)
226
+ else:
227
+ # 立即发布
228
+ await self.direct_publish(page)
229
+
230
+ async def direct_publish(self, page):
231
+ try:
232
+ publish_button = page.locator("button >> text=发布")
233
+ if await publish_button.count():
234
+ await publish_button.click()
235
+ except Exception as e:
236
+ baijiahao_logger.error(f"直接发布视频失败: {e}")
237
+ raise # 重新抛出异常,让重试装饰器捕获
238
+
239
+ async def add_title_tags(self, page):
240
+ title_container = page.get_by_placeholder('添加标题获得更多推荐')
241
+ if len(self.title) <= 8:
242
+ self.title += " 你不知道的"
243
+ await title_container.fill(self.title[:30])
244
+
245
+ async def main(self):
246
+ async with async_playwright() as playwright:
247
+ await self.upload(playwright)
248
+
utils/log.py CHANGED
@@ -49,3 +49,4 @@ xhs_logger = create_logger('xhs', 'logs/xhs.log')
49
  tiktok_logger = create_logger('tiktok', 'logs/tiktok.log')
50
  bilibili_logger = create_logger('bilibili', 'logs/bilibili.log')
51
  kuaishou_logger = create_logger('kuaishou', 'logs/kuaishou.log')
 
 
49
  tiktok_logger = create_logger('tiktok', 'logs/tiktok.log')
50
  bilibili_logger = create_logger('bilibili', 'logs/bilibili.log')
51
  kuaishou_logger = create_logger('kuaishou', 'logs/kuaishou.log')
52
+ baijiahao_logger = create_logger('baijiahao', 'logs/baijiahao.log')
utils/network.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ import time
3
+ from functools import wraps
4
+
5
+
6
+ def async_retry(timeout=60, max_retries=None):
7
+ def decorator(func):
8
+ @wraps(func)
9
+ async def wrapper(*args, **kwargs):
10
+ start_time = time.time()
11
+ attempts = 0
12
+ while True:
13
+ try:
14
+ return await func(*args, **kwargs)
15
+ except Exception as e:
16
+ attempts += 1
17
+ if max_retries is not None and attempts >= max_retries:
18
+ print(f"Reached maximum retries of {max_retries}.")
19
+ raise Exception(f"Failed after {max_retries} retries.") from e
20
+ if time.time() - start_time > timeout:
21
+ print(f"Function timeout after {timeout} seconds.")
22
+ raise TimeoutError(f"Function execution exceeded {timeout} seconds timeout.") from e
23
+ print(f"Attempt {attempts} failed: {e}. Retrying...")
24
+ await asyncio.sleep(1) # Sleep to avoid tight loop or provide backoff logic here
25
+
26
+ return wrapper
27
+
28
+ return decorator