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}" + )