从零开发一个智能面经爬取系统:技术选型、架构设计与踩坑记录
项目背景
在求职过程中,面经是求职者了解公司面试流程、准备面试的重要参考资料。牛客网等平台汇集了大量真实的面试经验分享,但手动浏览和整理这些内容效率较低。因此,我决定开发一个自动化的面经爬取系统,能够:
- 自动爬取牛客网等平台的面经内容
- 使用 AI 智能判断是否为真实面经
- 自动分类(公司、岗位、编程语言)
- 生成 AI 总结
- 提供友好的前端界面展示
技术选型
后端技术栈
| 技术 | 用途 | 选择理由 |
|---|---|---|
| Python 3.11+ | 主语言 | 生态丰富,爬虫和 AI 库完善 |
| FastAPI | Web 框架 | 高性能异步框架,自动生成 API 文档 |
| SQLAlchemy | ORM | 灵活的数据库操作 |
| SQLite | 数据库 | 轻量级,本地开发友好 |
| Playwright | 爬虫引擎 | 支持动态页面,绕过反爬虫 |
| PaddleOCR | 图片识别 | 国产 OCR,中文识别效果好 |
| DeepSeek API | AI 服务 | 性价比高,中文理解能力强 |
前端技术栈
| 技术 | 用途 | 选择理由 |
|---|---|---|
| Vue 3 | 前端框架 | 组合式 API,开发体验好 |
| Element Plus | UI 组件库 | 组件丰富,文档完善 |
| Vite | 构建工具 | 快速热更新,开发效率高 |
系统架构
┌─────────────────────────────────────────────────────────────┐
│ 前端展示层 │
│ (Vue 3 + Element Plus) │
└─────────────────────────────────────────────────────────────┘│▼
┌─────────────────────────────────────────────────────────────┐
│ API 服务层 │
│ (FastAPI + Uvicorn) │
└─────────────────────────────────────────────────────────────┘│┌───────────────────┼───────────────────┐▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 爬虫引擎层 │ │ NLP 处理层 │ │ 数据存储层 │
│ (Playwright) │ │ (DeepSeek API) │ │ (SQLite) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
核心功能实现
1. 动态页面爬取
牛客网采用动态渲染,直接请求 API 会返回 HTML 而非 JSON。使用 Playwright 模拟真实浏览器行为:
from playwright.sync_api import sync_playwrightdef fetch_detail_with_browser(post_id: str):with sync_playwright() as p:browser = p.chromium.launch(headless=True)page = browser.new_page()page.goto(f"https://www.nowcoder.com/discuss/{post_id}")# 提取内容result = page.evaluate('''() => {const title = document.querySelector('h1')?.textContent || '';const content = document.querySelector('.post-content')?.textContent || '';return { title, content };}''')browser.close()return result
踩坑记录:
- Windows 下异步 Playwright 会报
NotImplementedError,需要设置事件循环策略或使用同步版本 - 页面内容选择器需要排除评论区,否则会爬取大量无关内容
2. AI 智能判断与分类
使用 DeepSeek API 判断内容是否为真实面经,并提取分类信息:
async def judge_interview(self, title: str, content: str) -> InterviewJudgeResult:prompt = f"""请判断以下内容是否为真实的面试经验分享(面经),并进行分类。标题:{title}
内容:{content[:2000]}请严格按照以下JSON格式回复:
{{"is_interview": true或false,"company": "面试公司名称","position": "面试岗位","language": "编程语言","tags": ["标签1", "标签2"]
}}重要规则:
1. 只有在内容中明确提到时才填写,否则填null
2. 不要强行分类,无法确定时返回null
"""response = await self._call_api(prompt)return self._parse_response(response)
踩坑记录:
- AI 容易"强行分类",需要在 prompt 中明确要求不确定时返回 null
- 内容长度需要限制,否则 API 调用会超时或超出 token 限制
3. 爬取任务状态管理
使用类变量维护任务状态,确保跨请求共享:
class CrawlService:_tasks: Dict[str, Dict] = {} # 类变量,所有实例共享async def start_crawl(self, request: CrawlRequest) -> str:task_id = str(uuid.uuid4())CrawlService._tasks[task_id] = {"status": "running","total_count": 0,"success_count": 0,}asyncio.create_task(self._run_crawl(task_id, request))return task_id
踩坑记录:
- 最初使用实例变量
self.tasks,导致不同请求无法共享状态 - 服务重启后 running 状态的任务需要清理,否则前端显示异常
4. 前端自动刷新
使用 keep-alive 和 onActivated 钩子实现页面切换时自动刷新:
<template><router-view v-slot="{ Component }"><keep-alive><component :is="Component" /></keep-alive></router-view>
</template><script setup>
import { onActivated } from 'vue'onActivated(() => {fetchInterviews() // 页面激活时刷新数据
})
</script>
踩坑总结
1. Docker 部署问题
最初计划使用 Docker 部署,但遇到多个问题:
- Windows 下 Docker 安装路径配置复杂
- WSL2 虚拟磁盘占用大量 C 盘空间
- 网络配置问题导致镜像拉取失败
解决方案:改用本地模式,使用 SQLite 替代 MySQL/Redis,简化部署流程。
2. 反爬虫机制
牛客网有反爬虫机制,直接请求 API 会返回 HTML:
- 使用 Playwright 模拟真实浏览器
- 设置合理的 User-Agent
- 添加请求延迟,避免被封
3. 内容提取问题
页面结构变化导致选择器失效:
- 使用多个备选选择器
- 添加回退机制,从整个页面提取内容
- 排除评论区等无关内容
4. 环境变量加载
.env 文件加载时机问题:
- 在
settings.py中直接加载.env,确保配置在其他模块导入前生效
from dotenv import load_dotenv
from pathlib import PathBASE_DIR = Path(__file__).resolve().parent.parent
load_dotenv(BASE_DIR / ".env")
项目结构
interview_crawler/
├── api/ # FastAPI 后端
│ ├── main.py # 应用入口
│ ├── routers/ # 路由
│ └── services/ # 业务逻辑
├── crawler/ # 爬虫模块
│ └── spiders/ # 爬虫实现
├── nlp/ # NLP 处理
│ ├── processor.py # 分类处理
│ └── ai_service.py # AI 服务
├── utils/ # 工具模块
│ └── image_handler.py # 图片处理和 OCR
├── db/ # 数据库
│ └── models.py # 数据模型
├── config/ # 配置
│ └── settings.py # 配置项
├── frontend/ # Vue 前端
│ └── src/
│ ├── views/ # 页面组件
│ ├── api/ # API 调用
│ └── router/ # 路由配置
├── .env # 环境变量配置
├── requirements.txt # Python 依赖
└── README.md # 项目说明
启动方式
# 后端
cd interview_crawler
python -m venv venv
venv\Scripts\activate
pip install -r requirements.txt
python run_server.py# 前端
cd frontend
npm install
npm run dev
访问地址:
- 前端界面:http://localhost:3001
- API 文档:http://localhost:8000/docs
总结
这个项目从零开始,完整经历了需求分析、技术选型、架构设计、功能实现、问题排查的全过程。主要收获:
- 技术选型要务实:Docker 虽好,但本地开发用 SQLite 更简单高效
- 爬虫要考虑反爬:使用 Playwright 模拟浏览器是有效方案
- AI 要善用 Prompt:好的 prompt 能显著提升分类准确性
- 状态管理要谨慎:异步任务的状态管理需要特别注意
- 用户体验很重要:自动刷新、实时状态显示等细节提升用户体验
项目代码已开源,欢迎交流讨论!
本文首发于博客园,记录开发过程中的技术选型和踩坑经验,希望对有类似需求的开发者有所帮助。
