考試安排查詢腳本(CUP)

  • 2019 年 10 月 3 日
  • 筆記

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

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