From f5cb31079cd2f790996549c81fb726afe10471d9 Mon Sep 17 00:00:00 2001 From: changyandaoc Date: Sat, 13 Jun 2026 11:54:29 +0800 Subject: [PATCH 1/6] =?UTF-8?q?fix(uploader):=20=E9=80=82=E9=85=8D?= =?UTF-8?q?=E6=8A=96=E9=9F=B3=E4=B8=8E=E8=A7=86=E9=A2=91=E5=8F=B7=E5=8F=91?= =?UTF-8?q?=E5=B8=83=E9=A1=B5=E6=9C=80=E6=96=B0=20DOM=20=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 抖音 douyin_uploader: - 填标题/描述:改用 input[placeholder*=填写作品标题] 与 div.zone-container[contenteditable] 定位 - 话题输入后按 Escape 收起下拉,避免浮层拦截 - '选择封面'与'发布'改用 force 点击,并移除拦截点击的新手引导(shepherd)浮层 - 封面弹窗适配:div.dy-creator-content-modal + input.semi-upload-hidden-input;横竖版切换改用'设置竖封面'按钮;完成后等弹窗关闭 视频号 tencent_uploader: - upload_video_file 遍历所有 frame 查找文件框 + 重试等待 说明:两平台发布均需有头模式(--headed),无头下视频号上传框不渲染。 --- uploader/douyin_uploader/main.py | 34 +++++++++++++++---------------- uploader/tencent_uploader/main.py | 31 ++++++++++++++++++++++++---- 2 files changed, 44 insertions(+), 21 deletions(-) diff --git a/uploader/douyin_uploader/main.py b/uploader/douyin_uploader/main.py index 5b25597..b89c5e4 100644 --- a/uploader/douyin_uploader/main.py +++ b/uploader/douyin_uploader/main.py @@ -263,17 +263,12 @@ class DouYinBaseUploader(BaseVideoUploader): await asyncio.sleep(1) async def fill_title_and_description(self, page: Page, title: str, description: str, tags: list[str] | None = None): - description_section = ( - page.get_by_text("作品描述", exact=True) - .locator("xpath=ancestor::div[2]") - .locator("xpath=following-sibling::div[1]") - ) - - title_input = description_section.locator('input[type="text"]').first + # 2026-06 抖音发布页 DOM:标题=input[placeholder*=填写作品标题],描述=div.zone-container[contenteditable] + title_input = page.locator('input[placeholder*="填写作品标题"]').first await title_input.wait_for(state="visible", timeout=10000) await title_input.fill(title[:30]) - description_editor = description_section.locator('.zone-container[contenteditable="true"]').first + description_editor = page.locator('div.zone-container[contenteditable="true"]').first await description_editor.wait_for(state="visible", timeout=10000) await description_editor.click() await page.keyboard.press("Control+KeyA") @@ -283,6 +278,7 @@ class DouYinBaseUploader(BaseVideoUploader): for tag in tags or []: await page.keyboard.type(" #" + tag) await page.keyboard.press("Space") + await page.keyboard.press("Escape") # 收起话题下拉,避免浮层拦截后续点击 async def set_location(self, page: Page, location: str = ""): if not location: @@ -471,12 +467,12 @@ class DouYinVideo(DouYinBaseUploader): return douyin_logger.info(_msg("🏃", "小人正在设置视频封面")) - await page.click('text="选择封面"') - cover_locator_str = 'div[id*="creator-content-modal"]' - cover_locator = page.locator(cover_locator_str) - await page.wait_for_selector(cover_locator_str) + await page.click('text="选择封面"', 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) - upload_input = cover_locator.locator("div[class^='semi-upload upload'] >> input.semi-upload-hidden-input") + upload_input = cover_locator.locator("input.semi-upload-hidden-input").first if self.thumbnail_landscape_path: await page.wait_for_timeout(1000) @@ -485,15 +481,15 @@ class DouYinVideo(DouYinBaseUploader): douyin_logger.info(_msg("🖼️", "横版封面上传完成")) if self.thumbnail_portrait_path: - await cover_locator.locator("div[class*='steps'] div").nth(1).click() + 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("🖼️", "竖版封面上传完成")) - await cover_locator.locator('button:visible:has-text("完成")').click() + await cover_locator.locator('button:has-text("完成")').first.click() douyin_logger.info(_msg("🥳", "视频封面设置完成")) - await page.wait_for_selector("div.extractFooter", state="detached") + await cover_locator.wait_for(state="detached", timeout=15000) async def upload(self, playwright: Playwright) -> None: douyin_logger.info(_msg("🧍", "小人先检查 cookie、视频文件、封面和发布时间")) @@ -573,9 +569,13 @@ class DouYinVideo(DouYinBaseUploader): while True: try: + # 移除会拦截发布按钮点击的新手引导/话题下拉浮层 + await page.evaluate( + "() => { document.querySelectorAll('.shepherd-element, .shepherd-modal-overlay-container, [class*=\"mention-wrapper\"]').forEach(e => e.remove()); }" + ) publish_button = page.get_by_role("button", name="发布", exact=True) if await publish_button.count(): - await publish_button.click() + await publish_button.click(force=True) await page.wait_for_url( "https://creator.douyin.com/creator-micro/content/manage**", timeout=3000, diff --git a/uploader/tencent_uploader/main.py b/uploader/tencent_uploader/main.py index 94cded1..db887f7 100644 --- a/uploader/tencent_uploader/main.py +++ b/uploader/tencent_uploader/main.py @@ -495,12 +495,35 @@ class TencentBaseUploader(BaseVideoUploader): await page.locator("div.input-editor").click() async def open_upload_page(self, page: Page) -> None: - await page.goto(TENCENT_UPLOAD_URL) - await page.wait_for_url(TENCENT_UPLOAD_URL) + await page.goto(TENCENT_UPLOAD_URL, timeout=120000, wait_until="domcontentloaded") + await page.wait_for_url(TENCENT_UPLOAD_URL, timeout=120000) async def upload_video_file(self, page: Page, file_path: str) -> None: - file_input = page.locator('input[type="file"]') - await file_input.set_input_files(file_path) + async def find_file_input(): + for fr in page.frames: # 主 frame + 所有 iframe(视频号编辑器可能在 iframe 内) + try: + fi = fr.locator('input[type="file"]') + if await fi.count(): + return fi.first + except Exception: + continue + return None + + fi = await find_file_input() + if fi is None: + # 助手落在首页:先点「发表视频」唤出编辑器与上传控件 + publish_btn = page.get_by_text("发表视频").first + if await publish_btn.count(): + await publish_btn.click() + await asyncio.sleep(3) + for _ in range(20): + fi = await find_file_input() + if fi is not None: + break + await asyncio.sleep(1) + if fi is None: + raise RuntimeError("未找到视频号文件上传框") + await fi.set_input_files(file_path) async def set_short_title(self, page: Page, title: str, short_title: str | None = None) -> None: short_title_element = ( From 5e28092f201ef881a402078cf9eba16e46a6714a Mon Sep 17 00:00:00 2001 From: changyandao Date: Sat, 13 Jun 2026 15:02:29 +0800 Subject: [PATCH 2/6] =?UTF-8?q?fix(douyin):=20version=5F2=20=E5=8F=91?= =?UTF-8?q?=E5=B8=83=E9=A1=B5=E9=80=82=E9=85=8D=E2=80=94=E2=80=94=E6=A0=87?= =?UTF-8?q?=E9=A2=98=E8=B6=85=E6=97=B6/=E5=B0=81=E9=9D=A2=E8=BE=93?= =?UTF-8?q?=E5=85=A5/=E8=AF=9D=E9=A2=98=E4=B8=8A=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 标题/描述等待 10s→120s:version_2(post/video) 要等视频上传完才渲染表单(实测约40s) - 封面上传修正:封面弹窗有4个隐藏input,前2个是左侧AI参考图,真封面是 nth(1);旧 .first 传错位置导致封面不生效 - set_thumbnail 先清 shepherd 引导浮层,避免拦截选择封面点击 - 完成按钮用 exact 文本,避免误中完成编辑 --- uploader/douyin_uploader/main.py | 56 ++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 18 deletions(-) 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: From e192fa0cb1a5d44c546930380b2e1092fcc7c158 Mon Sep 17 00:00:00 2001 From: changyandao Date: Sat, 13 Jun 2026 15:33:01 +0800 Subject: [PATCH 3/6] =?UTF-8?q?fix(tencent):=20=E8=A7=86=E9=A2=91=E5=8F=B7?= =?UTF-8?q?=E5=AE=9A=E6=97=B6=E6=97=B6=E9=97=B4=E9=80=89=E6=8B=A9=E5=99=A8?= =?UTF-8?q?=E6=94=B6=E5=B0=BE=E5=AE=B9=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit set_schedule_time 键入小时后改用 Enter 确认 + 收起浮层做容错(直接点 input-editor 可能被 weui-desktop-dialog 时间选择浮层遮挡),避免定时步偶发卡死 --- uploader/tencent_uploader/main.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/uploader/tencent_uploader/main.py b/uploader/tencent_uploader/main.py index db887f7..32feaa2 100644 --- a/uploader/tencent_uploader/main.py +++ b/uploader/tencent_uploader/main.py @@ -492,7 +492,13 @@ class TencentBaseUploader(BaseVideoUploader): await page.click('input[placeholder="请选择时间"]') await page.keyboard.press("Control+KeyA") await page.keyboard.type(publish_date.strftime("%H")) - await page.locator("div.input-editor").click() + await page.keyboard.press("Enter") # 确认小时并关闭时间下拉 + await page.wait_for_timeout(500) + # 收起时间选择浮层:直接点描述区可能被 weui-desktop-dialog 遮挡,做容错 + try: + await page.locator("div.input-editor").click(timeout=5000) + except Exception: + await page.keyboard.press("Escape") async def open_upload_page(self, page: Page) -> None: await page.goto(TENCENT_UPLOAD_URL, timeout=120000, wait_until="domcontentloaded") From b9ca5448929ee3fb288e3d2e5a18b2204204b5c4 Mon Sep 17 00:00:00 2001 From: changyandao Date: Sat, 13 Jun 2026 16:23:23 +0800 Subject: [PATCH 4/6] =?UTF-8?q?fix(douyin):=20goto=20=E6=94=B9=20domconten?= =?UTF-8?q?tloaded=20=E9=81=BF=E5=85=8D=E6=8A=96=E9=9F=B3=20SPA=20load=20?= =?UTF-8?q?=E4=BA=8B=E4=BB=B6=E8=B6=85=E6=97=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 创作者中心发布页 load 事件常迟迟不触发,默认 goto(wait_until=load) 偶发 30s 超时。 3 处 content/upload 的 goto 改 wait_until=domcontentloaded + timeout=90000; 对应 wait_for_url 保持仅 timeout(不接受 wait_until) --- uploader/douyin_uploader/main.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/uploader/douyin_uploader/main.py b/uploader/douyin_uploader/main.py index fea5b3f..e8ad943 100644 --- a/uploader/douyin_uploader/main.py +++ b/uploader/douyin_uploader/main.py @@ -55,7 +55,7 @@ async def cookie_auth(account_file): context = await browser.new_context(storage_state=account_file) context = await set_init_script(context) page = await context.new_page() - await page.goto("https://creator.douyin.com/creator-micro/content/upload") + await page.goto("https://creator.douyin.com/creator-micro/content/upload", wait_until="domcontentloaded", timeout=90000) try: await page.wait_for_url("https://creator.douyin.com/creator-micro/content/upload", timeout=5000) except Exception: @@ -522,10 +522,10 @@ class DouYinVideo(DouYinBaseUploader): context = await set_init_script(context) page = await context.new_page() - await page.goto("https://creator.douyin.com/creator-micro/content/upload") + await page.goto("https://creator.douyin.com/creator-micro/content/upload", wait_until="domcontentloaded", timeout=90000) 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") + await page.wait_for_url("https://creator.douyin.com/creator-micro/content/upload", timeout=90000) # 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) @@ -725,9 +725,9 @@ class DouYinNote(DouYinBaseUploader): upload_success = False try: page = await context.new_page() - await page.goto("https://creator.douyin.com/creator-micro/content/upload") + await page.goto("https://creator.douyin.com/creator-micro/content/upload", wait_until="domcontentloaded", timeout=90000) douyin_logger.info(_msg("🧭", "小人正在赶往图文发布页")) - await page.wait_for_url("https://creator.douyin.com/creator-micro/content/upload") + await page.wait_for_url("https://creator.douyin.com/creator-micro/content/upload", timeout=90000) await self.upload_note_content(page) upload_success = True From a083ad911f81c464796ee52c944ffe48493dd777 Mon Sep 17 00:00:00 2001 From: changyandao Date: Sat, 13 Jun 2026 17:34:39 +0800 Subject: [PATCH 5/6] =?UTF-8?q?fix(douyin=20login):=20=E4=BA=8C=E7=BB=B4?= =?UTF-8?q?=E7=A0=81=E6=8F=90=E5=8F=96=E5=A4=B1=E8=B4=A5=E4=B8=8D=E8=87=B4?= =?UTF-8?q?=E5=91=BD=20+=20=E7=99=BB=E5=BD=95=E6=88=90=E5=8A=9F=E5=88=A4?= =?UTF-8?q?=E5=AE=9A=E6=94=BE=E5=AE=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - _save_douyin_qrcode 定位不到二维码元素时不再抛错中止(有头浏览器可直接扫码),仅警告并继续轮询等登录跳转 - _is_douyin_login_completed 放宽到 creator-micro 任意页(不止 /home) - Path(image_path) 对空路径做守卫 --- uploader/douyin_uploader/main.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/uploader/douyin_uploader/main.py b/uploader/douyin_uploader/main.py index e8ad943..3927993 100644 --- a/uploader/douyin_uploader/main.py +++ b/uploader/douyin_uploader/main.py @@ -106,7 +106,12 @@ async def _extract_douyin_qrcode_src(page: Page) -> str: async def _save_douyin_qrcode(page: Page, account_file: str, previous_qrcode_path: Path | None = None, qrcode_callback=None) -> dict: - qrcode_src = await _extract_douyin_qrcode_src(page) + # 提取二维码 src 仅为了保存/终端显示;定位不到时不致命——有头浏览器里二维码可见,直接扫码即可 + try: + qrcode_src = await _extract_douyin_qrcode_src(page) + except Exception as exc: + douyin_logger.warning(_msg("😵", f"没定位到二维码元素({str(exc)[:50]})——请直接在弹出的浏览器里扫码,小人继续等登录跳转")) + return {"image_path": "", "image_data_url": ""} qrcode_path = save_data_url_image(qrcode_src, build_login_qrcode_path(account_file)) if previous_qrcode_path and previous_qrcode_path != qrcode_path: if remove_qrcode_file(previous_qrcode_path): @@ -126,7 +131,8 @@ async def _save_douyin_qrcode(page: Page, account_file: str, previous_qrcode_pat async def _is_douyin_login_completed(page: Page) -> bool: - if not page.url.startswith("https://creator.douyin.com/creator-micro/home"): + # 登录后会跳到 creator-micro 下任意页(home/content 等);登录页是 creator.douyin.com/ 根路径 + if "creator.douyin.com/creator-micro" not in page.url: return False login_markers = [ @@ -149,7 +155,7 @@ async def _is_douyin_login_completed(page: Page) -> bool: async def _wait_for_douyin_login(page: Page, account_file: str, qrcode_info: dict, qrcode_callback=None, poll_interval: int = 3, max_checks: int = 100) -> dict: - qrcode_path = Path(qrcode_info["image_path"]) + qrcode_path = Path(qrcode_info["image_path"]) if qrcode_info.get("image_path") else None for _ in range(max_checks): if await _is_douyin_login_completed(page): douyin_logger.info(_msg("🥳", f"扫码成功,已经跳转到登录后页面: {page.url}")) @@ -161,7 +167,7 @@ async def _wait_for_douyin_login(page: Page, account_file: str, qrcode_info: dic await expired_box.click() await asyncio.sleep(1) qrcode_info = await _save_douyin_qrcode(page, account_file, qrcode_path, qrcode_callback=qrcode_callback) - qrcode_path = Path(qrcode_info["image_path"]) + qrcode_path = Path(qrcode_info["image_path"]) if qrcode_info.get("image_path") else None await asyncio.sleep(poll_interval) @@ -185,7 +191,7 @@ async def douyin_cookie_gen( page = await context.new_page() await page.goto("https://creator.douyin.com/") qrcode_info = await _save_douyin_qrcode(page, account_file, qrcode_callback=qrcode_callback) - qrcode_path = Path(qrcode_info["image_path"]) + qrcode_path = Path(qrcode_info["image_path"]) if qrcode_info.get("image_path") else None douyin_logger.info(_msg("🧍", "请扫码,小人正在耐心等待登录完成")) result = await _wait_for_douyin_login( page, From 914a5f0910ca767f4c169aa888989a358831ed75 Mon Sep 17 00:00:00 2001 From: changyandao Date: Sat, 13 Jun 2026 18:23:35 +0800 Subject: [PATCH 6/6] =?UTF-8?q?fix(douyin):=20cookie=20=E6=A0=A1=E9=AA=8C?= =?UTF-8?q?=E6=94=B9=E6=9C=89=E5=A4=B4=EF=BC=8C=E9=81=BF=E5=85=8D=E6=97=A0?= =?UTF-8?q?=E5=A4=B4=E8=A2=AB=E5=8F=8D=E7=88=AC=E5=A2=99=E8=AF=AF=E5=88=A4?= =?UTF-8?q?=E5=A4=B1=E6=95=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cookie_auth 原硬编码 headless=True,但抖音无头会撞反爬墙→content/upload 跳登录→ 间歇性误报 cookie 失效(实测登录刚成功 90s 后校验即失败)。改 headless=False + 反检测 args。 --- uploader/douyin_uploader/main.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/uploader/douyin_uploader/main.py b/uploader/douyin_uploader/main.py index 3927993..05b123e 100644 --- a/uploader/douyin_uploader/main.py +++ b/uploader/douyin_uploader/main.py @@ -50,7 +50,11 @@ def _build_login_result(success: bool, status: str, message: str, account_file: async def cookie_auth(account_file): async with async_playwright() as playwright: - browser = await playwright.chromium.launch(headless=True, channel="chrome") + # 抖音无头会撞反爬墙→content/upload 跳登录→误判 cookie 失效(间歇性)。校验必须有头。 + browser = await playwright.chromium.launch( + headless=False, channel="chrome", + args=["--no-sandbox", "--disable-blink-features=AutomationControlled"], + ) try: context = await browser.new_context(storage_state=account_file) context = await set_init_script(context)