feat(monitor): 评论接口带上 a_bogus 签名;修好作品导出的空列
**评论能采了。** 之前 comment/list 一直回 200 + 空 body,被读成「这条没评论」—— 而它其实只是被网关挡了。缺的就是 a_bogus 签名,仓库里本来就有 (libs/douyin.js + execjs)。签上之后实测 200 / 9960 字节真评论。 只给评论接口签:作品、详情、博主资料三个不带签名也照常返回,而给它们加签名是 没验证过的改动。签名按需 import —— 那个模块 import 时就把 JS 喂给 execjs, 不该拖进监控层热路径。 **作品导出那几列一直是空的。** 列名写的是裸键 liked_count,而作品行的指标嵌在 metrics / deltas 里,row.get() 永远取到 None —— 导出来的表有「点赞/评论/收藏/ 分享」四列,每一格都没有数。原来的测试只断言了「作品ID」,所以没发现。 顺手补上:导出带上 博主备注/昵称、作品备注、发布时间,时间戳格式化成人能读的 形态(原来是一串 13 位毫秒,Excel 里没法看也没法排序)。
This commit is contained in:
@@ -41,6 +41,7 @@ import os
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
from urllib.parse import urlencode
|
||||
|
||||
import config
|
||||
import httpx
|
||||
@@ -281,14 +282,44 @@ def _cookie_value(cookie: str, name: str) -> str:
|
||||
return ""
|
||||
|
||||
|
||||
def _sign(params: Dict[str, Any], path: str, user_agent: str) -> Dict[str, Any]:
|
||||
"""给一组参数补上 ``a_bogus`` 签名,返回新 dict。
|
||||
|
||||
**按需 import**:那个模块在 import 的那一瞬间就把 ``libs/douyin.js`` 交给 execjs
|
||||
编译(还要读相对路径),把它拖进监控层的热路径不合适。
|
||||
|
||||
签名算在**不含 a_bogus 的那串 query 上**,追加到末尾 —— 和爬虫那条路一致,也是
|
||||
实测能过的形态。
|
||||
"""
|
||||
from media_platform.douyin.help import get_a_bogus_from_js
|
||||
|
||||
try:
|
||||
return {
|
||||
**params,
|
||||
"a_bogus": get_a_bogus_from_js(path, urlencode(params), user_agent),
|
||||
}
|
||||
except Exception as exc: # execjs 起不来 / JS 抛错,都算签名失败
|
||||
raise DouyinApiError(f"算 a_bogus 签名失败:{exc}") from exc
|
||||
|
||||
|
||||
async def _get(
|
||||
path: str, params: Dict[str, Any], identity: BrowserIdentity
|
||||
path: str,
|
||||
params: Dict[str, Any],
|
||||
identity: BrowserIdentity,
|
||||
*,
|
||||
signed: bool = False,
|
||||
) -> Dict[str, Any]:
|
||||
"""发一个 GET,返回 JSON。
|
||||
|
||||
只带调用方给的参数 —— **不要往里加 webid / msToken / browser_version 那一堆**,
|
||||
那正是爬虫那条路失败的原因。
|
||||
|
||||
``signed=True`` 时补一个 ``a_bogus``。**只有评论接口需要它**:作品、详情、博主资料
|
||||
三个不带签名也照常返回,而给它们加签名是没验证过的改动,不做。
|
||||
"""
|
||||
if signed:
|
||||
params = _sign(params, path, identity.user_agent)
|
||||
|
||||
url = f"{API_ORIGIN}{path}"
|
||||
async with httpx.AsyncClient(timeout=REQUEST_TIMEOUT_SECONDS) as client:
|
||||
response = await client.get(
|
||||
@@ -487,6 +518,10 @@ async def video_comments(
|
||||
"aid": 6383,
|
||||
},
|
||||
identity,
|
||||
# 这个接口**必须**签名。不签的话网关回 200 + 空 body,会被读成「这条没评论」,
|
||||
# 而它其实只是被挡了 —— 和登录失效长得一模一样。(实测:带上签名 200/9960 字节
|
||||
# 真评论,不带就是空的。)
|
||||
signed=True,
|
||||
)
|
||||
|
||||
records = [
|
||||
|
||||
Reference in New Issue
Block a user