Skip to content

feat(viewer): 添加知乎数据展示标签页#878

Open
J1anYi wants to merge 28 commits into
NanmiCoder:mainfrom
J1anYi:feat/zhihu-data-viewer
Open

feat(viewer): 添加知乎数据展示标签页#878
J1anYi wants to merge 28 commits into
NanmiCoder:mainfrom
J1anYi:feat/zhihu-data-viewer

Conversation

@J1anYi
Copy link
Copy Markdown

@J1anYi J1anYi commented Apr 20, 2026

Summary

在前端添加知乎标签页,复用小红书的卡片式布局展示已爬取的知乎回答数据。新增知乎 API 路由,支持数据列表、统计、筛选等功能。

Changes

API (Unit 1)

  • 新增 api/routers/zhihu.py - 知乎数据 API 路由
    • GET /api/zhihu - 获取回答列表,支持 creator 筛选和 search 搜索
    • GET /api/zhihu/stats - 获取统计数据
    • GET /api/zhihu/creators - 获取创作者列表
    • GET /api/zhihu/{content_id} - 获取回答详情
  • 更新 api/routers/__init__.py 导出 zhihu_router
  • 更新 api/main.py 注册 zhihu 路由

Frontend (Unit 2)

  • 更新 viewer/static/index.html
    • 新增平台标签导航(小红书/知乎)
    • 新增知乎面板和筛选器
    • 新增知乎详情模态框
  • 更新 viewer/static/css/style.css
    • 新增知乎品牌色 --zhihu-primary: #0066FF
    • 新增 .zhihu-* 相关样式
    • 响应式布局支持
  • 新增 viewer/static/js/zhihu-api.js - 知乎 API 封装
  • 新增 viewer/static/js/zhihu-app.js - 知乎应用逻辑
  • 新增 viewer/static/js/zhihu-modal.js - 知乎模态框逻辑

Features

  • 平台标签切换支持键盘导航(左右箭头)
  • ARIA 可访问性属性完整
  • 知乎卡片采用文本优先布局
  • 响应式设计(700px 断点)

Test Plan

  • GET /api/zhihu 返回 25 条回答数据
  • GET /api/zhihu/stats 返回正确统计
  • GET /api/zhihu?creator=xxx 筛选功能正常
  • GET /api/zhihu?search=关键词 搜索功能正常
  • 前端标签切换正常
  • 知乎卡片正确展示用户信息、摘要、互动数据
  • 模态框打开/关闭正常(ESC 键、点击遮罩)
  • 响应式布局正确

Screenshots

访问 http://localhost:8080/viewer/ 查看效果:

  • 点击「知乎」标签切换到知乎数据
  • 点击卡片查看完整回答内容

Post-Deploy Monitoring & Validation

  • 无需额外监控,此功能为纯前端展示,不涉及后台任务或数据库操作
  • 验证:访问 /viewer/ 页面,切换到知乎标签,确认数据展示正常

J1anYi added 3 commits April 20, 2026 10:31
实现了一个简洁的数据查看器前端,用于展示爬取的小红书笔记数据。

功能特性:
- 瀑布流卡片布局(CSS columns 真瀑布流)
- 关键词筛选和标题搜索
- 图片懒加载(Intersection Observer)
- 笔记详情模态框(图片画廊导航)
- 统计面板(笔记数、图片数、关键词分布)
- 实时监控(WebSocket + 5秒轮询)
- 视频类型标识和播放器支持

后端 API:
- GET /api/notes - 获取笔记列表(支持分页、关键词筛选、搜索)
- GET /api/notes/stats - 获取统计数据
- GET /api/notes/{note_id} - 获取笔记详情
- GET /api/notes/keywords - 获取关键词列表

安全改进:
- 添加 note_id 验证防止路径遍历攻击
- 添加输入参数验证(Query validators)
- 改进异常处理(使用更精确的异常类型)
- 添加类型注解
- 支持更多中文数字格式(亿、万、w、k)
新增知乎标签页,支持展示已爬取的知乎回答数据:

