Commit Graph
836 Commits
Author SHA1 Message Date
butubb 2613f7577f feat(creator): Phase 0 探针 —— 创作者后台数据可以纯请求拿到
Deploy VitePress site to Pages / build (push) Canceled after 0s
Deploy VitePress site to Pages / Deploy (push) Canceled after 0s
结论:签名可自造、主站 cookie 即可认证、接口与参数已与真实页面对齐。不需要浏览器、不需要独立的创作者登录。

- tools/probe_creator_api.py: 纯 HTTP 探针。用 XYW_ 算法自签(MD5 → base64 → AES-128-CBC,
  密钥与 IV 与 xhshow/config/config.py 逐字节一致),对 note/analyze/list 发请求
- tools/probe_creator_page.py: 打开真实数据分析页,记录页面自己发的请求,作为地面真相

Phase 0 的三条实测结论:
1. 签名可伪造。三种写法里只有「url= + 路径 + 查询串」被接受(200);仅路径、或裸路径都 406。
   并且不带 cookie 时返回的是应用层 401「无登录信息」而非网关 406 —— 说明签名每次都已通过
2. 主站 .xiaohongshu.com 的 cookie 就能认证创作者后台,不需要单独的创作者会话
3. 当前账号 dfg 返回空数据不是技术问题:permission/query 的 tip_msg 是
   「已为您申请数据权限,次日可查看」,display/status 均为 0,即权限尚未生效

关键佐证:真实页面调 note/analyze/list 用的查询串与本探针生成的完全一致,且拿到同一份空响应。
2026-10-07 16:22:15 +08:00
butubb 2b9ebdad87 fix(ui): 「每轮最多采集作品数」标签是错的——它是每个博主的上限
Deploy VitePress site to Pages / build (push) Canceled after 0s
Deploy VitePress site to Pages / Deploy (push) Canceled after 0s
爬虫里这个值是在 per-creator 的函数内比较的(client.py get_all_notes_by_creator 的 result 是局部变量),而外层 for 循环遍历全部目标。所以 100 个目标 × 20 篇 = 单轮最多 2000 篇,一篇都不会被丢弃。标签写成「每轮最多」会让人以为超出的会被截掉。

- creator 模式:标签改为「每个博主最多采集作品数」,并实时算出「N 个目标 × M 篇 → 单轮最多 X 篇」
- note 模式:禁用该输入并说明「此项不生效」——get_specified_notes 里没有任何 CRAWLER_MAX_NOTES_COUNT 引用,列出的每个链接都会被逐条抓
- 单轮估算超过 500 篇时给出警告:每篇还要抓最多 max_comments_count 条评论、并发为 1,容易触发限流,也可能跑不完就被默认 1 小时的任务超时中断
2026-10-07 15:46:11 +08:00
butubb 6eff6fcc83 fix: 扫码登录状态可独立查询 + 趋势图改回自适应纵轴
Deploy VitePress site to Pages / build (push) Canceled after 0s
Deploy VitePress site to Pages / Deploy (push) Canceled after 0s
【登录反馈】实测这台浏览器 loggedIn=true,其实早就登录成功了;看不到反馈是判据和设计的问题:

1. 判据不可靠。原先靠 web_session 的值变化判断——对照组显示:一个全新的空 profile 首次访问小红书就会被发一个 web_session,所以「有这个 cookie」什么都证明不了。可信信号是页面自己的 __INITIAL_STATE__.user.loggedIn,但它是 Vue 响应式引用,必须 .value 解包(这就是前面探针读到 [object Object] 和 None 的原因)。
2. 状态绑死在临时会话上。扫码会话是内存状态,进程一重启就没(部署、崩溃都算),面板于是悄悄退回初始态——一次成功扫码看起来像什么都没发生。

改法不是让会话活得久,而是把「登没登录」变成随时可查、与会话无关:
- 新增 GET /api/monitor/login/state,直接问浏览器要答案,带 5 秒缓存;force=true 先重载页面再读,用于状态陈旧
- qrlogin 改为常驻 Playwright 客户端 + 复用同一个标签页,并在重启后认领浏览器里已存在的 xhs 标签页,避免堆孤儿页
- 把「读不到状态」与「未登录」分开——前者显示具体错误,不再悄悄显示成未登录
- 面板顶部常驻显示登录态与昵称,带「重新检测」按钮;扫码成功后自动翻转

【趋势图】上一轮改过头了。dataviz 规范里没有「折线图必须从 0 起」这条——基线相关的条文全是讲柱状图的(柱状图用长度编码数值,不从 0 起比例就是错的;折线图用位置编码,轴只需如实框住数据)。改回自适应,保留上一轮修好的左侧刻度栏让范围始终可见;步长收敛到 1/2/5×10ⁿ,全平序列撑开一档避免除零。
2026-10-07 15:42:58 +08:00
butubb e608b51210 test: 修正列渲染断言——SQLAlchemy 会对保留字加反引号
Deploy VitePress site to Pages / build (push) Canceled after 0s
Deploy VitePress site to Pages / Deploy (push) Canceled after 0s
monitor_run.trigger 是 MySQL 保留字,渲染出来是 `trigger`。这比之前手写的列清单更正确:
清单里的裸 trigger 会直接语法错误。断言改为剥掉引号后比对列名。
2026-10-07 15:37:08 +08:00
butubb d937ff5fe6 fix(db): _ensure_columns 改为按模型元数据推导,并补上漏加的调度字段
Deploy VitePress site to Pages / build (push) Canceled after 0s
Deploy VitePress site to Pages / Deploy (push) Canceled after 0s
上一个提交加了 4 个调度字段,却没在 _ADDED_COLUMNS 里登记,后果是生产环境:
  - 任务列表接口报 Unknown column,UI 打不开任务列表
  - 调度器每 20 秒 tick 一次炸一次,定时任务完全不会触发
  最阴险的是启动完全正常——能连库、能起来,只是随后每条查询都失败。

- _ensure_columns 不再遍历手写清单,改为遍历 MonitorBase.metadata.sorted_tables,
  从根上消掉「加了字段忘了登记」这类漏
- 新增 _column_ddl:用 CreateColumn 渲染类型与可空性,并给 NOT NULL 列补 DEFAULT。
  模型的 default= 是 ORM 侧行为、不会进 DDL,而给已有数据的表加 NOT NULL 列必须有值,
  否则能否成功取决于服务端 sql_mode
- 主键列跳过:MySQL 不允许 AUTO_INCREMENT 与 DEFAULT 共存

新增 tests/test_monitor_column_migration.py 守住:每个 NOT NULL 列都必须能生成带
DEFAULT 的合法 ALTER。
2026-10-07 15:35:40 +08:00
butubb 8f4e5586e9 feat(schedule): 任务支持「每天定时 / 每周定时」,用选择器而不是手写 cron
Deploy VitePress site to Pages / build (push) Canceled after 0s
Deploy VitePress site to Pages / Deploy (push) Canceled after 0s
- schedule.py: 新增调度计算模块(纯函数,便于单测)。三种模式:interval(每 N 分钟)/ daily(选钟点)/ weekly(选星期 + 钟点)
- 钟点模式是「固定时刻」而非「固定延迟」——从日历重算,所以某轮跑晚了不会把之后每一轮都拖晚。interval 保持原语义:从上一轮开始计时
- 抖动只加给 interval。给「每天 9:00」也加抖动就成了 9:00–9:01 随机触发,操作者选的时间被悄悄改掉,只会像 bug
- 模型 / schema / service: 新增 schedule_mode / schedule_hours / schedule_days / schedule_minute。时钟字段存逗号分隔文本——几个小整数、永远整体读写,单开一张表只会换来 join。interval_minutes 保留且仍是默认值,已有任务不受影响
- service: 改动任何调度字段都按合并后的状态重算 next_run_at。重新启用也算改动,否则停用一个月再打开会带着一个月前的 next_run_at,一保存就立即触发
- 前端: 运行方式三选一 + 小时/星期胶囊多选 + 分钟下拉,并实时预览结果句子。任务卡片改显示后端拼好的 schedule_label,避免列表和编辑器对同一计划给出两种说法
- 校验: 钟点模式至少选一个时间,按周至少选一个星期

tests/test_schedule.py 新增 24 个用例,含「恰好等于当前时刻的档位归属下一天」这个会让调度器自循环的边界。
2026-10-07 15:33:17 +08:00
butubb 242e3a7837 fix(chart): 纵轴自 0 起、刻度归位、数据点恢复为正圆
Deploy VitePress site to Pages / build (push) Canceled after 0s
Deploy VitePress site to Pages / Deploy (push) Canceled after 0s
- 纵轴从 0 起,上界取整到好读的数(1/1.2/1.5/2/…)。此前按 [最小值,最大值] 自适应,491→506 这点变化被撑满整个图高、看着像暴涨,且上下两刻度就是 506 和 491 两个几乎一样的数,没有 0 做参照读不出量级
- 刻度线画在 0 / 中值 / 上界,标签贴在各自主线上、放进左侧刻度栏。原来是两个绝对定位的数字浮在图面上,会挤在一起读成一个数
- 数据点恢复为正圆:根因是 preserveAspectRatio="none" 把 600×160 的 viewBox 横向拉满容器,横纵缩放不一致,半径 4 的圆被压成椭圆。改为用 ResizeObserver 量出容器宽度、等比绘图
- 每个数据点都画出来(原先只画末点),点数多时自动缩小半径;悬停热区按点位间距铺满整段,不再固定 12px
- 当前值移到标题行,不再浮在图面上遮挡曲线
2026-10-07 15:26:42 +08:00
butubb c1068845a9 fix(deploy): 部署时必须强制重建容器
Deploy VitePress site to Pages / build (push) Canceled after 0s
Deploy VitePress site to Pages / Deploy (push) Canceled after 0s
代码是 bind mount,容器配置与镜像都没变,所以裸的 `docker compose up -d` 会判定无需变更直接跳过,改了 .py 也不会生效。前端产物是磁盘上的静态文件、能即时生效,这一点很容易把问题盖住,直到有人改了后端代码才发现。

首次实测即命中:跑 deploy.sh 的输出是 'Container mediacrawler Running',没有重启。
2026-10-07 15:22:00 +08:00
butubb 89b7b1e825 fix: 封面图不再被图床拒绝,作品栏补上封面
Deploy VitePress site to Pages / build (push) Canceled after 0s
Deploy VitePress site to Pages / Deploy (push) Canceled after 0s
- NoteCover: 新增共用封面组件,核心是 referrerPolicy="no-referrer"。小红书图床对带外部 Referer 的请求一律 403,而浏览器对跨域 <img> 默认就会带上本站源作为 Referer —— 于是封面全显示成破图,而 URL 本身完全正常。实测同一张图:无 Referer 200 / 67968B,Referer 为本站 403 / 0B
- CommentsFeed: 改用 NoteCover,修掉评论栏封面全部加载失败
- NotesTable: 作品栏此前完全没有渲染封面,补上缩略图;封面缺失时用占位块,避免行高随封面陆续到达而跳动

放在一个组件里而不是就地加属性,是为了让下一个显示封面的页面不会漏掉。
2026-10-07 15:21:29 +08:00
butubb fe45442b01 chore: deploy.sh 标记为可执行(Windows 上创建的文件没有 exec 位)
Deploy VitePress site to Pages / build (push) Canceled after 0s
Deploy VitePress site to Pages / Deploy (push) Canceled after 0s
2026-10-07 11:14:13 +08:00
butubb 74a592024c feat: 部署改为 git 驱动,容器以宿主用户运行
Deploy VitePress site to Pages / build (push) Canceled after 0s
Deploy VitePress site to Pages / Deploy (push) Canceled after 0s
服务器实测可经 Cloudflare 443 访问 Gitea 且 git 协议正常(此前我只测了 13000 端口就断言不可达,是错的),因此不再需要 tar + SFTP。

- docker-compose: 增加 user: "1000:1000"。容器此前以 root 运行,写进挂载目录的每轮 jsonl 产物都是 root 属主,导致宿主用户连自己的部署目录都挪不动 —— 这在把部署迁到 /mnt/data 时实际发生了
- deploy.sh: 一条命令走完 拉代码 →(webui/ 有改动时)重建前端 → 重启容器。前端产物 api/webui 是 gitignore 的,git pull 带不过来,必须在服务器上重建一次
- Dockerfile: 补 npm 包。corepack 只管 yarn/pnpm 不管 npm,而前端要在服务器上重建;这样服务器只需要 Docker,不必另配 Node 环境
2026-10-07 11:13:43 +08:00
butubb cffb407d15 feat: 生产库克隆脚本 + 容器补 Node 运行时
Deploy VitePress site to Pages / build (push) Canceled after 0s
Deploy VitePress site to Pages / Deploy (push) Canceled after 0s
- tools/clone_database.py: 同服务器跨库克隆并建立独立应用账号的 provisioning 脚本。本机与服务器都没有 mysql 客户端,所以走 INSERT...SELECT 而非 dump/reload。刻意只读写命令行指定的两个 schema,且给应用单开账号而不是复用管理员
- Dockerfile: 补 nodejs。douyin/help.py 在模块导入阶段就执行 execjs.compile(libs/douyin.js),缺 JS 运行时会抛 RuntimeUnavailableError;而 main.py 要导入全部 7 个平台,于是整个应用连带环境自检一起挂掉

已在 192.168.20.220 上验证:7 个平台导入全部通过。
2026-10-07 11:08:11 +08:00
butubb bcc7361145 refactor: 镜像只装依赖,代码改为挂载
Deploy VitePress site to Pages / build (push) Canceled after 0s
Deploy VitePress site to Pages / Deploy (push) Canceled after 0s
- Dockerfile: 去掉 COPY . .,镜像退化为纯依赖层,改代码不再需要重建镜像
- Dockerfile: 补 libgl1 / libxcb1 / libglib2.0-0 等 X11 库。opencv-python 在导入时需要它们,而 tools/utils.py 会经 slider_util 导入 cv2 —— 缺了不是某个边角功能挂掉,是整个应用起不来(已在真机冒烟中验证)
- Dockerfile: 补 tzdata,否则 TZ=Asia/Shanghai 被静默忽略,所有时间戳落成 UTC
- Dockerfile: apt 与 pip 均改走国内源。deb.debian.org 实测约 13 kB/s,96 MB 构建依赖要跑半小时以上
- docker-compose: 挂载 ./ 到 /app,部署流程从「重建镜像」变成「重传 + 重启」
- main.py: 启动时若缺 api/webui/index.html 就打印警告,避免只返回一段 JSON 却看着一切正常
2026-10-07 10:57:44 +08:00
butubb 37ca1b1cd6 feat: CDP 接管开关 + 扫码登录面板 + Docker 部署
Deploy VitePress site to Pages / build (push) Canceled after 0s
Deploy VitePress site to Pages / Deploy (push) Canceled after 0s
- runner: enable_cdp_mode 从硬编码 False 改为系统设置 cdp_enabled。服务器部署下爬虫接管已开启远程调试的 Chrome(默认 9222),复用其 profile 登录态;本机桌面默认仍为关,行为不变
- qrlogin: 新增 CDP 扫码登录。Chrome 在服务器上跑于 Xvfb,show_qrcode 依赖的 PIL 桌面看图程序不存在,二维码无处可显示;改为经 CDP 从页面取出二维码交给 WebUI 渲染。刻意复用 browser.contexts[0](新建 context 是无痕 profile,扫了也白扫),且绝不调用 browser.close()(会连带关掉操作者自己的 Chrome)
- webui: 设置页新增扫码面板,替换原本跳到采集页看终端二维码的入口
- Dockerfile / .dockerignore / docker-compose.yml: 服务器部署。host 网络是必需而非图省事——容器里 127.0.0.1:9222 必须落到宿主机回环
- UPSTREAM.md: 补充 gitcode 镜像,用于 GitHub 大包传输必断时补历史
2026-10-07 10:41:11 +08:00
butubb 4e60524f37 feat: 监控面板 / 登录鉴权 / 多平台切换 / MySQL
Deploy VitePress site to Pages / build (push) Canceled after 0s
Deploy VitePress site to Pages / Deploy (push) Canceled after 0s
在上游 MediaCrawler 之上新增一层:

- 监控层 api/monitor/ —— 多博主/多笔记的定时采集、指标快照差分、报表、
  企业微信通知。每轮采集写入独立目录,差分才成立。
- WebUI 登录鉴权 api/auth.py —— PBKDF2 口令 + 服务端会话,/api 全接口防护。
  WebSocket 单独加依赖:BaseHTTPMiddleware 对 ws 作用域直接放行,覆盖不到。
- 全局平台切换 + 能力矩阵 —— 如实区分「爬虫模块支持」与「监控层已接线」,
  未接通的平台直接拒绝建任务,而不是静默跑空。
- 监控库改用 MySQL 5.7(可回退 SQLite 供测试):逐表强制 utf8mb4
  (服务端与库默认都是 latin1),启动校验所连 schema 以防写错库,
  连接池 recycle + pre_ping 应对 MySQL 的 8 小时空闲断连。

修复上游缺陷:

- xhs/core.py: 主页抓取失败会跳掉整个博主,导致一条作品都抓不到,
  而那份资料只喂给一个空函数。改为尽力而为,失败不中断。
- xhs/login.py: cookie 登录只注入 web_session,冷启动签名会失败。
  新增 INJECT_ALL_COOKIES 开关(默认关闭,原有行为不变)。
- requirements.txt: 补上 websockets。它在上游 pyproject.toml 里有声明、
  这里漏了,导致 uvicorn 没有 WebSocket 能力,实时日志流从未工作。

改动过的上游文件清单及合并方式见 UPSTREAM.md。

测试:492 passed(另有 1 个既有的 Windows/gbk 上游测试失败,与本改动无关)
2026-10-07 09:58:40 +08:00
程序员阿江(Relakkes) 5d547f4586 update readme 2026-10-05 22:21:13 +08:00
程序员阿江(Relakkes) 83c638b9d1 docs: README 新增 OpenLux 赞助商,西语版补上 Atlas Cloud 2026-10-04 22:58:52 +08:00
程序员阿江(Relakkes) bf28178082 docs: 更新 README 中的 Pro 版介绍 2026-10-04 03:37:23 +08:00
程序员阿江(Relakkes) 380b426000 fix(dy): 补上 ArgusSecurityPlugin 要求的 x-tt-argus 请求头
抖音在边缘网关新挂了 ArgusSecurityPlugin,对 aweme/detail、aweme/post 这批接口做
业务前置校验:缺少 x-tt-argus 时直接 403,响应体为
"Blocked by ArgusSecurityPlugin Uifid Not Found";只补 uifid 参数但仍没有这个头
则是 "... Signature Not Found"——后者很容易被误判成 a_bogus / verifyFp 的问题。
网关当前不校验该头取值,传固定字符串即可(实测 "1" 就够)。
参考 https://github.com/Johnserf-Seed/f2/issues/443

注意这是权宜之计:网关哪天升级到真校验该值,会重新出现 Signature Not Found,
届时需要改为页面内注入 JS 让抖音自带 SDK 补齐 Argus 头。

验证:真实 cookie 下 get_video_by_id 恢复可用。

- client.py: 默认请求头补 x-tt-argus,cookie 有 UIFID 时带 uifid 头
  (两种都没有则不发,避免被当成「有但为空」)
- 新增 tests/test_douyin_argus_header.py(不发网络请求)
2026-09-19 13:25:59 +08:00
程序员阿江(Relakkes) 8ecfa31de2 chore(ks): 示例配置改用分享短链并补上支持的输入形式
- KS_SPECIFIED_ID_LIST 换成两条 /f/<token> 分享短链,用于验证短链解析链路
- 注释补上第三种输入形式(分享短链),与 help.py 的解析实现保持一致
2026-09-18 17:26:42 +08:00
程序员阿江(Relakkes) c7e6c9fdc0 feat(ks): 支持分享短链 /f/<token> 形式的视频输入
快手分享短链 https://www.kuaishou.com/f/X9Idt15MQb9L2cv 路径里是 share_token
而不是视频 ID,它只做 302 跳转,真实地址在 Location 里。原先的解析器只认
/short-video/<id> 和纯 ID,遇到短链会抛 ValueError 被 continue 掉——只有一行
ERROR 日志,看起来像"爬了但没数据"。

- help.py: 新增 /f/<token> 分支,返回 url_type="short"(token 不是视频 ID,
  必须跟随重定向),并补上第三种形式的文档
- client.py: 新增 resolve_short_url,GET 时 follow_redirects=False,读
  301/302/303/307/308 的 Location
- core.py: url_type == "short" 时先解析短链再解析一次,失败则跳过该条
- 新增 tests/test_kuaishou_url_parse.py(6 条,不发网络请求)

验证:两条真实短链分别解析到 3xyziwesje8e9jg / 3xbbkdxtxqm8sae,详情均成功;
带 query 的标准视频页不会被误判成短链。
2026-09-18 17:23:20 +08:00
程序员阿江(Relakkes) 1e1ae64cb4 fix(ks): 视频不可用时 photo 为 null 导致整轮爬取崩溃
快手对已删除/私密/不存在的视频,返回的 visionVideoDetail 里 photo/author 是
null——key 存在、值是 null。而 detail.get("photo", {}) 只在 key 缺失时给默认值,
key 存在且为 null 时拿到的仍是 None,紧接着的 photo.get(...) 抛 AttributeError。
该异常不在 get_video_info_task 的 except 列表里(只 catch DataFetchError /
KeyError),会穿过 asyncio.gather 直接把整轮爬取带崩。

- core.py: 用 (x or {}) 兜住 null;photo 为空时打 WARNING 并跳过该视频,
  不再返回半残的 detail 让下游存空记录、再去下载媒体
- 新增 tests/test_kuaishou_unavailable_video.py(不发网络请求)

验证:photo=null / photo 缺失 / visionVideoDetail 缺失 均返回 None;
真实 cookie 端到端确认不可用视频被跳过、正常视频照常返回详情。
2026-09-18 17:18:01 +08:00
程序员阿江(Relakkes) cf513e70a1 fix(dy): 补上 detail 接口的 uifid/verifyFp 风控参数
抖音 detail 接口的 Argus 风控要求 uifid / verifyFp / fp 三个参数,缺一则直接
403:响应体为 "Blocked by ArgusSecurityPlugin Uifid Not Found",补上 uifid 但
verifyFp 不对时换成 "... Signature Not Found"。原实现只传 aweme_id,于是
get_aweme_detail 全线失败,详情和媒体都拿不到。

- 三个参数取自浏览器 cookie:uifid 用 UIFID(缺失时退到 UIFID_TEMP),
  verifyFp/fp 用 s_v_web_id。必须用 cookie 里的 s_v_web_id——实测 uifid 搭配
  自生成的 verifyFp 会被判 Signature Not Found,两者需要同源
- 新增 tests/test_douyin_aweme_detail_params.py(不发网络请求)

验证:抓包比对真实浏览器发出的 detail 请求,参数逐项一致;真实爬取中详情与
74MB 视频均下载成功。
2026-09-18 17:00:11 +08:00
程序员阿江(Relakkes) 281b445f52 chore(xhs): 更新 XHS_SPECIFIED_NOTE_URL_LIST 示例笔记
换成当前可用的测试笔记,用于验证详情抓取与媒体下载链路。
2026-09-18 16:18:42 +08:00
程序员阿江(Relakkes) eec25bb0a0 fix(xhs): 适配上游 EF* 分档,修复视频只下到封面
小红书把 DASH 分档名从按编码命名(h264/h265/av1)改成了按内部档位命名
(EF4/EF5/EF6/EF7),同时 video.consumer 里的 origin_video_key 也没了。
extract_video_urls 写死 stream["h264"],两条路都取不到地址,于是静默返回
空列表:笔记照常入库,但 video_url 为空、媒体只下到封面,全程不报错。

- media.py: 遍历 stream 下所有列表型分档,不再写死分档名;同一分档内含多个
  分辨率(720P→4K),按 (height, avg_bitrate) 降序,最高档作主地址,其余作为
  备用地址交给下载器回退;origin_video_key 分支保留
- 测试 fixture 换成真实响应结构,旧的 h264 结构另存一份确保兼容性不被改坏

验证:真实响应下旧实现取到 0 条、新实现 5 条;真实下载得到 4K 视频
(54079413 字节 / hevc 3840x2160 / 281.87s),旧结构与图文笔记行为不变。
2026-09-18 16:10:18 +08:00
程序员阿江(Relakkes) c03ab60aac fix(bili): 登录态判定改为校验 cookie 有效性,避免静默降级到 480P
check_login_state 原先只判断 SESSDATA 是否存在。过期会话的 SESSDATA 会一直
留在浏览器里,于是 pong 报"账号未登录"后进入的登录流程会被立刻判成成功:
既不等待扫码,又把这份死 cookie 灌给 API client。

后果是隐蔽的:详情和评论两个接口不要求登录,照常爬到;而 playurl 依据
Cookie 决定清晰度,未登录状态会静默封顶在 480P,不报任何错。

- login.py: 抽出 is_login_cookie_refreshed(),要求 SESSDATA 被扫码换发成新值
  才算登录成功;进入登录流程前记录旧值,残留死 cookie 时输出告警
- core.py: 登录流程结束后补一次 pong 校验,仍失败则明确报错退出,
  不再带着未登录状态跑完全程
- 新增 tests/test_bilibili_login_state.py 覆盖上述分支
2026-09-18 15:48:06 +08:00
程序员阿江(Relakkes) 9cfc148577 fix(bili): 兼容首页新版 header 的登录入口选择器
B 站首页 header 新旧两套实现并行灰度,登录入口选择器不同:
- 新版 header:.header-avatar-unlogin-entry
- 旧版 header:.right-entry__outside.go-login-btn 内的 .header-login-entry

原代码只匹配旧版,且 header 版本按 cookie 分流(携带过期 SESSDATA 也会
被分到新版),导致未登录场景下点击登录按钮直接 30s 超时。

改为组合选择器,并在等待超时时输出明确的排查提示,替代原先裸的
Playwright traceback。两套 header 的按钮均调用同一个 openMiniLogin(),
弹窗及二维码结构未变,故无需改动。
2026-09-18 14:44:26 +08:00
程序员阿江(Relakkes) 0ca7b29cf0 feat(media): 重构媒体下载,支持 xhs/dy/ks/bili/wb 五平台
旧实现只覆盖 4 个平台,且把整个文件读进内存、无重试与完整性校验,
代码按平台复制粘贴了 4 份。本次用统一下载器替换:

- 新增 media_downloader/:流式写入、Range 续传、指数退避重试、大小校验、
  路径穿越防护;B 站 DASH 音视频分轨下载后交由 ffmpeg 无损合流
- 新增 media_platform/<平台>/media.py:从平台原始响应提取媒体地址,
  与下载器解耦;快手首次接入下载能力
- 开关:config.ENABLE_GET_MEDIA 与 --get_media,并打通 API/WebUI;
  同时修正旧配置项 ENABLE_GET_MEIDAS 的拼写
- 落盘按帖子聚合:{SAVE_DATA_PATH 或 data}/{platform}/media/{内容ID}/
- B 站装好 ffmpeg 时走 DASH 最高画质,否则降级 mp4 直链(产物 video-durl.mp4,
  避免低清文件阻塞后续的高清路径)
- 删除 4 个 *_store_media.py、AbstractStoreImage/Video 及各 client 的媒体 GET 方法

媒体下载失败只记录日志,不中断爬取主流程。
2026-09-17 22:54:22 +08:00
程序员阿江(Relakkes) 60e66f2a92 docs: update README.md 2026-09-15 16:15:03 +08:00
程序员阿江(Relakkes) d6f7c5bb90 docs: update README.md 2026-08-14 16:18:52 +08:00
程序员阿江(Relakkes) 5665a271ef docs: update README 2026-08-12 19:22:18 +08:00
程序员阿江(Relakkes) 439509782c update readme 2026-08-11 18:26:17 +08:00
程序员阿江(Relakkes) d594c20c13 fix(xhs,bilibili): 修复访问受限异常击穿与评论采集边界问题
xhs: PR #958 把 IPBlockError / PlatformAccessError 加入 request() 的
retry_if_not_exception_type 后,tenacity 会直接重抛原异常而不再包装成
RetryError,core 层的 except 分支接不住,单条笔记被限流会让整批
asyncio.gather 抛出,同批已抓取但未入库的数据全部丢失。

- get_note_detail_async_task / get_creators_and_notes 捕获访问受限异常,
  记录明确日志后跳过当前条目,恢复原有的"跳过并继续"语义
- 移除 request() 中已不可达的 IP_ERROR_CODE 分支

bilibili: 修复 get_video_all_comments 的两处翻页边界问题

- is_first_page 改为独立标志,接口返回 next=0 且 is_end=False 时
  不再把后续页误判为首页而重复注入置顶评论
- result 无条件累加,否则开启楼中楼抓取时循环守卫永不推进,
  max_count 完全失效并可能死循环;截断提前到抓取楼中楼之前,
  避免为已被丢弃的评论抓子评论
2026-08-11 18:10:16 +08:00
程序员阿江-Relakkes 3c25521bbb Merge pull request #958 from ottercoconut/agent/fix-xhs-raw-response-errors
fix(xhs): 在返回原始 HTML 前识别访问限制
2026-08-11 18:00:22 +08:00
程序员阿江-Relakkes 7fd42cf448 Merge pull request #957 from muzimu/fix_bilibili_top
fix(bilibili): 修复缺失置顶评论
2026-08-11 17:59:51 +08:00
ottercoconut 508abc504f 补全小红书响应与重试回归测试 2026-08-11 17:52:54 +08:00
ottercoconut 6937b12738 修复小红书原始响应错误分类与重复重试 2026-08-11 16:45:49 +08:00
muzimu 17c6d386b8 fix(bilibili): 修复缺失置顶评论 2026-08-08 18:01:26 +08:00
程序员阿江(Relakkes) 071c8c0aca fix(kuaishou): 降低服务端限流概率并支持限流退避重试
- 请求延时在固定基础上加 1-3 秒随机抖动,降低被限流概率
- 签名 REST 请求遇到限流(result:2)时指数退避重试 3 次,不再直接中断
- 重试时重新生成签名(签名绑定请求内容和时间窗口)
2026-08-05 17:39:33 +08:00
程序员阿江(Relakkes) 2a8063a9f2 docs: replace broken star history charts 2026-08-04 18:04:22 +08:00
程序员阿江(Relakkes) 02294b9ea4 style(kuaishou): 优化终端日志输出为条目摘要
- 视频详情不再打印完整 JSON,改为单行摘要(id/作者/点赞/播放/标题)
- 搜索每页输出搜索到的视频条数
- 创作者输出获取到的视频总数
2026-08-04 01:29:21 +08:00
程序员阿江(Relakkes) 2e558f1352 fix(kuaishou): 支持网页端 REST 接口签名请求
- 新增页面签名环境捕获脚本,通过 caver 属性赋值轨迹获取签名调用入口
- 作品列表/关键词搜索接口迁移到带签名的 REST v2 请求,并显式校验 result 状态码
- 修复分页停止条件,空列表时结束翻页
- 更新默认创作者主页 ID
2026-08-04 01:14:39 +08:00
程序员阿江-Relakkes 1779dde972 Merge pull request #950 from ztcools/fix/weibo-rfc2822-timestamp-timezone
fix(weibo): 修复 RFC2822 时间转换丢弃时区偏移导致时间戳晚 8 小时
2026-07-30 19:42:22 +08:00
ztcools 04631ba8e4 fix(weibo): 修复 RFC2822 时间转换丢弃时区偏移导致时间戳晚 8 小时
rfc2822_to_timestamp 先用 %z 解析出带偏移的 datetime,再用
replace(tzinfo=timezone.utc) 处理。replace 只是覆盖 tzinfo 而不做换算,
+0800 的时间被当成 UTC,算出的时间戳比真实时间晚 8 小时。

改用 astimezone(timezone.utc) 做真正的时区换算,与紧邻上方的
rfc2822_to_china_datetime 保持一致。

影响 store/weibo 落库的 create_time 字段(帖子与评论各一处)。同一条记录里
create_time 与 create_date_time 会相差 8 小时,跨日内容的日期还会整体偏移
一天。

补两条离线单测:一条断言 +0800 输入对应的时间戳,一条断言 create_time 与
create_date_time 指向同一时刻。两条测试在修复前均失败。
2026-07-30 15:07:07 +08:00
程序员阿江(Relakkes) 17f66121e0 fix: 升级 xhshow 至 0.2.0, 移除 GET 签名猴子补丁
xhshow 0.2.0 调整了 build_payload_array 参数顺序, 旧的位置参数调用会
导致 creator 等 GET 请求报 'float' object has no attribute 'encode'。
该版本已原生修复 GET 请求 a3_hash 问题(Cloxl/xhshow#104), 本地补丁
不再需要, GET 分支改用 sign_headers_get 高层接口。
2026-07-25 01:12:08 +08:00
程序员阿江(Relakkes) e6e863a1d3 docs: update README.md 2026-07-25 00:03:17 +08:00
程序员阿江(Relakkes) 0625e01a6b docs: 更新赞助商展示 2026-07-16 16:17:35 +08:00
程序员阿江(Relakkes) 076dcba978 fix: 修复 WebUI 源码缺失、环境检测路径及文档说明
- 调整 .gitignore,避免误忽略 webui/src/lib/ 和 webui/src/components/env/
- 补齐 WebUI 缺失的 lib 工具函数、API 封装及环境检测组件
- 修复 /api/env/check 使用相对路径导致启动目录不同时检测失败的问题
- 更新中/英/西三语 README 的 WebUI 开发调试与生产构建说明
2026-07-01 23:32:10 +08:00
程序员阿江(Relakkes) 92c98e658e Merge branch 'worktree-desktop-main-e750a46a' 2026-07-01 23:05:20 +08:00
程序员阿江(Relakkes) 65ddaa8828 refactor: 将 WebUI 源码纳入仓库,移除打包产物
- 新增 webui/ 前端源码目录(React+Vite+TypeScript+Tailwind)
- 删除 api/webui/ 中旧的打包静态资源
- 更新 .gitignore 忽略 api/webui/ 构建输出和 webui/node_modules
- 更新 README 增加前端构建说明
2026-07-01 23:05:03 +08:00