前言
在准备Java面试的过程中,拥有一个全面、结构化的面试题库是非常重要的。鱼皮开发的”面试鸭”平台聚合了大量优质面试题,涵盖Java基础、并发、Spring、MySQL、Redis等核心技术栈。然而,手动浏览网页逐题学习效率较低,如果能将这些面试题批量抓取到本地,不仅可以离线复习,还能进行关键词检索和分类整理。
本文基于真实项目源码,介绍如何通过分析面试鸭平台的API接口,使用Python编写爬虫程序,实现面试题数据的批量采集与本地化存储。我们将从最基础的版本出发,逐步迭代优化,最终实现一个具备多线程并发、随机User-Agent、自动终止判断等功能的生产级爬虫。
技术背景
本项目的核心技术栈包括:
- requests:发送HTTP POST请求,获取API返回的JSON数据
- json:解析和格式化存储API响应数据
- os:文件目录管理,确保输出路径存在
- time:请求间隔控制,避免触发反爬机制
- random:随机User-Agent选择,模拟不同浏览器环境
- concurrent.futures.ThreadPoolExecutor:多线程并发爬取,提升采集效率
面试鸭平台采用前后端分离架构,前端通过调用 api.mianshiya.com 下的RESTful API获取数据。我们通过浏览器开发者工具分析网络请求,发现面试题列表接口为 POST /api/question_bank/list_question,题库分类接口为 POST /api/questionBankCategory/list_questionBank,两者均接受JSON格式的请求体。
实现思路
整体爬虫架构分为两阶段:
- 获取题库列表:调用题库分类API,获取所有面试题库的ID和标题(如”Java基础面试题”、”Spring面试题”等)
- 逐库爬取题目:对每个题库ID,分页请求题目列表API,将JSON数据写入本地文件,同时提取题目标题写入TXT文件
迭代过程分为四个版本:
- V1:硬编码题库ID,基础分页请求
- V2:封装为函数,支持单个/多个ID传入
- V3:动态获取题库列表,自动遍历所有分类,增加异常处理
- V4:多线程并发 + 随机User-Agent + 超时控制 + 自动终止判断
核心代码解析
1. 基础版本:分页请求与数据写入
最初的版本直接硬编码题库ID,循环请求每一页数据:
1 | import requests |
这里的关键点是通过 list(set(...)) 对题库ID去重,避免重复请求。json.dump 的 ensure_ascii=False 参数确保中文字符正常显示。
2. 函数封装:支持灵活调用
将核心逻辑封装为函数,支持字符串(单个ID)或列表(多个ID)传入:
1 | def fetch_and_save_questions(question_bank_id): |
3. 动态获取题库列表 + 标题提取
第三版通过题库分类API动态获取所有题库信息,不再需要手动查找ID:
1 | import os |
这个版本的亮点在于 if current > 1 and not records: break —— 当第二页开始返回空数据时,自动跳出循环,避免对没有更多内容的题库发起无效请求。
4. 最终优化版:多线程并发 + 随机反爬
最终版本引入了线程池和多重反爬策略:
1 | import random |
关键代码汇总
最终版本的完整执行流程如下:
1 | if __name__ == "__main__": |
核心流程:
get_session_with_headers()创建带随机UA的Session- 调用题库分类API获取所有题库的ID和标题
- 使用
ThreadPoolExecutor(max_workers=10)创建10个线程 - 每个线程负责一个题库,分页爬取面试题
- 数据先缓存到内存
all_data,爬取完毕后统一写入文件 - 随机延时4~15秒,有效规避频率检测
运行效果
程序运行后,控制台输出类似:
1 | JSON 数据已写入文件: 优化强化output/response_output.json |
运行结束后,优化强化output/ 目录下生成多个JSON文件,优化强化txt/ 目录下生成对应的TXT文件,内容为各题库的面试题标题列表。
总结与优化方向
本项目从基础的API调用出发,逐步迭代为多线程并发爬虫,核心优化包括:
- 去重处理:
list(set(question_bank_ids))避免重复爬取 - 自动终止:检测
records为空时break,减少无效请求 - 随机延时:
random.uniform(4, 15)模拟人类操作节奏 - 多线程并发:
ThreadPoolExecutor提升整体采集效率约10倍 - 统一写入:数据先缓存到内存,爬取完成后一次性写入,减少IO开销
- 异常容错:
try/except捕获网络异常,continue跳过失败页
后续优化方向:
- 断点续传:记录已完成的题库ID,支持中断后恢复
- 代理IP池:引入代理IP,进一步降低被封风险
- 数据清洗:对JSON数据进行结构化提取,导入数据库
- 增量更新:定期运行,只抓取新增的面试题