2024年5月28日 星期二

Python 學習筆記 : 網頁爬蟲實戰 (九) 市立圖書館個人書房借書資訊 (下)

在前一篇測試中已在 Mapleboard 的 Ubuntu Mate 上成功地利用 Firefox 與 Chromium 執行 Selenium 爬蟲程式, 經觀察 Chromium 的執行速度比 Firefox 快一倍, 因此正式上線時會使用 Chromium. 本篇是此任務的結尾, 目標是將擷取到的市圖 "我的書房" 網頁中的借閱與預約書籍資訊做進一步處理, 萃取出下列資料後用 Line Notify 推播到 "市圖借書" 群組 : 
  • 今日到期書目與續借次數
  • 明日到期書目與續借次數
  • 逾期書目與續借次數
  • 近 7 日到期書目與續借次數
  • 預約書目與順位 
本系列之前的筆記參考 : 



六. 借閱與預約資訊處理 :      

先來計算借閱書目是否逾期, 這可以利用到期日與今日的 datetime 物件相減的結果來判斷, 到期日減今日如果值為 0 表示此書為今日到期, 負數表示逾期, -1 表示已逾期一天, -2 為逾期兩天 ... 如果是正數表示尚未到期. 這個計算要用到 Python 內建的 datetime 套件中的兩個模組 : datetime 與 timedelta :

>>> from datetime import datetime, timedelta   

datetime 模組常用函式如下 :


 datetime 模組常用函式 說明
 now()  傳回目前日期時間之 datetime 物件
 today() 傳回今日之 datetime 物件


datetime 物件的常用方法如下 :


 datetime 物件常用方法 說明
 strftime(格式字串)  將 datetime 物件依格式字串 (例如 '%Y%m%d') 轉成字串
 strptime(日期時間字串, 格式字串) 將日期時間字串轉成 datetime 物件


先使用一個借閱紀錄範例來測試 : 

>>> borrow_books=[{'book_name': 'PyTorch深度學習攻略 : 核心開發者親授!', 
               'due_date': '2024-05-27', 'due_times': '2'}, 
              {'book_name': '一行指令學Python : 用機器學習掌握人工智慧',
               'due_date': '2024-05-28', 'due_times': '1'},
              {'book_name': '用Python學AIoT智慧聯網',
               'due_date': '2024-05-29', 'due_times': '0'}]

以其中第一本書的到期日為例 :

>>> due_date=borrow_books[0]['due_date']   
>>> due_date   
'2024-05-27'   

呼叫 datetime.strptime() 函式將此格式之日期字串轉成 datetime 物件 :

>>> due_date=datetime.strptime(due_date,  '%Y-%m-%d')     
>>> due_date   
datetime.datetime(2024, 5, 27, 0, 0)

呼叫 datetime.today() 函式會傳回今日 (現在) 的 datetime 物件 : 

>>> datetime.today()   
datetime.datetime(2024, 5, 28, 15, 3, 21, 200197)   

但計算日期差不需要時間部分, 只要呼叫 strptime() 方法並傳入格式字串 '%Y-%m-%d' 就可去除時間部分, 傳回日期字串 : 

>>> today_str=datetime.today().strftime('%Y-%m-%d')   
>>> today_str  
'2024-05-28'  

但要做日期相減須轉回 datetime 物件 :

>>> today=datetime.strptime(today_str, "%Y-%m-%d")   
>>> today   
datetime.datetime(2024, 5, 28, 0, 0)  

用 due_date 去減 today 即可得到一個表示日期差的 timedelta 物件 : 

>>> due_date-today   
datetime.timedelta(days=-1)

若要取得日期差之整數值可利用其 days 屬性 :

>>> (due_date-today).days   
-1

到期日與今日差為 -1 表示已經逾期一天了. 參考 :


寫一個迴圈來走訪上面的借閱紀錄範例 : 

