【Python 爬虫】监控教务网成绩 + 成绩邮件推送

本博文转自 2018 年个人的 GitHub 博客编写的文章:Python 3.6 监控爬取成绩并推送到邮箱(基于百度云接口的验证码识别)

简介

本程序模拟登录正方教务系统获取本人当前学期成绩(也可以手动改学期学年),20 分钟检测一次,当有新成绩公布时就发送到邮箱,本程序用到的是我学校的教务网网址。

实现思想

本程序的实现主要步骤有:

  1. 首先登录进入成绩查询页面
  2. 将对应的所有成绩爬取下来
  3. 将爬取下来的成绩进行格式化(表格)
  4. 美化后的成绩表格发送到邮箱

详细步骤

  1. 登录
    1. 得到跳转后页面的地址(为了防止登录重复,教务系统后面会跳转后在网址后面加一串随机的 hash code)
    2. 利用百度云识别验证码
  2. 将需要的列爬取下来
  3. 每次爬取后让程序暂停 20 分钟,将每次所爬取的成绩数量进行对比,如果与第一次爬取的数量相同则不发邮件,否则发送邮件

源代码

'''
大连大学成绩查询助手V1.7.190216
Code By ZC Liang
2018.6.6
Completed on 2019.7.16
'''

import getpass
import http.cookiejar
import os
import pickle
import platform
import random
import re
import smtplib
import subprocess
import sys
import time
import urllib.parse
import urllib.request
from email.mime.text import MIMEText
from email.utils import formataddr

import bs4
import numpy as np
import pandas as pd
import prettytable as pt
import pymysql
import requests
from aip import AipOcr
from bs4 import BeautifulSoup
from PIL import Image
from prettytable import PrettyTable
from requests import ReadTimeout
from requests import ConnectionError

my_sender = '发件人邮箱账号'    # 发件人邮箱账号
my_pass = '发件人邮箱密码'              # 发件人邮箱密码(当时申请smtp给的口令)

# my_user = '收件人邮箱账号'      # 收件人邮箱账号,我这边发送给自己
email_send_to = ''  # 收件人邮箱账号

DstDir = os.getcwd()
searchCount = 0  # 查询次数
count = 0  # 循环计数
scorenum = 0  # 成绩条数
score = []
scorenp = np.array(score)
makeup_course_num = 0  # 重修课程数目
makeup_course_flag = -1  # 重修课程数目下标
courseList = []  # 选课情况查询列表
required_course_num = 0  # 本学期必修课总数
chosen_course_num = 0   # 本学期选课总数

# 准备Cookie和opener,因为cookie存于opener中,所以以下所有网页操作全部要基于同一个opener
cookie = http.cookiejar.CookieJar()
opener = urllib.request.build_opener(
    urllib.request.HTTPCookieProcessor(cookie))

final_url = ""  # 头 + 随机编码 + default2.aspx
final_url_head = ""
url_head = "202.199.155." + str(random.randint(33, 37))  # 随机产生网址

ddlxn = ""
ddlxq = ""

""" 你的 APPID AK SK """
APP_ID = '你的 APP_ID'
API_KEY = '你的 API_KEY'
SECRET_KEY = '你的 SECRET_KEY'

client = AipOcr(APP_ID, API_KEY, SECRET_KEY)

""" 读取图片 """


def get_file_content(filePath):
    with open(filePath, 'rb') as fp:
        return fp.read()


def DBUtil():
    conn = pymysql.connect(host="localhost", port=3307, user="用户名", passwd="密码", db="grade_query_system",
                           charset="utf8")
    cur = conn.cursor()
    sql = "select * from student"
    try:
        cur.execute(sql)
        # 获取所有记录列表
        results = cur.fetchall()
        for row in results:
            sId = row[0]
            sName = row[1]
            sPwd = row[2]
            sEmail = row[3]
            # 打印结果
            print("sId=%s,sName=%s,sPwd=%s,sEmail=%s" % (sId, sName, sPwd, sEmail))
    except Exception as e:
        print(e)
    finally:
        conn.close()


