打造专属面试题库:一次知识抽取与数据清洗实践
前言
研一刚开始时,我很喜欢逛牛客网、脉脉、小红书,搜集各种面试经验贴。看到那些热门面试题和“八股文”,就忍不住把自己代入进去,想如果是我会怎么回答。
但是,问题也随之而来:题目太多,新题也一直在冒出来,只靠收藏和记忆很难长期维护。
后来我就想,能不能自己整理一个专属面试题库,把这些经验贴里的问题抽取出来,再统一成结构化数据。
比较巧的是,刚把数据集搜集完,实习机会来了,这个项目就被暂时搁置了。
虽然项目当时没有完全落地,但从获取数据到清洗数据的过程,让我对数据处理和结构化有了更具体的理解。所以还是把这段经验整理出来,留作记录。
1. 数据从哪儿来:网页数据的获取
获取数据是搭建任何数据集的第一步。想拿到网页上的数据,通常有以下两种方法:
模拟网络请求: 用 Chrome开发者工具 或 Charles 这类抓包工具,找到页面背后的 API 接口。然后在合规范围内带上必要的 Cookie 或 Session,就能拿到相对干净、原始的数据。 写代码时也非常简单,JavaScript 中有
axios或fetch,Python 中有requests库,几行代码就能搞定。无头浏览器: 有时候网站的反爬虫机制比较强,直接请求 API 不太稳定。这时可以使用无头浏览器,模拟用户在页面上的真实操作,再从渲染结果里提取内容。 常用工具有 Playwright 和 Selenium。实际使用时也要注意访问频率、请求头和间隔,尽量减少对站点的影响。
2. 先把文本清出来:数据预处理
辛辛苦苦爬下来的原始数据,打开后经常是这样的(以下为脱敏数据示例):
<section data-v-5ea596c4="" data-v-405cdcf2=""><!----> <div data-v-5ea596c4="" class="tw-flex"><h1 data-v-5ea596c4="" class="tw-mb-5 tw-font-medium tw-text-size-title-lg-pure tw-text-gray-800">宇宙厂一面</h1> <!----></div> <!----> <!----> <div data-v-5ea596c4="" class="feed-content-text tw-text-gray-800 tw-mb-4 tw-break-all">base北京 2024年4月1日<br>上来三道算法题<br>1.最大公共前缀<br>2.最大子序列<br>3.重复字符串<br><br>开始聊项目细节,看到我的项目涉及数据存储相关,问我存储的内容;看到有智能代理相关项目,询问具体工作内容...<br>后面开始从项目延伸出考察知识点:<br>流式输出;<br>大文件上传<br>场景题:如果每次向智能程序询问的问题都是一样,你会怎么减少性能开销。<br>询问对前端框架的熟悉情况,我说对vue框架了解更多,面试官询问了该框架的相关内容,我阐述了一些基础响应式原理相关内容后,未被要求继续展开。<br>询问学习前端的时长和学习方式。<br>反问环节<br><br>全程1个小时9分钟,前面算法题用时接近40分钟(理论预计30分钟完成)。整体感觉还行,面试官没有深入追问更多细节。<br><br><a href="/creation/subject/3871fa77df8f40faa40474d054dbe5e3" target="_blank" class="subject font-green" nc-subject-type="0" nc-subject-tag-id="0">#面试问题记录#</a> <a href="/creation/subject/e7e06396e28b43b2928970fed37fc9ea" target="_blank" class="subject font-green" nc-subject-type="0" nc-subject-tag-id="0">#面试被问“你的缺点是什么?”怎么答#</a> </div> <!----> <!----> <!----> <!----> <!----> <!----> <!----> <div data-v-5ea596c4="" class="tw-my-3"><!----> <!----></div></section>这样的内容读起来很费劲,也没法直接用于后面的处理。
这时候就需要做预处理。但不能简单粗暴地把所有标签都删掉,那样会破坏文本原有的段落结构和语义。更稳妥的做法是,把 HTML 标签替换成空格或者空行,这样既能去掉无关内容,也能尽量保留原来的语义结构。
处理之后,大致会变成这样:
宇宙厂一面
base北京 2024年4月1日
上来三道算法题
1.最大公共前缀
2.最大子序列
3.重复字符串
开始聊项目细节,看到我的项目涉及数据存储相关,问我存储的内容;看到有智能代理相关项目,询问具体工作内容...
后面开始从项目延伸出考察知识点:
流式输出;
大文件上传;
场景题:如果每次向智能程序询问的问题都是一样,你会怎么减少性能开销。
询问对前端框架的熟悉情况,我说对vue框架了解更多,面试官询问了该框架的相关内容,我阐述了一些基础响应式原理相关内容后,未被要求继续展开。
询问学习前端的时长和学习方式。
反问环节
全程1个小时9分钟,前面算法题用时接近40分钟(理论预计30分钟完成)。整体感觉还行,面试官没有深入追问更多细节。
#面试问题记录#
#面试被问“你的缺点是什么?”怎么答#3. 把文本整理成结构化数据
经过初步清洗,我们拿到了一批纯文本。但这些文本里,公司、时间、问题、难度等信息仍然混在一起,机器并不能直接按字段读取。
这一步要做的,就是把非结构化文本转换成结构化 JSON 数据。
此时可以使用大语言模型(LLM),配合”提示工程”(Prompt Engineering)技巧来完成。例如,使用经典的 COSTAR 框架组织提示词,并指定LLM输出JSON格式。以下是提示词示例:
# CONTEXT(上下文)
你将处理一段来自面试经验分享的非结构化文本。这些文本通常包含面试公司信息、面试时间、面试内容、技术问题、面试难度等多种元素。文本已经过初步清洗,但仍需进一步结构化处理。
# OBJECTIVE(目标)
将非结构化的面试文本转换为结构化的JSON数据,提取关键信息。
# STYLE(风格)
- 精确:确保提取的信息准确无误
- 完整:尽可能提取所有相关信息
- 结构化:遵循严格的JSON结构
- 简明:去除冗余信息,保留核心内容
# TONE(语调)
专业、中立、客观,不添加个人评价或判断。
# AUDIENCE(受众)
数据处理者和开发人员,他们将使用这些结构化数据构建面试题库。
# RESPONSE(响应)
请返回一个JSON数组,包含以下字段(如果信息不存在则使用null):
[{
"company": "面试公司名称",
"level": "面试轮次(如一面、二面等)",
"tag": "问题类别(如数据结构与算法、数据库、计算机网络等)",
"difficulty": "难度等级(1-5的整数)",
"question": "具体的面试问题内容(每条数据仅包含一个问题)",
"time": "面试日期(YYYY-MM-DD格式)"
},...]处理大量数据时,逐条请求 LLM 效率较低,并发请求又可能产生较高成本。这类场景可以考虑使用批量 API(Batch API)。把所有请求打包成一个 .jsonl 文件(每行一个 JSON 请求),一次性提交给模型,处理完成后再统一拿结果。这样更适合离线任务,也更容易控制成本。
处理后,就能得到类似下面这样的 JSON 数据(这里只展示一条原始文本提取出的多条面试题数据):
[
{
"company": "宇宙厂",
"level": "一面",
"tag": "数据结构与算法",
"difficulty": 3,
"question": "最大公共前缀",
"time": "2024-04-01"
},
{
"company": "宇宙厂",
"level": "一面",
"tag": "项目经历",
"difficulty": 2,
"question": "聊项目细节",
"time": "2024-04-01"
},
{
"company": "宇宙厂",
"level": "一面",
"tag": "数据库",
"difficulty": 2,
"question": "项目涉及数据存储相关,存储的内容是什么?",
"time": "2024-04-01"
},
{
"company": "宇宙厂",
"level": "一面",
"tag": "计算机网络",
"difficulty": 3,
"question": "流式输出的实现或原理",
"time": "2024-04-01"
},
{
"company": "宇宙厂",
"level": "一面",
"tag": "技术知识",
"difficulty": 4,
"question": "大文件上传的实现或方案",
"time": "2024-04-01"
},
{
"company": "宇宙厂",
"level": "一面",
"tag": "性能优化",
"difficulty": 4,
"question": "如果每次向智能程序询问的问题都是一样,你会怎么减少性能开销?",
"time": "2024-04-01"
}
]4. 统一口径:二次清洗
拿到 LLM 处理过的数据,并不代表工作就结束了。
仔细看一遍,可能会发现标签、公司名、面试轮次还是很不统一。比如,“宇宙厂”、“抖Y”、“字J”、“bytedance”其实说的都是一家公司。
这时候就需要手动检查,或者用脚本、Excel 再做一轮规范化。
比如,把“宇宙厂”、“抖音”、“字节”、“bytedance”这些都统一成“字节跳动”;面试轮次也规范成“一面”、“二面”、“三面”这种标准格式。
清洗完毕,数据会规整很多:
{
"id": "3w5s8a",
"company": "字节跳动",
"level": "一面",
"tag": "计算机网络",
"difficulty": 3,
"question": "流式输出的实现或原理",
"time": "2024-04-01"
}5. 数据体积也可以顺手优化
当数据要在网络上传输时,体积大小也需要考虑。仔细观察 JSON 数据,你会发现很多 key(比如 "company", "question")都是重复的,占了不少空间。
这时可以给 key 做一层压缩:
比如,把完整的key缩写成单个字母:
{
"i": "3w5s8a",
"c": "字节跳动",
"l": "一面",
"t": "计算机网络",
"d": 3,
"q": "流式输出的实现或原理",
"m": "2024-04-01"
}更狠一点,直接省略key,用数组形式(当然,这需要前后端约定好顺序):
["3w5s8a", "字节跳动", "一面", "计算机网络", 3, "流式输出的实现或原理", "2024-04-01"]这样可以减少一部分数据体积。具体收益取决于字段数量和数据规模,在我的场景里大概能减少近 30%。
尾声:项目和数据
基于上面折腾出来的这套数据集,我顺手搭了个前端面试刷题小站:FE Rusher (没错,就是为了督促自己刷题,哈哈!)。
代码也放在 GitHub 上:ni00/FERusher,数据集在仓库的 ./data/fe.json 路径下,有兴趣可以参考。
如果想要创建后端版、算法版,甚至产品经理版的刷题网站,也可以直接在 data 文件夹下放入 JSON 数据,再按需要修改代码运行。