>>> for book in borrow_books:   
    book_name=book['book_name']   
    due_date=book['due_date']   
    due_date=datetime.strptime(due_date, '%Y-%m-%d')   
    today_str=datetime.today().strftime('%Y-%m-%d')   
    today=datetime.strptime(today_str, "%Y-%m-%d")   
    delta=(due_date-today).days
    if delta < 0:   
        print(f'{book_name} 已逾期 {abs(delta)} 天')   
    elif delta == 0:     
        print(f'{book_name} 今日到期')   
    elif delta == 1:   
        print(f'{book_name} 明日到期')  
        
結果如下 : 

PyTorch深度學習攻略 : 核心開發者親授! 已逾期 1 天
一行指令學Python : 用機器學習掌握人工智慧 今日到期
用Python學AIoT智慧聯網 明日到期

因為今日是 2024-05-28, 第一本書到期日為 2024-05-27 故顯示 '逾期 1 天'; 第二本書到期日為 2024-05-28 故顯示 '今日到期'; 第三本書到期日為 2024-05-29 故顯示 '明日到期'. 


七. 用 Line Notify 推播借閱與預約資訊 :    

前面的測試已完全擷取到個人書房借閱與預約資訊, 接下來要將這些訊息透過 Line Notify 推播到手機 Line 聊天室. 首先在手機 Line 中建立一個個人群組 "市圖借書預約資訊" (也可以推播到個人 Line 帳號, 但那樣會與其他個人聊天訊息混在一起) :




輸入群組名稱後按右上角的 "建立" 鈕即可 :




可以加入所有想獲得此推播訊息的人進入群組, 但最重要的是要將 Line Notify 加進來, 這樣程式發送的訊息才會被送進此群組. 按右上方的三條槓按鈕進入群組的設定頁面 :




按 "邀請" 鈕後, 在下列頁面中點選 Line Notify 邀請它進來群組內 :




可見 Line Notify 已加入此群組 :




然後登入 Line Notify 網站 :


按右上角帳號名稱會出現下拉式選單, 點選 "個人頁面" :




將個人頁面拉到最底下, 按 "發行權仗" 鈕 :




在上面輸入權仗名稱 "市圖借書預約資訊", 選擇要接收要接收訊息之聊天室為與權仗同名的 "市圖借書預約資訊" 群組, 然後按底下的 "發行" 鈕就會獲得一個權仗 :




先按左邊的複製鈕將權杖複製到純文字檔中儲存才能關閉此視窗 :




這樣就可以來撰寫 Line Notify 程式碼了 :

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.firefox.options import Options
import re
from datetime import datetime
import time
import requests

def line_notify(msg, token):
    url="https://notify-api.line.me/api/notify"
    headers={"Authorization": "Bearer " + token,
             "Content-Type": "application/x-www-form-urlencoded"
             }
    payload={"message": msg}
    r=requests.post(url, headers=headers, params=payload)
    return r.status_code

def get_books(account, password):
    try:
        # 登入我的書房
        options=Options()
        options.add_argument("--headless")
        driverpath='geckodriver.exe'
        browser=webdriver.Firefox(options=options)
        browser.implicitly_wait(20)
        browser.get("https://webpacx.ksml.edu.tw/personal/")
        loginid=browser.find_element(By.ID, "loginid")
        loginid.send_keys(account)
        pincode=browser.find_element(By.ID, 'pincode')
        pincode.send_keys(password)
        div_btn_grp=browser.find_element(By.CLASS_NAME, 'btn_grp')
        login_btn=div_btn_grp.find_element(By.TAG_NAME, 'input')
        browser.implicitly_wait(20)
        login_btn.click()
        # 擷取借閱紀錄
        div_redblock=browser.find_element(By.CLASS_NAME, 'redblock')
        browser.implicitly_wait(20)
        div_redblock.click()
        books=browser.find_elements(By.CLASS_NAME, 'bookdata')
        borrow_books=[]
        for book in books:
            item=dict()
            book_name=book.find_element(By.XPATH, './h2/a').text    
            item['book_name']=book_name.replace('/', '').strip()
            pattern=r'\d{4}-\d{2}-\d{2}'
            due_date=book.find_element(By.XPATH, './ul[4]/li[2]').text
            item['due_date']=re.findall(pattern, due_date)[0] 
            due_times=book.find_element(By.XPATH, './ul[5]/li[1]').text
            item['due_times']=re.findall(r'\d{1}', due_times)[0]   
            borrow_books.append(item)
        browser.back() # 回上一頁
        # 擷取預約紀錄
        div_blueblock=browser.find_element(By.CLASS_NAME, 'blueblock')
        browser.implicitly_wait(20)
        div_blueblock.click()
        books=browser.find_elements(By.CLASS_NAME, 'bookdata')
        reserve_books=[]
        for book in books:
            item=dict()
            book_name=book.find_element(By.XPATH, './h2/a').text    
            item['book_name']=book_name.replace('/', '').strip()
            sequence=book.find_element(By.XPATH, './ul[7]/li[1]').text
            item['sequence']=re.findall(r'\d+', sequence)[0]
            reserve_books.append(item)
        browser.close()
        return (borrow_books, reserve_books)        
    except Exception as e:
        print(e)
        return None, None
    
