考試安排查詢指令碼(CUP)

RhythmLian發表於2019-07-31

去年熱情高漲的時候心血來潮做了個簡易的查詢指令碼,限於當時技術水平(菜),實現得不是很好,這幾天終於想起來填坑了。環境依賴:

brew install python3
pip3 install requests
pip3 install tkinter
pip3 install fuzzywuzzy
pip3 install xlrd

首先,CUP教務處考試安排通知一般是釋出在網站的“考試通知”專欄裡的。比如:

這樣的一個通知,通常內部會有一個考試通知的xls表格檔案。

開啟以後:

每次考試通知的格式都是一致的。

基於此,思路就來了,先輸入考試通知釋出網頁的地址,然後程式自動獲取到檔案的下載地址,再自動將檔案下載下來,得到考試安排資訊。

程式碼:

def get_one_page(url, headers):
    try:
        response = requests.get(url, headers=headers)
        if response.status_code == 200:
            response.encoding = response.apparent_encoding
            return response.text
        return None
    except RequestException:
        return None


def countPoint(UrlPart):
    cnt = 0
    for i in UrlPart:
        if i != '.':
            break
        cnt += 1
    return cnt


def getNewXls(url):
    html = get_one_page(url, headers=headers)
    if not html:
        return False
    als = re.findall('<a.*?href="(.*?)"', html, re.S)
    for a in als:
        if a.endswith('xls'):
            cnt = countPoint(a)
            url = '/'.join(url.split('/')[:-cnt]) + a[cnt:]
            break
    content = requests.get(url, headers).content
    with open('content.xls', 'wb') as f:
        f.write(content)
    return True

在得到考試安排資訊後,我分析可以通過“課程&教師&班級”三種條件可以比較精確的搜尋到要查詢的考試安排。

通過這三個列名,可以建立一個簡易的搜尋字典:


  data = {'課程名': {}, '上課老師': {}, '主修班級': {}}

def init():
    xls = xlrd.open_workbook('content.xls')
    global name_col, teacher_col, sc_col, sheet
    sheet = xls.sheet_by_index(0)
    keys = sheet.row_values(0)
    for i in range(len(keys)):
        if keys[i] == '課程名':
            name_col = i
        elif keys[i] == '上課教師':
            teacher_col = i
        elif keys[i] == '主修班級':
            sc_col = i
    if not name_col or not teacher_col or not sc_col:
        exit('Unknown xls layout')
    ls = sheet.col_values(name_col)
    for i in range(1, len(ls)):
        if ls[i] not in data['課程名']:
            data['課程名'][ls[i]] = set()
        data['課程名'][ls[i]].add(i)
    ls = sheet.col_values(teacher_col)
    for i in range(1, len(ls)):
        if ls[i] not in data['上課老師']:
            data['上課老師'][ls[i]] = set()
        data['上課老師'][ls[i]].add(i)
    ls = sheet.col_values(sc_col)
    for i in range(1, len(ls)):
        cls = ls[i].split(',')
        for cl in cls:
            if cl not in data['主修班級']:
                data['主修班級'][cl] = set()
            data['主修班級'][cl].add(i)

而考慮查詢方便,必然不可能讓使用者(我)每次都輸入精準的資訊才能查到結果,這太不酷了。

所以我考慮間隔匹配+模糊匹配的方式來得到搜尋結果。

間隔匹配:

def fm(string, ls):
    res = []
    match = '.*?'.join([i for i in string])
    for i in ls:
        if re.findall(match, i):
            res.append((i, 100))
    return res

模糊匹配:(這裡使用了一個叫fuzzywuzzy的第三方庫,只有間隔匹配失敗後才會使用模糊匹配)

res = fm(aim, data[keys[i]].keys())
if not res:
     res = process.extract(aim, data[keys[i]].keys(), limit=3)

那麼如果使用者提供了多個搜尋條件怎麼處理呢?答案是利用集合的並交運算來處理。

比如搜尋表示式: xx&yy&zz。顯然我們通過搜尋演算法可以得到三個獨立集合分別為xx,yy和zz的結果,那麼對這三個集合取交即可得到正解。

def search(exp):
    if not pre_check():
        return None
    keys = ['課程名', '上課老師', '主修班級']
    res_set = set()
    flag = False
    for i in range(len(exp)):
        if i < 3:
            aim = exp[i].strip()
            if not aim:
                continue
            res = fm(aim, data[keys[i]].keys())
            if not res:
                res = process.extract(aim, data[keys[i]].keys(), limit=3)
            ts = set()
            for mth in res:
                if mth[1]:
                    ts = ts.union(data[keys[i]][mth[0]])
            if flag:
                res_set = res_set.intersection(ts)
            else:
                res_set = res_set.union(ts)
                flag = True
        else:
            break
    res = ''
    for line_num in res_set:
        line = sheet.row_values(line_num)
        res += '-' * 50 + '\n'
        res += '課程名稱:' + line[name_col] + '\n'
        res += '授課教師:' + line[teacher_col].replace('\n', ',') + '\n'
        cls = line[sc_col].split(',')
        linkstr = ''
        for i in range(len(cls)):
            linkstr += cls[i]
            if i + 1 == len(cls):
                break
            elif (i + 1) % 5 == 0:
                linkstr += '\n' + ' ' * 9
            else:
                linkstr += ','
        res += '主修班級:' + linkstr + '\n'
        day = "%04d年%02d月%02d日" % xldate_as_tuple(line[4], 0)[:3]
        res += '考試時間:' + day + '(周%s) ' % line[2] + line[5] + '\n'
        res += '考試地點:' + line[-1].replace('\n', ',') + '\n'
    return res

到這,指令碼的硬核部分就結束了~

然後我們基於這份程式碼,擼一個GUI出來。

大功告成~!

GitHub開源地址:https://github.com/Rhythmicc/CUP_EXAM

 

相關文章