8.3 KiB
8.3 KiB
批量导入试卷 — 交接文档
概述
batch_import.py 用于批量向 PastPaper Master 数据库填充试卷。它会自动完成:
- 创建 DB 记录
- 上传 PDF 到 Supabase Storage
- Gemini Vision 提取题目结构
- DeepSeek 生成 AI 解题三件套(knowledge reminder + hint + solution)
凭据 & 账号
.env 文件(项目根目录)
## Supabase
SUPABASE_URL=https://pvcxipwovpwrurebouwg.supabase.co
SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InB2Y3hpcHdvdnB3cnVyZWJvdXdnIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzM0MDAzMzIsImV4cCI6MjA4ODk3NjMzMn0.pq9JhSSdok4eHOul7rmLLN7AjXNCw0Mz8fxXEu-eQLY
SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InB2Y3hpcHdvdnB3cnVyZWJvdXdnIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTc3MzQwMDMzMiwiZXhwIjoyMDg4OTc2MzMyfQ.JUlHLKYhf7MaLU_YfmqUXmCBgQOv3vEbsSUke6tS41w
SUPABASE_DB_PASSWORD=nyddiq-5mefde-senSih
## LLM
DEEPSEEK_BASE_URL=https://api.deepseek.com/v1
DEEPSEEK_API_KEY=sk-f7768364050d4a38bb0f42030ea138da
GOOGLE_GEMINI_API_KEY=AIzaSyBm_SMw5iwxn5KxWmVoyAJMMpjfu86m-yU
# 以下备用,批量导入不需要
LAOZHANG_BASE_URL=https://api.laozhang.ai/v1
LAOZHANG_API_KEY=sk-oqKIhugRggjtjzPg0e07Bb2aB9Fb44B2A904BfA1E9C67947
DASHSCOPE_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
DASHSCOPE_API_KEY=sk-20164094f29e416aad0437d9e678b04f
管理后台
| 服务 | 地址 | 账号 | 用途 |
|---|---|---|---|
| Supabase | https://supabase.com/dashboard | 找 soda 要登录 | 数据库管理、Storage、Auth |
| Google AI Studio | https://aistudio.google.com | 用自己的 Google 账号,API key 在 .env 里 | 监控 Gemini 用量,免费额度 |
| DeepSeek | https://platform.deepseek.com | 用 .env 里的 key 登录 | 监控用量和余额,这个会花钱 |
| Gitea | https://git.deepknow.site | 用户名 soda,密码 Jermaine0805 |
代码仓库 |
API 费用监控(重要!)
| 模型 | 用途 | 单价 | 单份试卷约费用 |
|---|---|---|---|
| Gemini 2.5 Flash | Vision 提取 + 答案匹配 | 免费额度(每分钟有限) | $0 |
| DeepSeek V3 | AI trio 生成 | $0.28/M input, $1.10/M output | ~$0.5-1.5 |
批量导入前先去 DeepSeek 平台看余额! 50 份试卷大约消耗 $25-75。
服务器信息
| 项目 | 值 |
|---|---|
| 生产服务器 | 129.226.210.66 |
| SSH | ssh -i ~/.ssh/id_ed25519 root@129.226.210.66 |
| 后端容器 | pastpaper-backend-1 |
| 项目路径 | /opt/pastpaper/ |
| 前端静态文件 | /opt/1panel/www/pastpaper/ |
| Gitea 服务器 | 43.134.230.28(1Panel 面板管理) |
第一步:获取试卷 PDF(PeterGao Scraper)
概述
HKUST 历年试卷来源: https://petergao.cc/ustpastpaper/
该网站有 quota 限制(每账号每学期 50 次下载),需要多个 HKUST 学生账号协作下载。
Scraper 位置
cd /path/to/PastPaper\ Master/pastpaper-scraper/
GitHub repo: https://github.com/ZhaoYiping789/PeterGao-raper.git
环境
# 安装 uv
curl -LsSf https://astral.sh/uv/install.sh | sh
# 安装依赖
uv sync
运行
# 批量下载(按 group 分配)
uv run python batch_download.py --group B
流程:
- 脚本显示本轮要下载的课程(每轮不超过 45 quota)
- 输入一个 ITSC 用户名(如
chanxm) - 网站向该用户发验证邮件
- 让该用户把邮件链接发给你
- 粘贴链接到脚本
- 自动下载,完成后换下一个账号
获取账号
需要找 HKUST 学生要 ITSC 账号配合。每个账号每学期可用 50 quota,全站约 3400 份试卷需要约 52 个账号。联系 soda 协调。
下载结果
PDF 保存在 pastpaper-scraper/papers/{课程名}/ 目录下。
第二步:整理文件结构
将 scraper 下载的 PDF 整理成以下结构:
papers_to_import/
├── COMP2211/
│ ├── 2024_spring_midterm.pdf
│ ├── 2024_spring_midterm_answer.pdf <- 答案,自动匹配
│ ├── 2024_fall_final.pdf
│ └── 2023_spring_midterm.pdf
├── COMP2011/
│ └── ...
├── MATH1014/
│ └── ...
└── FINA2303/
└── ...
规则:
- 一级目录名 = 课程代码(自动转大写)
- 文件名:
{year}_{term}_{examtype}.pdf - 答案:
{year}_{term}_{examtype}_answer.pdf(可选) - term:
spring/fall/summer - examtype:
midterm/final/quiz
scraper 下载的文件名格式比较杂(如 (COMP2211)[2024](s)midterm~xxx.pdf),需要手动或写脚本重命名。
优先导入的课程
用户量大,优先补充:
- COMP2011, COMP2211, COMP2711H
- MATH1013, MATH1014, MATH2023
- PHYS1112
- ELEC2100
- FINA2303
第三步:批量导入
在本地运行(推荐)
cd /path/to/PastPaper\ Master/backend
source .venv/bin/activate # 或 .venv/bin/python
# 试运行(不实际导入,只打印)
python batch_import.py /path/to/papers_to_import/ --batch --dry-run
# 正式导入(串行)
python batch_import.py /path/to/papers_to_import/ --batch
# 并发导入(最多 2 个同时,别超过 2)
python batch_import.py /path/to/papers_to_import/ --batch --concurrency 2
单份导入
python batch_import.py paper.pdf \
--course COMP2211 --year 2024 --term spring --exam midterm
# 带答案
python batch_import.py paper.pdf --answer answer.pdf \
--course COMP2211 --year 2024 --term spring --exam midterm
自动查重
脚本会跳过已存在的试卷(相同 course_code + year + term + exam_type)。
处理时间估计
| 阶段 | 耗时 |
|---|---|
| PDF 渲染 | 2-5s |
| Vision 提取(每 8 页一批) | 30-60s/批 |
| 答案匹配 | 20-40s |
| AI trio 生成(每 3 题一批) | 15-25s/批 |
| 总计(30 题试卷) | ~3-5 min |
| 总计(40+ 题试卷) | ~5-8 min |
并发不要超过 2,Gemini 会限流(429 错误,脚本自动重试但更慢)。
常见问题
Q: 处理失败(status=error)怎么办?
cd backend/
.venv/bin/python -c "
import sys; sys.path.insert(0, '.')
from dotenv import load_dotenv; load_dotenv('../.env')
from app.services.supabase_client import get_supabase
sb = get_supabase()
errors = sb.table('papers').select('id, course_code').eq('status', 'error').execute().data
for p in errors:
sb.table('paper_questions').delete().eq('paper_id', p['id']).execute()
sb.table('papers').delete().eq('id', p['id']).execute()
print('Deleted', p['course_code'])
"
然后重新导入即可。
Q: 只重新生成 AI trio(题目已提取成功)?
.venv/bin/python -c "
import sys; sys.path.insert(0, '.')
from dotenv import load_dotenv; load_dotenv('../.env')
from app.services.supabase_client import get_supabase
sb = get_supabase()
PAPER_ID = 'xxxxxxxx-xxxx-...' # 替换为实际 ID
qs = sb.table('paper_questions').select('id').eq('paper_id', PAPER_ID).execute().data
for q in qs:
sb.table('paper_questions').update({'solution': None, 'ai_hint': None, 'knowledge_reminder': None}).eq('id', q['id']).execute()
sb.table('papers').update({'status': 'processing'}).eq('id', PAPER_ID).execute()
print(f'Reset {len(qs)} questions')
"
# 重启后端触发自动续传
ssh -i ~/.ssh/id_ed25519 root@129.226.210.66 "sudo docker restart pastpaper-backend-1"
Q: 如何部署后端代码改动?
scp -i ~/.ssh/id_ed25519 app/services/paper_processor.py root@129.226.210.66:/opt/pastpaper/backend/app/services/
ssh -i ~/.ssh/id_ed25519 root@129.226.210.66 "cd /opt/pastpaper && sudo docker compose up -d --build backend"
Q: 如何部署前端改动?
cd frontend && npm run build
cp public/favicon.jpg dist/
ssh -i ~/.ssh/id_ed25519 root@129.226.210.66 "rm -rf /opt/1panel/www/pastpaper/assets"
scp -i ~/.ssh/id_ed25519 dist/index.html dist/favicon.jpg root@129.226.210.66:/opt/1panel/www/pastpaper/
scp -i ~/.ssh/id_ed25519 -r dist/assets root@129.226.210.66:/opt/1panel/www/pastpaper/
Q: DeepSeek 余额不够了?
去 https://platform.deepseek.com 充值。用的是 DeepSeek V3(deepseek-chat),很便宜但批量跑几百份也会花几十美元。
Q: Gemini 限流(429)?
免费额度有每分钟请求限制。脚本内置自动重试(指数退避),等几秒就好。如果频繁 429,降低并发到 1。