if __name__ == '__main__':
    start=time.time()
    token='7CLpVmFpNihuN6GB0bQcc5M1nOhpAtony1966QFMgzz'   # 範例權仗
    users=[['family87', '123456'],
           ['amy08123', '123456'],
           ['kelly19', '123456'],
           ['peter120', '123456'],
           ['shinping92', '123456'],
           ['daddy587', '123456']]      # 範例帳號
    for user in users:   # 走訪每個帳號
        account=user[0]
        password=user[1]
        borrow_books, reserve_books=get_books(account, password)
        if borrow_books: 
            borrow=[]
            for book in borrow_books:
                book_name=book['book_name']   
                due_times=book['due_times']
                due_date=book['due_date']
                due_date=datetime.strptime(due_date, '%Y-%m-%d')   
                today_str=datetime.today().strftime('%Y-%m-%d')   
                today=datetime.strptime(today_str, "%Y-%m-%d")   
                delta=(due_date-today).days
                if delta < 0:
                    msg=f'▶ {book_name} (逾期 {abs(delta)} 天, ' +\
                         '續借次數 {due_times})'
                    borrow.append(msg)
                elif delta == 0:        # 今日到期之書
                    msg=f'▶ {book_name} (今日到期, 續借次數 {due_times})'
                    borrow.append(msg)
                elif delta == 1:        # 明日到期之書
                    msg=f'▶ {book_name} (明日到期, 續借次數 {due_times})'
                    borrow.append(msg)
                elif 1 < delta < 7:    # 2~7 日內到期之書
                    msg=f'▶ {book_name} ({book["due_date"]} 到期, '\
                        f'續借次數 {due_times})'
                    borrow.append(msg)                    
            if len(borrow) != 0:   # 有 7 日內到期之書
                borrow.insert(0, f'\n❖ {account} 的借閱 :')
                msg='\n'.join(borrow)
                code=line_notify(msg, token)
                if code==200:
                    print('Line 訊息發送成功!')
                else:
                    print(f'Line 訊息發送失敗! (code={code})')
        if reserve_books:     # 有預約書才處理
            reserve=[]      # 儲存處理後的預約書
            i=1
            for book in reserve_books:  # 走訪每本預約書
                book_name=book['book_name']
                sequence=book['sequence']
                this_year=str(datetime.today().year)  # 取得今年西元年
                if sequence == this_year:
                    msg=f'{i}. {book_name} (已到館)'
                else:
                    msg=f'{i}. {book_name} (順位 {sequence})'
                reserve.append(msg)
                i += 1
            if len(reserve) != 0:
                reserve.insert(0, f'\n❖ {account} 的預約 :')
                msg='\n'.join(reserve)
                code=line_notify(msg, token)
                if code==200:
                    print('Line 訊息發送成功!')
                else:
                    print(f'Line 訊息發送失敗! (code={code})')
    end=time.time()
    print(f'執行時間:{end-start}')

