返回 Social Auto Upload
main.py
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 time
8 import asyncio
9
10 from conf import LOCAL_CHROME_PATH, LOCAL_CHROME_HEADLESS
11 from utils.base_social_media import set_init_script
12 from utils.log import baijiahao_logger
13 from utils.network import async_retry
14
15
16 async def baijiahao_cookie_gen(account_file):
17 async with async_playwright() as playwright:
18 options = {
19 'args': [
20 '--lang en-GB'
21 ],
22 'headless': LOCAL_CHROME_HEADLESS, # Set headless option here
23 }
24 # Make sure to run headed.
25 browser = await playwright.chromium.launch(**options)
26 # Setup context however you like.
27 context = await browser.new_context() # Pass any options
28 context = await set_init_script(context)
29 # Pause the page, and start recording manually.
30 page = await context.new_page()
31 await page.goto("https://baijiahao.baidu.com/builder/theme/bjh/login")
32 await page.pause()
33 # 点击调试器的继续,保存cookie
34 await context.storage_state(path=account_file)
35 baijiahao_logger.success("cookie saved")
36
37
38 async def cookie_auth(account_file):
39 async with async_playwright() as playwright:
40 browser = await playwright.chromium.launch(headless=LOCAL_CHROME_HEADLESS)
41 context = await browser.new_context(storage_state=account_file)
42 context = await set_init_script(context)
43 # 创建一个新的页面
44 page = await context.new_page()
45 # 访问指定的 URL
46 await page.goto("https://baijiahao.baidu.com/builder/rc/home")
47 await page.wait_for_timeout(timeout=5000)
48
49 if await page.get_by_text('注册/登录百家号').count():
50 baijiahao_logger.error("等待5秒 cookie 失效")
51 return False
52 else:
53 baijiahao_logger.success("[+] cookie 有效")
54 return True
55
56
57 async def baijiahao_setup(account_file, handle=False):
58 if not os.path.exists(account_file) or not await cookie_auth(account_file):
59 if not handle:
60 return False
61 baijiahao_logger.error("cookie文件不存在或已失效,即将自动打开浏览器,请扫码登录,登陆后会自动生成cookie文件")
62 await baijiahao_cookie_gen(account_file)
63 return True
64
65 class BaiJiaHaoVideo(object):
66 def __init__(self, title, file_path, tags, publish_date: datetime, account_file, proxy_setting=None):
67 self.title = title # 视频标题
68 self.file_path = file_path
69 self.tags = tags
70 self.publish_date = publish_date
71 self.account_file = account_file
72 self.date_format = '%Y年%m月%d日 %H:%M'
73 self.local_executable_path = LOCAL_CHROME_PATH
74 self.headless = LOCAL_CHROME_HEADLESS
75 self.proxy_setting = proxy_setting
76
77 async def set_schedule_time(self, page, publish_date):
78 """
79 todo 时间选择,日后在处理 百家号的时间选择不准确,目前是随机
80 """
81 publish_date_day = f"{publish_date.month}月{publish_date.day}日" if publish_date.day >9 else f"{publish_date.month}月0{publish_date.day}日"
82 publish_date_hour = f"{publish_date.hour}点"
83 publish_date_min = f"{publish_date.minute}分"
84 await page.wait_for_selector('div.select-wrap', timeout=5000)
85 for _ in range(3):
86 try:
87 await page.locator('div.select-wrap').nth(0).click()
88 await page.wait_for_selector('div.rc-virtual-list div.cheetah-select-item', timeout=5000)
89 break
90 except:
91 await page.locator('div.select-wrap').nth(0).click()
92 # page.locator(f'div.rc-virtual-list-holder-inner >> text={publish_date_day}').click()
93 await page.wait_for_timeout(2000)
94 await page.locator(f'div.rc-virtual-list div.cheetah-select-item >> text={publish_date_day}').click()
95 await page.wait_for_timeout(2000)
96
97 # 改为随机点击一个 hour
98 for _ in range(3):
99 try:
100 await page.locator('div.select-wrap').nth(1).click()
101 await page.wait_for_selector('div.rc-virtual-list div.rc-virtual-list-holder-inner:visible', timeout=5000)
102 break
103 except:
104 await page.locator('div.select-wrap').nth(1).click()
105 await page.wait_for_timeout(2000)
106 current_choice_hour = await page.locator('div.rc-virtual-list:visible div.cheetah-select-item-option').count()
107 await page.wait_for_timeout(2000)
108 await page.locator('div.rc-virtual-list:visible div.cheetah-select-item-option').nth(
109 random.randint(1, current_choice_hour-3)).click()
110 # 2024.08.05 current_choice_hour的获取可能有问题,页面有7,这里获取了10,暂时硬编码至6
111
112 await page.wait_for_timeout(2000)
113 await page.locator("button >> text=定时发布").click()
114
115
116 async def handle_upload_error(self, page):
117 # 日后实现,目前没遇到
118 return
119 print("视频出错了,重新上传中")
120
121 async def upload(self, playwright: Playwright) -> None:
122 # 使用 Chromium 浏览器启动一个浏览器实例
123 browser = await playwright.chromium.launch(headless=self.headless, executable_path=self.local_executable_path, proxy=self.proxy_setting)
124 # 创建一个浏览器上下文,使用指定的 cookie 文件
125 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')
126 # context = await set_init_script(context)
127 await context.grant_permissions(['geolocation'])
128
129 # 创建一个新的页面
130 page = await context.new_page()
131 # 访问指定的 URL
132 await page.goto("https://baijiahao.baidu.com/builder/rc/edit?type=videoV2", timeout=60000)
133 baijiahao_logger.info(f"正在上传-------{self.title}.mp4")
134 # 等待页面跳转到指定的 URL,没进入,则自动等待到超时
135 baijiahao_logger.info('正在打开主页...')
136 await page.wait_for_url("https://baijiahao.baidu.com/builder/rc/edit?type=videoV2", timeout=60000)
137
138 # 点击 "上传视频" 按钮
139 await page.locator("div[class^='video-main-container'] input").set_input_files(self.file_path)
140
141 # 等待页面跳转到指定的 URL
142 while True:
143 # 判断是是否进入视频发布页面,没进入,则自动等待到超时
144 try:
145 await page.wait_for_selector("div#formMain:visible")
146 break
147 except:
148 baijiahao_logger.info("正在等待进入视频发布页面...")
149 await asyncio.sleep(0.1)
150
151 # 填充标题和话题
152 # 这里为了避免页面变化,故使用相对位置定位:作品标题父级右侧第一个元素的input子元素
153 await asyncio.sleep(1)
154 baijiahao_logger.info("正在填充标题和话题...")
155 await self.add_title_tags(page)
156
157 upload_status = await self.uploading_video(page)
158 if not upload_status:
159 baijiahao_logger.error(f"发现上传出错了... 文件:{self.file_path}")
160 raise
161
162 # 判断视频封面图是否生成成功
163 while True:
164 baijiahao_logger.info("正在确认封面完成, 准备去点击定时/发布...")
165 if await page.locator("div.cheetah-spin-container img").count():
166 baijiahao_logger.info("封面已完成,点击定时/发布...")
167 break
168 else:
169 baijiahao_logger.info("等待封面生成...")
170 await asyncio.sleep(3)
171
172 await self.publish_video(page, self.publish_date)
173 await page.wait_for_timeout(2000)
174 if await page.locator('div.passMod_dialog-container >> text=百度安全验证:visible').count():
175 baijiahao_logger.error("出现验证,退出")
176 raise Exception("出现验证,退出")
177 await page.wait_for_url("https://baijiahao.baidu.com/builder/rc/clue**", timeout=5000)
178 baijiahao_logger.success("视频发布成功")
179
180 await context.storage_state(path=self.account_file) # 保存cookie
181 baijiahao_logger.info('cookie更新完毕!')
182 await asyncio.sleep(2) # 这里延迟是为了方便眼睛直观的观看
183 # 关闭浏览器上下文和浏览器实例
184 await context.close()
185 await browser.close()
186
187
188 @async_retry(timeout=300) # 例如,最多重试3次,超时时间为180秒
189 async def uploading_video(self, page):
190 while True:
191 upload_failed = await page.locator('div .cover-overlay:has-text("上传失败")').count()
192 if upload_failed:
193 baijiahao_logger.error("发现上传出错了...")
194 # await self.handle_upload_error(page) # 假设这是处理上传错误的函数
195 return False
196
197 uploading = await page.locator('div .cover-overlay:has-text("上传中")').count()
198 if uploading:
199 baijiahao_logger.info("正在上传视频中...")
200 await asyncio.sleep(2) # 等待2秒再次检查
201 continue
202
203 # 检查上传是否成功
204 if not uploading and not upload_failed:
205 baijiahao_logger.success("视频上传完毕")
206 return True
207
208 async def set_schedule_publish(self, page, publish_date):
209 while True:
210 schedule_element = page.locator("div.op-btn-outter-content >> text=定时发布").locator("..").locator(
211 'button')
212 try:
213 await schedule_element.click()
214 await page.wait_for_selector('div.select-wrap:visible', timeout=3000)
215 await page.wait_for_timeout(timeout=2000)
216 baijiahao_logger.info("开始点击发布定时...")
217 await self.set_schedule_time(page, publish_date)
218 break
219 except Exception as e:
220 baijiahao_logger.error(f"定时发布失败: {e}")
221 raise # 重新抛出异常,让重试装饰器捕获
222
223 @async_retry(timeout=300) # 例如,最多重试3次,超时时间为180秒
224 async def publish_video(self, page: Page, publish_date):
225 if publish_date != 0:
226 # 定时发布
227 await self.set_schedule_publish(page, publish_date)
228 else:
229 # 立即发布
230 await self.direct_publish(page)
231
232 async def direct_publish(self, page):
233 try:
234 publish_button = page.locator("button >> text=发布")
235 if await publish_button.count():
236 await publish_button.click()
237 except Exception as e:
238 baijiahao_logger.error(f"直接发布视频失败: {e}")
239 raise # 重新抛出异常,让重试装饰器捕获
240
241 async def add_title_tags(self, page):
242 title_container = page.get_by_placeholder('添加标题获得更多推荐')
243 if len(self.title) <= 8:
244 self.title += " 你不知道的"
245 await title_container.fill(self.title[:30])
246
247 async def main(self):
248 async with async_playwright() as playwright:
249 await self.upload(playwright)
250
251
252
253 # 使用 AI成片 功能
254 async def ai2video(self, playwright: Playwright) -> None:
255 # 使用 Chromium 浏览器启动一个浏览器实例
256 browser = await playwright.chromium.launch(headless=self.headless, executable_path=self.local_executable_path, proxy=self.proxy_setting)
257 # 创建一个浏览器上下文,使用指定的 cookie 文件
258 context = await browser.new_context(
259 viewport={"width": 1600, "height": 900},
260 storage_state=f"{self.account_file}",
261 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'
262 )
263 # context = await set_init_script(context)
264 await context.grant_permissions(['geolocation'])
265
266 # 创建一个新的页面
267 page = await context.new_page()
268 # 访问指定的 URL
269 await page.goto("https://aigc.baidu.com/make", timeout=60000)
270 # 等待页面跳转到指定的 URL,没进入,则自动等待到超时
271 baijiahao_logger.info('正在打开主页...')
272 await page.wait_for_url("https://aigc.baidu.com/make", timeout=60000)
273
274 # 点击"全网"标签
275 await page.locator('div.rounded-lg.border:has-text("全网")').click()
276 await asyncio.sleep(1) # 这里延迟是为了方便眼睛直观的观看
277
278 # 点击 "上传视频" 按钮
279 # await page.locator("div[class^='video-main-container'] input").set_input_files(self.file_path)
280
281 # region 操作处
282
283 # 生成日期时间键名(格式:ai2video_YYYYMMDDHHMM)
284 now = datetime.now()
285 datetime_str = now.strftime("%Y%m%d%H%M")
286 processed_key = "ai2video_processed_titles"
287 batch_key = f"ai2video_{datetime_str}"
288
289 # 初始化LocalStorage
290 await page.evaluate(f"""
291 if (!localStorage.getItem("{processed_key}")) {{
292 localStorage.setItem("{processed_key}", JSON.stringify([]));
293 }}
294 if (!localStorage.getItem("{batch_key}")) {{
295 localStorage.setItem("{batch_key}", JSON.stringify([]));
296 }}
297 """)
298
299 # 定位新闻列表容器(转义特殊CSS字符)
300 container_selector = '.overflow-auto.flex-grow.h-0.saas-scrollbar.mt\-\[-4px\].pl\-\[24px\].pr\-\[10px\].pb\-\[18px\]'
301 news_items = await page.locator(container_selector).locator('div.py\-\[6px\].group.cursor-pointer').all()
302
303 for item in news_items:
304 try:
305 # 获取新闻标题
306 title_elem = item.locator('div.flex.text-gray-darker.items-center.relative.pr\-\[56px\] > span')
307 title = await title_elem.text_content()
308 if not title:
309 continue
310
311 # 检查是否已处理过
312 is_processed = await page.evaluate(
313 f"""title => {{
314 const processedList = JSON.parse(localStorage.getItem("{processed_key}") || "[]");
315 return processedList.includes(title);
316 }}""",
317 title
318 )
319
320 if is_processed:
321 print(f"[跳过] {title}")
322 continue
323
324 # 悬停显示按钮(根据HTML结构,按钮在悬停时显示)
325 await item.hover()
326
327 # 点击生成文案按钮
328 button = item.locator('button:has-text("生成文案")')
329 await button.click()
330 print(f"[点击] {title}")
331
332 # 等待30秒
333 # await page.wait_for_timeout(30000)
334 print(f"[等待完成] {title}")
335
336 # 监听"一键成片"按钮
337 print(f"[开始监听] 一键成片按钮")
338 should_exit_while_loop = False # 添加标志变量
339 while True:
340 # 定位"一键成片"按钮
341 one_key_button = page.locator("button:has-text('一键成片')")
342
343 # 检查按钮是否存在
344 if await one_key_button.count() > 0:
345 # 检查按钮是否有disabled属性
346 is_disabled = await one_key_button.get_attribute("disabled")
347
348 if is_disabled is None:
349 # 按钮不再被禁用,点击它
350 print(f"[发现可点击按钮] 一键成片")
351 await one_key_button.click() # 先点击一键成片按钮
352
353 # 等待可能出现的"温馨提示"窗口
354 print(f"[检查] 是否出现温馨提示窗口")
355 await page.wait_for_timeout(2000) # 等待2秒,让窗口有时间显示
356
357 try:
358 # 检查是否存在"温馨提示"窗口,设置较短的超时时间
359 tip_window = page.locator("div:has-text('温馨提示') >> visible=true")
360 if await tip_window.count() > 0:
361 print(f"[发现] 温馨提示窗口")
362
363 # 定位并点击"知道了"按钮,设置较短的超时时间
364 know_button = page.locator("button:has-text('知道了')")
365 if await know_button.count() > 0:
366 try:
367 # 设置较短的超时时间进行点击
368 await know_button.click(timeout=5000)
369 print(f"[已点击] 知道了按钮")
370 except Exception as e:
371 print(f"[警告] 点击知道了按钮时出错: {str(e)}")
372 else:
373 print(f"[警告] 未找到知道了按钮")
374 else:
375 print(f"[信息] 未出现温馨提示窗口,继续执行")
376 except Exception as e:
377 print(f"[警告] 处理温馨提示窗口时出错: {str(e)}")
378 # 继续执行,不要因为这个错误中断流程
379
380 # 记录到LocalStorage前打印日志
381 print(f"[开始记录] 准备将标题 '{title}' 记录到LocalStorage")
382
383 # 记录到LocalStorage
384 await page.evaluate(
385 f"""
386 (title, processedKey, batchKey) => {{
387 // 更新已处理列表
388 const processedList = JSON.parse(localStorage.getItem(processedKey) || "[]");
389 if (!processedList.includes(title)) {{
390 processedList.push(title);
391 localStorage.setItem(processedKey, JSON.stringify(processedList));
392 }}
393
394 // 更新当前批次记录
395 const batchList = JSON.parse(localStorage.getItem(batchKey) || "[]");
396 if (!batchList.includes(title)) {{
397 batchList.push(title);
398 localStorage.setItem(batchKey, JSON.stringify(batchList));
399 }}
400 }}
401 """,
402 title, processed_key, batch_key
403 )
404
405 # 记录完成后打印日志
406 print(f"[记录完成] 标题 '{title}' 已成功记录到LocalStorage")
407
408 print(f"[记录完成] {title}")
409
410 # 监听新打开的标签页
411 print(f"[监听] 等待新标签页打开")
412 # 获取当前所有页面
413 current_pages = context.pages
414 current_page_count = len(current_pages)
415
416 # 等待新标签页打开(最多等待10秒)
417 new_page = None
418 max_wait_time = 10 # 最大等待时间(秒)
419 start_time = time.time()
420
421 while time.time() - start_time < max_wait_time:
422 # 获取最新的页面列表
423 pages = context.pages
424 # 如果页面数量增加,说明新标签页已打开
425 if len(pages) > current_page_count:
426 # 获取最新打开的页面(通常是列表中的最后一个)
427 new_page = pages[-1]
428 print(f"[发现] 新标签页已打开")
429 break
430 # 短暂等待后再次检查
431 await asyncio.sleep(0.5)
432
433 # 如果找到新标签页,获取其标题和URL并保存
434 if new_page:
435 # 等待页面加载完成
436 try:
437 await new_page.wait_for_load_state("domcontentloaded", timeout=5000)
438 # 获取页面标题和URL
439 page_title = await new_page.title()
440 page_url = new_page.url
441
442 print(f"[获取] 标题: {page_title}")
443 print(f"[获取] URL: {page_url}")
444
445 # 将标题和URL保存到url.txt文件
446 with open("url.txt", "a", encoding="utf-8") as f:
447 f.write(f"{page_title}\n{page_url}\n\n")
448
449 print(f"[保存] 标题和URL已保存到url.txt")
450
451 # 等待5秒后关闭新标签页
452 print(f"[等待] 5秒后将关闭新标签页")
453 await asyncio.sleep(5)
454 await new_page.close()
455 print(f"[关闭] 新标签页已关闭")
456 except Exception as e:
457 print(f"[错误] 处理新标签页时出错: {str(e)}")
458 try:
459 # 尝试关闭页面,即使出错
460 await new_page.close()
461 print(f"[关闭] 新标签页已关闭(出错后)")
462 except:
463 pass
464 else:
465 print(f"[警告] 未检测到新标签页打开")
466
467 # 跳出整个while循环
468 print(f"[操作] 跳出所有循环,不再处理其他新闻")
469 should_exit_while_loop = True # 设置标志变量
470 break # 跳出while循环
471
472 # 检查是否需要跳出while循环
473 if should_exit_while_loop:
474 break
475
476 # 每秒检查一次按钮状态
477 await page.wait_for_timeout(1000)
478
479 # 检查是否需要跳出for循环
480 if should_exit_while_loop:
481 print(f"[操作] 跳出for循环,完全结束处理")
482 break # 跳出for循环
483 except Exception as e:
484 print(f"处理新闻时出错: {str(e)}")
485 continue
486
487
488 # endregion 操作处
489
490 print(f"[循环完成] 准备关闭浏览器")
491
492 # 暂停 1000s
493 await asyncio.sleep(1000) # 这里延迟是为了方便眼睛直观的观看
494
495 # 退出前保存 storage 信息
496 await context.storage_state(path=self.account_file) # 保存cookie
497 baijiahao_logger.info('cookie更新完毕!')
498 await asyncio.sleep(2) # 这里延迟是为了方便眼睛直观的观看
499 # 关闭浏览器上下文和浏览器实例
500 await context.close()
501 await browser.close()
502
503
504 async def mainAi(self):
505 async with async_playwright() as playwright:
506 await self.ai2video(playwright)
507
507 lines PYTHON