diff --git a/uploader/douyin_uploader/main.py b/uploader/douyin_uploader/main.py index b89c5e4..fea5b3f 100644 --- a/uploader/douyin_uploader/main.py +++ b/uploader/douyin_uploader/main.py @@ -264,12 +264,13 @@ class DouYinBaseUploader(BaseVideoUploader): async def fill_title_and_description(self, page: Page, title: str, description: str, tags: list[str] | None = None): # 2026-06 抖音发布页 DOM:标题=input[placeholder*=填写作品标题],描述=div.zone-container[contenteditable] + # version_2(post/video) 发布页要等视频上传完才渲染表单(实测约 40s),故等待超时给到 120s title_input = page.locator('input[placeholder*="填写作品标题"]').first - await title_input.wait_for(state="visible", timeout=10000) + await title_input.wait_for(state="visible", timeout=120000) await title_input.fill(title[:30]) description_editor = page.locator('div.zone-container[contenteditable="true"]').first - await description_editor.wait_for(state="visible", timeout=10000) + await description_editor.wait_for(state="visible", timeout=120000) await description_editor.click() await page.keyboard.press("Control+KeyA") await page.keyboard.press("Delete") @@ -467,29 +468,46 @@ class DouYinVideo(DouYinBaseUploader): return douyin_logger.info(_msg("🏃", "小人正在设置视频封面")) - await page.click('text="选择封面"', force=True) + # 先清掉 shepherd 新手引导浮层,否则它会拦截“选择封面”点击导致弹窗打不开 + await page.evaluate( + "() => document.querySelectorAll('.shepherd-element,.shepherd-modal-overlay-container').forEach(e=>e.remove())" + ) + await page.get_by_text("选择封面", exact=True).first.click(force=True) cover_locator_str = 'div.dy-creator-content-modal' cover_locator = page.locator(cover_locator_str).first - await page.wait_for_selector(cover_locator_str, timeout=15000) + await page.wait_for_selector(cover_locator_str, timeout=20000) - upload_input = cover_locator.locator("input.semi-upload-hidden-input").first - - if self.thumbnail_landscape_path: - await page.wait_for_timeout(1000) - await upload_input.set_input_files(self.thumbnail_landscape_path) - await page.wait_for_timeout(2000) - douyin_logger.info(_msg("🖼️", "横版封面上传完成")) + await page.wait_for_timeout(1500) + # version_2 封面弹窗有 4 个隐藏 file input: + # [0]/[1] 左侧“AI生成参考图”上传/替换,[2]/[3] 才是“上传封面”/替换。 + # 旧代码用 .first 传到了 AI 参考图(不会成为封面)→ 这就是“传了却没封面”的根因。 + # 取 input.semi-upload-hidden-input 的第 2 个(nth(1)),即真正的封面上传输入。 + cover_upload = cover_locator.locator("input.semi-upload-hidden-input").nth(1) if self.thumbnail_portrait_path: - await cover_locator.locator('button:has-text("设置竖封面")').first.click() - await page.wait_for_timeout(1000) - await upload_input.set_input_files(self.thumbnail_portrait_path) - await page.wait_for_timeout(2000) - douyin_logger.info(_msg("🖼️", "竖版封面上传完成")) + # 弹窗默认就在“设置竖封面”页;防御性点一下 tab(已激活则忽略) + try: + await cover_locator.get_by_text("设置竖封面", exact=True).first.click(timeout=3000) + await page.wait_for_timeout(800) + except Exception: + pass + await cover_upload.set_input_files(self.thumbnail_portrait_path) + await page.wait_for_timeout(3000) + douyin_logger.info(_msg("🖼️", "竖版封面已上传到预览")) + elif self.thumbnail_landscape_path: + try: + await cover_locator.get_by_text("设置横封面", exact=True).first.click(timeout=3000) + await page.wait_for_timeout(800) + except Exception: + pass + await cover_upload.set_input_files(self.thumbnail_landscape_path) + await page.wait_for_timeout(3000) + douyin_logger.info(_msg("🖼️", "横版封面已上传到预览")) - await cover_locator.locator('button:has-text("完成")').first.click() + # 点红色主按钮“完成”应用封面(exact 避免误中“完成编辑”) + await cover_locator.get_by_role("button", name="完成", exact=True).first.click() douyin_logger.info(_msg("🥳", "视频封面设置完成")) - await cover_locator.wait_for(state="detached", timeout=15000) + await cover_locator.wait_for(state="detached", timeout=20000) async def upload(self, playwright: Playwright) -> None: douyin_logger.info(_msg("🧍", "小人先检查 cookie、视频文件、封面和发布时间")) @@ -508,6 +526,8 @@ class DouYinVideo(DouYinBaseUploader): douyin_logger.info(_msg("🏃", f"小人开始搬运视频: {self.title}.mp4")) douyin_logger.info(_msg("🧭", "小人正在赶往上传主页")) await page.wait_for_url("https://creator.douyin.com/creator-micro/content/upload") + # wait_for_url 完成时上传页可能尚未渲染出文件 input(实测偶发),先等它挂载再 set_input_files + await page.wait_for_selector("div[class^='container'] input", state="attached", timeout=60000) await page.locator("div[class^='container'] input").set_input_files(self.file_path) while True: