aytony

求古寻论,散虑逍遥。

0%

利用爬虫爬取历年学校的高考分数线

本学期的信息课大作业是数据管理与分析,我们组的课题是用爬虫爬取历年学校的高考分数线,并整理在 MariaDB 数据库里,简单写了一个前端,供用户访问。

链接:高考分数查询

网站选取

爬虫网站选取的是高考网 ,其中有2667所学校的历年高考成绩、录取批次、录取省份等信息,是一个较为合适的爬取目标。

其中的一个页面

数据库结构

数据库选取的是 MariaDB 10,MySQL 的一个类似数据库。MariaDB 下创建一个数据库名称 score_spider ,下面分招生省创建表格。

数据库结构

爬虫撰写

网页遍历

首先分析网站的 URL 结构,发现一个成绩页面的 URL 如下图所示:

“中国人民大学在北京地区综合改革录取分数线”页面URL

其中包含三个数字:2,1,10,其意义分别如下:

  • 第一个数字 \(2\) 代表学校编号,经二分后确认在范围 \([1, 2668]\) 范围内。
  • 第二个数字 \(1\) 代表招生省份,在 \([1, 39]\) 范围内,其中有若干无效项,去除后剩余34个有效项。
  • 第三个数字 \(10\) 代表高考分科,有理科、文科、综合改革等科目,范围在 \([1, 10]\)

在遍历每个页面时,可以通过遍历这三个数字对应的所有组合来遍历每个页面。

网页结构

典型的网页页面

观察网页源码,发现学校名称字段在全网页的第一个 <h2> 标签下,可以凭此方便地分析学校名称。同时,所有的数据都在一个 <table> 标签下,可以通过遍历 <table> 下的各个项来搜集数据。对于表格的每一行,需要的的数据是第一项(年份)、第二项(最低分数线)、第六项(录取批次),利用 Python 的库和功能可以方便地分析并提取信息。

信息提取

最终的每条数据包含招生省份、年份、学校、文理分科、录取批次、录取分数线六个字段,插入到对应招生省的表后剩余五个字段。下面是五个字段的提取方法:

  • 省份:预处理出对应的名称与 URL 编号的关系,直接使用。
  • 年份:分析网页表格中的信息。
  • 学校:分析网页中的学校名称字段。
  • 文理分科:提前预处理出对应科目与 URL 编号的关系。
  • 录取批次、录取分数线:分析网页表格中的信息。

另外注意,对于搜集到的无效字段应进行正确的判断与舍弃,否则可能会引起 Python 库报错。

数据存储

在分析好每个学校的数据后,爬虫代码会自动将处理并过滤好的信息插入到 MariaDB 数据库中。

运行效果

爬虫会将每个爬到的数据输出至控制台。

爬虫运行效果

前端网页设计

前端网页较为简单,只是利用 php 脚本访问 MariaDB 数据库并进行查询操作,再把查询结果打印在网页上。简单依照 哔哩哔哩的视频 写了一个美化页面的 CSS 样式,并利用 GET 方式进行查询,把结果放到无框表格中打印在网页上,以获得整洁美观性。

最终的前端效果

源码

Spider.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
import re
import requests
from bs4 import BeautifulSoup
import pymysql
import random

provinces = ["", "北京", "天津", "辽宁", "吉林", "黑龙江", "上海", "江苏", "浙江", "安徽", "福建", "山东", "湖北", "湖南", "广东", "重庆", "四川", "陕西", "甘肃", "河北", "山西", "内蒙古", "河南", "海南", "广西", "贵州", "云南", "西藏", "青海", "宁夏", "新疆", "江西", "", "香港", "", "", "", "", "澳门", "台湾"]

def insert_info(cursor, school, province, year, subj, level, score):
sql = "INSERT INTO " + province + "(年份, 学校, 文理分科, 录取批次, 录取分数线) VALUES(" + str(year) + ", '" + school + "', '" + subj + "', '" + level + "', " + str(score) + ")"
cursor.execute(sql)


for school in range(1, 2668):
#connect to the database
db = pymysql.connect(
host = "",
user = "",
passwd = "",
db = "score_spider",
port = 3307,
charset = "utf8mb4"
)
cursor = db.cursor()

# print the version of the MariaDB to validate the connection
cursor.execute("SELECT VERSION()")
print("Database version: " + str(cursor.fetchone()))

for location in range(1, 40):
if not provinces[location].isspace():
#get the name of the province
province_name = provinces[location]

for typ in range(1, 11):
page = requests.get('http://college.gaokao.com/school/tinfo/' + str(school) + '/result/' + str(location) + '/' + str(typ) + '/')
soup = BeautifulSoup(page.text, "html.parser")
tab = soup.find_all('tr')

# all table informations in variable "tab"
if(tab):
# get the title of the pages
title_str = soup.title.string

# get the name of the school
school_name = str(soup.find("h2").string)

