# -*- coding: utf-8 -*-
|
from playwright.async_api import Page
|
import asyncio
|
from auto_media_publisher.uploaders.uploader_base import UploaderBase
|
|
CONTENT_UPLOAD_URL="https://creator.xiaohongshu.com/publish/publish?from=menu"
|
VIDEO_PUBLISH_SECCESSFULL_URL = "https://creator.xiaohongshu.com/publish/success?source=&bind_status=not_bind&__debugger__=&proxy="
|
|
|
class UploaderXiaohongshu(UploaderBase):
|
|
async def set_schedule_time(self, page):
|
try:
|
|
scheduled_date_hour = self.scheduled_time.strftime("%Y-%m-%d %H:%M")
|
await page.locator("label").filter(has_text="定时发布").locator("span").nth(1).click()
|
await asyncio.sleep(1)
|
await page.get_by_placeholder("选择日期和时间").click()
|
await asyncio.sleep(1)
|
await page.keyboard.press("Control+KeyA")
|
await page.keyboard.type(str(scheduled_date_hour))
|
await page.keyboard.press("Enter")
|
await asyncio.sleep(1)
|
except Exception as e:
|
self.logger.error(f" [exception] 正在设置发布时间错误:{e}")
|
await asyncio.sleep(2)
|
|
async def set_tile_and_description(self,page):
|
try:
|
self.logger.info("正在填充标题和话题...")
|
await page.get_by_placeholder("填写标题会有更多赞哦~").click()
|
await page.get_by_placeholder("填写标题会有更多赞哦~").fill(self.title[:20])
|
await page.locator("#quillEditor div").click()
|
description_tags = self.description + ' ' + ' '.join(f"#{tag}" for tag in self.tags)
|
await page.locator("#quillEditor div").fill(description_tags)
|
await page.mouse.click(0, 0) # 页面左上角一般是空白
|
except Exception as e:
|
self.logger.error(f" [exception] 正在设置 标题和描述错误:{e}")
|
await asyncio.sleep(2)
|
|
async def set_cover_image(self,page):
|
try:# STEP 3 设置封面
|
self.logger.info("正在设置 封面...")
|
await page.get_by_text("设置封面", exact=True).wait_for()
|
await page.get_by_text("设置封面", exact=True).click()
|
img_upload_button = page.get_by_text("上传图片", exact=True)
|
await img_upload_button.wait_for(state='visible') # 确保按钮可见
|
|
async with page.expect_file_chooser() as fc_info:
|
await img_upload_button.click()
|
file_chooser = await fc_info.value
|
await file_chooser.set_files(self.path_cover)
|
await asyncio.sleep(2)
|
await page.get_by_role("button", name="确定").click(timeout=10000)
|
except Exception as e:
|
self.logger.error(f" [exception] 正在上传视频中... 错误:{e}")
|
await asyncio.sleep(2)
|
|
async def upload_media_and_wait(self,page):
|
try:
|
upload_button = page.locator("input[class^='upload-input']")
|
await upload_button.wait_for(state='visible') # 确保按钮可见
|
|
async with page.expect_file_chooser() as fc_info:
|
await upload_button.click()
|
file_chooser = await fc_info.value
|
await file_chooser.set_files(self.path_media)
|
await asyncio.sleep(2)
|
|
# wait for video upload finish
|
try:
|
locator = page.locator('div:has-text("上传成功")')
|
count = await locator.count()
|
if count > 0:
|
# 判断是否至少有一个匹配的元素是可见的
|
for i in range(count):
|
if await locator.nth(i).is_visible():
|
self.logger.info(" [-] 视频上传完毕")
|
break
|
else:
|
self.logger.info(" [-] 正在上传视频中(元素存在但不可见)...")
|
await asyncio.sleep(3)
|
continue
|
break # 跳出 while True
|
else:
|
self.logger.info(" [-] 正在上传视频中...")
|
await asyncio.sleep(3)
|
except Exception as e:
|
self.logger.error(f" [exception] 正在上传视频中... 错误:{e}")
|
await asyncio.sleep(2)
|
except Exception as e:
|
self.logger.error(f" [exception] 正在上传视频中... 错误:{e}")
|
await asyncio.sleep(2)
|
|
async def _upload_core(self) -> bool:
|
try:
|
page = await self.context.new_page()
|
await page.goto(CONTENT_UPLOAD_URL, timeout=120000)
|
self.logger.info('正在打开主页...')
|
await page.wait_for_url(CONTENT_UPLOAD_URL, timeout=120000)
|
|
await self.upload_media_and_wait(page)
|
|
await self.set_tile_and_description(page)
|
|
if self.path_cover:
|
await self.set_cover_image(page)
|
|
# 定时任务
|
if self.scheduled_time != 0:
|
self.logger.info("正在设置 定时发布 时间...")
|
await self.set_schedule_time(page)
|
|
# 判断视频是否发布成功
|
await page.get_by_role("button", name="定时发布").click()
|
await asyncio.sleep(1)
|
max_retry = 10
|
for _ in range(max_retry):
|
try:
|
current_url = page.url
|
if "success" in current_url: # 或者更精确匹配你需要的路径片段
|
self.logger.info(f"视频发布成功,跳转URL: {current_url}")
|
break
|
else:
|
self.logger.info(f"视频正在发布中... 当前URL: {current_url}")
|
await asyncio.sleep(1)
|
except Exception as e:
|
self.logger.warning(f"等待跳转异常: {e}")
|
await asyncio.sleep(1)
|
else:
|
self.logger.error("超过最大重试次数,发布可能失败。")
|
|
# 始终保存 cookie 并关闭浏览器
|
await self.context.storage_state(path=self.cookie_path)
|
self.logger.info('cookie更新完毕!')
|
await asyncio.sleep(3)
|
return True
|
except Exception as e:
|
self.logger.error(f"{self.user} upload video {self.path_media} fails, error is :{e}")
|
return False
|