# 判断操作系统类型


def getOpeningSystem():
    return platform.system()


# 判断是否联网


def isConnected():
    # userOs = getOpeningSystem()
    # if userOs == "Windows":
    #     subprocess.check_call(
    #         ["ping", "-n", "2", url_head], stdout=subprocess.PIPE)
    # else:
    #     subprocess.check_call(
    #         ["ping", "-c", "2", url_head], stdout=subprocess.PIPE)
    try:
        response = requests.get('http://' + url_head, timeout=1)
        if response.status_code == 200:
            return True
        else:
            print('网络检测错误status_code: ' + response.status_code, 'http://' + url_head)
            return False
    except (ConnectionError, ReadTimeout):
        print('无网络连接。')


# 获取重定向编码


def check_for_redirects(url):
    r = requests.head(url)
    if r.ok:
        return r.headers['location']
    else:
        return '[no redirect]'


#   图像转换并识别


def image_util(img):
    new_im = img.convert("RGB")  # 将验证码图片转换成24位图片
    new_im.save('./ScoreHelper/CheckCode1.jpg')  # 将24位图片保存到本地

    arr = np.array(Image.open('./ScoreHelper/CheckCode1.jpg').convert("L"))

    b = 255 - arr
    im = Image.fromarray(b.astype('uint8'))  # 翻转

    # d = 255 * (arr / 255) ** 2
    # im = Image.fromarray(d.astype('uint8'))  # 灰度

    #  此处验证过,翻转比灰度识别率更高
    im.save('./ScoreHelper/CheckCode2.jpg')


#   验证码识别
def code_recognition():
    try:
        #   调用百度云识别验证码
        result = client.basicAccurate(get_file_content('./ScoreHelper/CheckCode2.jpg'))
        word = result.get('words_result')
        res = ""
        if word == "":
            word = "0000"
        if len(word):
            res = re.findall('[a-zA-Z0-9]+', word[0].get('words'))[0]
        elif res != None and len(res) > 4:  # 教务系统所有的验证码都是四位的,若大于四位,则挑选前四位
            res = res[0:4]
        return res
    except Exception as e:
        print(e)


#   登录


def login():
    # 构造表单
    params = {
        'txtUserName': sid,
        'Textbox1': '',
        'Textbox2': spwd,
        'RadioButtonList1': '学生',
        'Button1': '',
        'lbLanguage': '',
        'hidPdrs': '',
        'hidsc': '',
    }

    #   获取验证码
    res = opener.open(final_url_head + '/checkcode.aspx').read()
    with open('./ScoreHelper/CheckCode.jpg', 'wb') as file:
        file.write(res)
    img = Image.open('./ScoreHelper/CheckCode.jpg')

    #   图片处理
    image_util(img)

    # img.show()

    print('验证码识别结果:' + code_recognition())
    vcode = code_recognition()

    # img.close()

    params['txtSecretCode'] = vcode

    #   获取ViewState
    response = urllib.request.urlopen('http://' + url_head + '/')
    html = response.read().decode('gb2312')
    viewstate = re.search(
        '<input type="hidden" name="__VIEWSTATE" value="(.+?)"', html)
    params['__VIEWSTATE'] = viewstate.group(1)
    #   尝试登录
    loginurl = final_url
    print("\n本次登录所用网址为:" + loginurl + "\n")
    data = urllib.parse.urlencode(params).encode('gb2312')
    response = opener.open(loginurl, data)
    if response.geturl() == final_url:
        print('登录失败,可能是姓名,学号,密码或验证码填写错误!')
        return False
    else:
        return True


#   获取本学期必修课数目


