Commit Graph
12 Commits
Author SHA1 Message Date
butubb b9aa590814 feat(monitor): 作品标签 —— 词表在设置里维护,作品上贴
Deploy VitePress site to Pages / build (push) Canceled after 0s
Deploy VitePress site to Pages / Deploy (push) Canceled after 0s
和「作品备注」刻意并存,不是一回事:备注是一句话的自由文字,标签是**从一份固定
词表里选的分类**。分类必须封闭,否则「重点 / 重要 / 优先」各写各的,筛就没法用了。

* `monitor_tag`:词表,全局一套(不按平台分)—— 「重点」是给人自己用的心智,不该在
  小红书和抖音各定义一遍。颜色存的是**调色板里的名字**,不是色值:Tailwind 的类名是
  静态提取的,拼出来的 `text-${color}` 它看不见,线上会静默变无色。
* `monitor_note_tag`:作品↔标签,**多对多**。一条作品可以既是重点又是竞品;只能贴一个
  的话人就会跑去备注里写自由文字,这份词表就白建了。全部替换式提交,不是逐个增删。
* 删除标签时**显式删关联**,不靠外键级联 —— 测试跑 SQLite,它默认不开外键约束。
* 组头上的筛选做成平铺开关片,多选是「或」。

顺带:/monitor/notes 现在支持按 tag_id 筛。
2026-10-11 09:16:17 +08:00
butubb 5c7577cab0 fix(monitor): 作品的跳转链接跟到标题后面,不再挂在整行最右边
Deploy VitePress site to Pages / build (push) Canceled after 0s
Deploy VitePress site to Pages / Deploy (push) Canceled after 0s
挂在最后一列时它脱离了被修饰的东西 —— 眼睛得横穿整张表才找得到那个链接,
而它属于的是最左边那条作品。

一并去掉那一列(表头也去掉),列数从 9 回到 8:行比表头多一格,本来就对不齐。

现在两边都是 8 格:箭头 / 作品 / 4 项指标 / 发布日期 / 首次发现。
2026-10-11 09:02:28 +08:00
butubb 44277600e4 feat(monitor): 组头上加一个跳到博主主页的按钮
Deploy VitePress site to Pages / build (push) Canceled after 0s
Deploy VitePress site to Pages / Deploy (push) Canceled after 0s
`creator_hash` 是单向的,拼不出主页地址 —— 所以这个 id 必须在采集时单独存下来。
抖音的资料接口本来就返回 sec_uid,只是之前没落库。

* `monitor_creator_stat` 加 `creator_id` 列(老数据默认空,下一次采集补上);
* 链接在 `adapters.creator_url()` 里拼,**空串是有意义的返回值**:平台不认识、
  或者没拿到 id,就返回空,界面据此不画按钮 —— 画一个点开 404 的按钮比不画糟;
* 没有作品的博主照样有链接:他恰恰是你想点进去看的那个人。

已知缺口:小红书那条路(爬虫子进程)根本不落创作者资料,所以那边没有链接。
2026-10-11 08:52:51 +08:00
butubb e0581682e1 fix(monitor): 没有作品的博主在作品栏里也要看得见
Deploy VitePress site to Pages / build (push) Canceled after 0s
Deploy VitePress site to Pages / Deploy (push) Canceled after 0s
分组是从作品推出来的(按作品的 creator_hash 归组),于是没有作品的博主根本
不进列表:目标加了、资料采到了、粉丝数就躺在库里,界面上什么都看不见。

而这类博主恰恰是最该看见的 —— 还在涨粉,只是最近没发东西。藏起来正好藏反了。
线上就有一个:5 个目标里 3 个没作品,那 3 个连同已采到的粉丝数一起消失了。

改成 **账号快照 ∪ 作品** 两个来源:

* 有快照没作品 → 一个 0 篇的组,备注和粉丝数照常显示,组里写「暂无作品」;
* 有作品没快照 → 一个没有账号指标的组(小红书那条路不产生快照)。

/notes 因此多返回一份 `creators`,而不是让前端从作品里推 —— 作品推不出上面
第一类人。组头改读它,`MonitorNote` 上那几个字段降级成「顺着作品问作者」用。
2026-10-10 20:57:07 +08:00
butubb d02fec5914 fix(monitor): 组头取账号指标时,别被组里第一条作品带偏
Deploy VitePress site to Pages / build (push) Canceled after 0s
Deploy VitePress site to Pages / Deploy (push) Canceled after 0s
分组只按博主,而账号指标是挂在 (任务, 博主) 上的 —— 同一个博主被两个任务
监控时,组里可能只有一部分作品带指标。取第一条的话,「第一个任务还没采过」
就会让整组看起来没有指标。
2026-10-10 18:24:42 +08:00
butubb 20e672834c feat(monitor): 博主的粉丝数,以及给作品起备注
Deploy VitePress site to Pages / build (push) Canceled after 0s
Deploy VitePress site to Pages / Deploy (push) Canceled after 0s
两件都是「作品栏里把这东西认出来」的延伸:

* **账号级指标**:作品列表只会说「这条涨了多少赞」,说不了「这个人整个
  账号的粉丝在涨还是在掉」。抖音的资料接口本来就有粉丝数/总获赞/作品数,
  每轮顺手记一条快照(`monitor_creator_stat`,粒度 = 任务×博主×轮次,
  和作品指标同形)。组头显示最近一条。

  快照在「一条作品都没采到」的早退**之前**落:作品列表被风控挡住的那一轮,
  正是「粉丝还在涨、但新作品没在发现」最该被看见的时刻。