# get the subject
subject_name = ""
if typ == 1:
subject_name = "理科"
elif typ == 2:
subject_name = "文科"
elif typ == 3:
subject_name = "综合"
elif typ == 4:
subject_name = "其他"
elif typ == 8:
subject_name = "艺术理"
elif typ == 9:
subject_name = "艺术文"
elif typ == 10:
subject_name = "综合改革"
else:
subject_name = "其他"

for item in tab:
# kick out the table headline
if item.find("th") != None:
continue

# get the year
year = item.contents[1].string
if not year or str(year).isspace() or str(year).find("-") != -1:
continue
year = int(year)

# get the score
score = item.contents[3].string
if not score or str(score).isspace() or str(score).find("-") != -1:
continue
score = int(score)

# get the level
level = item.contents[11].string
if not level or str(level).isspace() or str(level).find("-") != -1:
level = "不详"

print([school_name, province_name, subject_name, year, level, score])
insert_info(cursor, school_name, province_name, year, subject_name, level, score)

# commit the change of the school
db.commit()

# disconnect to MariaDB
db.close()

index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
<!DOCTYPE html>
<html>

<head>
<style>
</style>

<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="stylesheet" type="text/css" href="style.css" />
<link rel="icon" type="image/x-icon" href="images/icon.ico" />
<title>高考分数线查询</title>
</head>

<body>

<?php

// config
$server_name = "";
$port = 3307;
$username = "";
$password = "";
$database_name = "score_spider";

function create_database_if_not_exist($conn, $name)
{
if(!$conn->select_db($name))
{
$sql = "CREATE DATABASE " . $name;
if($conn->query($sql) === true)
{
echo "<p>Succeed creating database $name .</p>";
}
else
{
echo "<p>Failed creating database: " . $conn->error . "<p>";
}
}
}

function is_table_exist($conn, $dbname, $tablename)
{
$conn->select_db($dbname);
$sql = "SHOW TABLES LIKE '$tablename'";
$result = $conn->query($sql);
return mysqli_num_rows($result) != 0;
}

function create_table_if_not_exist($conn, $dbname, $tablename)
{
if(!is_table_exist($conn, $dbname, $tablename))
{
$sql = "CREATE TABLE $tablename (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
年份 INT NOT NULL,
学校 VARCHAR(255) NOT NULL,
文理分科 CHAR(31) NOT NULL,
录取批次 CHAR(31) NOT NULL,
录取分数线 SMALLINT NOT NULL
)";

if($conn->query($sql) == true)
{
echo "<p>Succeed creating table " . $tablename . " in database " . $dbname . ".</p>";
}
else
{
echo "<p>Failed creating database: " . $conn->error . ".</p>";
}
}
}

// function insert_info($conn, $dbname, $province, $year, $school, $score, $rank)
// {
// $conn->select_db($dbname);
// create_table_if_not_exist($conn, $dbname, $province);

// $sql = "INSERT INTO $province (年份, 学校, 一本分数, 一本排名)
// VALUES($year, '$school', $score, $rank)";

// if ($conn->query($sql) === TRUE)
// {
// echo "<p> A new info inserted. </p>";
// }
// else
// {
// echo "<p> Failed inserting info: " . $conn->error . "</p>";
// }
// }

function create_all_tables_if_not_exist($conn, $db_name)
{
create_table_if_not_exist($conn, $db_name, "河北");
create_table_if_not_exist($conn, $db_name, "山西");
create_table_if_not_exist($conn, $db_name, "辽宁");
create_table_if_not_exist($conn, $db_name, "吉林");
create_table_if_not_exist($conn, $db_name, "黑龙江");
create_table_if_not_exist($conn, $db_name, "江苏");
create_table_if_not_exist($conn, $db_name, "浙江");
create_table_if_not_exist($conn, $db_name, "安徽");
create_table_if_not_exist($conn, $db_name, "福建");
create_table_if_not_exist($conn, $db_name, "江西");
create_table_if_not_exist($conn, $db_name, "山东");
create_table_if_not_exist($conn, $db_name, "河南");
create_table_if_not_exist($conn, $db_name, "湖北");
create_table_if_not_exist($conn, $db_name, "湖南");
create_table_if_not_exist($conn, $db_name, "广东");
create_table_if_not_exist($conn, $db_name, "海南");
create_table_if_not_exist($conn, $db_name, "四川");
create_table_if_not_exist($conn, $db_name, "贵州");
create_table_if_not_exist($conn, $db_name, "云南");
create_table_if_not_exist($conn, $db_name, "陕西");
create_table_if_not_exist($conn, $db_name, "甘肃");
create_table_if_not_exist($conn, $db_name, "青海");
create_table_if_not_exist($conn, $db_name, "台湾");
create_table_if_not_exist($conn, $db_name, "内蒙古");
create_table_if_not_exist($conn, $db_name, "广西");
create_table_if_not_exist($conn, $db_name, "西藏");
create_table_if_not_exist($conn, $db_name, "宁夏");
create_table_if_not_exist($conn, $db_name, "新疆");
create_table_if_not_exist($conn, $db_name, "北京");
create_table_if_not_exist($conn, $db_name, "上海");
create_table_if_not_exist($conn, $db_name, "天津");
create_table_if_not_exist($conn, $db_name, "重庆");
create_table_if_not_exist($conn, $db_name, "香港");
create_table_if_not_exist($conn, $db_name, "澳门");
}

