fix(douyin): 两个让抖音采集根本跑不起来的上游缺陷
用户建了个抖音博主任务,两次都是 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」的映射
用真实输出证实了。
This commit is contained in:
+36
-4
@@ -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 问后台接口,而不是读页面快照。
|
||||
|
||||
---
|
||||
|
||||
## 三、上游更新时怎么操作
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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}"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user