* **作品备注**:博主备注回答「这个账号是谁」,这条回答「这条我要盯着」。
  一个博主底下常常只有一两件值得盯的作品,所以不能合并成一条。键取
  (platform, note_id),跨任务共用一份。

两边都守住同一条口径:**不知道就是 null,不写成 0** —— 0 在趋势图上是一条
砸到底的线,和「还没采到」是两回事。
2026-10-10 18:09:03 +08:00
butubb 0a88474c92 feat(monitor): 给博主起备注 —— 作品栏里才认得出「这是谁」
Deploy VitePress site to Pages / build (push) Canceled after 0s
Deploy VitePress site to Pages / Deploy (push) Canceled after 0s
作品栏按 creator_hash 把作品归到博主的组里,可那是个哈希;creator_name 是平台昵称,
粉丝少的号常常认不出。两样都没法把账号对上人。

* 新表 monitor_creator_alias,键取 **(platform, creator_hash)** 而不是按任务:
  哈希对同一个 uid 是稳定的,所以同一个博主出现在多个任务里时,备注只填一次。
* list_notes 带上 creator_alias(整体查一次再在内存里取,不是每条作品查一次)。
* `PUT /monitor/creators/{creator_hash}`,空串即清掉那条备注。
* 作品栏的博主组头:**优先显示备注**,起过备注之后平台昵称降成副标题(它仍是有用的
  对照);组头上一个铅笔按钮就地编辑,回车保存、失焦保存、Esc 取消。

Esc 那条要单独处理:取消之后紧接着的失焦会把刚放弃的内容存进去,所以用一个标记让那次
失焦闭嘴。

测试 +6:没起过时是空串、起了会跟着作品返回、**跨任务共用一条**、**不串到别的平台**、
空串清掉、前后空格会被去掉。
2026-10-10 18:00:13 +08:00
butubb 3486c7f524 feat(monitor): 作品栏和评论栏显示作品的发布日期
Deploy VitePress site to Pages / build (push) Canceled after 0s
Deploy VitePress site to Pages / Deploy (push) Canceled after 0s
需求:抖音和小红书都要能看到作品的发布日期。

`MonitorNote.published_at` 其实**一直在库里**(ingest 早就按平台取:小红书 time、
抖音 create_time),只是从来没往 API 和界面上透 —— 后端序列化没这个键,前端类型里
也没有,所以界面上只有「首次发现」。

* 后端:list_notes 的序列化补上 published_at;_note_meta_map 也带上,于是
  list_comments 多一个 note_published_at,分组接口的桶多一个 published_at。
* 前端:NotesTable 新增「发布日期」列(要让分组表头的 colspan 从 +4 变 +5);
  评论栏作品那一层在标题旁显示日期 —— 同名作品不少,日期能帮着认。
* 新增 formatDate:发布日期问的是「哪一天发的」,绝对日期比「3天前」好认,也不会
  每天看都在变。具体到分钟的版本放在 title 里,悬停可见。

刻意和「首次发现」分开:前者是作者发布的那天,后者是我们第一次看到它的那天。把一个
早就存在的作品加进监控时,两者能差好几个月 —— 测试里就用不同的值把这两者钉住。

顺带修正一处过时注释:前端类型里还写着 creator_name 是「已脱敏的昵称」,
脱敏已经在上一个提交里关掉了(config.MASK_NICKNAME)。

测试 +2:桶要带发布日期;作品列表接口要带,且它不等于 first_seen_at。
2026-10-10 16:06:22 +08:00
butubb 2e6fa955b0 feat(monitor): 博主组头补上指标合计与本轮增量
Deploy VitePress site to Pages / build (push) Canceled after 0s
Deploy VitePress site to Pages / Deploy (push) Canceled after 0s
折叠之后组头只有「N 篇」,等于把信息藏了起来 —— 折叠应该意味着「收起来但仍可一眼读到」,
而不是「看不见」。现在组头与数据行列对齐,每个指标列给出该博主的合计,下面再带本轮增量
(那才是监控真正要看的)。

合计口径与项目一致:某个指标在所有作品上都是 null 时,合计是 null 而不是 0 ——
「0」是真实值、「null」是不知道,合计成 0 会让「还没采到」看起来像「互动为零」。
2026-10-08 13:58:26 +08:00
butubb 0eb6ba31c5 feat(monitor): 作品栏按博主分组折叠,评论栏改为 博主/作品/评论 三级
Deploy VitePress site to Pages / build (push) Canceled after 0s
Deploy VitePress site to Pages / Deploy (push) Canceled after 0s
先回答现状:跳转作品的按钮本来就有(作品栏每行末尾的外链图标);
评论栏原本是**两级**(作品 -> 评论),第一级是作品不是博主,所以并不是三级。

- MonitorNote 新增 creator_name(爬虫产出的 jsonl 里本来就有昵称,只是没存)。
  存的是**已脱敏**的值(张***三),与项目一贯的匿名化姿态一致 —— 爬虫刻意不落原始
  user_id(tools/user_hash.py),所以 creator_hash 是唯一稳定的分组依据。
  实测该哈希是无盐 sha256,能用任务目标的 external_id 反算配对。
- 入库时刷新 creator_name:作者改昵称是常事,只在首次写一次会一直显示旧的
- 作品栏:按 creator_hash 分组,组头可折叠(默认展开 —— 折叠的默认值不该藏数据),
  行内跳转按钮加了 title 说明
- 评论栏:一级博主、二级作品、三级评论。_note_meta_map 补上博主维度,
  评论流和作品分组都带上它
- 认不出博主的作品归到「未知博主」,不丢

顺带修一个语法错误:JSX 注释放在三元表达式分支里是非法的(那是子节点语法不是表达式),
移进 div 内。
2026-10-08 09:43:19 +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 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