def get_RequiredCourse_num():
    global required_course_num

    print("正在查询本学期必修课数目...")
    #   构造url
    url = ''.join([
        final_url_head + '/xsxkqk.aspx',
        '?xh=',
        sid,
        '&xm=',
        urllib.parse.quote(sname),
        '&gnmkdm=N121615',
    ])
    #   构建查询学生选课情况表单
    params = {
        'ddlxn': ddlxn,
        'ddlxq': ddlxq,
    }

    #   构造Request对象,填入Header,防止302跳转,获取新的View_State
    req = urllib.request.Request(url)
    req.add_header('Referer', final_url)
    req.add_header('Origin', 'http://' + url_head + '/')
    req.add_header(
        'User-Agent',
        'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36')
    response = opener.open(req)
    html = response.read().decode('gb2312')
    viewstate = re.search(
        '<input type="hidden" name="__VIEWSTATE" value="(.+?)"', html)
    params['__VIEWSTATE'] = viewstate.group(1)

    #   查询所有成绩
    req = urllib.request.Request(
        url, urllib.parse.urlencode(params).encode('gb2312'))
    req.add_header('Referer', final_url)
    req.add_header('Origin', 'http://' + url_head + '/')
    response = opener.open(req)
    soup = BeautifulSoup(response.read().decode('gb2312'), 'html.parser')
    html = soup.find('table', class_='datelist')

    #   指定要输出的列,原网页的表格列下标从0开始
    #   用于标记是否是遍历第一行
    flag = True
    #   根据DOM解析所要数据,首位的each是NavigatableString对象,其余为Tag对象
    #   遍历行
    counter = 0
    for each in html:
        columnCounter = 0
        column = []

        if type(each) == bs4.element.NavigableString:
            pass
        else:
            #   遍历列
            for item in each.contents:
                if item != '\n':
                    if counter > 0 and columnCounter == 3:
                        courseList.append(str(item.contents[0]).strip())
                    columnCounter += 1
            if flag:
                flag = False
            counter += 1

    for each in courseList:
        if each == "必修课程":
            required_course_num += 1


#   获取成绩

#   该学期选课数目查询

def get_ChosenCourse_num():
    global chosen_course_num

    print("正在查询本学期必修课数目...")
    #   构造url
    url = ''.join([
        final_url_head + '/xsxkqk.aspx',
        '?xh=',
        sid,
        '&xm=',
        urllib.parse.quote(sname),
        '&gnmkdm=N121615',
    ])
    #   构建查询学生选课情况表单
    params = {
        'ddlxn': ddlxn,
        'ddlxq': ddlxq,
    }

    #   构造Request对象,填入Header,防止302跳转,获取新的View_State
    req = urllib.request.Request(url)
    req.add_header('Referer', final_url)
    req.add_header('Origin', 'http://' + url_head + '/')
    req.add_header(
        'User-Agent',
        'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36')
    response = opener.open(req)
    html = response.read().decode('gb2312')
    viewstate = re.search(
        '<input type="hidden" name="__VIEWSTATE" value="(.+?)"', html)
    params['__VIEWSTATE'] = viewstate.group(1)

    #   查询所有成绩
    req = urllib.request.Request(
        url, urllib.parse.urlencode(params).encode('gb2312'))
    req.add_header('Referer', final_url)
    req.add_header('Origin', 'http://' + url_head + '/')
    response = opener.open(req)
    soup = BeautifulSoup(response.read().decode('gb2312'), 'html.parser')
    html = soup.find('table', class_='datelist')

    #   指定要输出的列,原网页的表格列下标从0开始
    #   用于标记是否是遍历第一行
    flag = True
    #   根据DOM解析所要数据,首位的each是NavigatableString对象,其余为Tag对象
    #   遍历行
    counter = 0
    for each in html:
        columnCounter = 0
        column = []

        if type(each) == bs4.element.NavigableString:
            pass
        else:
            #   遍历列
            for item in each.contents:
                if item != '\n':
                    if counter > 0 and columnCounter == 3:
                        courseList.append(str(item.contents[0]).strip())
                    columnCounter += 1
            if flag:
                flag = False
            counter += 1

    for each in courseList:
        chosen_course_num += 1


def getScore():
    global searchCount
    global scorenum
    global scorenp
    global ddlxn
    global ddlxq
    score = []

    #   构造url
    url = ''.join([
        final_url_head + '/xscjcx_dq.aspx',
        '?xh=',
        sid,
        '&xm=',
        urllib.parse.quote(sname),
        '&gnmkdm=N121605',
    ])
    #   构建查询全部成绩表单
    params = {
        'ddlxn': ddlxn,  # 全部为 %C8%AB%B2%BF
        'ddlxq': ddlxq,
        'btnCx': '查询',
    }

    #   构造Request对象,填入Header,防止302跳转,获取新的View_State
    req = urllib.request.Request(url)
    req.add_header('Referer', final_url)
    req.add_header('Origin', 'http://' + url_head + '/')
    req.add_header(
        'User-Agent',
        'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.81 Safari/537.36')
    response = opener.open(req)
    html = response.read().decode('gb2312')
    viewstate = re.search(
        '<input type="hidden" name="__VIEWSTATE" value="(.+?)"', html)
    params['__VIEWSTATE'] = viewstate.group(1)

    #   查询所有成绩
    req = urllib.request.Request(
        url, urllib.parse.urlencode(params).encode('gb2312'))
    req.add_header('Referer', final_url)
    req.add_header('Origin', 'http://' + url_head + '/')
    response = opener.open(req)
    soup = BeautifulSoup(response.read().decode('gb2312'), 'html.parser')
    html = soup.find('table', class_='datelist')

    print("执行第" + str(searchCount) + "次查询:")
    print('你的所有成绩如下:')

    #   指定要输出的列,原网页的表格列下标从0开始
    outColumn = [3, 4, 6, 7, 9, 11, 13]
    #   用于标记是否是遍历第一行
    flag = True
    #   根据DOM解析所要数据,首位的each是NavigatableString对象,其余为Tag对象
    #   遍历行
    for each in html:
        columnCounter = 0
        column = []

        if type(each) == bs4.element.NavigableString:
            pass
        else:
            #   遍历列
            for item in each.contents:
                if item != '\n':
                    if columnCounter in outColumn:
                        #   要使用str转换,不然陷入copy与deepcopy的无限递归
                        column.append(str(item.contents[0]).strip())
                    columnCounter += 1
            if flag:
                table = PrettyTable(column)
                flag = False
            else:
                table.add_row(column)
            score.extend([column])
    searchCount += 1
    scorenp = np.array(score)
    #   table.set_style(pt.PLAIN_COLUMNS)

    print(table)
    print("分条统计:")
    scorenum = sendScore(table)
    print("成绩数目: " + str(scorenum) + "条")


def sendScore(table):
    global scorenum
    global count
    global email_send_to
    global scorenp
    for i in table:
        print(i.get_string())
        count += 1

    if count > scorenum:
        try:
            scorenum = count

            saveScoreNum()

            # 文本模式
            # context = i.get_string().replace("+"," ")
            # context = context.replace("-"," ")
            # context = context.replace("2017 2018","2017-2018")
            # if(scorenum == 1):
            #     msg=MIMEText("有成绩下来了:" + context,'plain','utf-8')
            # else:
            #     msg=MIMEText("又有成绩下来了:" + context,'plain','utf-8')
            # msg = prettyScore()

            # html格式
            msg = prettyScore()

            # 括号里的对应发件人邮箱昵称、发件人邮箱账号
            msg['From'] = formataddr(["1115810371@qq.com", my_sender])
            # 括号里的对应收件人邮箱昵称、收件人邮箱账号
            msg['To'] = formataddr([email_send_to, email_send_to])

            if count == required_course_num:
                msg['Subject'] = "第" + str(count) + "次成绩推送加平均绩点"
            else:
                msg['Subject'] = "第" + str(count) + "次成绩推送"  # 邮件的主题,也可以说是标题

            # 发件人邮箱中的SMTP服务器,端口是465
            server = smtplib.SMTP_SSL("smtp.qq.com", 465)
            server.login(my_sender, my_pass)  # 括号中对应的是发件人邮箱账号、邮箱密码
            # 括号中对应的是发件人邮箱账号、收件人邮箱账号、发送邮件
            server.sendmail(my_sender, [email_send_to, ], msg.as_string())
            server.quit()  # 关闭连接
            print("发送成功,请注意在此邮箱查收:" + email_send_to)

        except Exception as e:
            print(e)
            print("发送失败!")
    count = 0
    if scorenum != required_course_num:
        print("程序休息中...(按'Ctrl C'结束)")
        # time.sleep(1200)  # 二十分钟查一次
        time.sleep(300)  # 5分钟查一次
    return scorenum


#   接收构造成功的表格
def prettyScore():
    global scorenp
    try:
        # context = MIMEText(html,_subtype='html',_charset='utf-8')  #解决乱码
        msg = MIMEText(str(htmlText(scorenum)), "html", "gb2312")
    except Exception as e:
        print(e)
    return msg


#   构造邮件内容:成绩表格
def htmlText(scorenum):
    global required_course_num

    html = """

                        <table color="CCCC33" width="800" border="1" cellspacing="0" cellpadding="5" text-align="center">

                                <tr>

                                        <td>课程名称</td>

                                        <td>课程性质</td>

                                        <td>学分</td>

                                        <td>平时成绩</td>

                                        <td>期末成绩</td>

                                        <td>成绩</td>

                                </tr>   


                    """ + addtrs(scorenum) + """
                        </table>
                    """

    #   最后一次推送时计算GPA并与成绩表格一起推送
    if scorenum == required_course_num:
        html += """
                        <br/>
                        <div class='gpa_text' style='font-size: 25px;font-style: italic;'>-->平均绩点:%s <--</div>
                    """ % (getGPA()) + """
                        <br/>
                        <div class='end_words' style='font-size: 20px;'>本学期考试成绩查询完成!</div>
                    """
    return html


#   在发送的表格里添加成绩行
def addtrs(scorenum):
    global scorenp
    i = 1
    array = []
    while i <= scorenum:
        trs = '''
                                    <tr>   

                                            <td>%s </td>

                                            <td>%s </td>

                                            <td>%s </td>

                                            <td>%s </td>

                                            <td>%s </td>

                        ''' % (scorenp[i][0], scorenp[i][1], scorenp[i][2], scorenp[i][3], scorenp[i][4])
        if (scorenp[i][5].isalpha() and scorenp[i][5] == "A") or (scorenp[i][5].isdigit() and int(scorenp[i][5]) >= 90):
            #   等级A和90以上的成绩标记为绿色
            trs += '<td style="color:springgreen;">'

        elif (scorenp[i][5].isalpha() and scorenp[i][5] == "F") or (
                scorenp[i][5].isdigit() and int(scorenp[i][5]) < 60):
            #   不及格的成绩标记为红色
            trs += '<td style="color:red;">'

        else:
            #   普通成绩不标记
            trs += '<td>'

        trs += '''
                            %s </td>

                    </tr>
        ''' % (scorenp[i][5])
        array.append(trs)
        i += 1
    s = ""
    for x in array:
        s += str(x)
    return s


#   计算GPA
def getGPA():
    global scorenp
    global scorenum
    global makeup_course_num
    global makeup_course_flag

    sc = []
    GPAlist = []
    i = 1
    j = 0
    coursenum = 0

    while i <= scorenum:
        if scorenp[i][1] != "必修课程" or scorenp[i][6] == "是":
            #   排除非必修课以及重修课
            makeup_course_num += 1
            i += 1
            continue
        else:
            #   有些成绩是等级,需要转换为数字
            if scorenp[i][5].isalpha() and scorenp[i][5] != "F":
                sc.append(745 - 10 * ord(scorenp[i][5]))  # 算式:x - (x - A) + 10 * (D - x) 即 745 - 10 * x
            elif scorenp[i][5] == "F":
                sc.append(0)
            else:
                sc.append(int(scorenp[i][5]))

            if int(sc[j]) < 60:
                #   不及格的科目绩点为0
                GPAlist.append(0)
            else:
                #   计算单科绩点
                GPAlist.append((int(sc[j]) - 50) / 10 * float(scorenp[i][2]))
            i += 1
            j += 1
            coursenum += 1

    i = 1
    j = 0
    sum = 0
    scoresum = 0

    while i <= scorenum:
        if scorenp[i][1] != "必修课程" or scorenp[i][6] == "是":
            i += 1
            continue
        sum += GPAlist[j]
        scoresum += float(scorenp[i][2])
        j += 1
        i += 1
    GPA = sum / scoresum
    print("平均绩点:" + str(GPA))
    return GPA


#   根据当前日期设置查询学期
def setSemester():
    global ddlxn
    global ddlxq

    try:
        localtime = time.localtime(time.time())  # 获取当前日期

        #   第一学期是从当年9月到次年2月,第二学期则是从当年3月到8月
        if (int((localtime.tm_mon) >= 9 and int(localtime.tm_mon) <= 12) or (
                int(localtime.tm_mon) >= 1 and int(localtime.tm_mon) <= 2)):
            # if (str(localtime.tm_year) == "2020" and int((localtime.tm_mon) >= 7)):
            #     print("您已毕业,无须监控成绩!")
            #     sys.exit(0)
            if (int(localtime.tm_mon) >= 1 and int(localtime.tm_mon) <= 2):
                ddlxn = str(localtime.tm_year - 1) + '-' + str(int(localtime.tm_year))
            else:
                ddlxn = str(localtime.tm_year) + '-' + str(int(localtime.tm_year) + 1)
            ddlxq = '1'
        else:
            ddlxn = str(int(localtime.tm_year) - 1) + '-' + str(localtime.tm_year)
            ddlxq = '2'

    except Exception as e:
        print(e)


#   保存上次查询的成绩数目
def saveScoreNum():
    global scorenum
    try:
        with open(r'./ScoreHelper/scoreinfo.bin', 'w') as file_object:
            file_object.write(str(scorenum))
            file_object.close()
    except Exception as e:
        print(e)


#   获取保存的上次查询的成绩数目
def getScoreNum():
    global scorenum
    try:
        if os.path.isfile(r"./ScoreHelper/scoreinfo.bin"):
            openFile = open('./ScoreHelper/scoreinfo.bin', 'r')
            num = openFile.readline()
            openFile.close()
            scorenum = int(num)
            return num
        else:
            with open(r'./ScoreHelper/scoreinfo.bin', 'w') as file_object:
                file_object.write("0")
                file_object.close()
                scorenum = 0
    except Exception as e:
        print(e)


if __name__ == '__main__':
    setSemester()
    getScoreNum()
    try:
        searchCount = 1
        print('欢迎使用大连大学成绩查询助手!')
        print('正在检查网络...')
        if isConnected():
            with open(r'./ScoreHelper/uinfo.bin', 'rb') as file:
                udick = pickle.load(file)
                sname = udick['sname']
                sid = udick['sid']
                spwd = udick['spwd']
                email_send_to = udick['email_send_to']

            #   构造登录地址
            final_url = 'http://' + url_head + \
                        check_for_redirects('http://' + url_head + '/default2.aspx')
            final_url_head = final_url[0:48]

            loginCount = 0
            while not login():
                if loginCount > 3:
                    #   超过三次未登录自动更换网址
                    url_head = "202.199.155." + str(random.randint(33, 37))
                    final_url = 'http://' + url_head + \
                                check_for_redirects('http://' + url_head + '/default2.aspx')
                    final_url_head = final_url[0:48]
                    loginCount = 0
                loginCount += 1
                print("正在等待重试...")
                time.sleep(3)
                continue

            get_ChosenCourse_num()
            getScore()
            counter = 0
            while scorenum <= required_course_num:
                counter += 1
                if scorenum == required_course_num:
                    print("本学期成绩查询完成!")
                    break
                if counter > 0:
                    getScore()
    except FileNotFoundError:
        # if os.path.exists(r'' + DstDir + '\\ScoreHelper'):
        #     os.remove(r'' + DstDir + '\\ScoreHelper')
        os.mkdir(r'./ScoreHelper/ScoreHelper')  # 注:针对Windows目录结构
        print('这是你第一次使用,请按提示输入信息,以后可不必再次输入~')
        sid = input('请输入学号:')
        sname = input('请输入姓名:')
        # 隐藏密码
        # spwd = getpass.getpass('请输入密码:')
        spwd = input('请输入密码:')
        email_send_to = input('请输入要将成绩发送到的邮箱地址:')
        udick = {'sname': sname, 'sid': sid,
                 'spwd': spwd, 'email_send_to': email_send_to}
        file = open(r'./ScoreHelper/uinfo.bin', 'wb')
        pickle.dump(udick, file)
        file.close()
        final_url = 'http://' + url_head + \
                    check_for_redirects('http://' + url_head + '/default2.aspx')
        final_url_head = final_url[0:48]

        #   登录失败,重试
        while not login():
            sname = input('请输入姓名:')
            sid = input('请输入学号:')
            # spwd = getpass.getpass('请输入密码:')
            spwd = input('请输入密码:')
            email_send_to = input('请输入要将成绩发送到的邮箱地址:')
            udick = {'sname': sname, 'sid': sid,
                     'spwd': spwd, 'email_send_to': email_send_to}
            file = open(r'./ScoreHelper/uinfo.bin', 'wb')
            pickle.dump(udick, file)
            file.close()
            final_url = 'http://' + url_head + \
                        check_for_redirects('http://' + url_head + '/default2.aspx')
            final_url_head = final_url[0:48]
        get_ChosenCourse_num()
        getScore()
        counter = 0
        while scorenum <= required_course_num:
            counter += 1
            if scorenum == required_course_num:
                print("本学期成绩查询完成!")
                break
            if counter > 0:
                getScore()
            print(scorenum)

    except subprocess.CalledProcessError:
        print("网络连接不正常!请检查网络!")
    except Exception as e:
        print(e)
        print("失败!可能是你没有完成教学评价!没有完成教学评价则无法查看成绩!或用户中途取消或网络故障。")
    finally:
        # if os.path.exists(r'' + DstDir + '\\ScoreHelper\\CheckCode.jpg'):
        #     os.remove(r'' + DstDir + '\\ScoreHelper\\CheckCode.jpg')
        print("程序将在3秒后退出...")
        time.sleep(3)
        os.system("python 成绩监控并推送.py")

注:

  • 本程序使用了大量的 Python 第三方库
  • scoreinfo.bin 文件用来记录上次查询到的成绩数目,以免每次启动程序时重复发送邮件

运行结果


NOTE

  • 当百度云识别不成功时,为了防止网页错误,我设置了3秒间隔重试登录

  • 当多次识别不成功时,程序可能结束

  • 关于自动判断学期的功能,我是根据我个人的毕业时间来算的结束监控的年月日期,请按需修改

  • 当教务网关闭以及断网的时候本程序就不灵了。。。

  • 由于session有时间限制,所以每隔一段时间(约27小时)就要重新登录

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Beta Lemon

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值