之前版本的爬蟲我都把帳號密碼金鑰等等都寫在程式碼裡, 這實在不是好的做法, 今天將爬蟲進行改版, 將帳密金鑰都存放在隱藏檔 .env 中, 於程式裡利用 dotenv 從 .env 檔讀取出來. 關於 dotenv 用法參考 :
本系列全部文章索引參考 :
先將目前的第 10 版爬蟲複製一份到第 11 版 :
pi@kaopi3:~ $ cp nkust_lib_10.py nkust_lib_11.py
用 nano 編輯程式碼 :
pi@kaopi3:~ $ nano nkust_lib_11.py
修改為如下 :
# nkust_lib_11.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
import re
from dotenv import dotenv_values
async def telegram_send_text(text):
bot=Bot(token=TELEGRAM_TOKEN)
try:
await bot.send_message(
chat_id=TELEGRAM_ID,
text=text
)
return True
except Exception as e:
print(f'Error sending text: {e}')
return False
def get_nkust_lib():
browser=None
msg="無法取得資料" # ✅ 防止未賦值
try:
options=Options()
options.add_argument("--headless=new")
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage")
options.binary_location="/usr/bin/chromium"
service=Service("/usr/bin/chromedriver")
browser=webdriver.Chrome(service=service, options=options)
browser.implicitly_wait(60)
browser.set_window_size(1920, 1080)
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')
if len(md_list) > 1:
md_list[1].click()
print('按其他讀者 ... OK')
# 登入系統
login_user_name=browser.find_element(By.ID, 'LoginUserName')
login_user_name.send_keys(NKUST_LIB_ID)
login_password=browser.find_element(By.ID, 'LoginPassword')
login_password.send_keys(NKUST_LIB_PWD)
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)
# 抓取所有借閱書目
loan_items=browser.find_elements(By.TAG_NAME, 'md-list-item')
unrenew_books=[]
for item in loan_items:
try:
# ✅ 1. 必須有書名才是書
title_elem=item.find_elements(By.CSS_SELECTOR, "h3 a")
if not title_elem:
continue
title=title_elem[0].text.strip()
item_text=item.text.strip()
# ✅ 2. 取得到期日
due_text=''
due_elem=item.find_elements(By.XPATH, './/p[@data-qa="automation_mlc_record_date"]')
if due_elem:
m=re.search(r'\d{2}/\d{2}/\d{4}', due_elem[0].text)
if m:
due_text=m.group(0)
# ✅ 3. 只收真正「被預約 / 無法續借」
if any(k in item_text for k in ["被預約", "無法續借", "recall"]):
unrenew_books.append((title, due_text))
except Exception:
continue
if unrenew_books:
msg="被預約的書:\n" + "\n".join(
[f"{i+1:>2}. {t[0]} 到期日: {t[1]}" for i, t in enumerate(unrenew_books)]
)
else:
msg="全部書籍皆已續借"
print('搜尋被預約書籍 ... OK')
except Exception as e:
print(e)
finally:
if browser:
browser.quit() # ✅ 防呆
return msg # ✅ msg 一定有值
if __name__ == '__main__':
start=time.time()
config=dotenv_values('.env')
NKUST_LIB_ID=config.get('NKUST_LIB_ID')
NKUST_LIB_PWD=config.get('NKUST_LIB_PWD')
TELEGRAM_TOKEN=config.get('TELEGRAM_TOKEN')
TELEGRAM_ID=config.get('TELEGRAM_ID')
#print(NKUST_LIB_ID)
#print(NKUST_LIB_PWD)
#print(TELEGRAM_TOKEN)
#print(TELEGRAM_ID)
msg=get_nkust_lib()
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}')
黃底高亮者為修改或添加的部分, 執行結果如下 :
pi@kaopi3:~ $ python nkust_lib_11.py
按其他讀者 ... OK
登入系統 ... OK
按名字顯現選單 ... OK
按我的借閱 ... OK
按全部續借 ... OK
訊息傳送成功!
2026-04-18 00:37:52
❖ 所有借閱資料已成功續借
執行時間:81.74577593803406
2026-04-18 補充 :
早上又對程式碼進行局部微調, 版本號仍維持 v11, 主要修改如下 :
1. 添加主機名稱訊息 :
利用 socket.gethostname() 即可得到主機名稱. 這樣收到 Telegram 才知道是哪台主機的爬蟲完成的.
利用 socket.gethostname() 即可得到主機名稱. 這樣收到 Telegram 才知道是哪台主機的爬蟲完成的.
2. 使用 SD 卡儲存 Chrome/Chromium 暫存資料 :
由於 Pi 400 的 Trixie 仍然開發演進中, 最新版本改用 Chrome 而非 Chromium, 跑 Selenium 爬蟲時使用 DRAM 來暫存資料, 可能導致 DRAM 塞滿而使瀏覽器閃退, 於是改為強制將暫存資料放在 chrome_tmp 資料夾下, 瀏覽器設定修改為 :
# 設定一個在 SD 卡上的暫存目錄 (for Trixie)
chrome_tmp_path=os.path.expanduser('~/chrome_tmp')
if not os.path.exists(chrome_tmp_path):
os.makedirs(chrome_tmp_path)
options=Options()
options.add_argument("--headless=new") # 新版無頭擬真瀏覽器
options.add_argument("--no-sandbox") # Trixie 必加
options.add_argument("--disable-dev-shm-usage") # 避免擠爆 /dev/shm
options.add_argument('--disable-gpu') # 避免 GPU 驅動崩潰
# 強迫使用 SD 卡空間 (特別是 Trixie 必須)
options.add_argument(f'--user-data-dir={chrome_tmp_path}')
# 限制快取大小為 100MB (防止 chrome_tmp 資料夾隨著時間變得巨大)
options.add_argument('--disk-cache-size=104857600')
以上設定雖然是針對 Trixie, 但在 Bulleye (Pi 3 & Pi 3A+) 上跑也是可以的. 完整程式碼如下 :
# nkust_lib_11.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
import re
from dotenv import dotenv_values
import os
import socket
async def telegram_send_text(text):
bot=Bot(token=TELEGRAM_TOKEN)
try:
await bot.send_message(
chat_id=TELEGRAM_ID,
text=text
)
return True
except Exception as e:
print(f'Error sending text: {e}')
return False
def get_nkust_lib():
browser=None
msg="無法取得資料" # ✅ 防止未賦值
try:
# 設定一個在 SD 卡上的暫存目錄 (for Trixie)
chrome_tmp_path=os.path.expanduser('~/chrome_tmp')
if not os.path.exists(chrome_tmp_path):
os.makedirs(chrome_tmp_path)
options=Options()
options.add_argument("--headless=new") # 新版無頭擬真瀏覽器
options.add_argument("--no-sandbox") # Trixie 必加
options.add_argument("--disable-dev-shm-usage") # 避免擠爆 /dev/shm
options.add_argument('--disable-gpu') # 避免 GPU 驅動崩潰
# 強迫使用 SD 卡空間 (特別是 Trixie 必須)
options.add_argument(f'--user-data-dir={chrome_tmp_path}')
# 限制快取大小為 100MB (防止 chrome_tmp 資料夾隨著時間變得巨大)
options.add_argument('--disk-cache-size=104857600')
options.binary_location="/usr/bin/chromium"
service=Service("/usr/bin/chromedriver")
browser=webdriver.Chrome(service=service, options=options)
browser.implicitly_wait(60)
browser.set_window_size(1920, 1080)
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')
if len(md_list) > 1:
md_list[1].click()
print('按其他讀者 ... OK')
# 登入系統
login_user_name=browser.find_element(By.ID, 'LoginUserName')
login_user_name.send_keys(NKUST_LIB_ID)
login_password=browser.find_element(By.ID, 'LoginPassword')
login_password.send_keys(NKUST_LIB_PWD)
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)
# 抓取所有借閱書目
loan_items=browser.find_elements(By.TAG_NAME, 'md-list-item')
unrenew_books=[]
for item in loan_items:
try:
# ✅ 1. 必須有書名才是書
title_elem=item.find_elements(By.CSS_SELECTOR, "h3 a")
if not title_elem:
continue
title=title_elem[0].text.strip()
item_text=item.text.strip()
# ✅ 2. 取得到期日
due_text=''
due_elem=item.find_elements(By.XPATH, './/p[@data-qa="automation_mlc_record_date"]')
if due_elem:
m=re.search(r'\d{2}/\d{2}/\d{4}', due_elem[0].text)
if m:
due_text=m.group(0)
# ✅ 3. 只收真正「被預約 / 無法續借」
if any(k in item_text for k in ["被預約", "無法續借", "recall"]):
unrenew_books.append((title, due_text))
except Exception:
continue
if unrenew_books:
msg="被預約的書:\n" + "\n".join(
[f"{i+1:>2}. {t[0]} 到期日: {t[1]}" for i, t in enumerate(unrenew_books)]
)
else:
msg="全部書籍皆已續借"
print('搜尋被預約書籍 ... OK')
except Exception as e:
print(e)
finally:
if browser:
browser.quit() # ✅ 防呆
return msg # ✅ msg 一定有值
if __name__ == '__main__':
start=time.time()
config=dotenv_values('.env')
NKUST_LIB_ID=config.get('NKUST_LIB_ID')
NKUST_LIB_PWD=config.get('NKUST_LIB_PWD')
TELEGRAM_TOKEN=config.get('TELEGRAM_TOKEN')
TELEGRAM_ID=config.get('TELEGRAM_ID')
#print(NKUST_LIB_ID)
#print(NKUST_LIB_PWD)
#print(TELEGRAM_TOKEN)
#print(TELEGRAM_ID)
host_name=socket.gethostname()
print(f'主機 : {host_name}')
msg=get_nkust_lib()
if msg:
now=datetime.now().strftime('%Y-%m-%d %H:%M:%S')
msg='\n' + now + '\n' + msg + '\n' + f'(host_name)'
if asyncio.run(telegram_send_text(msg)):
print('訊息傳送成功!')
else:
print('訊息傳送失敗!')
print(msg)
end=time.time()
print(f'執行時間:{end-start}')
執行結果 :
pi@kaopi3:~ $ python nkust_lib_11.py
主機 : kaopi3
按其他讀者 ... OK
登入系統 ... OK
按名字顯現選單 ... OK
按我的借閱 ... OK
按全部續借 ... OK
搜尋被預約書籍 ... OK
訊息傳送成功!
2026-04-18 11:14:19
全部書籍皆已續借
(kaopi3)
執行時間:358.6611738204956
沒有留言 :
張貼留言