此處的 token 變數即上面為了傳訊息到 "市圖借書預約資訊" 群組所申請的權仗 (此為範例而已), users 是記錄借書帳密的二維串列, 第一個元素是帳號, 第二個是密碼, 使用迴圈走訪此串列, 依序用各使用者之帳密呼叫 get_books() 去擷取市圖 "我的書房" 的借閱與預約資訊, 它會傳回兩個字典 : borrow_books 與 reserve_books, 分別用來儲存借閱資訊與預約資訊, 無資料則傳回 None, None. 傳回資訊經過處理後串成字串呼叫 line_notify() 推播至群組, 結果如下 :




八. 在 Mapleboard 上佈署爬蟲程式 :    

由於我的兩塊 Pi 3 的桌面怪怪的, Putty 遠端存取 OK, 但用 VNC 做遠端開發因程式集選單要很久才會打開, 所以我這次把爬蟲改為佈署到 Mapleboard 上用 Chomium 的 WebDriver 來跑 Selenium 爬蟲, 最後設定 Chrontab 定時於每天下午 4 點執行, 作法與在樹莓派幾乎相同 (預設使用者目錄為 /home/使用者, 樹莓派是 /home/pi). 

關於在 Mapleboard 上跑 Selenium 的測試參考 :


由於在 Mapleboard 跑 Chromium 速度比 Firefox 快, 所以將上面在 Win11 + Firefox 上跑的程式依據下面兩篇改寫為 Chromium 版 :

# ksml_books_7.py
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
import re
from datetime import datetime
import time
import requests

def line_notify(msg, token):
    url="https://notify-api.line.me/api/notify"
    headers={"Authorization": "Bearer " + token,
             "Content-Type": "application/x-www-form-urlencoded"
             }
    payload={"message": msg}
    r=requests.post(url, headers=headers, params=payload)
    return r.status_code

def get_books(account, password):
    try:
        # 登入我的書房
        options=Options()
        options.add_argument("--headless")
        driverpath='/usr/lib/chromium-browser/chromedriver'
        service=Service(driverpath)
        browser=webdriver.Chrome(options=options, service=service)
        browser.implicitly_wait(20)
        browser.get("https://webpacx.ksml.edu.tw/personal/")
        loginid=browser.find_element(By.ID, "loginid")
        loginid.send_keys(account)
        pincode=browser.find_element(By.ID, 'pincode')
        pincode.send_keys(password)
        div_btn_grp=browser.find_element(By.CLASS_NAME, 'btn_grp')
        login_btn=div_btn_grp.find_element(By.TAG_NAME, 'input')
        browser.implicitly_wait(20)
        login_btn.click()
        # 擷取借閱紀錄
        div_redblock=browser.find_element(By.CLASS_NAME, 'redblock')
        browser.implicitly_wait(20)
        div_redblock.click()
        books=browser.find_elements(By.CLASS_NAME, 'bookdata')
        borrow_books=[]
        for book in books:
            item=dict()
            book_name=book.find_element(By.XPATH, './h2/a').text    
            item['book_name']=book_name.replace('/', '').strip()
            pattern=r'\d{4}-\d{2}-\d{2}'
            due_date=book.find_element(By.XPATH, './ul[4]/li[2]').text
            item['due_date']=re.findall(pattern, due_date)[0] 
            due_times=book.find_element(By.XPATH, './ul[5]/li[1]').text
            item['due_times']=re.findall(r'\d{1}', due_times)[0]   
            borrow_books.append(item)
        browser.back() # 回上一頁
        # 擷取預約紀錄
        div_blueblock=browser.find_element(By.CLASS_NAME, 'blueblock')
        browser.implicitly_wait(20)
        div_blueblock.click()
        books=browser.find_elements(By.CLASS_NAME, 'bookdata')
        reserve_books=[]
        for book in books:
            item=dict()
            book_name=book.find_element(By.XPATH, './h2/a').text    
            item['book_name']=book_name.replace('/', '').strip()
            sequence=book.find_element(By.XPATH, './ul[7]/li[1]').text
            item['sequence']=re.findall(r'\d+', sequence)[0]
            reserve_books.append(item)
        browser.close()
        return (borrow_books, reserve_books)        
    except Exception as e:
        print(e)
        return None, None
    
if __name__ == '__main__':
    start=time.time()
    token='7CLpVmFpNihuN6GB0bQcc5M1nOhpAtony1966QFMgzz'   # 範例權仗
    users=[['family87', '123456'],
           ['amy08123', '123456'],
           ['kelly19', '123456'],
           ['peter120', '123456'],
           ['shinping92', '123456'],
           ['daddy587', '123456']]      # 範例帳號
    for user in users:
        account=user[0]
        password=user[1]
        borrow_books, reserve_books=get_books(account, password)
        if borrow_books:   # 有借書, 不是傳回 None
            borrow=[]    # 儲存處理過的借書資訊
            for book in borrow_books:    # 走訪借書串列
                book_name=book['book_name']   
                due_times=book['due_times']
                due_date=book['due_date']
                due_date=datetime.strptime(due_date, '%Y-%m-%d')    # 轉成 datetime 物件
                today_str=datetime.today().strftime('%Y-%m-%d')    # 轉成日期字串去除時間
                today=datetime.strptime(today_str, "%Y-%m-%d")    # 轉回 datetime 物件
                delta=(due_date-today).days   # 計算逾期天數
                if delta < 0:   # 借書逾期了
                    msg=f'▶ {book_name} (逾期 {abs(delta)} 天, ' +\
                         '續借次數 {due_times})'
                    borrow.append(msg)
                elif delta == 0:   # 今日到期
                    msg=f'▶ {book_name} (今日到期, 續借次數 {due_times})'
                    borrow.append(msg)
                elif delta == 1:    # 明日到期
                    msg=f'▶ {book_name} (明日到期, 續借次數 {due_times})'
                    borrow.append(msg)
                elif 1 < delta < 7:   # 2~7 日內到期
                    msg=f'▶ {book_name} ({book["due_date"]} 到期, '\
                        f'續借次數 {due_times})'
                    borrow.append(msg)                    
            if len(borrow) != 0:    # 有處理過的借書資料
                borrow.insert(0, f'\n❖ {account} 的借閱 :')
                msg='\n'.join(borrow)
                code=line_notify(msg, token)
                if code==200:
                    print('Line 訊息發送成功!')
                else:
                    print(f'Line 訊息發送失敗! (code={code})')
        if reserve_books:    # 有預約書, 不是傳回 None
            reserve=[]    # 儲存處理過的預約資訊
            i=1   # 計數器
            for book in reserve_books:    # 走訪預約書串列
                book_name=book['book_name']
                sequence=book['sequence']
                this_year=str(datetime.today().year)   # 今年年分
                if sequence == this_year:   # 預約書到館 sequence 欄會抓到今年年分
                    msg=f'{i}. {book_name} (已到館)'
                else:
                    msg=f'{i}. {book_name} (順位 {sequence})'
                reserve.append(msg)
                i += 1
            if len(reserve) != 0:   # 有處理過之預約資料
                reserve.insert(0, f'\n❖ {account} 的預約 :')
                msg='\n'.join(reserve)
                code=line_notify(msg, token)
                if code==200:
                    print('Line 訊息發送成功!')
                else:
                    print(f'Line 訊息發送失敗! (code={code})')
    end=time.time()
    print(f'執行時間:{end-start}')

此檔案存放在 /home/tony1966/python 下, 這要用在編輯 Crontab 時要用到 :

tony1966@LX2438:~/python$ pwd   
/home/tony1966/python  

首先要用 chmod 指令將此程式檔改為可執行 :

tony1966@LX2438:~/python$ sudo chmod +x ksml_books_7.py  
tony1966@LX2438:~/python$ ls -ls ksml_books_7.py   
8 -rwxrwxr-x 1 tony1966 tony1966 5845 May 29 23:38 ksml_books_7.py     

先手動下指令測試看看 :

tony1966@LX2438:~/python$ /usr/bin/python3 /home/tony1966/python/ksml_books_7.py
Line 訊息發送成功!
Line 訊息發送成功!
Line 訊息發送成功!
Line 訊息發送成功!
Line 訊息發送成功!
Line 訊息發送成功!
Line 訊息發送成功!
Line 訊息發送成功!
Line 訊息發送成功!
執行時間:161.23561120033264

