# 小红书监控功能使用说明 > 定时重复采集一批博主或笔记,与上一轮快照对比,产出**新增作品 / 新增评论 / 点赞收藏评论数涨跌**。 本功能是在 MediaCrawler 之上新增的一层,代码集中在 `api/monitor/`,不侵入原有的 `media_platform/`、`store/` 等目录。 --- ## 一、为什么需要单独一层 原项目是**一次性采集**:跑完即退出,没有调度、没有历史、没有差分。直接复用会遇到三个硬伤: 1. **指标会被覆盖**。`store/xhs/_store_impl.py::XhsDbStoreImplement.update_content()` 对已存在的笔记执行 `UPDATE ... SET liked_count = ...`,历史值直接丢失。跑第二遍根本看不出"点赞从 100 涨到了 500"。 2. **单进程串行**。`api/services/crawler_manager.py` 是全局单例,同一时刻只能跑一个 `main.py` 子进程。 3. **运行输出无法区分**。`AsyncFileWriter` 的文件名只带日期(`creator_contents_2026-10-07.jsonl`), 同一天多次运行会追加进同一个文件。 监控层为此做了对应处理:独立的快照库(保留历史)、调度器与手动采集互斥排队、以及**每轮采集写入独立目录** (复用早已存在、但 API 层从未转发的 `--save_data_path` 参数)。 --- ## 二、快速开始 ### 1. 准备环境 ```bash # 依赖(若未安装 uv,本项目的解释器探测会自动回退到 .venv) python -m venv .venv .venv/Scripts/python -m pip install -r requirements.txt .venv/Scripts/python -m playwright install chromium # 非 CDP 模式必需 # 前端 cd webui && npm install && npm run build ``` ### 2. 启动 ```bash .venv/Scripts/python -m api.main # 或 uvicorn api.main:app --port 8080 ``` 打开 ,右上角切换到「监控」。 > 解释器探测顺序:`uv`(若在 PATH)→ 项目 `.venv` → 当前解释器。 > `/api/env/check` 使用同一套逻辑,不会出现"检测失败但其实能跑"的情况。 ### 3. 配置登录态(**无人值守的前提**) 在监控页左下角「小红书登录态」粘贴 Cookie。定时监控不能每次都扫码,必须持久化登录态。 > **强烈建议先手动扫码登录一次**,以播种 `browser_data/xhs_user_data_dir`, > 之后再粘贴 Cookie 才可靠。原因见下方「限制」。 ### 4. 新建监控任务 - **类型** - `博主`:监控其作品,填博主主页链接或纯 ID - `笔记`:批量监控指定内容,填笔记链接或纯 ID - **目标**:每行一个。**建议只填纯 ID** —— 链接里的 `xsec_token` 会过期,纯 ID 永久有效。 - **间隔**:最小 30 分钟。每次运行都要拉起一次浏览器并多次请求平台,间隔过短容易触发风控。 - **每篇评论抓取条数**:默认 50。这个值直接决定能发现多少新评论,见下方限制。 任务创建后立即生效,也可随时点「立即运行」手动触发一轮。 --- ## 二·五、报表 「报表」视图是**跨任务**的统计,用来回答"这批账号这段时间表现如何"。 **筛选**:勾选参与统计的任务(默认全选),选日期区间(或点「近 7/30/90 天」)。 **两类指标,含义不同,所以分列展示**: | 列 | 含义 | |---|---| | 新增作品 / 新增评论 | 该日**首次发现**的作品数 / 评论条数 | | 点赞 Δ / 评论 Δ / 收藏 Δ / 分享 Δ | 该日**互动增量**:Σ(当日末值 − 当日之前最后一次采到的值) | 增量的口径有两个要点: - **作品首次出现的那天从 0 起算**,所以新作品的全部点赞都计入其首次发现日。这样做是为了让"新作品带来了多少赞"这件事可见,而不是把它的既有数据丢掉。 - **某天没采到某篇作品,那天的增量算 0**,不会把跨天的增长平摊到每一天。 底部会标明两件事:一是**哪些指标无法解析**(小红书可能返回 `"1.2万"` 这类值,解析失败的不会被当成 0 计入,否则会伪造出一个大的负增长),二是评论数受接口限制只覆盖前 N 条。 > 实现上聚合是在 Python 里做的,不是一条大 SQL。原因:按笔记、按天的"上一个基线值"查询是窗口操作,SQLite 表达起来很别扭,而这里的数据量很小,可读性比压榨查询计划更值钱。 --- ## 二·六、企业微信通知 在「监控」视图左下角配置 Webhook 地址(企业微信群 → 添加群机器人 → 复制 Webhook 地址)。 **两个设计取舍**: 1. **一轮只发一条汇总**,不是每条事件发一条。一次跑出 20 篇新作品时,你收到的是"新增作品 20 篇"加前 10 条标题,而不是 20 条消息。 2. **推送失败绝不影响采集**。通知是在数据提交之后、用独立会话发送的,任何网络错误只记日志。爬虫跑成功了不会因为 webhook 挂了而被回滚。 **触发时机**(仅这两类): - 任务失败 / 疑似登录态失效 - 发现新增作品 指标变化和新增评论**不会**推送(指标变化太频繁,评论量可能很大)。 **任务范围**:每个任务在编辑弹窗里有「推送企业微信通知」开关,**默认关闭**。这样一个 webhook 不会被一堆无关任务刷屏。 - 配置好地址后可以点「发测试」验证,也可以「保存前先测」。 - 地址里的 key 等同凭据,**服务端只回传打码形式**,要换只能重新粘贴(和 Cookie 一致)。 - 任务卡片上的 `last_notified_at`(列表接口会返回)可以回答"为什么这轮没收到推送"。 --- ## 二·七、评论视图与导出 「评论」页默认**按作品分组**:每篇作品一个可折叠区块,**默认只展开最新的一组**, 避免打开就是一屏文字。切到「平铺」则是一条流,每条评论下方标注它属于哪篇作品 (封面缩略图 + 标题 + 跳原文链接)。 顶部可按作品筛选,选项里带每篇的评论数: ``` 全部作品 烤面筋热量计算 (33) 孜卷热量计算 (3) ``` > 评论与作品的关联是后端 JOIN 出来的(`note_title` / `note_cover` / `note_url`), > 因为评论表本身只存 `note_id`,光看 ID 没有任何可读性。 ### 导出 评论页和报表页都有「导出」按钮,走浏览器下载: | 端点 | 内容 | |---|---| | `?kind=notes` | 作品表(含互动增量列) | | `?kind=comments` | 评论(含所属作品标题) | | `?kind=report` | 报表按天汇总 | - 支持 `csv` 与 `xlsx` - **CSV 带 UTF-8 BOM**(`utf-8-sig`)—— 否则 Excel 打开中文是乱码,这是最常见的投诉 - 下载是**页面导航**(`window.open`),带不了自定义请求头,所以导出依赖 Cookie 鉴权 —— 这也是会话必须存在 Cookie 里的原因之一 --- ## 二·八、登录与访问控制 面板默认要求登录 —— `/api` 下的所有接口都需要会话,只有 `/api/health`、 `/api/auth/login`、`/api/auth/logout` 例外。静态资源(页面本身、JS/CSS)不受限制, 否则登录页自己都加载不出来。 ### 首次启动 自动生成一个随机密码并**打印在启动日志里**(只打印一次): ``` ==================================================================== WebUI 首次启动,已生成登录密码: 94Shn1fMa7dV0jqF 请立即登录并修改。 ==================================================================== ``` > 刻意**不做**"打开页面让你设置密码"的流程。在局域网监听下,任何能访问到的人 > 都能抢先设置密码成为管理员;自动生成 + 打印避免了这种抢占,也避免了把自己锁在外面。 ### 忘记密码 设置环境变量 `MC_PASSWORD` 后重启即可: ```bash MC_PASSWORD=我的新密码 # Linux/macOS set MC_PASSWORD=我的新密码 # Windows cmd ``` 该变量**优先级始终高于**数据库里的密码,且**不会被写入磁盘**。登录后到设置页改成正式密码即可。 ### 环境变量 配置写在项目根目录的 `.env`(已被 gitignore)。 | 变量 | 默认 | 说明 | |---|---|---| | `MC_HOST` | `127.0.0.1` | 监听地址。**要局域网访问须设为 `0.0.0.0`** | | `MC_PORT` | `8080` | 端口 | | `MC_PASSWORD` | 空 | 覆盖数据库密码,忘记密码时的恢复通道 | | `MC_COOKIE_SECURE` | 关 | **面板走 HTTPS 时才开**。局域网明文下开启会导致浏览器丢弃 Cookie,表现为**登录页反复刷新且无任何报错** | | `MC_SESSION_TTL_HOURS` | `336` | 登录有效期(14 天) | | `MC_TRUST_PROXY` | 关 | 仅在受信任的反向代理之后开启,否则 `X-Forwarded-For` 可被伪造以绕过登录节流 | | `MC_CORS_ORIGINS` | 空 | 附加的允许来源,逗号分隔 | | `MC_CORS_ORIGIN_REGEX` | 空 | 允许来源的正则,用于局域网里的 Vite 开发服务器 | > 本项目的 `.env` 此前**从未被加载过**(代码里没有任何 `load_dotenv` 调用,尽管 > `python-dotenv` 一直是依赖、`.env.example` 也一直在仓库里)。现已修复。 ### 安全边界(请务必了解) - **局域网是明文 HTTP**,所以 Cookie 没开 `Secure`,`SameSite=Lax`。 这意味着**同网段抓包能看到会话令牌**。安全边界是"内网 + 密码",不是传输加密。 - **超出可信网络之外请走 HTTPS 反向代理**,不要把本服务直接暴露到公网。 - **登录节流是进程内的**:重启即清零。单用户单 worker 场景足够; 若日后多 worker,节流会按 worker 各算各的。反向代理下 `request.client.host` 是代理地址, 需配合 `MC_TRUST_PROXY` 才能正确识别来源。 - `/docs`、`/redoc`、`/openapi.json` **已关闭** —— 它们默认不鉴权,等于免费公开整个 API 地图。 ### 会话与登出 - 会话存在服务端(`auth_session` 表),库里只存令牌的 **SHA-256**,不存令牌本身 - 退出登录、**修改密码**都会立即失效(改密码会踢掉所有设备,并给当前设备补发一个新会话) - 令牌可放在 Cookie(浏览器自动携带,WebSocket 与文件下载都依赖它) 或 `Authorization: Bearer`(方便脚本调用) --- ## 二·九、设置页 原先挤在监控页左下角的 Cookie 与 Webhook 面板已迁到这里,并补齐了采集策略、代理与账号安全。 ### 分区与生效方式 | 分区 | 内容 | 生效时机 | |---|---|---| | 登录态 | 小红书 Cookie | 下一轮采集 | | 通知 | 企业微信 Webhook | 下一条推送 | | 采集策略 | 新任务默认间隔、默认单轮上限、默认评论条数 | **仅影响新建任务** | | 采集策略 | 请求间隔、抓二级评论 | 下一轮采集 | | 采集策略 | 活跃时段 | 定时任务的下一次触发 | | 代理 | 开关、提供方、池大小、静态地址 | 下一轮采集 | | 账号安全 | 修改密码 | 立即(其他设备全部掉线) | **活跃时段**:只在此时段内触发定时采集,窗口外任务保持到期状态、不会丢失, 窗口一开照常执行。默认 `0–23` 即全天;也支持跨午夜(如 `22–6`)。 **"仅影响新建任务"** 的那几项是刻意的:改了默认间隔不应该把已有任务的间隔一起改掉。 ### 设计要点 - **敏感值永不回传**:Cookie 和 Webhook 的 `GET` 只返回「是否已配置」与长度,不返回值。 表单不会把没动过的敏感项覆盖掉。 - **部分更新**:只有请求里出现的 key 会被写入。表单一角改动不会清空其他设置。 - **设置项由后端声明**:`api/monitor/app_settings.py` 里的注册表(类型、范围、选项、默认值) 是唯一事实来源,前端**按它生成表单**。加一个设置项不需要改前端字段清单。 - **越界即拒绝**:超出范围、未知的 key、非法的枚举值都返回 400 而不是静默接受。 > 「扫码登录」入口**尚未实现**。它需要跑起爬虫子进程、捕获二维码并实时推流, > 属于一个独立功能而非设置项,这里不做一个半成品。 --- ## 二·十、平台切换与能力矩阵 **右上角的下拉框统一切换平台**,「采集 / 监控 / 报表 / 设置」全部跟着变。选择会记住, 刷新后不会跳回小红书。采集页原来那个平台下拉已移除,避免出现两个事实来源。 ### 已接通 vs 未接通 矩阵里有两个**不同**的概念,混淆会误导: | 字段 | 含义 | |---|---| | `crawler_modes` / `metrics` / `comment_levels` / `media` | **上游爬虫模块**能做什么 | | `monitor_wired` | **监控层**是否已接线 | **7 个平台的爬虫模块都实现了 search / detail / creator**,真正的差异在指标上: | 平台 | 指标 | 评论层级 | 媒体 | 监控接线 | |---|---|---|---|---| | 小红书 | 点赞 / 评论 / 收藏 / 分享 | 2 | ✅ | ✅ | | 抖音 | 点赞 / 评论 / 收藏 / 分享(**无播放量**) | 2 | ✅ | ❌ | | 快手 | 点赞 / 播放(无评论、分享、收藏) | 1 | ✅ | ❌ | | B站 | 点赞 / **播放** / **弹幕** / 评论 / 收藏 / 投币 / 分享(最全) | 2 | ✅ | ❌ | | 微博 | 点赞 / 评论 / 转发(无收藏) | 2 | ✅ | ❌ | | 贴吧 | 仅回复数 | 2 | ❌ | ❌ | | 知乎 | 赞同 / 评论 | 2 | ❌ | ❌ | > **要更正一个常见误解**:这个代码库里**抖音不存播放量**(只映射点赞/收藏/评论/分享)。 > 有播放量的是 **B 站**,它还有弹幕。 未接通的平台**可以选,但各页会显示明确的说明面板**,并且**创建任务会被直接拒绝**: ``` 400 抖音的爬虫已支持,但监控层尚未接通,暂时无法创建监控任务。 ``` 而不是接受任务、然后让它永远跑不出数据 —— 那正是之前"博主主页解析失败被误报成登录失效"的同一种静默故障。 ### 设置的两层 | 位置 | 范围 | 内容 | |---|---|---| | 左侧导航「设置」 | **按平台** | 登录 Cookie、采集策略、代理 | | 右上角「系统设置」 | **全局** | 通知、活跃时段、上游更新、账号安全 | **这不是随便分的**:企业微信只有一个群、调度器只有一套时段规则、密码只有一份 —— 把它们放进"小红书专属"的页面里,会让人以为它们是按平台存的。 存储上键名带作用域前缀:`platform.<平台>.<项>` 与 `system.<项>`。 **旧键会在启动时自动迁移**(`xhs_cookie` → `platform.xhs.cookie`), 且是幂等的:新键已存在时以新键为准,不会覆盖你后来改的值。 --- ## 二·十一、上游更新检查 本仓库在 [NanmiCoder/MediaCrawler](https://github.com/NanmiCoder/MediaCrawler) 之上加了一整层 (监控 / 鉴权 / 多平台面板),差异管理与合并流程在根目录 `UPSTREAM.md` 里。但那份流程有个 隐含前提:**得有人知道上游动了**。部署脚本只从我们自己的 Gitea `git pull`,上游的提交不主动 去 fetch 就永远看不见 —— 拖着不合并的代价是复利的,越久越难合。 这一项就是替你定时去 fetch 的:按间隔(默认每天一次)拉一次上游,算出「当前部署落后几个 提交」,有更新就推一条企业微信,并把结果与提交列表显示在**右上角「系统设置」→「上游更新」**。 ### 配置 | 项 | 默认 | 说明 | |---|---|---| | 检查上游仓库更新 | **关** | 总开关。默认关:它要联网 fetch,且需要容器里有 git(见下) | | 上游检查间隔(分钟) | 1440 | 每天一次。最小 30 分钟 | | 上游仓库地址 | GitHub 上游 | 国内直连 GitHub 不稳时改成 gitcode 镜像,见 `UPSTREAM.md` | | 上游分支 | `main` | | | 上游有更新时推送通知 | 开 | 只在出现**此前没推过**的提交时发一条,同一个更新不会反复推 | ### 几个刻意的行为 - **只读,不写工作区**:只 `git fetch <地址> <分支>` 到 `FETCH_HEAD` —— 不建 remote、不写 `refs/remotes`、不碰索引与工作区。所以它不会打断正在跑的采集,也不会和 `./deploy.sh` 的 `git pull` 抢锁。 - **不受活跃时段限制**:活跃时段是给采集定的(避免半夜去抓平台)。检查只是 fetch 一个公开 仓库,半夜跑反而更合适。 - **失败也是一种结果**:上游不通(尤其直连 GitHub)很常见。界面会显示失败原因与上次检查 时间,失败不推送,也**不会**因此改变下一次检查的时间 —— 每个间隔重试一次,而不是每个 调度 tick(20 秒)都去撞一次。 - **同一个更新只推一次**:推送状态记的是上游 tip。推过之后,上下游没动就不会再推;上游又 有新提交(tip 变了)时会再推一条。 - **「立即检查」不发通知**:点这个按钮的人正看着结果,没必要再给自己推一条群消息。那次 检查只写结果,没推的那批提交留给下一次定时检查推。 ### 部署前提:镜像里要有 git `python:3.11-slim` 不带 git,`Dockerfile` 里已显式安装。**因此这次更新需要重建镜像**: ```bash docker compose build && ./deploy.sh ``` `./deploy.sh` 只重建前端,不会重建镜像。漏了这步的话,检查会报「未找到 git 命令」—— 界面上看得见,不会静默。 --- ## 三、必须知道的限制 ### 1. 「新增评论」是近似值 —— 最重要的一条 小红书评论接口 `/api/sns/web/v2/comment/page` **没有排序参数**,只能拿到平台默认排序(热评优先)的 前 N 条。因此: - 我们只能"每次抓前 N 条做差集",**新发布但沉底的评论不会被发现** - N 调大能提高发现率,但请求量线性增长,风控风险上升 - 评论事件区分两种,UI 上也分别标注: - `new_comment_posted`(新评论):`create_time` 晚于上一轮开始时间,是真·新发布 - `new_comment_seen`(新出现评论):只是本轮才进入可见窗口的历史评论 **这条限制无法通过调参绕过**,是该接口的固有限制。 ### 2. Cookie 失效是「静默失败」 `login_by_cookies()` 只注入 `web_session`,而 API 签名还需要 `a1` / `webId` 等; 更麻烦的是**cookie 登录不做任何校验** —— 坏 Cookie 不会让进程报错退出,而是 **退出码 0、抓到 0 条**。 监控层因此把「退出码 0 且 0 条作品」判定为 `suspected_auth_failure` 并在 UI 上标红, 而不是当成"该博主没发新作品"。这是无人值守场景最容易误报的地方。 本实现额外做了两件事: - 通过 `--inject_all_cookies` 注入**完整** Cookie(默认关闭,保持上游行为不变) - 通过 `--cookies_file` 传 Cookie,避免明文出现在进程列表里 ### 3. 作品窗口被截断 `每轮最多采集作品数`(默认 20)限定了"该博主的作品"到底指多少条。 UI 会把该上限显示在作品表旁,避免误以为看到了全部。 ### 4. 昵称与用户 ID 已被上游脱敏 `store/xhs/__init__.py` 落库前调用 `mask_nickname()` 与 `anonymize_user_id()`, 存储的是**打码昵称**与哈希后的 `creator_hash`,没有真实昵称和 user_id。 这是上游的隐私保护设计,监控层未做改动。 ### 5. 不发「笔记被删」事件 `creator` 模式只取前 N 条,笔记"消失"多半只是掉出窗口;`detail` 模式遇到 `xsec_token` 过期也会失败。二者与"真被删"无法区分,因此不产生删除事件, 改为在作品表里展示 `last_seen_at`。 ### 6. 定时任务与手动采集互斥 二者共用同一个爬虫子进程。监控任务运行期间点「采集」会被拒绝(返回 400); 反之若有手动采集在跑,到期的监控任务会**保持到期状态排队**,不会丢失,空闲后自动补上。 --- ## 四、数据存放 | 内容 | 位置 | |---|---| | 监控库(任务/快照/事件/评论/设置/会话) | **MySQL**,库名由 `MYSQL_DB_NAME` 指定 | | 每轮原始 jsonl | `data/monitor_runs/{task_id}/{run_id}/{platform}/jsonl/` | > 爬虫每轮的原始产出**仍然写独立 jsonl 目录**,不进 MySQL。 > 这是差分机制的基础:每轮写在单独目录里,才能算出"这轮新增了什么"。 > 多轮数据混在同一批表里的话,这个判断就做不到了。 ### MySQL 配置与安全边界 连接信息写在 `.env`(已被 gitignore,不会进版本库): ```ini MYSQL_DB_HOST=<数据库地址> MYSQL_DB_PORT=3306 MYSQL_DB_USER=<账号> MYSQL_DB_PWD=<密码> MYSQL_DB_NAME=mediacrawler ``` > 真实凭据只写在 `.env` 里(已被 gitignore),**不要写进这个文档或任何会提交的文件**。 **"只操作这个库"由两层保证,缺一不可**: 1. **数据库授权(真正的保证)**。账号应只被授予目标库的权限: ```sql REVOKE ALL PRIVILEGES, GRANT OPTION FROM 'MediaCrawler'@'%'; GRANT ALL PRIVILEGES ON `mediacrawler`.* TO 'MediaCrawler'@'%'; FLUSH PRIVILEGES; ``` 这样该账号 `SHOW DATABASES` 只能看到目标库,**代码就算写错也碰不到别的库**。 2. **启动自检(防配置写错)**。应用启动时会执行 `SELECT DATABASE()`, 与 `MYSQL_DB_NAME` 不符就**拒绝启动**,而不是往错误的库里写。 **字符集**:这台服务的服务端和库默认都是 `latin1`。代码在建表时**逐表强制 `utf8mb4`**,不依赖库默认值 —— 否则中文会被拒或变成问号。 **连接保活**:MySQL 默认 8 小时断开空闲连接,而监控服务是常驻的。 已配置 `pool_recycle=3600` + `pool_pre_ping`,避免"server has gone away"。 **表引擎**:全部 InnoDB(`monitor_run.exit_code` 用 `BIGINT` —— Windows 的退出码是 无符号 32 位,`0xC0000142` 会溢出有符号 `INT`)。 ### 从 SQLite 迁移(如有旧数据) ```bash python -m api.monitor.migrate_from_sqlite --dry-run # 先看要迁什么 python -m api.monitor.migrate_from_sqlite # 正式迁移 ``` 保留原主键(否则 `task_id` 关联会错位);目标库非空时会拒绝执行,除非加 `--force`。 监控库中的 Cookie 为明文存储,这是当前版本的已知取舍。 --- ## 五、API 所有操作都有对应的 HTTP 接口,UI 只是其中一层封装: ``` GET /api/monitor/overview 看板汇总 GET /api/monitor/tasks 任务列表 POST /api/monitor/tasks 新建任务 PATCH /api/monitor/tasks/{id} 修改 DELETE /api/monitor/tasks/{id} 删除 POST /api/monitor/tasks/{id}/run 立即运行(后台执行,立即返回) GET /api/monitor/tasks/{id}/runs 运行历史 GET /api/monitor/notes 作品表(含与上一轮的 Δ) GET /api/monitor/notes/{id}/series 单篇指标时间序列 GET /api/monitor/comments 评论流(带所属作品;?note_id= 筛选,?group_by=note 按作品分组) GET /api/monitor/comment-notes 有评论的作品及其条数(评论筛选下拉用) GET /api/monitor/export 导出(?kind=notes|comments|report&format=csv|xlsx) GET /api/monitor/events 变化事件流 POST /api/monitor/events/read 标记已读 GET /api/monitor/cookie 登录态健康度(**不返回 Cookie 值**) POST /api/monitor/cookie 保存 Cookie DELETE /api/monitor/cookie 清除 Cookie GET /api/config/platforms 平台能力矩阵(含 monitor_wired,前端据此渲染切换器) GET /api/settings 设置 + 表单描述(?platform=,敏感值只回状态) PUT /api/settings 部分更新(只写请求里出现的 key) GET /api/auth/me 身份探测(401 即未登录) POST /api/auth/login 登录(发 HttpOnly Cookie) POST /api/auth/logout 退出 POST /api/auth/password 修改密码(踢掉所有其他设备) GET /api/monitor/report 报表(?task_id=1&task_id=2&start_date=&end_date=) GET /api/monitor/webhook 通知配置状态(**只返回打码地址**) POST /api/monitor/webhook 保存 Webhook 地址 DELETE /api/monitor/webhook 删除 Webhook POST /api/monitor/webhook/test 发送测试消息 GET /api/monitor/upstream 最近一次上游检查的缓存结果(没查过返回 {}) POST /api/monitor/upstream/check 立刻检查一次(等 fetch 跑完才返回,**不发通知**) ``` > `task_id` 用**重复参数**而非逗号拼接(`?task_id=1&task_id=2`);不传表示统计全部任务。 --- ## 六、故障排查 | 现象 | 原因 / 处理 | |---|---| | 任务一直不运行 | 未配置 Cookie(调度器会跳过并保持任务到期);或全局已有采集在跑 | | 任务标红「疑似登录态失效」 | Cookie 过期。重新粘贴;若反复失败,先手动扫码登录一次播种浏览器 profile | | 抓到的作品数长期为 0 | 同上;也可能是该博主确实没有作品 | | 发现不了新评论 | 评论接口无时间排序所致,调大「每篇评论抓取条数」可缓解但无法根治 | | 首轮没有任何"新增"事件 | 刻意设计:首轮建立基线,全部数据视为已有,不产生变化事件 |