- API: 新增 /api/zhihu 路由,支持列表、统计、创作者筛选
- 前端: 新增平台标签切换(小红书/知乎)
- 卡片: 知乎采用文本优先卡片布局,品牌色 #0066FF
- 模态框: 点击卡片查看完整回答内容
- 响应式: 700px 以下切换垂直布局
- ARIA: 标签导航支持键盘导航和可访问性

测试验证:
- 25 条知乎回答数据正确展示
- 创作者筛选和搜索功能正常
- 模态框交互和 ESC 关闭正常
@J1anYi J1anYi requested a review from NanmiCoder as a code owner April 20, 2026 04:14
@dosubot dosubot Bot added size:XXL This PR changes 1000+ lines, ignoring generated files. enhancement New feature or request labels Apr 20, 2026
J1anYi added 24 commits April 20, 2026 19:51
- 添加 watchdog 依赖监听 JSONL 文件变化
- 创建 FileWatcherService 服务,支持 200ms 防抖
- 通过 asyncio.run_coroutine_threadsafe 桥接线程和 asyncio
- WebSocket 连接时发送初始统计数据
- 文件变化时广播 stats_update 消息
- 前端 WebSocket 连接后自动停止轮询,断开时恢复

关闭 #实时数据同步
问题根因:
1. z-index 层级问题 - modal-close 的 z-index 太低
2. 函数名冲突 - subscriptions-app.js 的 setupModalEvents 覆盖了 modal.js
3. 变量名冲突 - bilibili-app.js 的 currentSearch 与 app.js 冲突
4. 缺少 stopPropagation - 事件冒泡导致问题

修复内容:
- 提高 modal-close 的 z-index 到 1000
- 统一所有平台 ESC 键关闭判断逻辑 (!== 'none')
- 重命名 subscriptions-app.js 中的 setupModalEvents 为 setupSubscriptionModalEvents
- 重命名 bilibili-app.js 中的全局变量避免冲突
- 为所有平台模态框添加 stopPropagation
- 确保 modal-content 和 modal-overlay 的 z-index 正确分层
问题:
- /ws/status 端点只发送爬虫状态,不接收广播消息
- broadcast_platform_update() 发送的 data_update 消息无法到达前端

修复:
1. /ws/status 现在使用 manager.connect() 连接到 ConnectionManager
2. 添加 type 字段到 crawler_status 消息,前端可以正确解析
3. /ws/status 现在可以接收 data_update, stats_update 等广播消息

数据流修复后:
文件变化 → FileWatcher → broadcast_platform_update()
                              ↓
                    manager.broadcast() → 所有 WebSocket 连接
                              ↓
                    前端 WSClient 接收 data_update → 刷新数据
修复:
- 添加内联 onclick 作为刷新按钮的临时修复方案
- 刷新后自动滚动到页面顶部
- 添加调试日志帮助排查问题

待优化:
- 需要清理内联 onclick,改用正规事件绑定
- 提醒框重复弹出问题
- 提醒框内容显示优化
每个文件变更触发两个 WebSocket 消息(data_update 和 stats_update),
导致提醒框每次弹出两次。移除 stats_update 的通知触发,仅保留 console.log。

满足: BUG-02
将 else 分支改为显式处理 stats_update,跳过通知触发。
仅保留 console.log 用于调试。

满足: BUG-02
将 else 分支改为显式处理 stats_update,跳过通知触发。
仅保留 console.log 用于调试。

满足: BUG-02
将 else 分支改为显式处理 stats_update,跳过通知触发。
仅保留 console.log 用于调试。

满足: BUG-02
添加 NOTIFICATION_DEDUP 常量,包含:
- recentNotifications Map 用于存储通知哈希和时间戳
- 5 秒去重窗口配置
- 30 秒清理间隔配置

Requirement: NOTIF-03
添加两个新函数:
- generateNotificationHash: 基于 platform + titles + count 生成哈希
- isDuplicateNotification: 检查是否在 5 秒窗口内重复

包含过期条目清理机制,防止内存泄漏

Requirement: NOTIF-03
在通知显示前添加去重检查:
- 生成通知哈希
- 检查是否在 5 秒窗口内重复
- 重复时跳过并记录日志

