0. 背景说明
对于人大某些卷王(?不是我)而言,第一时间得知自己的期末成绩是相当有必要的。针对该需求,本人搭建了一个可移植的、基于phantomjs、pytesseract与smtplib的微人大成绩监测系统,让大家在冲浪电竞的时候也能让电脑在后台监测自己的成绩单是否更新,并在邮箱中查看已经更新的成绩单,免去了登录网站-输入密码-切换浏览视图等繁琐的步骤。
我本来打算在2020年初收工这个项目,结果因为疫情暴发,遇到了些挫折,没有动力做……转眼一年过去了,结果微人大成绩查询网站暂停维护了,转而迁移到了一个“花里胡哨”的网站,我不得不分析网站架构,并重新设计了爬取的逻辑。
1. 环境配置
本人的系统为MacOS,如果您想按照本博客搭建一个属于你自己的监测系统,则必备的环境配置如下:
1-1. phantomjs配置
phantomjs是一个无界面的浏览器。包含界面的浏览器有诸如firefox-webdriver、chrome-webdriver,但每次运行脚本时都会自动打开浏览器,十分烦人,就像你的电脑被黑客攻击了一样,执行一些莫名其妙的操作,你就无法专心地处理自己的事务了。因此我推荐phantomjs这个开源工具。
首先需要在官网下载这个工具。直接下载需要翻墙,如果您没有机场,我为您提供了百度网盘的链接:
链接: https://pan.baidu.com/s/1YBZLS_WmJW3EA3cSYmhbrg 密码: aee7
下载速度虽然慢了些,不过大小只有十几M,几分钟就能下载完毕。(如果过期了,请私聊我)
然后我将文件夹放在用户Walden的文件夹底下。(本人的英文名为Walden)
这里有一个小细节,就是需要指定selenium的版本号为2.48.0(降级下载),否则爬取时还会报错。
1-2. pytesseract配置
用pip命令下载pytesseract第三方库,最好提前配置好豆瓣源,用过的都说好!
如图所示:
在运行时,可能会遇到诸如环境变量设置的问题,这里我推荐一篇博客,照着修改就行了。
https://blog.csdn.net/weixin_44010678/article/details/107818994?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-1.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-1.control
简而言之,就是将上述图片中的地址打开,然后将其中的py文件内的路径改为图片中的路径即可。
1-3. smtp配置
调用该库,我们便可以让程序在后台给自己发送邮件了。
发送邮件需要用程序进程登录邮箱,此处需要一些基本的授权码。
以QQ邮箱为例:
打开网页顶端设置界面,进入邮箱设置-账户,下拉找到以下窗口:
开启SMTP服务,然后生成授权码,这样就可以用smtplib的login方法登录你的邮箱了。
2. 流程说明
首先初始化一个phantomjs类browser,设置其爬取的网站url为成绩单网页的url;
然后读取爬取的源代码,切换到其中的一个frame以输入个人信息;
用browser找到username、password、code等输入的位置,填入对应的信息;
其中,code对应的验证码,需要调用browser.save_screenshot方法将验证码剪切下来(当然也可以跳转到二级界面下载验证码,就是麻烦了些),然后用pytesseract读取生成字符串,经过预处理字符串后填入对应的位置;
用browser模拟登录,跳转到成绩单界面;
解析网页源代码,找到“各学期汇总”一栏,将字符串预处理后,得到总学分、学分绩、平均绩点等关键信息;
与内存中的Str_back_up比较,如果有变化,说明成绩单更新了,用smtp将包含该字符串的邮件发送到个人邮箱,并更新Str_back_up,进程挂起,过一段时间后重复全流程。如果成绩单没更新,则同样挂起相同的时间,然后重复全流程。
注意,pytesseract的验证码识别准确率并非100%(做过ocr的同好们都知道需要有庞大的数据集和各种各样的神经网络算法如CNN+BiLSTM+CTC/CTPN才能提高准确率),因此如果验证码识别失败,自然读不到“各学期汇总”这一栏,此时程序立即重新开始全流程。一般情况下,2~3次循环就可以成功进入成绩单界面了。
3. 项目演示
在终端,切换到程序所在的目录,然后sudo python code.py即可。运行结果如下:
4. 源代码
from selenium import webdriver
import time
import cv2
from PIL import Image
import pytesseract
import urllib.request
import smtplib
from email.mime.text import MIMEText
from email.header import Header
Str_back_up = ""
smtp_server='smtp.qq.com'#固定写死
smtp_port=465#固定端口
url = 'http://jw.ruc.edu.cn/Njw2017/index.html#/student/course-score-search/'
# phantomJS路径
path = '/Users/walden/phantomjs/bin/phantomjs'
from_addr='<sending_email>' #邮件发送账号
to_addrs='<receiving_email>' #接收邮件账号
qqCode='<authorization_code>' #授权码(这个要填自己获取到的)
from_Header = "微人大成绩更新提示系统"#发件人
to_Header = "<receiving_name>"#收件人
myMessage = "<sending_content>"#发送的内容
mail_Header = "您的微人大成绩单已更新"#邮件标题
user_name = '<user_name>'#微人大学号
user_password = '<user_password>'#微人大密码
waiting_time = 300#重启任务的时间
while(True):
print('当前时间:',time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time())))
print("任务启动...")
browser = webdriver.PhantomJS(path,service_args=['--ignore-ssl-errors=true', '--ssl-protocol=TLSv1'])
browser.get(url)
time.sleep(3)
browser.save_screenshot('./跳转到url的网页截图.png')
#切换到iframe,才能展开登录界面
browser.switch_to_frame("login-iframe")
print("成功切换至iframe...")
time.sleep(3)
search = browser.find_element_by_name('username')
search.send_keys(user_name)
print('提交账号...')
search = browser.find_element_by_name('password')
search.send_keys(user_password)
print('提交密码...')
browser.save_screenshot('./welcome.png')
img = cv2.imread('./welcome.png')
img = img[301:341, 217:316]
print('保存验证码到本地...')
cv2.imwrite("./验证码.png",img)
print('读取验证码图片...')
image = Image.open('./验证码.png')
content = pytesseract.image_to_string(image) # 解析图片
content = content.replace(' ','')
content = content.replace('\n','')
content = content.replace('\r', '')
print('验证码为:',content)
# 在搜索框输入内容
search = browser.find_element_by_name('code')#输入验证码
search.send_keys(content)
print('提交验证码...')
print("信息输入成功,准备跳转...")
browser.save_screenshot('./before.png')
print('将填充后的网页截图保存到本地...')
browser.find_element_by_id('login-submit').click()#提交跳转
print('提交表单,请等待3s...')
time.sleep(3)
browser.save_screenshot('./after.png')
print('将跳转后的网页截图保存到本地...')
browser.get(url)
time.sleep(3)
browser.save_screenshot('./跳转到成绩单的网页截图.png')
index = browser.page_source.find("各学期汇总:")
Str_new = browser.page_source[index:index+105]
Str_new = Str_new.replace(' ','')
Str_new = Str_new.replace('\n','')
Str_new = Str_new.replace('\r', '')
Str_new = Str_new.replace(' ','')
Str_new = Str_new.replace('</td>','')
Str_new = Str_new.replace('<td>','')
Str_new = Str_new.replace('<','')
Str_new = Str_new.replace('/','')
Str_new = Str_new.replace('t','')
Str_new = Str_new.replace('\\','')
Str_new = Str_new.replace('r','')
Str_new = Str_new.replace('>','')
if(Str_new == ''):
print("跳转至成绩单网页失败,正在重试...\n\n")
else:
print('跳转成功!')
print('查询结果:','>>>\033[0;30;47m',Str_new,'\033[0m<<<')
if(Str_back_up != Str_new):
print('成绩单更新!\n\n')
Str_back_up = Str_new
myMessage = Str_new
print("正在准备发送邮件...")
stmp = smtplib.SMTP_SSL(smtp_server, smtp_port)
stmp.login(from_addr, qqCode)
message = MIMEText(myMessage, 'plain', 'utf-8')
message['From'] = Header(from_Header, 'utf-8')
message['To'] = Header(to_Header, 'utf-8')
message['Subject'] = Header(mail_Header, 'utf-8')
try:
stmp.sendmail(from_addr, to_addrs, message.as_string())
except Exception as e:
print("邮件发送失败!请检查!")
print("邮件发送成功!")
else:
print('成绩单未更新!\n\n')
print('任务结束,'+str(waiting_time)+'秒后重启...')
time.sleep(waiting_time)
browser.quit()
写博客比较仓促,且代码没做优化,若有不足,多多包涵~