很多考研同学都会关注院校招生人数、专业分数线等信息,手动整理效率极低。本篇用 Python 完成接口爬虫 + 数据存储 + 可视化分析全流程实战,从考研网站抓取院校专业数据,再通过图表直观分析数据,适合爬虫、数据分析入门学习。
## 一、项目的环境准备
pip install requests pandas pyecharts
## 二、整体开发思路
整个项目分为两大模块:数据爬虫和数据可视化。
1. 爬虫模块:通过三层接口请求,依次获取院校、专业、分数线,最终保存为 CSV 文件;
2. 可视化模块:读取 CSV 数据、清洗数据,使用 `pyecharts` 绘制多类图表并整合展示。
## 第一部分:数据爬取(分段拆解)
爬虫采用三层 POST 接口请求,下面按功能拆分模块,逐个讲解。
### 1.1 导入依赖 + 初始化全局变量
先导入需要的库,并定义列表用来存放最终爬取的数据。
```python import random import requests import time import pandas # 定义一个空列表,装整体数据 add_data = [] ```### 1.2 配置请求头(模拟浏览器,防拦截)
网站接口会校验访问来源,配置请求头伪装成正常浏览器访问。
```python # 请求头 h = { "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36", "referer": "https://www.kaoyan.cn/" } ```### 1.3 第一层请求:获取北京地区院校列表
调用接口拿到院校名称、学校唯一 ID,`province_id=11` 代表北京市。
```python # 院校接口地址与请求参数 school_api = "https://api.kaoyan.cn/pc/school/schoolList" school_params = { "page": 1, "limit": 20, "province_id": "11", "type": "", "feature": "", "school_name": "" } # 发送POST请求,解析返回的JSON数据 res_school = requests.post(school_api, headers=headers, data=school_params) school_list = res_school.json()["data"]["data"] ```### 1.4 遍历院校 + 第二层请求:获取院校专业信息
循环每一所学校,根据学校 ID 调用接口,获取该学校下所有专业信息。
```python # 遍历每一所学校 for school in school_list: school_name = school["school_name"] school_id = school["school_id"] print(f"正在爬取:{school_name}") # 专业接口 + 请求参数 major_api = "https://api.kaoyan.cn/pc/school/planListV2" major_params = {"limit": 5, "school_id": school_id} # 请求专业数据 res_major = requests.post(major_api, headers=headers, data=major_params) major_list = res_major.json()["data"]["data"] ```
### 1.5 遍历专业 + 第三层请求:查询专业分数线
循环每个专业,根据专业 ID 查询分数线,增加异常捕获处理无分数的情况。承接上一段循环,在内部继续编写:
```python # 遍历当前学校下的所有专业 for major in major_list: major_name = major["special_name"] recruit_num = major["recruit_number"] study_type = major["recruit_type_name"] depart = major["depart_name"] plan_id = major["plan_id"] # 分数线接口 + 参数 score_api = "https://api.kaoyan.cn/pc/school/planDetailV2" score_params = {"is_apply": 2, "plan_id": plan_id} # 请求分数线数据 res_score = requests.post(score_api, headers=headers, data=score_params) # 异常捕获:部分专业暂无分数线,避免程序报错崩溃 try: min_score = res_score.json()["data"]["min_score"] except: min_score = "暂无" ```
### 1.6 数据组装 + 延时防反爬
把单条数据整理成字典存入列表,设置随机休眠,降低请求频率。
```python # 组装单条完整数据 row = { "学校": school_name, "专业": major_name, "院系": depart, "招生数": recruit_num, "学习方式": study_type, "分数线": min_score } all_data.append(row) # 随机延时 1~3 秒,简单规避反爬 time.sleep(random.randint(1, 3)) ```
### 1.7 数据本地持久化:保存为 CSV 文件
所有数据爬取完成后,使用 `pandas` 导出为本地表格文件。
```python # 转为DataFrame并保存CSV df = pd.DataFrame(all_data) df.to_csv("mnt/北京考研数据.csv", index=False, encoding="utf-8-sig") print("数据爬取完成,已保存!") ``` ## 爬虫的完整代码 ```python import random import requests import time import pandas # 定义请求网址:发送3次请求 # 定义一个空列表。来装每个学校的专业数据 alldata = [] # 1.第一个请求网址 以北京城市的大学数据为例子 xxurl = "https://api.kaoyan.cn/pc/school/schoolList" # 请求头 h = { "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36", "referer": "https://www.kaoyan.cn/" } # 传递的信息 xxdata = { "page": 1, "limit": 20, "province_id": "11", "type": "", "feature": "", "school_name": "" } # 发请求 post请求单独传递的参数,复制给data xxres = requests.post(url=xxurl, headers=h, data=xxdata) xxlist = xxres.json()["data"]["data"] # 直接调用json() ''' 需要从学校信息里面获取每个学校名称和唯一id值, ''' for xx in xxlist: xname = xx["school_name"] print(f"开始爬取{xname}的专业") xid = xx["school_id"] # 携带这个id向该学校的详情页发请求,获取对应学校专业 zyurl = "https://api.kaoyan.cn/pc/school/planListV2" # 专业需要携带的请求,发请求 zydata = {"limit": 5, "school_id": xid} # 带上每个学校不同的id号,获取对应的专业 # 获取专业发请求 第二次请求: zyres = requests.post(url=zyurl, headers=h, data=zydata) ''' 需要从专业信息里面提取什么,和下一步关联是什么? 专业,招生数,学习方式,院系 ''' zylist = zyres.json()["data"]["data"] # 循环每一个专业,提取四个内容,找分数线的关键数据 for zy in zylist: zyname = zy["special_name"] num = zy["recruit_number"] stype = zy["recruit_type_name"] departName = zy["depart_name"] # 从当前每个专业数据里面获取对应专业唯一的id,携带id发送第三次请求,拿到每个专业的分数线 pid = zy["plan_id"] ''' 准备发第三次请求,携带专业id,拿分数线 ''' fsurl = "https://api.kaoyan.cn/pc/school/planDetailV2" # 准备传递的数据 fsdata = {"is_apply": 2, "plan_id": pid} # 发请求 fsres = requests.post(url=fsurl, headers=h, data=fsdata) # 获取专业分数。某些专业没有分数数据,找不到会报错,程序崩溃。try捕获报错 try: score = fsres.json()["data"]["min_score"] except Exception as e: print(f"该专业暂无分数{e}") score = "暂无" # 整合数据,6个内容 info = { "学校": xname, "专业": zyname, "院系": departName, "招生数": num, "学习方式": stype, "分数线": score, } alldata.append(info) # 加一个等待时间,避免短时间爬取太快 time.sleep(random.randint(1, 3)) # 存为本地的csv数据 用pandas转数据为表格数据类型 df = pandas.DataFrame(alldata) df.to_csv("北京考研数据.csv", index=False) print("保存成功") ```
## 第二部分:数据可视化
读取上面生成的 CSV 数据,做数据清洗 + 多图表绘制,依旧按功能拆分代码。
### 2.1 导入可视化相关库
```python import pandas as pd from pyecharts.charts import Page, Bar, Line from pyecharts import options as opts ```
### 2.2 读取数据 + 数据清洗
过滤无效数据,筛选出 985 院校、全日制的有效数据,并转换字段类型。
```python # 读取本地CSV文件 df = pd.read_csv("mnt/全国考研数据.csv") # 数据清洗:过滤无效值、筛选目标数据 df_clean = df[df["招生人数"] != "--"] df_clean = df_clean[df_clean["学习方式"] == "全日制"] df_clean = df_clean[df_clean["985"] == "是"] df_clean["招生人数"] = df_clean["招生人数"].astype(int) ```
### 2.3 模块一:绘制专业招生人数 TOP15 柱状图
统计热门专业招生总数,取前 15 名绘制柱状图,并优化 X 轴文字展示。
```python # 分组统计各专业招生总人数,倒序取前15 top15_major = df_clean.groupby("专业")["招生人数"].sum()\ .sort_values(ascending=False).head(15)\ .reset_index(name="招生总人数") # 创建柱状图对象 bar1 = Bar() bar1.add_xaxis(top15_major["专业"].tolist()) bar1.add_yaxis("专业招生总人数", top15_major["招生总人数"].tolist(), color="red") # 配置X轴:文字倾斜、完整展示名称 bar1.set_global_opts( xaxis_opts=opts.AxisOpts(axislabel_opts={"interval": 0, "rotate": 30}) ) ```
### 2.4 模块二:筛选金融专业数据
单独提取金融专业数据,按学校分组,统计招生数与最低分数线。
```python # 筛选金融专业所有数据 df_finance = df_clean[df_clean["专业"] == "金融"] # 按学校分组,聚合计算招生总数、最低分数线 finance_school = df_finance.groupby("学校名称").agg( 总招生数=("招生人数", "sum"), 最低分数=("分数线", "min") ).reset_index().sort_values("最低分数").head(10) ```
### 2.5 模块三:绘制分数线折线图
基于上一步处理好的数据,绘制院校分数线折线图。
```python # 创建折线图 line = Line() line.add_xaxis(finance_school["学校名称"].tolist()) line.add_yaxis( "最低分数线", finance_school["最低分数"].tolist(), linestyle_opts=opts.LineStyleOpts(color="#E53E3E") ) # X轴样式优化 line.set_global_opts( xaxis_opts=opts.AxisOpts(axislabel_opts={"interval": 0, "rotate": 30}) ) ```
### 2.6 模块四:绘制招生人数柱状图 + 图表叠加
绘制金融专业招生人数柱状图,并将折线图叠加在柱状图上,实现组合图表。
```python # 创建柱状图(招生人数) bar2 = Bar() bar2.add_xaxis(finance_school["学校名称"].tolist()) bar2.add_yaxis("金融专业招生数", finance_school["总招生数"].tolist(), color="data") bar2.set_global_opts( xaxis_opts=opts.AxisOpts(axislabel_opts={"interval": 0, "rotate": 30}) ) # 折线图叠加到柱状图中 bar2.overlap(line) ```
### 2.7 模块五:整合所有图表,生成 HTML 文件
使用 `Page` 容器把多张图表放在同一个页面,最终渲染出网页文件。
```python # 创建页面容器 page = Page(layout=Page.SimplePageLayout) # 加入所有图表 page.add(bar1, bar2) # 渲染为本地HTML,浏览器可直接打开 page.render("考研数据可视化.html") print("图表绘制完成!") ```
## 完整数据可视化代码
```python # 从绘图的库中引入柱状图和折线图 from pyecharts.charts import Page, Bar, Line # 需要安装:pip install pyecharts from pyecharts import options as opts import pandas # 读取、清除脏数据、过滤需要的数据 dfData = pandas.read_csv("全国考研数据.csv") # 招生人数清洗 newdf = dfData[dfData["招生人数"] != "--"] newdf = newdf[newdf["学习方式"] == "全日制"] newdf = newdf[newdf["985"] == "是"] # 转换类型 newdf["招生人数"] = newdf["招生人数"].astype(int) # 分组统计不同专业的招生数,从高到低取前15个 zydata = newdf.groupby("专业")["招生人数"].sum().sort_values( ascending=False ).head(15).reset_index(name="招生总人数") # 创建柱状图 bar = Bar() x_data = zydata["专业"].tolist() y_data = zydata["招生总人数"].tolist() bar.add_xaxis(x_data) bar.add_yaxis("各专业招生总数", y_data, color="red") # 优化X轴,让名称完整显示并倾斜30度 bar.set_global_opts( xaxis_opts=opts.AxisOpts( axislabel_opts={ "interval": 0, "rotate": 30 } ) ) # 查看金融专业的招生情况 major = newdf[newdf["专业"] == "金融"] # 按学校聚合招生总数与最低分数 majorschool = major.groupby("学校名称").agg( 总招生数=("招生人数", "sum"), 最低分数=("分数线", "min") ).reset_index() # 按最低分数排序,取前10个 resultdata = majorschool.sort_values( by="最低分数", ascending=True ).head(10) # 绘制各学校金融专业分数线对比折线图 x_data = resultdata["学校名称"].tolist() y_data = resultdata["最低分数"].tolist() line = Line() line.add_xaxis(x_data) line.add_yaxis( "各学校金融专业分数线", y_data, z_level=2, linestyle_opts=opts.LineStyleOpts(color="#E53E3E") ) line.set_global_opts( xaxis_opts=opts.AxisOpts( axislabel_opts={ "interval": 0, "rotate": 30 } ) ) # 绘制各学校金融专业招生人数对比柱状图 y2_data = resultdata["总招生数"].tolist() bar2 = Bar() bar2.add_xaxis(x_data) bar2.add_yaxis("金融专业招生总数对比", y2_data, color="data") bar2.set_global_opts( xaxis_opts=opts.AxisOpts( axislabel_opts={ "interval": 0, "rotate": 30 } ) ) # 把折线图合并到柱状图中 bar2.overlap(line) # 创建装所有图表的容器 page = Page(layout=Page.SimplePageLayout) # 把三个图表整合在一个页面里 page.add(bar, line, bar2) # 渲染合成的图表 page.render("考研数据可视化.html") print("绘制结束") ```
##三、总结
1. 爬虫部分:采用接口直爬方式,分三层请求数据,搭配请求头、延时、异常捕获应对基础反爬,最终落地为 CSV 表格;
2. 可视化部分:先清洗脏数据,再分别绘制柱状图、折线图,支持图表叠加与多图合并,可视化结果直观易懂;
3. 拓展方向:修改省份 ID 爬取全国数据、增加分页逻辑、新增饼图 / 散点图丰富分析维度。
本期内容就分享到这里啦!如果你在实践过程中遇到任何问题,或者想获取文中提到的完整源码、
配置文件模板,以及更多进阶玩法,可扫码领取(无套路,纯分享)。