作为一个正在学习爬虫的学生,怎么会不想着爬爬学校的教务系统呢。今天晚上欧洲经济史闲着无聊,做了一个爬学校OA的爬虫,整个代码达到如下目的:
- 登录学校OA系统。
- 爬取所有课程成绩并存储为excel文件。
- 一旦有新课程出分,发送带有成绩附件的邮件到邮箱。
学校的OA系统以及成绩单界面
我们首先来看看学校的OA系统登录界面。
登录结束进入个人主页。
点击研究生院进入如下页面。
点击其中的“查看在校成绩”,进入下面的页面就能看到每门课的成绩了。
废话不多说,下面一步步的说明整个爬虫的步骤。
完整代码需要导入的包。
- 导入Selenium包。这里面注意到专门引入了webdriver中的keys。主要作用是用来使用键盘回车键以及配合各种浏览器的快捷键。
- 导入numpy和pandas,将爬到的成绩数据转化成DataFrame并存成excel。这个地方导入这两个包,可能有人会觉得有点突兀。但是个人习惯对于Python的科学计算包更加熟悉。所以遇到数据问题一般都会用这两个包。
- 导入time模块。很多时候需要等待网页加载JS完成,另外最后设置每5分钟执行一次函数需要用到这个模块。
- 导入邮件模块,用于发送带有附件的邮件。
爬取成绩部分代码
接下来看成绩爬取代码。
- 打开OA系统登录页面,输入账号密码并登录。这里面需要注意的地方是,在输入学号之后,由于网页自身设计的原因不能直接定位到密码栏输入密码(坑爹的北大OA登录页=。=),这里面需要摁下键盘的回车键(也即Keys.RETURN)才能继续输入密码。
- 进入了OA个人主页后,再点击进入业务办理→研究生院这一页。
- 这时候利用快捷键直接新开了个tab,打开成绩单页。
- 利用正则表达式,提取出成绩页面的表头以及表内容,并转换为DataFrame格式。同时计算一下有多少门课的成绩出分了。后面判断是否有新出分就根据这个来判断。
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
|
# 使用Chromedriver
driver
=
webdriver
.
Chrome
(
)
# 打开portal登录页
driver
.
get
(
"https://portal.pku.edu.cn/portal2013/login.jsp"
)
# 输入账号密码
stu_id
=
driver
.
find_element_by_id
(
"user_name"
)
.
send_keys
(
"这里输入学号"
)
# 这个页面有毒,无法定位password,因此按下回车键再输入
stu_id1
=
driver
.
find_element_by_id
(
"user_name"
)
stu_id1
.
send_keys
(
(
Keys
.
RETURN
)
)
stu_pwd
=
driver
.
find_element_by_id
(
"password"
)
stu_pwd
.
send_keys
(
"这里输入密码"
)
# 记住我的账号,不过貌似这个小按钮基本没啥用
remember_me
=
driver
.
find_element_by_id
(
"remember_text"
)
.
click
(
)
# 登录
login_id
=
driver
.
find_element_by_id
(
"submit_button"
)
.
click
(
)
time
.
sleep
(
5
)
# 进入研究生院
driver
.
find_element_by_id
(
"button-1027-btnWrap"
)
.
click
(
)
time
.
sleep
(
2
)
driver
.
find_element_by_id
(
"menuitem-1051-iconEl"
)
.
click
(
)
# 打开新的tab,载入成绩单页面。
driver
.
find_element_by_tag_name
(
'body'
)
.
send_keys
(
Keys
.
COMMAND
+
'n'
)
driver
.
get
(
"https://portal.pku.edu.cn/portal2013/bizcenter/sgims/redirectToSGIMSO.do?urlRoot=yjxjTeaching&modId=yjxjcjcxYJS"
)
time
.
sleep
(
2
)
# 得到js加载完的网页代码
html
=
driver
.
page_source
print
(
html
)
# .execute_script("return document.getElementsByTagName('html')[0].innerHTML")
# 用简单的正则得到成绩数据
import
re
pattern_head
=
re
.
compile
(
"</a>(.*?)<img"
)
item_head
=
re
.
findall
(
pattern_head
,
html
)
pattern_body
=
re
.
compile
(
"\"on\">(.*?)</div>"
)
item_content
=
re
.
findall
(
pattern_body
,
html
)
# 将得到的结果输出excel
n_col
=
int
(
len
(
item_head
)
)
# 每门课多少数据
n_row
=
int
(
len
(
item_content
)
/
len
(
item_head
)
)
# 多少门课
grade_excel
=
pd
.
DataFrame
(
data
=
np
.
zeros
(
[
n_row
,
n_col
]
)
,
columns
=
item_head
)
for
i
in
range
(
n_row
)
:
grade_excel
.
ix
[
i
]
=
item_content
[
i
*
21
:
(
i
+
1
)
*
21
]
# 计算已经出分的门数
num_existed_grades
=
sum
(
grade_excel
[
'成绩'
]
!=
'\xa0'
)
driver
.
close
(
)
|
利用Python发邮件。
- 确定发送、收取邮箱。输入发送邮箱代码。
- 定义好msg对象,由于有附件,这里面定义了Multipart对象。
- 往msg对象里面填东西。并利用attach方法添加邮件内容“附件里是您的成绩单,请查收。”
- 定义附件为part,往里面填附件地址并加入到msg对象中区。
- 设置服务器、接口并发送。
打包封装代码
将上面两端代码封装成函数,每5分钟执行一次。下面的这段代码含义为,每分钟执行一次代码,一旦出了新成绩,将成绩存成excel文件并发送邮件。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
start_time
=
time
.
time
(
)
interval
=
300
# 300秒运行一次代码
# 循环多少次,设置成10000次
loop_num
=
10000
num_box
=
np
.
zeros
(
loop_num
)
for
i
in
range
(
loop_num
)
:
time
.
sleep
(
start_time
+
i
*
interval
+
1
-
time
.
time
(
)
)
num
,
grade_excel
=
get_grades
(
)
if
(
i
>
0
)
&
(
num_box
[
i
]
>
num_box
[
i
-
1
]
)
:
writer
=
pd
.
ExcelWriter
(
'My_grade.xlsx'
)
pd
.
DataFrame
(
grade_excel
)
.
to_excel
(
writer
,
'My_grade'
)
writer
.
save
(
)
send_grades
(
)
|
完整代码
完整的代码如下。需要注意要将学号、密码改为自己的学号和密码。发送邮箱、发送邮箱密码改为自己的邮箱和密码。收件邮箱改为自己的邮箱。
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
|
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# 导入Selenium的webdriver
from
selenium
import
webdriver
# 用来使用键盘、快捷键
from
selenium
.
webdriver
.
common
.
keys
import
Keys
# 导入numpy 和pandas模块
# 将爬取的数据转化成DataFrame并存成excel文件
import
pandas
as
pd
import
numpy
as
np
#导入time模块,代码中多次用到。
import
time
# 导入邮件模块
import
smtplib
from
email.mime
.
multipart
import
MIMEMultipart
from
email.mime
.
base
import
MIMEBase
from
email.mime
.
text
import
MIMEText
from
email.utils
import
formatdate
from
email
import
encoders
####################爬取成绩####################
def
get_grades
(
)
:
# 使用Chromedriver
driver
=
webdriver
.
Chrome
(
)
# 打开portal登录页
driver
.
get
(
"https://portal.pku.edu.cn/portal2013/login.jsp"
)
# 输入账号密码
stu_id
=
driver
.
find_element_by_id
(
"user_name"
)
.
send_keys
(
"这里输入学号"
)
# 这个页面有毒,无法定位password,因此按下回车键再输入
stu_id1
=
driver
.
find_element_by_id
(
"user_name"
)
stu_id1
.
send_keys
(
(
Keys
.
RETURN
)
)
stu_pwd
=
driver
.
find_element_by_id
(
"password"
)
stu_pwd
.
send_keys
(
"这里输入密码"
)
# 记住我的账号,不过貌似这个小按钮基本没啥用
remember_me
=
driver
.
find_element_by_id
(
"remember_text"
)
.
click
(
)
# 登录
login_id
=
driver
.
find_element_by_id
(
"submit_button"
)
.
click
(
)
time
.
sleep
(
5
)
# 进入研究生院
driver
.
find_element_by_id
(
"button-1027-btnWrap"
)
.
click
(
)
time
.
sleep
(
2
)
driver
.
find_element_by_id
(
"menuitem-1051-iconEl"
)
.
click
(
)
# 打开新的tab,载入成绩单页面。
driver
.
find_element_by_tag_name
(
'body'
)
.
send_keys
(
Keys
.
COMMAND
+
'n'
)
driver
.
get
(
"https://portal.pku.edu.cn/portal2013/bizcenter/sgims/redirectToSGIMSO.do?urlRoot=yjxjTeaching&modId=yjxjcjcxYJS"
)
time
.
sleep
(
2
)
# 得到js加载完的网页代码
html
=
driver
.
page_source
print
(
html
)
# .execute_script("return document.getElementsByTagName('html')[0].innerHTML")
# 用简单的正则得到成绩数据
import
re
pattern_head
=
re
.
compile
(
"</a>(.*?)<img"
)
item_head
=
re
.
findall
(
pattern_head
,
html
)
pattern_body
=
re
.
compile
(
"\"on\">(.*?)</div>"
)
item_content
=
re
.
findall
(
pattern_body
,
html
)
# 将得到的结果输出excel
n_col
=
int
(
len
(
item_head
)
)
# 每门课多少数据
n_row
=
int
(
len
(
item_content
)
/
len
(
item_head
)
)
# 多少门课
grade_excel
=
pd
.
DataFrame
(
data
=
np
.
zeros
(
[
n_row
,
n_col
]
)
,
columns
=
item_head
)
for
i
in
range
(
n_row
)
:
grade_excel
.
ix
[
i
]
=
item_content
[
i
*
21
:
(
i
+
1
)
*
21
]
# 计算已经出分的门数
num_existed_grades
=
sum
(
grade_excel
[
'成绩'
]
!=
'\xa0'
)
driver
.
close
(
)
return
num_existed_grades
,
grade_excel
####################发送邮件####################
def
send_grades
(
)
:
send_from
=
"10512076@qq.com"
send_to
=
"10512076@qq.com"
pswd
=
"这里输入发送邮箱密码"
msg
=
MIMEMultipart
(
)
msg
[
'From'
]
=
send_from
msg
[
'To'
]
=
send_to
msg
[
'Date'
]
=
formatdate
(
localtime
=
True
)
msg
[
'Subject'
]
=
"您有新成绩出来啦"
msg
.
attach
(
MIMEText
(
"附件里是您的成绩单,请查收。"
)
)
part
=
MIMEBase
(
'application'
,
"octet-stream"
)
part
.
set_payload
(
open
(
"My_grade.xlsx"
,
"rb"
)
.
read
(
)
)
encoders
.
encode_base64
(
part
)
part
.
add_header
(
'Content-Disposition'
,
'attachment; filename="My_grade.xlsx"'
)
msg
.
attach
(
part
)
smtp
=
smtplib
.
SMTP
(
"smtp.qq.com"
,
timeout
=
30
)
# 连接smtp邮件服务器,端口默认是25
smtp
.
login
(
send_from
,
pswd
)
smtp
.
sendmail
(
send_from
,
send_to
,
msg
.
as_string
(
)
)
smtp
.
quit
(
)
start_time
=
time
.
time
(
)
interval
=
300
# 300秒运行一次代码
# 循环多少次,设置成10000次
loop_num
=
10000
num_box
=
np
.
zeros
(
loop_num
)
for
i
in
range
(
loop_num
)
:
time
.
sleep
(
start_time
+
i
*
interval
+
1
-
time
.
time
(
)
)
num
,
grade_excel
=
get_grades
(
)
if
(
i
>
0
)
&
(
num_box
[
i
]
>
num_box
[
i
-
1
]
)
:
writer
=
pd
.
ExcelWriter
(
'My_grade.xlsx'
)
pd
.
DataFrame
(
grade_excel
)
.
to_excel
(
writer
,
'My_grade'
)
writer
.
save
(
)
send_grades
(
)
|
运行结果截图