看起來沒問題, 然後用 crontab -e 指令編輯 Cron table :

tony1966@LX2438:~/python$ crontab -e    
no crontab for tony1966 - using an empty one

Select an editor.  To change later, run 'select-editor'.
  1. /bin/nano        <---- easiest   (選 1 用 nano 編輯 Crontab)
  2. /usr/bin/vim.tiny
  3. /usr/bin/code
  4. /bin/ed

Choose 1-4 [1]: 1

這會開啟 Nano 來編輯 Cron table, 在最底下輸入 :

0 16 * * * /usr/bin/python3 /home/tony1966/python/ksml_books_7.py    

這表示要電腦在每天下午 4 點執行 ksml_books_7.py, 關於 Crontab 用法參考 : 


然後按 Ctrl + O, 按 Enter 存檔後按 Ctrl + X 跳出 Nano. 

忙了一周終於把長久以來想做的事給完成了! 了了一樁心願, 而且透過此爬蟲任務也深刻地理解 Selenium 強大的功能, 也學會了 XPATH 用法, 哎呀, 真是一舉多得啊! 


2024-06-05 補充 :

今天參考下面網站的網頁圖示對送出訊息樣式 (主要是項目的標示) 做了一些修改 : 

https://tw.piliapp.com/symbol/  (網頁特殊符號)

程式修改部分為借閱之 delta :

                if delta < 0:
                    msg=f'🅧 {book_name} (逾期 {abs(delta)} 天, ' +\
                         '續借次數 {due_times})'
                    borrow.append(msg)
                elif delta == 0:
                    msg=f'❶ {book_name} (今日到期, 續借次數 {due_times})'
                    borrow.append(msg)
                elif delta == 1:   
                    msg=f'❷ {book_name} (明日到期, 續借次數 {due_times})'
                    borrow.append(msg)
                elif delta == 2:   
                    msg=f'❸ {book_name} (後天到期, 續借次數 {due_times})'
                    borrow.append(msg)
                elif 2 < delta < 8:   
                    msg=f'▶ {book_name} ({book["due_date"]} 到期, '\
                        f'續借次數 {due_times})'
                    borrow.append(msg)

❶ 表示當日到期;  ❷ 表示明日到期; 新增 ❸ 為後天到期, 其餘至第七日才使用 ▶ 符號. 

預約書項目符號修改部分 : 

            i=0
            j=['①', '②', '③', '④', '⑤']
            for book in reserve_books:
                book_name=book['book_name']
                sequence=book['sequence']
                this_year=str(datetime.today().year)
                if sequence == this_year:
                    msg=f'{j[i]} {book_name} (已到館)'
                else:
                    msg=f'{j[i]} {book_name} (順位 {sequence})'
                reserve.append(msg)
                i += 1

此處增加一個 j 串列來儲存預約書數量, i 初始值從 1 改為 0. 





2024-06-06 補充 :

我覺得上面修改的項目符號有點不合理, 借閱書當日到期應該顯示 ⓿ 才對, 因為已經不足一天, 明日到期才是 ❶. 另外, 到館之預約書改用黑底數字標示, 到期日 3 日以上的標示改用 ✦ 符號, 程式碼再度修改如下, 借閱書部分 :

                if delta < 0:
                    msg=f'🅧 {book_name} (逾期 {abs(delta)} 天, ' +\
                         '續借次數 {due_times})'
                    borrow.append(msg)
                elif delta == 0:
                    msg=f'⓿ {book_name} (今日到期, 續借次數 {due_times})'
                    borrow.append(msg)
                elif delta == 1:   
                    msg=f'❶ {book_name} (明日到期, 續借次數 {due_times})'
                    borrow.append(msg)
                elif delta == 2:   
                    msg=f'❷ {book_name} (後天到期, 續借次數 {due_times})'
                    borrow.append(msg)
                elif 2 < delta < 8:   
                    msg=f'✦ {book_name} ({book["due_date"]} 到期, '\
                        f'續借次數 {due_times})'
                    borrow.append(msg)  

🅧 表示已逾期;  表示當日到期; ❶ 表示明日到期; ❷ 為後天到期; 其餘至第七日才使用  符號. 

預約書顯示部分 :

            i=0
            j=['①', '②', '③', '④', '⑤']
            k=['❶', '❷', '❸', '❹', '❺']
            for book in reserve_books:
                book_name=book['book_name']
                sequence=book['sequence']
                this_year=str(datetime.today().year)
                if sequence == this_year:
                    msg=f'{k[i]} {book_name} (已到館)'
                else:
                    msg=f'{j[i]} {book_name} (順位 {sequence})'
                reserve.append(msg)
                i += 1

此處新增一個 k 串列儲存黑底的 1~5, 當預約書為已到館狀態就用 k 串列顯示, 否則用 j 串列, 結果如下 : 





經過以上修改後的外觀總算讓我滿意了.


2024-06-16 補充 :

由於發現一些小問題, 所以今天對程式做了一些修補, 一是當借書逾期時 due_times 欄位變成無值, 直接顯示變數名稱 {due_times}; 二是已到館的預約書只顯示 "已到館", 如果能加上取書的保留期限那就更完美了, 三是刪除了多餘的 implicitly_wait() 呼叫, 此方法只要在 WebDriver 物件建立後呼叫一次即可.




逾期的書一定要先還, 因此已續借次數沒有意義, 解決辦法就是直接拿掉逾期書的 {due_times} 輸出, 只顯示已逾期幾天 :

                if delta < 0:
                    msg=f'🅧 {book_name} (逾期 {abs(delta)} 天)'
                    borrow.append(msg)

至於新增已到館預約書之保留期限日期, 我修改了判斷預約書是否為已到館之邏輯, 之前是用 sequnce 欄位, 因為預約中書籍的順位 sequence 的 XPATH 為 "./ul[7]/li[1]", 此位置在已到館書籍則為 "流通狀態", 裡面包著的 span 元素其內容就是保留期限 :





已到館的書順位就是 0, 此資訊不重要, 因此可以利用 XPATH="./ul[7]/li[1]" 這個原本是 sequence 順位的欄位來判別是否已到館, 如果 span 元素內容含有 "預約待取" 字樣表示已到館, 那麼就用正規式擷取後面的保留期限, 程式碼修改如下 : 

            sequence=book.find_element(By.XPATH, './ul[7]/li[1]').text
            if '預約待取' in sequence:  # 已到館
                item['ready_for_pickup']=True    # 已到館
                reg=r'\d{4}-\d{2}-\d{2}'   # 保留期限日期的正規式
                item['expiration']=re.findall(reg, sequence)[0]   
                item['sequence']='0'    # 預約順位不重要設為 0
            else: # 預約中
                item['ready_for_pickup']=False   # 預約中
                item['expiration']=''    # 預約中保留期限日期無意義設為 ''
                item['sequence']=re.findall(r'\d+', sequence)[0]  # 取出目前順位
            reserve_books.append(item)

此處 item 字典新增 ready_to_pickup 與 expiration 欄位, 前者值為 True/False 分別表示 "已到館"/預約中", 後者則用來儲存已到館書的保留期限. 

主程式中處理預約書的部分修改為 :

            for book in reserve_books:
                book_name=book['book_name']
                sequence=book['sequence']
                ready_for_pickup=book['ready_for_pickup']  # 是否已到館
                expiration=book['expiration']  # 保留期限日期
                if ready_for_pickup:   # 已到館
                    msg=f'{k[i]} {book_name} (已到館, 保留期限 {expiration})'
                else:
                    msg=f'{j[i]} {book_name} (順位 {sequence})'
                reserve.append(msg)
                i += 1

經過這樣調整後程式的邏輯就更明確了, 結果也符合要求 :




佈署於 Mapleboard 的程式碼也隨之更新 (帳號密碼權杖等均為虛構) : 


沒有留言 :