Requirement: NOTIF-03
修改通知内容构建逻辑:
- 当 data.titles 存在时显示标题内容
- 显示最多 2 个标题,超出显示 "等N条"
- 保持对 keyword 和 count 的兼容

Requirement: NOTIF-03
- 添加 new_count 字段记录新增数据数量
- 添加 titles 字段记录新增记录的标题列表
- 支持前端显示实际增量而非固定数字

Refs: BUG-03, NOTIF-01
- 添加 _previous_counts 字典追踪各平台记录数
- 添加 set_previous_count/get_previous_count 函数
- 添加 calculate_new_count 函数计算增量

Refs: BUG-03, NOTIF-01
- 添加 get_recent_notes 函数获取平台最新记录
- 添加 get_platform_notes_count 函数获取平台记录总数
- 支持 JSONL 和 JSON 两种数据格式

Refs: BUG-03, NOTIF-01
- 调用 get_platform_notes_count 获取当前记录总数
- 使用 calculate_new_count 计算新增数量
- 调用 get_recent_notes 获取最新记录标题
- DataUpdateMessage 消息包含 new_count 和 titles 字段

Refs: BUG-03, NOTIF-01
Phase 3: 数据排序与滚动刷新优化

变更内容:
- 添加无限滚动状态变量和加载更多函数
- 实现 IntersectionObserver 监听滚动到底部
- 添加加载指示器和没有更多数据提示
- 添加排序选择器 UI (最新发布)
- 排序偏好保存到 localStorage

技术实现:
- currentOffset/currentLimit 管理分页状态
- loadMoreNotes() 增量加载更多数据
- setupInfiniteScrollSentinel() 设置滚动触发器
- sortSelect 事件绑定和 localStorage 持久化
1. 笔记卡片添加时间显示
   - 新增 formatTime() 函数,支持相对时间格式化
   - 卡片底部显示发布时间(刚刚/X分钟前/X小时前/X天前/日期)

2. 修复无限滚动加载完成提示未显示
   - 将 showNoMoreData() 移到 finally 块中
   - 只有 hasMoreData 为 true 时才调用 showLoadingMore(false)
   - 确保"没有更多数据了"提示不会被覆盖
1. 时间戳格式兼容
   - formatTime() 同时支持秒级(10位)和毫秒级(13位)时间戳
   - 修复 API 返回毫秒级时间戳导致日期错误的问题

2. 无限滚动完成提示
   - showNoMoreData() 中停止 scrollObserver 监听
   - 避免重复触发 loadMoreNotes() 覆盖提示内容
- renderMoreNotes 在 sentinel 之前插入新卡片
- 确保 sentinel 始终在列表末尾
- 这样滚动到底部时能正确触发下一轮加载
- Add SQLite task database service (image_task_db.py)
- Add image download service (image_downloader.py)
- Add message queue service (image_queue.py)
- Add queue management API router (image_queue.py)
- Update lifespan in main.py to initialize services on startup
Phase 7: Frontend image display optimization

- API now returns local_image_url, remote_image_url, and first_image_url fields
- Added /local-images static route to serve downloaded images
- Frontend prefers local images and falls back to remote URLs on error
- Added image loading state animations (shimmer effect)
- Prevents infinite fallback loops with fallbackUsed flag
- Add asyncio import for fire-and-forget task spawning
- Import image_queue_service from api.services
- Enqueue image download tasks after note storage
- Use try-except to isolate failures from main flow
- Covers: CRAWL-01, CRAWL-02
## 主要功能
- 图片下载任务队列 (asyncio.Queue)
- SQLite 任务数据库 (状态管理、重试机制)
- 定时任务调度器 (超时检测、自动重试)
- 图片存储服务 (hash-based 路径、格式验证)
- 前端图片显示 (本地优先、远程 fallback)
- 爬虫集成 (自动入队下载任务)

## Bug 修复
- notes.py: 修复 image_list 字符串格式兼容问题

## 配置变更
- 默认端口从 8080 改为 8081

## 日志增强
- crawler_manager: 添加行号前缀和控制台输出
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant