# -*- coding: utf-8 -*-
|
from playwright.async_api import Page
|
import asyncio
|
import traceback
|
|
from auto_media_publisher.uploaders.uploader_base import UploaderBase
|
|
CONTENT_UPLOAD_URL="https://creator.douyin.com/creator-micro/content/upload"
|
VIDEO_PUBLISH_SECCESSFULL_URL = "https://creator.douyin.com/creator-micro/content/manage?enter_from=publish"
|
|
|
class UploaderDouyin(UploaderBase):
|
|
async def set_schedule_time_douyin(self, page):
|
try:
|
# 选择包含特定文本内容的 label 元素
|
label_element = page.locator("[class^='radio']:has-text('定时发布')")
|
# 在选中的 label 元素下点击 checkbox
|
await label_element.click()
|
await asyncio.sleep(1)
|
scheduled_time_hour = self.scheduled_time.strftime("%Y-%m-%d %H:%M")
|
await asyncio.sleep(1)
|
await page.locator('.semi-input[placeholder="日期和时间"]').click()
|
await page.keyboard.press("Control+KeyA")
|
await page.keyboard.type(str(scheduled_time_hour))
|
await page.keyboard.press("Enter")
|
await asyncio.sleep(1)
|
except Exception as e:
|
self.logger.error(f'[ERROR ] {self.user} , {self.platform} ,设置 发布时间 发生错误:{e}')
|
|
async def _try_set_cover_image(self, page: Page):
|
try:
|
if self.path_cover:
|
await page.click('text="选择封面"')
|
await page.locator("#dy-creator-content-modal-body").wait_for(timeout=30000)
|
await page.click('text="设置竖封面"')
|
await page.wait_for_timeout(2000) # 等待2秒
|
# 定位到上传区域并点击
|
await page.locator("div[class^='semi-upload upload'] >> input.semi-upload-hidden-input").set_input_files(self.path_cover)
|
await page.wait_for_timeout(3000) # 等待2秒
|
await page.locator("button:visible:has-text('完成')").click()
|
except Exception as e:
|
self.logger.error(f'[ERROR ] {self.user} , {self.platform} ,设置 封面 发生错误:{e}')
|
|
async def _try_set_location(self, page: Page):
|
try:
|
|
# 1. 打开下拉框
|
await page.locator(".anchor-container-hgj7gj > div > .semi-select-arrow").click()
|
await asyncio.sleep(1) # 这段可以保留,也可以换成 wait_for
|
await page.get_by_role("option", name="位置").click()
|
await page.locator('div.semi-select span:has-text("输入地理位置")').click()
|
await page.keyboard.press("Control+A") # 推荐比按Backspace更可靠
|
await page.keyboard.press("Backspace")
|
|
# 4. 输入地理位置
|
await page.wait_for_timeout(1000)
|
await page.keyboard.type(self.location)
|
await page.wait_for_timeout(2000) # 可以尝试替换成更精确的 wait_for() 检查 listbox 是否加载
|
listbox = page.get_by_role("listbox")
|
|
target = listbox.locator("div").filter(has_text=self.location).nth(2)
|
await target.click()
|
|
except Exception as e:
|
self.logger.error(f'[ERROR ] {self.user} , {self.platform} ,设置 位置 发生错误:{e}')
|
|
async def handle_upload_error(self, page):
|
self.logger.info('视频出错了,重新上传中')
|
await page.locator('div.progress-div [class^="upload-btn-input"]').set_input_files(self.path_media)
|
|
async def _try_upload_mp4_and_wait(self,page):
|
try:
|
await page.locator("div[class^='container'] input").set_input_files(self.path_media)
|
while True:
|
# 判断重新上传按钮是否存在,如果不存在,代表视频正在上传,则等待
|
try:
|
number = await page.locator('[class^="long-card"] div:has-text("重新上传")').count()
|
if number > 0:
|
self.logger.info(" [-]视频上传完毕")
|
break
|
else:
|
self.logger.info(" [-] 正在上传视频中...")
|
await asyncio.sleep(2)
|
|
if await page.locator('div.progress-div > div:has-text("上传失败")').count():
|
self.logger.error(" [-] 发现上传出错了... 准备重试")
|
await self.handle_upload_error(page)
|
except Exception as e:
|
self.logger.error(f" [exception] 正在上传视频中... 错误:{e}")
|
self.logger.debug(traceback.format_exc()) # 打印堆栈信息(如果 logger 支持 debug)
|
await asyncio.sleep(2)
|
return True
|
except Exception as e:
|
self.logger.error(f"{self.user} upload video {self.path_media} fails, error is :{e}")
|
return False
|
|
async def _try_set_tilte_and_description(self,page):
|
try:
|
await asyncio.sleep(1)
|
self.logger.info(f' [-] 正在填充标题和话题...')
|
await page.get_by_placeholder("填写作品标题,为作品获得更多流量").click()
|
await page.get_by_placeholder("填写作品标题,为作品获得更多流量").fill(self.title[:30])
|
await page.locator(".zone-container").click()
|
description_tags = self.description + ' ' + ' '.join(f"#{tag}" for tag in self.tags)
|
await page.locator(".zone-container").fill(description_tags)
|
self.logger.info(f'总共添加{len(self.tags)}个话题')
|
except Exception as e:
|
self.logger.error(f'[ERROR ] {self.user} , {self.platform} ,设置title 发生错误:{e}')
|
|
async def _upload_core(self) -> bool:
|
try:
|
page = await self.context.new_page()
|
await page.goto(CONTENT_UPLOAD_URL, timeout=120000)
|
self.logger.info(f'[+]正在上传-------{self.title}.mp4')
|
await page.wait_for_url(CONTENT_UPLOAD_URL, timeout=120000)
|
|
# 1、上传并等待视频上传结束
|
await self._try_upload_mp4_and_wait(page)
|
# 2、设置标题和描述
|
await self._try_set_tilte_and_description(page)
|
# 3、设置封面
|
await self._try_set_cover_image(page)
|
# 4、设置位置 和 商品
|
await self._try_set_location(page)
|
third_part_element = '[class^="info"] > [class^="first-part"] div div.semi-switch'
|
# 定位是否有第三方平台
|
if await page.locator(third_part_element).count():
|
# 检测是否是已选中状态
|
if 'semi-switch-checked' not in await page.eval_on_selector(third_part_element, 'div => div.className'):
|
await page.locator(third_part_element).locator('input.semi-switch-native-control').click()
|
|
# 5、设置定时发布
|
if self.scheduled_time != 0:
|
await self.set_schedule_time_douyin(page)
|
|
|
|
# 6 发布并判断视频是否发布成功
|
while True:
|
# 判断视频是否发布成功
|
try:
|
publish_button = page.get_by_role('button', name="发布", exact=True)
|
if await publish_button.count():
|
await publish_button.click()
|
await page.wait_for_url(VIDEO_PUBLISH_SECCESSFULL_URL,timeout=3000) # 如果自动跳转到作品页面,则代表发布成功
|
self.logger.info(" [-]视频发布成功")
|
break
|
except:
|
self.logger.info(" [-] 视频正在发布中...")
|
await page.screenshot(full_page=True)
|
await asyncio.sleep(0.5)
|
|
# 7、 更新Cookie信息,退出
|
await self.context.storage_state(path=self.cookie_path) # 保存cookie
|
self.logger.info(f' {self.user} , {self.platform} [-]cookie更新完毕!')
|
await asyncio.sleep(2) # 这里延迟是为了方便眼睛直观的观看
|
|
return True
|
except Exception as e:
|
self.logger.error(f"{self.user} upload video {self.path_media} fails, error is :{e}")
|
return False
|