add 百家號上傳實現
Browse files- README.MD +2 -1
- examples/get_baijiahao_cookie.py +10 -0
- examples/get_douyin_cookie.py +1 -0
- examples/get_kuaishou_cookie.py +1 -0
- examples/get_tencent_cookie.py +1 -0
- examples/get_tk_cookie.py +1 -0
- examples/upload_video_to_baijiahao.py +27 -0
- uploader/baijiahao_uploader/__init__.py +0 -0
- uploader/baijiahao_uploader/main.py +248 -0
- utils/log.py +1 -0
- utils/network.py +28 -0
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
|