From 42f2209534b5e8bd2780cbf77913f63cd3e732f1 Mon Sep 17 00:00:00 2001 From: butubb <1422726308@qq.com> Date: Sat, 10 Oct 2026 15:29:02 +0800 Subject: [PATCH] =?UTF-8?q?fix(douyin):=20=E4=B8=A4=E4=B8=AA=E8=AE=A9?= =?UTF-8?q?=E6=8A=96=E9=9F=B3=E9=87=87=E9=9B=86=E6=A0=B9=E6=9C=AC=E8=B7=91?= =?UTF-8?q?=E4=B8=8D=E8=B5=B7=E6=9D=A5=E7=9A=84=E4=B8=8A=E6=B8=B8=E7=BC=BA?= =?UTF-8?q?=E9=99=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户建了个抖音博主任务,两次都是 exit 1、产物目录空空。查下来是上游两处缺陷, 与我们那层监控无关 —— 但它们在别的网络上未必复现,所以社区里没人报。 1) media_platform/douyin/core.py:101 —— 首页 goto 永远等不到 load await self.context_page.goto(self.index_url) # 默认 wait_until="load" 抖音首页的 load 事件不会触发(有长连接/埋点类请求一直挂着)。实测同一台 Chrome、 同一个地址:domcontentloaded 0.7 秒返回,load 等满 90 秒仍超时。后果是整个采集 一步没走就崩,退出码 1,看起来像「抖音不能用」。改成显式 domcontentloaded —— 上游的贴吧和知乎本来就是这么写的,抖音这个页面只是恰好属于「永远不 load」那类。 2) media_platform/douyin/login.py:266 —— 注入 cookie 后页面是陈旧的 login_by_cookies 把 cookie 塞进 context,但页面是在这之前加载的;SPA 只在加载时 读一次登录态,localStorage.HasUserLogin 还停在"未登录",紧接着 check_login_state 会对着这个陈旧值轮询到超时(600×1 秒=十分钟)再 sys.exit()。下一轮才正常,因为 那时 cookie 已在 profile 里 —— 表现是"第一次白等十分钟、第二次才行",很容易被当 偶发。修复:注入后 reload(domcontentloaded)。 与 xhs 那个 __INITIAL_STATE__ 快照问题是同一类:页面状态是加载那一刻的快照。 本仓库扫码登录与运营模块也各自踩过。 UPSTREAM.md 的第二类「上游 bug 修复」表补上这两条与成因说明(原来只有两条 xhs 的)。 验证:在服务器容器里手工复现,改完后真实采到数据 —— Parsed sec_user_id: MS4wLjABAAAArLubjxXEcLeiqehxgk4il4AHMu0hXu6_qlmD8z5WmMs get_all_user_aweme_posts ... video len : 1 douyin aweme id:7690458980574358513, title:中秋哪儿都堵... 产物落在 douyin/jsonl/ 下,顺带把适配器里「平台 id 是 dy、目录是 douyin」的映射 用真实输出证实了。 --- UPSTREAM.md | 40 ++++++++++++++++++++++++++++++---- media_platform/douyin/core.py | 7 +++++- media_platform/douyin/login.py | 11 ++++++++++ 3 files changed, 53 insertions(+), 5 deletions(-) diff --git a/UPSTREAM.md b/UPSTREAM.md index ccc0fd8..d1064a2 100644 --- a/UPSTREAM.md +++ b/UPSTREAM.md @@ -64,14 +64,16 @@ Dockerfile / .dockerignore / docker-compose.yml 服务器部署用 | 文件 | 修的问题 | |---|---| -| `media_platform/xhs/core.py` | 见下节 | -| `media_platform/xhs/login.py` | 同上(cookie 加固) | +| `media_platform/xhs/core.py` | 见下节 1 | +| `media_platform/xhs/login.py` | 见下节 2(cookie 加固) | +| `media_platform/douyin/core.py` | 见下节 3(首页 `goto` 永远超时,采集根本起不来) | +| `media_platform/douyin/login.py` | 见下节 4(注入 cookie 后页面陈旧,白等十分钟) | --- -## 二、应该给上游提 PR 的两个修复 +## 二、应该给上游提 PR 的四个修复 -这两处是**上游自身的缺陷**,提上去以后就不用自己背着: +这四处都是**上游自身的缺陷**,提上去以后就不用自己背着: ### 1. 博主主页抓取失败会跳掉整个博主(`xhs/core.py`) @@ -90,6 +92,36 @@ Dockerfile / .dockerignore / docker-compose.yml 服务器部署用 `a1` / `webId` 等签名所需 cookie 只能靠持久化 profile 补,冷启动时签名会失败。 默认行为保持不变,用 `INJECT_ALL_COOKIES` 开关控制。 +### 3. 抖音首页的 `goto` 永远等不到 `load`(`douyin/core.py:101`) + +```python +await self.context_page.goto(self.index_url) # 默认 wait_until="load" +``` + +抖音首页的 `load` 事件**不会触发**(有长连接/埋点类请求一直挂着)。实测:同一台 +Chrome、同一个地址,`domcontentloaded` 0.7 秒返回,而 `load` 等满 90 秒仍然超时。 +后果是整个采集**一步都没走就崩**,退出码 1 —— 看起来像"抖音不能用"。 + +修复:显式 `wait_until="domcontentloaded"`。上游的贴吧(`tieba/core.py`)和知乎 +(`zhihu/core.py`)本来就是这么写的,抖音这个页面只是恰好属于"永远不 load"的那类。 + +> 这个缺陷在本机可能复现不出来(换个网络/有缓存时 `load` 也许能触发),所以社区里 +> 没人报。它和网络快慢无关:不是"慢",是那个事件根本不会发生。 + +### 4. 注入 cookie 后页面是陈旧的(`douyin/login.py:266`) + +`login_by_cookies()` 把 cookie 塞进 browser context,但**页面是在这之前加载的** —— +SPA 只在加载时读一次登录态,`localStorage.HasUserLogin` 于是还停在"未登录", +紧接着的 `check_login_state()` 会对着这个陈旧的值轮询到超时(600 次 × 1 秒 = 十分钟), +然后 `sys.exit()`。**下一轮**才正常,因为那时 cookie 已经在 profile 里了。 + +表现是"第一次跑白等十分钟、第二次才行",很容易被当成偶发。 + +修复:注入完 cookie 后 `reload(wait_until="domcontentloaded")`,让站点立刻重新判定会话。 + +> 与第 1 条同源:都是"页面状态是加载那一刻的快照"。本仓库的扫码登录(`api/monitor/qrlogin.py`) +> 和运营模块也各自踩过这个坑,那里的判据改成了拿 cookie 问后台接口,而不是读页面快照。 + --- ## 三、上游更新时怎么操作 diff --git a/media_platform/douyin/core.py b/media_platform/douyin/core.py index 10380a6..f798665 100644 --- a/media_platform/douyin/core.py +++ b/media_platform/douyin/core.py @@ -98,7 +98,12 @@ class DouYinCrawler(AbstractCrawler): await self.browser_context.add_init_script(path="libs/stealth.min.js") self.context_page = await self.browser_context.new_page() - await self.context_page.goto(self.index_url) + # wait_until="domcontentloaded" instead of the default "load": the douyin + # home page never fires the load event (some long-lived request keeps it + # pending), so the default burns the whole timeout and the crawl dies + # before it starts. Measured here: domcontentloaded returns in 0.7s while + # load still times out at 90s. Tieba and Zhihu already do the same. + await self.context_page.goto(self.index_url, wait_until="domcontentloaded") self.dy_client = await self.create_douyin_client(httpx_proxy_format) if not await self.dy_client.pong(browser_context=self.browser_context): diff --git a/media_platform/douyin/login.py b/media_platform/douyin/login.py index 4b76d11..873532e 100644 --- a/media_platform/douyin/login.py +++ b/media_platform/douyin/login.py @@ -272,3 +272,14 @@ class DouYinLogin(AbstractLogin): 'domain': ".douyin.com", 'path': "/" }]) + # Reload after injecting. The page was loaded *before* these cookies existed, + # so its `localStorage.HasUserLogin` still holds the logged-out value and + # check_login_state() polls it until the timeout expires (10 minutes) without + # ever succeeding -- only the *next* run works, because by then the cookies + # are in the profile. Reloading makes the site re-evaluate the session now. + try: + await self.context_page.reload(wait_until="domcontentloaded") + except Exception as exc: # pragma: no cover - reload is best effort + utils.logger.warning( + f"[DouYinLogin.login_by_cookies] reload after cookie injection failed: {exc}" + )