要爬取数据,首先得了解网页的 "构造"。通过查看网页源代码发现,大学排名数据整齐地放在一个 HTML 表格中(标签为<table>
),表格的每行对应一所大学(标签为<tr>
),每行中的单元格(标签为<td>
)则分别存储名次、学校名称、总分等信息。
简单来说,网页中的数据就像一个 Excel 表格,我们只需要找到对应的 "行" 和 "列",就能精准提取信息。
代码实现:三步搞定数据爬取
我们用 Python 的requests
库获取网页内容,BeautifulSoup
库解析网页结构,再用csv
库保存数据,核心代码分为三个函数:获取网页内容(get_html 函数):发送网络请求,获取网页的 HTML 文本,并处理可能出现的请求错误(如网络超时)。python运行
import requests
def get_html(url, time=3):
try:
r = requests.get(url, timeout=time) # 发送请求
r.encoding = r.apparent_encoding # 自动识别编码
r.raise_for_status() # 若请求失败(状态码≠200),抛出异常
return r.text # 返回网页文本
except Exception as error:
print(error)
-
解析数据(parser 函数) :用
BeautifulSoup
解析 HTML,定位到表格的每行数据,提取学校名称、总分等关键信息,存入列表。python运行from bs4 import BeautifulSoup def parser(html): soup = BeautifulSoup(html, "lxml") # 解析网页 out_list = [] # 遍历表格的每一行(tr标签) for row in soup.select("table>tbody>tr"): td_html = row.select("td") # 获取行中的单元格(td标签) # 提取单元格中的文本(去除空格) row_data = [ td_html[1].text.strip(), # 学校名称 td_html[2].text.strip(), # 总分 td_html[3].text.strip(), # 全国排名 td_html[4].text.strip(), # 星级排名 td_html[5].text.strip() # 办学层次 ] out_list.append(row_data) # 存入列表 return out_list
-
保存数据(save_csv 函数):将解析后的列表数据写入 CSV 文件,方便后续用 Excel 或 Python 读取。python运行
import csv def save_csv(item, path): with open(path, "wt", newline="", encoding="utf-8") as f: csv_write = csv.writer(f) csv_write.writerows(item) # 写入多行数据
运行结果
调用上述函数后,我们成功生成了school.csv
文件,打开后可以看到清晰的数据:
学校名称,总分,全国排名,星级排名,办学层次
北京大学,100,1,8★,世界一流大学
清华大学,99.81,2,8★,世界一流大学
浙江大学,80.72,4,8★,世界一流大学
...
爬取的数据往往不完美,比如school.csv
中的 "总分" 列存在空值(部分学校未显示分数)。我们用pandas
库处理这些问题,常用 4 种方法:
直接删除含空值的行
如果空值较少,且删除后不影响整体分析,可以直接去掉包含空字段的行:
python运行
import pandas as pd
df = pd.read_csv("school.csv")
new_df = df.dropna() # 删除含空值的行
print(new_df.to_string())
python运行
df = pd.read_csv("school.csv")
df.fillna("暂无分数信息", inplace=True) # 替换空值
print(df.to_string())
用均值填充空值如果 "总分" 是数值型数据,可用列的平均值(均值)填补空值,让数据更符合整体趋势:
python运行
df = pd.read_csv("school.csv")
x = df["总分"].mean() # 计算总分的均值
df["总分"].fillna(x, inplace=True) # 用均值替换空值
print(df.to_string())
用中位数填充空值均值容易受极端值影响(比如某所学校总分特别高),此时可用中位数(将数据排序后中间的数值)更稳健地填补空值:
python运行
df = pd.read_csv("school.csv")
x = df["总分"].median() # 计算总分的中位数
df["总分"].fillna(x, inplace=True) # 用中位数替换空值
print(df.to_string())
1. 柱形图:对比不同星级学校的数量
星级排名反映了学校的综合实力,我们用柱形图展示各星级学校的数量:
- 8 星学校:8 所
- 7 星学校:16 所
- 6 星学校:36 所
- 5 星学校:59 所
- 4 星学校:103 所
- 3 星学校:190 所
- 2 星学校:148 所
- 1 星学校:260 所
代码实现(用matplotlib
库):
python运行
import matplotlib.pyplot as plt
import numpy as np
# 数据
x = np.array(["8星", "7星", "6星", "5星", "4星", "3星", "2星", "1星"])
y = np.array([8, 16, 36, 59, 103, 190, 148, 260])
# 绘图
plt.title("不同星级的学校个数")
plt.rcParams["font.sans-serif"] = ["SimHei"] # 解决中文显示问题
plt.bar(x, y) # 垂直柱形图(plt.barh(x,y)为水平柱形图)
plt.show()
1 星和 3 星学校数量最多,8 星学校最少,符合 "顶尖学校稀缺" 的常识。
2. 饼图:展示各星级学校的占比
如果想知道各星级学校在总量中的占比(如 8 星学校约占 1%,1 星学校约占 31.7%),饼图是更好的选择:代码实现:
python运行
import matplotlib.pyplot as plt
import numpy as np
# 占比数据(单位:%)
y = np.array([1, 2, 4.5, 7.2, 12.5, 23.1, 18, 31.7])
labels = ["8星", "7星", "6星", "5星", "4星", "3星", "2星", "1星"]
# 绘图
plt.pie(y, labels=labels) # 饼图
plt.title("不同星级的学校个数占比")
plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.show()
饼图直观呈现:1 星学校占比超过三成,3 星学校占比约两成,两者合计超过一半,反映了 "普通学校占多数" 的分布特征。今天的学习就到这里了,下期再见。