# OfferGet
**Repository Path**: du1in9/OfferGet
## Basic Information
- **Project Name**: OfferGet
- **Description**: 多平台面经搜集工具:从牛客网、CSDN、掘金、思否等平台搜索面试经验,支持精准公司匹配、详情阅读与导出。
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 1
- **Forks**: 0
- **Created**: 2026-06-09
- **Last Updated**: 2026-06-10
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# OfferGet
多平台面经搜集工具:从 **牛客网、CSDN、掘金、思否** 等平台聚合搜索面试经验,支持精准公司/岗位匹配、详情阅读与导出。
仓库地址:[https://gitee.com/du1in9/OfferGet](https://gitee.com/du1in9/OfferGet)
## 预览
| 桌面版(Windows) | Android 版 |
|---|---|
|  |  |
## 功能概览
- 按 **公司**、**岗位** 搜索面经,字段均可留空
- **精准公司匹配**:排除「腾讯会议」等产品名误命中
- **多来源并行**:牛客网、CSDN、掘金、思否可同时勾选
- 结果按 **发布时间倒序** 排列
- 面经详情支持 Markdown / 图片渲染;桌面版可导出 TXT / MD / PDF
- 不采集个人信息,无统计/广告 SDK(详见各端 `PRIVACY.txt`)
---
## 实现原理
### 总体架构
OfferGet 采用 **「多来源适配器 + 统一筛选层 + 聚合搜索」** 的分层设计。桌面版(Python)与 Android 版(Kotlin)在业务逻辑上对齐,各自实现相同的来源适配与筛选规则。
```mermaid
flowchart TB
subgraph UI["表现层"]
D[desktop/main.py
CustomTkinter]
M[mobile/MainScreen.kt
Jetpack Compose]
end
subgraph Hub["聚合层"]
SH[search_hub / SearchRepository]
end
subgraph Sources["来源适配层"]
NC[牛客网 nowcoder_api]
CS[CSDN csdn_source]
JU[掘金 juejin_source]
SF[思否 segmentfault_source]
end
subgraph Core["公共能力"]
REL[筛选规则 PostRelevance]
HTTP[HTTP + SSL http_client]
REN[正文解析 content_render]
end
D --> SH
M --> SH
SH --> NC & CS & JU & SF
NC & CS & JU & SF --> REL
NC & CS & JU & SF --> HTTP
NC & CS & JU & SF --> REN
```
用户输入公司名与岗位后,聚合层按固定顺序依次调用各来源适配器;每个适配器负责 **构造搜索词 → 请求平台 API → 解析响应 → 本地筛选 → 返回统一数据结构** `InterviewPost`。单来源失败不影响其他来源,错误信息汇总展示在状态栏。
---
### 1. 数据来源与请求方式
| 来源 | 搜索接口 | 请求方式 | 详情获取 |
|------|----------|----------|----------|
| 牛客网 | `gw-c.nowcoder.com/api/sparta/pc/search` | POST JSON,带「面经」标签 (id=818) | 讨论帖 / 动态帖 API,或页面 HTML 兜底 |
| CSDN | `so.csdn.net/api/v3/search` | GET,`t=blog` | 文章页 `#content_views` |
| 掘金 | `api.juejin.cn/search_api/v1/search` | GET,cursor 分页 | 文章页 HTML |
| 思否 | `segmentfault.com/search` | GET,解析 `__NEXT_DATA__` JSON | 文章页 `.article-content` |
各来源均模拟浏览器 User-Agent,并设置对应 `Referer` / `Origin`,降低被反爬拦截的概率。牛客、CSDN 等使用 **信创 CA(Xcc Trust)** 证书链;桌面版打包时通过 `certifi` 根证书 + PyInstaller 运行时钩子解决 HTTPS 校验问题(见 `desktop/http_client.py`、`desktop/pyi_rth_certifi.py`)。
---
### 2. 搜索词构建策略
不同平台的索引习惯不同,搜索词策略也分开处理:
**牛客 / CSDN(严格标题匹配来源)**
```
公司 + 岗位 → 例:「腾讯 后端」
```
**掘金 / 思否(外链来源,岗位表述多样)**
```
公司 + 岗位 + 同义词 + 「面试」 → 例:「网易 前端 React Vue 面试」
```
**思否专用(避免泛词污染)**
思否对 `Java`、`后台` 等泛词极其敏感,追加这些词会返回大量 PDF 资源帖。因此思否单独使用精简多查询策略:
```
「公司 岗位 面试」「公司 岗位 面经」「公司 面经 岗位」
```
多组查询结果按 URL 去重后合并。
---
### 3. 面经筛选规则(核心逻辑)
搜索结果从平台返回后,会在本地进行二次筛选,避免「关键词模糊命中」带来的噪声。规则实现在 `nowcoder_api.py`(桌面)/ `PostRelevance.kt`(Android)。
#### 3.1 牛客 / CSDN:严格标题匹配
```python
is_post_relevant = is_company_relevant AND is_position_relevant
```
- **公司匹配** `is_company_relevant`:只在 **标题** 中判断
- 先屏蔽误命中短语(如「腾讯会议」「字节码」)
- 再匹配「腾讯-后端一面」「【腾讯】Java 凉经」等面试主体模式
- **岗位匹配** `is_position_relevant`:标题须 **完整包含** 岗位关键词(如「后端」)
#### 3.2 掘金 / 思否 / CSDN:外链宽松匹配
```python
is_external_post_relevant = 面试信号 AND 公司匹配 AND 岗位同义词匹配
```
- **面试信号**:标题或摘要须含 `面经|面试|校招|社招|凉经|…` 等
- **公司匹配**:在标题+摘要中查找,同样屏蔽产品名误命中
- **岗位同义词**:「后端」可匹配 `后台 / Java / Go / 服务端 / …`(见 `EXTERNAL_POSITION_ALIASES`)
这就是为什么同一组关键词下,牛客返回条数往往少于 CSDN/掘金——牛客筛得更严,只保留标题里明确写了公司+岗位的帖子。
#### 3.3 误匹配防护示例
| 标题 | 搜「腾讯+后端」 | 原因 |
|------|----------------|------|
| 腾讯-后端开发一面 | ✅ | 标题明确 |
| 腾讯会议产品面经 | ❌ | 产品名误命中 |
| 新东方测试开发面经 | ❌ | 非腾讯主体 |
| 2023 Go 面经:…小红书… | ✅(外链) | 摘要含公司 + Go≈后端 |
---
### 4. 分页与结果合并
- **牛客**:最多 20 页(API 上限),每页约 20 条原始结果,按时间倒序翻页
- **CSDN / 掘金**:最多 20 页;CSDN 搜索前会先访问首页获取 Cookie
- **思否**:每组查询最多 3 页,多查询合并去重
- 所有来源结果按 `created_at` **倒序** 合并展示;桌面版列表按来源分组统计条数
单来源异常(网络错误、反爬空结果等)被 `try/except` 隔离,写入 `errors` 字典,不阻断其他来源。
---
### 5. 详情解析与展示
面经详情统一抽象为 **内容块** `DetailBlock`:
| 块类型 | 说明 |
|--------|------|
| `paragraph` | 普通段落 |
| `heading` | 标题(h1–h6) |
| `quote` | 引用 |
| `code` | 代码块 |
| `image` | 图片(支持相对路径转绝对 URL) |
解析流程:各来源拉取 HTML → `content_render.py` / `ContentParser.kt` 去标签、识别结构 → UI 按块类型渲染。图片请求携带来源站点 Referer(`image_utils.py`),避免防盗链 403。
桌面版额外支持将 blocks 导出为 TXT / Markdown / PDF(`export_utils.py`)。
---
### 6. 桌面版 UI 与线程模型
`main.py` 基于 CustomTkinter:
1. 主线程负责界面;搜索在 **后台线程** 执行,通过 `after()` 回调更新 UI
2. 布局:搜索栏 → 状态栏(单行)→ 左侧列表(40%)+ 右侧详情(60%)
3. 详情懒加载 + 邻近预取,减少重复请求
4. PyInstaller **onedir** 打包,内置 `certifi/cacert.pem` 保证 HTTPS 可用
---
### 7. Android 版对应关系
| 桌面模块 | Android 模块 |
|----------|--------------|
| `search_hub.py` | `SearchRepository.kt` |
| `nowcoder_api.py` | `NowcoderApi.kt` |
| `csdn_source.py` | `CsdnSource.kt` |
| `juejin_source.py` | `JuejinSource.kt` |
| `segmentfault_source.py` | `SegmentFaultSource.kt` |
| `PostRelevance` 规则 | `PostRelevance.kt` |
| `content_render.py` | `ContentParser.kt` |
| OkHttp | `HttpClient.kt` |
UI 使用 Jetpack Compose + ViewModel,搜索逻辑与桌面版一致。
---
### 8. 测试体系
桌面版在 `desktop/tests/` 提供完整测试:
| 类型 | 数量 | 说明 |
|------|------|------|
| 单元测试 | 36 | 筛选规则、搜索词、HTML 解析、聚合逻辑、Mock |
| 集成测试 | 180 | 33 组真实公司/岗位场景,验证四来源连通性与结果质量 |
```bash
cd desktop
pytest # 全部(约 6 分钟,需联网)
pytest -m "not integration" # 仅单元测试
pytest -m integration -v # 33 组多样化集成场景
```
场景定义见 `desktop/tests/search_scenarios.py`,覆盖互联网大厂、多样岗位、外企、边界输入等。
---
## 项目结构
```
OfferGet/
├── desktop/ # Python + CustomTkinter 桌面版
│ ├── main.py # UI 入口
│ ├── search_hub.py # 多来源聚合
│ ├── nowcoder_api.py # 牛客 API + 筛选规则
│ ├── csdn_source.py # CSDN 适配
│ ├── juejin_source.py # 掘金适配
│ ├── segmentfault_source.py
│ ├── content_render.py # 正文解析
│ ├── http_client.py # HTTPS / SSL
│ ├── tests/ # 单元 + 集成测试
│ └── scripts/ # 打包 / 测试脚本
├── mobile/ # Kotlin + Compose Android 版
│ └── app/src/main/java/com/offerget/
│ ├── data/ # API 适配 + 筛选
│ └── ui/ # 界面
├── docs/screenshots/
└── README.md
```
---
## 快速开始
### 桌面版
**环境**:Python 3.10+,Windows / macOS / Linux
```bash
cd desktop
pip install -r requirements.txt
python main.py
```
**Windows 打包**:
```powershell
cd desktop
.\scripts\build_release.ps1
# 输出:dist\OfferGet\ 与 dist\OfferGet-portable-win64.zip
```
更多说明:[desktop/README.md](desktop/README.md)、[desktop/USAGE.txt](desktop/USAGE.txt)
### Android 版
**环境**:Android Studio,或 JDK 17 + Android SDK 35
```bash
cd mobile
# 复制 local.properties.example → local.properties,填写 sdk.dir
gradlew.bat assembleRelease # Windows
```
更多说明:[mobile/README.md](mobile/README.md)
---
## 隐私与免责
- 桌面版:[desktop/PRIVACY.txt](desktop/PRIVACY.txt)
- Android 版:[mobile/app/src/main/assets/PRIVACY.txt](mobile/app/src/main/assets/PRIVACY.txt)
- 数据来自各平台 **公开搜索接口**,仅供个人学习参考
- 请合理使用,避免高频请求;原文链接由系统浏览器打开,隐私策略由目标网站负责
## 开源协议
本项目以学习交流为目的开源,欢迎 Issue 与 Pull Request。