2025年10月30日 星期四

Python 學習筆記 : 母校圖書館借書與預約爬蟲程式改版 v8

我在去年六月寫了一個母校圖書館爬蟲程式做為那年春季爬蟲練功項目之一, 主要功能是自動幫我做線上續借並用 LINE Notify 通報所借書籍有哪些被預約, 但那時其實只有第一個功能達成目標而已, 被預約書之資訊一直無法抓到, 參考 :


今年四月因應 LINE Notify 終止服務, 我將爬蟲程式改版為 v7, 但只是改用 Telegram 傳送訊息而已, 並未解決無法爬取被預約書問題, 參考 : 


今天花了很多時間與 ChatGPT 協作, 經過多次的來回修改程式碼, 終於能抓到被預約書資訊了. 由於母校圖書館網站使用 Angular 做為前端框架, 網頁內容都是動態載入的, 必須用 Selenium 模擬瀏覽器才能抓到渲染後的內容, 被預約書資訊因為層層包覆尤其難抓, 還好有 ChatGPT 相助, 這次終於搞定了.

這是未按 "全部續借" 前 : 




按 "全部續借" 後 : 




程式碼如下 :

# nkust_lib_8.py
from selenium import webdriver   
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
import time
import requests
from datetime import datetime
import asyncio
from telegram import Bot

async def telegram_send_text(text):
    bot=Bot(token=token)
    try:
        await bot.send_message(
            chat_id=chat_id,
            text=text
            )
        return True
    except Exception as e:
        print(f'Error sending text: {e}')
        return False

def get_nkust_lib():
    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(60)
        browser.maximize_window()
        url='https://nkust.primo.exlibrisgroup.com/discovery/login?'  +\
            'vid=886NKUST_INST:86NKUST&lang=zh-tw'   
        browser.get(url)
        # 按其他讀者
        md_list=browser.find_elements(By.TAG_NAME, 'md-list-item')
        md_list[1].click()
        print('按其他讀者 ... OK')
        # 登入系統
        login_user_name=browser.find_element(By.ID, 'LoginUserName')   
        login_user_name.send_keys('我的帳號')  
        login_password=browser.find_element(By.ID, 'LoginPassword')   
        login_password.send_keys('我的密碼')
        login_btn=browser.find_element(By.CLASS_NAME, 'button-large')
        login_btn.click()
        print('登入系統 ... OK')
        # 按名字顯現選單
        user_btn=browser.find_element(By.CLASS_NAME, 'user-button')
        actions=ActionChains(browser)
        actions.move_to_element(user_btn)
        actions.click(user_btn)
        actions.perform()
        print('按名字顯現選單 ... OK')
        # 按我的借閱鈕
        xpath='/html/body/div[3]/md-menu-content/md-menu-item[3]/button'    
        my_borrow=browser.find_element(By.XPATH, xpath)   
        actions.move_to_element(my_borrow)
        actions.click(my_borrow)
        actions.perform()
        print('按我的借閱 ... OK')
        # 按全部續借
        xpath='/html/body/primo-explore/div/prm-account/md-content' +\
              '/div[2]/prm-account-overview/md-content/md-tabs/' +\
              'md-tabs-content-wrapper/md-tab-content[2]/div/' +\
              'div/prm-loans/div[1]/div[2]/div[2]/button'
        all_borrow=browser.find_element(By.XPATH, xpath)
        actions.move_to_element(all_borrow)
        actions.click(all_borrow)
        actions.perform()
        print('按全部續借 ... OK')
        # 檢查續借結果
        xpath='/html/body/primo-explore/div/prm-account/md-content' +\
              '/div[2]/prm-account-overview/md-content/md-tabs' +\
              '/md-tabs-content-wrapper/md-tab-content[2]/div/div' +\
              '/prm-loans/div[2]/prm-alert-bar/div/div/span'
        alert_span=browser.find_element(By.XPATH, xpath)
        if '所有借閱資料已成功續借' in alert_span.text:
            msg='❖ 所有借閱資料已成功續借'
        else:
            msg='❖ 只有部分借閱資料已成功續借'
            # 檢查是否有 "載入更多結果" 按鈕
            for i in range(3):  # 最多 3 頁
                load_more=browser.find_elements(By.CLASS_NAME, 'button-confirm')
                if not load_more:  # 已無 "載入更多結果" 按鈕 : 跳出迴圈
                    break
                load_more[0].click()  # 按 "載入更多結果" 按鈕
                time.sleep(2)  # 等待 2 秒
            # 抓取所有借閱書目
            loan_items=browser.find_elements(By.TAG_NAME, 'md-list-item')
            unrenew_books=[]  # 儲存被預約而不可續借之書名  
            for item in loan_items:
                try:
                    # 1. 抓書名
                    title_elem=item.find_element(By.CSS_SELECTOR, "h3 a")
                    title=title_elem.text.strip()
                    # 2. 取得整個 item 的文字
                    item_text=item.text.strip()
                    # 3. 判斷借閱狀態是否包含 "已續借"
                    if "已續借" not in item_text:
                        unrenew_books.append(title)
                    #print(f"[DEBUG] {title} -> {item_text}")
                except Exception as e:        
                    continue # 若抓不到書名就跳過
            if unrenew_books:
                msg="被預約的書:\n" + "\n".join([f"{i+1:>2}. {t}" for i, t in enumerate(unrenew_books)])
            else:
                msg="全部書籍皆已續借"
            print('搜尋被預約書籍 ... OK')
    except Exception as e:
        print(e)
    finally:
        browser.close()
        return msg

if __name__ == '__main__':
    start=time.time()
    msg=get_nkust_lib()
    token='Telegram 權杖'
    chat_id='Telegramd 聊天室ID'  
    if msg:
        now=datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        msg='\n' + now + '\n' + msg
        if asyncio.run(telegram_send_text(msg)):
            print('訊息傳送成功!')
        else:
            print('訊息傳送失敗!')
    print(msg)
    end=time.time()
    print(f'執行時間:{end-start}')

程式利用 Selenium 模擬操作瀏覽器, 在借閱書目第一頁按全部續借鈕後持續按底下的 "載入更多結果" 鈕持續疊加載入其他頁直到最後一頁 (我只能借 30 本所以最多三頁, 每頁 10 本), 這時被預約而無法再續借的書會列在最後面. 此程式將每個書目的文字內容抓出來, 取出書名, 然後判斷其餘內容是否含有 '已續借' 字樣, 若無即表示該書已被預約, 將其書名放入串列以便後續串接成字串, 測試結果如下 : 

pi@raspberrypi:~ $ python3 nkust_lib_8.py     
按其他讀者 ... OK
登入系統 ... OK
按名字顯現選單 ... OK
按我的借閱 ... OK
按全部續借 ... OK

2025-10-30 20:16:20
被預約的書:
 1. Python金融市場賺大錢聖經 : 寫出你的專屬指標 / 張峮瑋作
 2. 少年Py的大冒險 : 成為Python AI深度學習達人的第一門課 / 蔡炎龍等著
 3. 統計學 : 使用Python語言 / 林進益著
執行時間:362.01835203170776

檢查手機有收到 Telegram 訊息 : 




更改程式檔權限為可執行 :

pi@raspberrypi:~ $ chmod +x nkust_lib_8.py  
pi@raspberrypi:~ $ ls -ls nkust_lib_8.py   
8 -rwxr-xr-x 1 pi pi 5408 10月 30 20:10 nkust_lib_8.py   

修改高雄 Pi 3 的 crontab : 

將原先定時執行的 v7 版程式改為 v8 版即可 :

pi@raspberrypi:~ $ crontab -e   

0 6,16 * * * /usr/bin/python3 /home/pi/nkust_lib_8.py

這樣便完成改版工程啦! 收工. 

沒有留言 :