fix(youtube): wait for upload to finish before publishing; add optional proxy

- Wait until the upload progress reaches 100% before clicking publish. The
  browser-based upload only progresses while the page is open, so publishing
  (and closing the browser) mid-upload cut it off and left the video stuck
  partway (e.g. 76%).
- Add optional YT_PROXY (conf.py): where youtube.com is blocked, direct
  connections time out and the chromium does not use the system proxy, so
  allow pointing it at a local proxy explicitly.
This commit is contained in:
nirolee
2026-06-14 15:18:05 +09:00
parent 991bcdfdf3
commit aa5112e985
3 changed files with 50 additions and 1 deletions
+2
View File
@@ -187,6 +187,8 @@ sau youtube upload-video --account <account_name> --file videos/demo.mp4 --title
> 是因为**未通过 Google 合规审核的 API 项目上传的视频会被强制锁为私享、无法改公开**,对个人/单频道不实用; > 是因为**未通过 Google 合规审核的 API 项目上传的视频会被强制锁为私享、无法改公开**,对个人/单频道不实用;
> 浏览器自动化没有此限制,可直接发布公开视频,也与本项目其它平台的 cookie 方案一致。 > 浏览器自动化没有此限制,可直接发布公开视频,也与本项目其它平台的 cookie 方案一致。
> `--playlist` 适合连载/系列追更;`--visibility` 可选 `public`/`unlisted`/`private`。 > `--playlist` 适合连载/系列追更;`--visibility` 可选 `public`/`unlisted`/`private`。
> 上传会**等进度到 100% 再点发布**(浏览器上传靠窗口开着传,传一半就发布会被掐断卡在中途)。
> youtube.com 被墙的地区:在 `conf.py` 设 `YT_PROXY = "http://127.0.0.1:7890"`(chromium 不吃系统代理,需显式指定)。
补充说明: 补充说明:
+4
View File
@@ -5,3 +5,7 @@ XHS_SERVER = "http://127.0.0.1:11901" # only used by xhs-related flows
LOCAL_CHROME_PATH = "" # optional, e.g. C:/Program Files/Google/Chrome/Application/chrome.exe LOCAL_CHROME_PATH = "" # optional, e.g. C:/Program Files/Google/Chrome/Application/chrome.exe
LOCAL_CHROME_HEADLESS = True # default headless behavior for uploader/examples LOCAL_CHROME_HEADLESS = True # default headless behavior for uploader/examples
DEBUG_MODE = True # default debug behavior DEBUG_MODE = True # default debug behavior
# Optional proxy for the YouTube uploader. Where youtube.com is blocked, direct
# connections time out and the (patchright) chromium does NOT use the system proxy.
# Point this at your local proxy port, e.g. "http://127.0.0.1:7890". None = no proxy.
YT_PROXY = None
+44 -1
View File
@@ -21,6 +21,13 @@ from uploader.base_video import BaseVideoUploader
from utils.base_social_media import set_init_script from utils.base_social_media import set_init_script
from utils.log import youtube_logger from utils.log import youtube_logger
try:
# 国内直连 youtube.com 会超时,且 patchright 启的 chromium 不吃系统代理。
# 在 conf.py 设 YT_PROXY = "http://127.0.0.1:7890"(本地代理端口)即可;不设则不走代理。
from conf import YT_PROXY
except Exception:
YT_PROXY = None
STUDIO_URL = "https://studio.youtube.com" STUDIO_URL = "https://studio.youtube.com"
UPLOAD_URL = "https://www.youtube.com/upload" UPLOAD_URL = "https://www.youtube.com/upload"
VISIBILITY = {"public": "PUBLIC", "unlisted": "UNLISTED", "private": "PRIVATE"} VISIBILITY = {"public": "PUBLIC", "unlisted": "UNLISTED", "private": "PRIVATE"}
@@ -121,6 +128,34 @@ async def _click_if_present(page: Page, selector: str, timeout: int = 4000) -> b
return False return False
async def _wait_upload_complete(page: Page, max_polls: int = 360) -> bool:
"""等网页上传从 X% 跑到 100% 再发布。浏览器上传靠窗口开着才传得完,
若上传到一半就点发布并关闭浏览器,上传会被掐断卡在中途(如 76%)。
出现“处理/检查/上传完成”或不再“正在上传”即视为传完。max_polls*5s=30min 上限。"""
last = ""
for _ in range(max_polls):
txt = ""
for sel in (".progress-label", "span.progress-label", "ytcp-video-upload-progress"):
loc = page.locator(sel).first
try:
if await loc.count():
txt = (await loc.inner_text()).strip()
if txt:
break
except Exception:
pass
if txt:
if any(k in txt for k in ("处理", "检查", "上传完成", "已上传", "Processing", "complete", "Checks", "Finished")):
youtube_logger.info(_msg("✅", f"上传完成: {txt[:40]}"))
return True
if txt != last:
youtube_logger.info(_msg("⏳", f"上传中: {txt[:40]}"))
last = txt
await page.wait_for_timeout(5000)
youtube_logger.warning(_msg("⚠️", "等上传超时(30min),仍尝试发布"))
return False
class YouTubeVideo(BaseVideoUploader): class YouTubeVideo(BaseVideoUploader):
def __init__(self, title, file_path, tags, account_file, *, def __init__(self, title, file_path, tags, account_file, *,
description="", thumbnail_path=None, playlist=None, description="", thumbnail_path=None, playlist=None,
@@ -137,7 +172,10 @@ class YouTubeVideo(BaseVideoUploader):
self.headless = headless self.headless = headless
async def upload(self, playwright: Playwright) -> None: async def upload(self, playwright: Playwright) -> None:
browser = await playwright.chromium.launch(headless=self.headless, channel="chrome") browser = await playwright.chromium.launch(
headless=self.headless, channel="chrome",
proxy={"server": YT_PROXY} if YT_PROXY else None,
)
context = await browser.new_context(storage_state=self.account_file) context = await browser.new_context(storage_state=self.account_file)
context = await set_init_script(context) context = await set_init_script(context)
page = await context.new_page() page = await context.new_page()
@@ -235,6 +273,11 @@ class YouTubeVideo(BaseVideoUploader):
youtube_logger.info(_msg("🌐", f"设置可见性 = {self.visibility}")) youtube_logger.info(_msg("🌐", f"设置可见性 = {self.visibility}"))
await _click_if_present(page, f"tp-yt-paper-radio-button[name='{VISIBILITY[self.visibility]}']", 10000) await _click_if_present(page, f"tp-yt-paper-radio-button[name='{VISIBILITY[self.visibility]}']", 10000)
# 10.5) 关键:等上传真正传完再发布。浏览器上传靠窗口开着传,
# 传到一半就点发布+关浏览器 = 上传被掐断卡在中途(如 76%)。
youtube_logger.info(_msg("📤", "等待上传完成(传完才发布)…"))
await _wait_upload_complete(page)
# 11) 发布 # 11) 发布
await page.wait_for_timeout(1200) await page.wait_for_timeout(1200)
if not await _click_if_present(page, "#done-button", 15000): if not await _click_if_present(page, "#done-button", 15000):