?>

<?php

function transform_input($data)
{
$data = trim($data);
$data = stripslashes($data);
$data = htmlspecialchars($data);
return $data;
}

function test_input($province, $year, $school)
{
if(!preg_match("/^[0-9]*$/", $year))
return false;
if(empty($province))
return false;
if(empty($school))
return false;
return true;
}

?>

<form class="box" action=<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?> method="get">
<h1> 分数查询 </h1>
<input type="text" name="province" placeholder="省份" />
<input type="text" name="year" placeholder="年份" />
<input type="text" name="school" placeholder="学校" />
<input type="submit" name="" value="查询" />
</form>

<div class="scrtxt">

<p>数据管理与分析课程作业报告:<a href="https://aytony.top/?p=588" target="_blank">利用爬虫爬取历年学校的高考分数线 – MindStation</a> </p>

<?php

if($_SERVER["REQUEST_METHOD"] == "GET")
{
// preparation - get the data
$province = transform_input($_GET["province"]);
$year = transform_input($_GET["year"]);
$school = transform_input($_GET["school"]);

// check is the text empty
if(test_input($province, $year, $school))
{
// initialize - create connection
$conn = new mysqli($server_name, $username, $password, "", $port);
if($conn->connect_error)
{
die("Error connection: " . $conn->connect_error);
}

// DEBUG ONLY: Create database and tables
// create_database_if_not_exist($conn, $database_name);
// create_all_tables_if_not_exist($conn, $database_name);

// initialize - set charset
$conn->query("set names 'utf8'");
$conn->query("set character_set_client=utf8");
$conn->query("set character_set_results=utf8");

// create_all_tables_if_not_exist($conn, $database_name);

// run - print the result
if(is_table_exist($conn, $database_name, $province))
{
$conn->select_db($database_name);
if(!empty($year))
$sql = "SELECT * FROM " . $province . " WHERE 年份=" . $year . " AND 学校 LIKE '%" . $school . "%' ORDER BY 录取分数线 DESC";
else
$sql = "SELECT * FROM " . $province . " WHERE 学校 LIKE '%" . $school . "%' ORDER BY 年份, 录取分数线 DESC";
$result = $conn->query($sql);

if (mysqli_num_rows($result) > 0)
{
echo "<table>";
echo "<tr><th>年份</th><th>学校</th><th>文理分科</th><th>录取批次</th><th>录取分数线</th></tr>";
while($row = mysqli_fetch_assoc($result))
{
echo "<tr><th>" . $row["年份"]. "</th><th>" . $row["学校"]. "</th><th>" . $row["文理分科"]. "</th><th>" . $row["录取批次"] . "</th><th>" . $row["录取分数线"] . "</th></tr>";
}
echo "</table>";
}
else
{
echo "无结果";
}
}
else
{
echo "省份 " . $province . " 不存在";
}
// cleanup - disconnect
$conn->close();
}
else
{
echo "请输入正确的省份(必填)、年份(选填)和学校(必填)";
}
}

?>
</div>

</body>

</html>

style.css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
body
{
margin: 0;
padding: 0;
font-family: sans-serif;
background: #66ccff;
}

.scrtxt
{
width: 600px;
padding: 40px;
position: absolute;
top: 750px;
left: 50%;
transform: translate(-50%, 0%);
background: #191919;
text-align: center;
color: white;
border-radius: 24px;
}

a:link
{
color: #ee99cc;
font-family: 宋体;
text-align: left;
text-decoration:underline;
}

a:visited
{
color: #ee99cc;
font-family: 宋体;
text-align: left;
text-decoration: underline;
}

.box
{
width: 300px;
padding: 40px;
position: absolute;
top: 400px;
left: 50%;
transform: translate(-50%, -50%);
background: #191919;
text-align: center;
border-radius: 24px;
}

.box h1
{
color: white;
font-weight: 500;
}

.box input[type = "text"]
{
border: 0;
background: none;
display: block;
margin: 20px auto;
text-align: center;
border: 2px solid #3498db;
padding: 14px 10px;
width: 200px;
outline: none;
color: white;
border-radius: 24px;
transition: 0.25s;
}

.box input[type = "text"]:focus
{
width: 280px;
border-color: #2ecc71;
}

.box input[type = "submit"]
{
border: 0;
background: none;
display: block;
margin: 20px auto;
text-align: center;
border: 2px solid #2ecc71;
padding: 14px 40px;
outline: none;
color: white;
border-radius: 24px;
transition: 0.25s;
cursor: pointer;
}

.box input[type = "submit"]:hover
{
background: #2ecc71;
}

2020.1.5 Update: 更新了新版 index.php 和 style.css。