No description
- Python 91%
- Vue 4.3%
- TypeScript 3.1%
- JavaScript 0.7%
- Batchfile 0.6%
- Other 0.1%
背景:绿包升级=整包替换程序目录,原散落其中的 browser_data(各平台 扫码登录态)、LLM 密钥(仅存前端 localStorage)、微博 Cookie(写死在 MediaCrawler base_config.py)、历史库全部随版本丢失。 方案:个人数据收敛到程序目录外的用户数据目录(默认 %APPDATA%\Yujian, 开发态回落 backend/data,env YUJIAN_USERDATA_DIR 可覆盖): - backend/app/runtime_paths.py:新增 userdata_dir(),区分可再生组件 data_dir - backend/app/services/userdata.py(新):目录入口 + config.json(LLM/微博 Cookie)+ migrate_legacy_state() 幂等一次性迁移旧版散落数据 - backend/app/config.py / login_state.py:*.db 与登录注册表改落用户数据目录 - backend/app/api/settings.py(新):GET/PUT/DELETE /api/llm/config - analyze._resolve_llm:请求密钥优先、回落存储配置(无密钥不再 422 语义卡死) - crawler_runner:spawn 子进程注入 YUJIAN_BROWSER_DATA_DIR/YUJIAN_SQLITE_DB; 微博 Cookie 经 config.json 显式 --cookies 传递,不再回写程序目录 base_config - MediaCrawler:base_config.BROWSER_DATA_DIR / db_config.SQLITE_DB_PATH 支持 env 覆盖;各平台 core 与 cdp_browser 的 browser_data 硬编码路径统一改之 - 前端:App.vue/api/analysis.js 将 LLM 配置真源从 localStorage 迁到后端 config.json(启动 sync + 保存即上传),提示文案/徽标展示 config.json 路径 测试与文档:conftest 增加重置模块级限流桶夹具(修复全量跑顺序污染导致的 429);新增 test_llm_config_api 3 用例(往返/回落/无密钥 422);后端 174 项 全绿;README 增「数据持久化与迁移」节并同步接口/环境变量/测试数。 |
||
|---|---|---|
| .vscode | ||
| backend | ||
| branding | ||
| docs | ||
| MediaCrawler-main | ||
| public | ||
| src | ||
| tools | ||
| .gitignore | ||
| build-app.bat | ||
| docker-compose.yml | ||
| Dockerfile | ||
| index.html | ||
| nginx.conf | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| start-local.bat | ||
| start-local.sh | ||
| start-prod.bat | ||
| start-tracker.bat | ||
| vite.config.js | ||
| yujian.bat | ||
舆见 · 舆情速读
面向普通网民的舆情速读工具 —— 输入一个想关注的事件,几分钟看懂「发生了什么 / 大家怎么看 / 接下来会怎样」。可把话题加入后台跟踪,定时自动更新。
✨ 特性
- 真实多平台爬取:微博 / B站 / 小红书 / 抖音 / 贴吧(MediaCrawler 驱动本机浏览器,登录态持久化;平台失败自动跳过,全失败才报错)
- 双模同跑管线 + 档位分层:轻量扫描(sweep,列表级元数据)+ 深度增强(enrich,头部博文评论)分阶段并发。手动普通分析走 fast 档(5 平台广度 + 削深度,目标 3-5 分钟);话题跟踪后台轮次走 full 档(深度累积)。请求间隔按平台分档(宽松平台快、严格平台慢),兼顾速度与风控
- 话题跟踪 + 近期缓存:搜索框勾选「跟踪话题」即注册后台周期爬取(默认 60 分钟/轮,独立 worker 进程,仅 1 个话题,手动分析优先抢占)。近期话题冷热分层:7 天内再分析 → 爬新 + 读库全窗合并(趋势跨天);超 30 天未用且不在跟踪 → 惰性 GC 物理释放
- LLM 强制依赖:用户关键词直接爬取 → 传统统计 → 末端 LLM 基于参考集 A 生成 4W 要素 + 概述 / 舆论总结 / 观点标签 / 演化阶段 / 走势预判(OpenAI 兼容密钥,无传统兜底;无 LLM 前置检索规划)
- 真实统计口径:声量 = Σ平台真实评论数;趋势 = 按抓取到的时间戳真实分桶(篇/天,覆盖率 ≥70% 才启用);细分情绪(8 类词典命中,按带情绪样本占比);高频词 TF-IDF 带权
- 可视化零依赖:全部图表原生 SVG 手绘——高频词云形词云(螺旋铺排椭圆云、8 色彩盘、字号随权重)、情感环图、趋势曲线;平台单字品牌徽标体系;暖纸底设计系统,无 UI/图表库
🧱 架构
┌───────────────────────────────────────────────────────────────┐
│ 前端 Vue3 + Vite(原生 SVG) │
│ SearchPanel(跟踪开关)· LoadingState(分平台进度+平台徽标) │
│ TrackStatusBar(跟踪状态卡/细条)· 结果三步结构 + 参考集/评论 │
├───────────────────────────────────────────────────────────────┤
│ FastAPI 后端(backend/app/) │
│ /api/analyze[/stream] · /api/tracker/* · /api/crawl │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ run_dual(手动分析,fast 档) │ │
│ │ Stage① sweep 平台并发轻扫(24 条/平台) │ │
│ │ Stage② enrich 并发深析(3 篇 × 20 评论/平台) │ │
│ │ → 统计(趋势/声量/情感/词云) → LLM 汇总 │ │
│ ├─────────────────────────────────────────────────────┤ │
│ │ tracker_worker(独立进程,话题跟踪 full 档) │ │
│ │ 每 60min 空闲抢占 run_staged_crawl → 记录轮次 │ │
│ │ 手动分析优先:preempt 信号 → worker 让出 │ │
│ │ 跨进程互斥:crawl_gate(SQLite BEGIN IMMEDIATE) │ │
│ ├─────────────────────────────────────────────────────┤ │
│ │ topic_cache:近期话题索引 + 惰性 GC(TTL/LRU 释放) │ │
│ └─────────────────────────────────────────────────────┘ │
│ 数据:MediaCrawler SQLite(vendored)· tracker.db 状态库 │
└───────────────────────────────────────────────────────────────┘
结果页(三步看懂 + 真实素材)
- 发生了什么 —— 事件概述 + 四要素(Who/What/When/Where)
- 大家怎么看 —— 情感环图(正/中/负占比与主导情绪)+ 细分情绪(愤怒/质疑/担忧/祈福/支持等 8 类,按极性三色绿/灰/红条形);核心观点标签 + 高频词云形词云(字号=讨论热度)
- 接下来会怎样 —— 近 3 天热度预测曲线 + 舆论演化阶段
下方附参考集 A(真实检索内容,官方权威置顶)、来源分布(分平台 icon 徽标与占比)、真实网友评论(高赞排序,卡片内滚动收纳不拉长页面)。
🚀 快速开始
方式 A:本地一键脚本(推荐,完整功能)
start-local.bat REM 开发模式:vite dev :5173
start-local.bat prod REM 生产形态:build + preview :4173
- 自动检查/安装依赖(仅首次)、自动清理 8000/5173/4173 端口占用
- 弹出 三个 cmd 窗口:
yujian-backend(:8000)、yujian-tracker(话题跟踪 worker)、yujian-frontend - 关闭方式:关掉这三个
yujian-*窗口 - 脚本为纯 ASCII 英文输出,任何 Windows 代码页均可运行
方式 B:手动开发
# 后端(依赖 Python 3.11+)
cd backend
python -m venv .venv
.venv/Scripts/python.exe -m pip install -r requirements.txt
.venv/Scripts/python.exe -m uvicorn app.main:app --reload --port 8000
# 话题跟踪 worker(新终端,需要跟踪功能时才起)
.venv/Scripts/python.exe -m app.services.tracker_worker
# 前端(新终端,仓库根)
npm install
npm run dev # 访问 http://localhost:5173(vite 代理 /api → :8000)
方式 C:Docker(仅 API 展示形态)
start-prod.bat # 等价 docker compose up -d --build,访问 http://localhost:8080
⚠️ Docker 容器内没有宿主机浏览器与扫码登录态,MediaCrawler 真实爬取与话题跟踪 worker 需在宿主机运行。跑完整功能请用方式 A。
📡 话题跟踪(核心用法)
- 搜索框输入话题关键词 → 点击下方「跟踪话题 · 后台定时更新」开关(主按钮变为「开始跟踪」)
- 提交即注册后台跟踪:立即首轮爬取,此后每 60 分钟自动更新一轮(间隔可用环境变量调整)
- 搜索框下方出现跟踪状态卡片(最近一轮篇数/评论 / 错误 / 下次更新倒计时);分析结果页顶部有细条入口
- 点卡片「查看最新分析」→ 汇总该话题已爬取的数据走读库直出分析(不再触发新爬取,趋势用全窗历史)
- 点「停止跟踪」随时结束
约束与并发:
- 仅允许跟踪 1 个话题(新话题自动替换旧话题)——避免多任务叠加触发平台风控
- 手动分析优先:手动输入话题分析时,若后台 worker 正在爬取,会发送抢占信号,worker 让出(最多等约 60 秒),随后手动任务立即开始
- worker 进程与 API 服务跨进程互斥由
crawl_gate(SQLiteBEGIN IMMEDIATE门闩)保证,崩溃后自动让锁
🗂️ 分析档位与近期话题缓存
| 机制 | 说明 |
|---|---|
| fast 档(手动普通分析) | 5 平台全保留,削 enrich 深度:sweep 24 条 / 深析 3 篇 × 20 评论 → 目标 3-5 分钟 |
| full 档(话题跟踪轮次) | 回落 settings 深度:sweep 30 条 / 深析 4 篇 × 30 评论,后台累积 |
| 活跃合并 | 同一话题 7 天内再次分析 → 爬新 + 读库全窗(TTL 30 天内)合并,趋势跨天、样本更大 |
| 惰性 GC | 超 30 天未再分析且不在跟踪的话题 → 从媒体库物理删除;同时 LRU 上限 20 个近期话题 |
🤖 LLM 配置(强制依赖)
- 首页搜索框下方填写任一 OpenAI 兼容 API 密钥(DeepSeek / OpenAI / 通义 / 智谱 / Kimi 预设可选)并「保存配置」
- 密钥保存到本机用户数据目录
config.json(见下节「数据持久化与迁移」),升级版本/更换机器不丢失;保存后分析请求可不再逐次携带密钥 - 密钥无效 / 模型失败 → 422 明确报错(无传统兜底);跟踪话题的「查看最新分析」同样需要密钥
🗄️ 数据持久化与迁移(升级不丢登录态/密钥)
个人数据(LLM 密钥、微博 Cookie、各平台登录态 browser_data、爬取历史库/快照/跟踪状态)一律收敛到程序目录外的用户数据目录:
- 开发态:
backend/data/;绿色分发(frozen):%APPDATA%\Yujian(可用YUJIAN_USERDATA_DIR覆盖,子进程登录态目录另可用YUJIAN_BROWSER_DATA_DIR覆盖) - 目录内
config.json统一保存 LLM 密钥/端点/模型与微博 Cookie(明文仅存本机);browser_data/为各平台 Edge profile 登录态;crawl_db/+*.db为爬取与状态历史库 - 升级版本=整包替换程序目录,用户数据目录原样保留 → 免重扫码、免重填密钥;更换机器/版本=拷贝整个用户数据目录到新机同名位置即可
- 从旧版本升级时,首次爬取前自动一次性迁移:把旧程序目录内散落的
browser_data/ 爬取库 /base_config.py中的 COOKIES 收进用户数据目录(幂等,搬完置migrated标记),实现「升级不重登」
🕷️ MediaCrawler(唯一内容源)
- 内容与评论仅来自 MediaCrawler(vendored 于仓库根
MediaCrawler-main/)爬取微博/小红书/抖音/B站/贴吧 - 后端通过 CDP 自动拉起本机 Edge,独立 profile 保存扫码登录态(仅首次需扫码);profile 与运行期库均落在上述用户数据目录,不入 git
- sweep 为列表级轻扫(跳过详情/全文/图片,速度优先);enrich 才抓头部博文评论
- 平台失败自动跳过继续;全失败才返回 503 提示换关键词
🔌 API 契约
| 接口 | 方法 | 说明 |
|---|---|---|
/api/analyze |
POST | 手动分析(即搜即爬),body {topic, autoCrawl?},autoCrawl=false 走读库直出 |
/api/analyze/stream |
POST | SSE 流式版:progress / result / error 事件,实时推送各平台爬取进度(含档位/平台维度 extra) |
/api/crawl |
POST | 独立触发 MediaCrawler 爬取(sweep / enrich) |
/api/hot-topics |
GET | 推荐话题(热搜聚合) |
/api/tracker/start |
POST | 开始跟踪话题 {topic}(自动替换旧话题,立即首轮) |
/api/tracker/stop |
POST | 停止跟踪 |
/api/tracker/status |
GET | 跟踪状态(前端轮询 / worker 调度共用) |
/api/cache |
DELETE | 清空分析缓存与爬取快照 |
/api/llm/config |
GET | 读取已保存的 LLM 配置(含密钥与 config.json 路径,供换机回填) |
/api/llm/config |
PUT | 保存 LLM 配置到用户数据目录 config.json(apiKey 留空仅更新端点/模型) |
/api/llm/config |
DELETE | 清除已保存的 LLM 配置 |
/api/login/status |
GET | 各平台登录态(qrcode / cookie / ok) |
/api/login/{platform} |
POST | 引导登录(lt=qrcode|cookie,可见浏览器扫码) |
/health |
GET | 健康检查 |
⚙️ 配置(环境变量,前缀 YUJIAN_)
| 变量 | 默认 | 说明 |
|---|---|---|
YUJIAN_PORT |
8000 | 服务端口 |
YUJIAN_CORS_ORIGINS |
* |
CORS 白名单(生产建议收紧) |
YUJIAN_CRAWL_PLATFORMS |
wb,bili,xhs,dy,tieba |
参与双模分析的平台 |
YUJIAN_CRAWL_SWEEP_NOTES |
30 | 轻扫每平台条数(列表级,full 档) |
YUJIAN_CRAWL_ENRICH_NOTES |
4 | 深析每平台头部博文篇数(full 档) |
YUJIAN_CRAWL_ENRICH_COMMENTS |
30 | 深析每篇评论条数(full 档) |
YUJIAN_CRAWL_SWEEP_SLEEP_MAP |
wb:2,bili:2,tieba:2,xhs:5,dy:5 |
轻扫每平台请求间隔(秒,分档) |
YUJIAN_CRAWL_ENRICH_SLEEP_MAP |
wb:5,bili:5,tieba:5,xhs:10,dy:10 |
深析每平台请求间隔(秒,分档) |
YUJIAN_MANUAL_SWEEP_NOTES |
24 | 手动 fast 档轻扫条数/平台 |
YUJIAN_MANUAL_ENRICH_NOTES |
3 | 手动 fast 档深析头部博文篇数/平台 |
YUJIAN_MANUAL_ENRICH_COMMENTS |
20 | 手动 fast 档每篇评论条数 |
YUJIAN_TOPIC_ACTIVE_DAYS |
7 | 近期缓存活跃窗口(天内再分析=爬新+全窗合并) |
YUJIAN_TOPIC_TTL_DAYS |
30 | 话题物理释放 TTL(超期且不在跟踪 → GC 删除) |
YUJIAN_TOPIC_MAX_KEPT |
20 | 同时保留的近期话题上限(LRU 淘汰最旧) |
YUJIAN_TRACK_INTERVAL_MINUTES |
60 | 话题跟踪周期(分钟) |
YUJIAN_TRACK_PREEMPT_WAIT |
60 | 手动分析等 worker 让出上限(秒) |
YUJIAN_TRACKER_POLL_SECONDS |
30 | worker 调度 tick(秒) |
YUJIAN_USERDATA_DIR |
见下 | 用户数据目录(配置/登录态/历史库);开发态默认 backend/data,绿色包默认 %APPDATA%\Yujian |
YUJIAN_BROWSER_DATA_DIR |
继承用户数据目录 | MediaCrawler 各平台 Edge profile 目录(登录态),子进程注入 |
YUJIAN_SQLITE_DB |
继承用户数据目录 | MediaCrawler 爬取库 sqlite 文件路径,子进程注入 |
🧪 测试
cd backend
.venv/Scripts/python.exe -m pytest tests/
# 174 passed
覆盖:双模编排、档位分层(fast/full 预算)、近期缓存与 GC、跨进程门闩、跟踪状态库、worker 调度、autoCrawl 读库直出、接口契约、统计边界(真实趋势分桶 / 细分情绪占比 / 词云带权)、爬虫 runner 超时/抢占、LLM 配置持久化与回落、旧数据迁移幂等。
📦 项目结构
public opinion analysis/
├── src/ # 前端 Vue3 源码
│ ├── App.vue # 状态机 idle/loading/success/error + 跟踪集成
│ ├── composables/ # platforms.js(平台元数据唯一源)等
│ ├── api/ # analysis.js(SSE) · tracker.js · mockAnalysis.js(规整)
│ └── components/ # SearchPanel / TrackStatusBar / LoadingState /
│ # PlatformIcon / SentimentDonut / OpinionTags(词云) 等
├── backend/ # FastAPI 后端
│ ├── app/
│ │ ├── main.py # CORS + 路由注册 + 健康检查
│ │ ├── api/ # analyze.py(SSE) · tracker.py
│ │ ├── pipeline/ # run_dual/staged · crawler_loader · prepare · stats · summarizer
│ │ ├── services/ # analyzer · crawler_runner · crawl_gate · tracker_store ·
│ │ │ # topic_cache(近期话题 GC)· nlp(情感/情绪/关键词)
│ │ └── config.py # 全量设置(pydantic-settings,YUJIAN_ 前缀)
│ ├── tests/ # pytest(离线夹具)
│ └── Dockerfile
├── MediaCrawler-main/ # vendored 爬虫(含登录态/运行库,不入 git)
├── tools/ # MediaCrawler 手动登录/爬取脚本
├── start-local.bat / .sh # 一键起 backend + tracker + frontend
├── start-tracker.bat # 单独起跟踪 worker
├── start-prod.bat # Docker 形态
├── docker-compose.yml / nginx.conf / Dockerfile
└── vite.config.js
📋 URL 参数
| 参数 | 作用 |
|---|---|
?q=话题 |
打开页面即自动分析该话题(分享 / 截图用) |
⚖️ 合规与边界
- 数据仅来自公开平台搜索与内容页,登录态仅用于解除平台风控,不采集私信等个人数据
- 内容仅用于学习与研究,请遵守各平台服务条款与 robots 约束;控制频率以避免风控与打扰
- 本工具输出由 LLM 生成,仅供信息参考,不构成任何决策依据
📄 License
实训项目,仅用于学习与研究。