完成市圖爬蟲後再接再厲, 繼續來寫母校高科大圖書館爬蟲. 高科大是我的第一母校, 距離也很近 (比第二母校中山大學近多了), 而且三校合併之後校友借書量也增為三倍, 真是太佛心了. 這次的爬蟲任務有二 :
- 每天自動續借
- 將被預約書籍用 Line 訊息通知
目的是為了省去常常要登入續借的麻煩 (懶人最愛自動化), 若借閱中書被預約要加速讀完趕快拿去還.
本系列之前的筆記參考 :
一. 檢視目標網頁 :
母校圖書館登入網址如下 :
校友須按下方 "其他讀者" 進去登入網頁 :
輸入帳號與密碼後按底下的 "登入" 鈕就會進入個人書房頁面 :
按右上角的姓名, 於彈出的選單中按 "我的借閱" 會列出借閱書目 :
按右上角的 "全部借閱" 會更新到期日為今日 :
如果都沒被預約順利續借完成, 則書目列表最上面會顯示 "所有借閱資料已成功續借"; 否則會顯示 "部分書籍續借失敗". 因目前都借閱成功, 故先進行自動續借部分的測試但不要佈署, 因為每天自動續借會無法進行下一步測試.
使用 Chrome 擴充套件 Quick Javascript Switch 關閉 Javascript 發現整個網站從登入頁面開始都是透過 Javascript 產生的動態網頁, 因此無法使用 requests 套件來抓, 必須使用 Selenium 才行.
二. 使用 Selenium 擷取網頁 :
先使用 LG 筆電的 Firefox 測試 (因我的 Chrome 版本太新無法與 Selenium 匹配), 完成後再改為 Chromium 版佈署至 Mapleboard 的 Ubuntu Mate 上.
首先在 Chrome 上按 F12 開啟開發者工具視窗, 然後連線上面的圖書館登入網址, 在右邊開發者工具視窗的 Element 頁籤上點一下, 按 Ctrl + F 後在底下輸入框搜尋 "其他讀者" 即可找到此網頁載入之 HTML 碼位置 :
可見這個 "其他讀者" 項目是放在一個自訂標籤 "md-list-item" 中, 改用 md-list-item 搜尋可知整個網頁中有兩個這樣的標籤, "其他讀者" 是第二個, 而第一個則是上面的 "教職員工生".
先匯入 Selenium 相關套件模組 :
>>> import selenium
>>> from selenium import webdriver
>>> from selenium.webdriver.common.by import By
紀錄版本資訊 :
>>> selenium.__version__
'4.11.2'
>>> webdriver.__version__
'4.11.2'
建立 WebDriver 物件並連線目標網站 :
>>> browser=webdriver.Firefox()
>>> url='https://nkust.primo.exlibrisgroup.com/discovery/login?vid=886NKUST_INST:86NKUST&lang=zh-tw'
>>> browser.get(url)
>>> browser.implicitly_wait(10)
使用標籤名稱搜尋 md-list-item 元素 :
>>> md_list=browser.find_elements(By.TAG_NAME, 'md-list-item')
>>> len(md_list)
2
>>> type(md_list[1])
<class 'selenium.webdriver.remote.webelement.WebElement'>
可見此網頁中確實有兩個 md-list-item 標籤, 我們的目標是其中的第二個 WebElement 物件, 只要呼叫其 click() 方法即可進入 "其他讀者" 的登入頁面 :
>>> md_list[1].click()
這時在開發者工具的 Element 頁籤搜尋 "讀者證號" 即可找到登入頁面中填寫帳號密碼的兩個輸入框, 這兩個 input-text 元素的 id 分別為 LoginUserName 與 LoginPassword :
可以用 id 來取得這兩個輸入框的 WebElement 物件並呼叫 send_key() 填入帳密 :
>>> login_user_name=browser.find_element(By.ID, 'LoginUserName')
>>> login_user_name.send_keys('MyID')
>>> login_password=browser.find_element(By.ID, 'LoginPassword')
>>> login_password.send_keys('MyPassword')
搜尋 "登入" 可以找到登入按鈕 button 元素 :
但卻沒有 name 或 id 屬性來取得此 button 元素, 只有 class 屬性可用來識別它, 搜尋其中的第一個樣式類別 button-large 發現可找到兩個, 不過其中第一個是 icon-button-class 樣式, 所以其實 button-large 樣式只有一個, 只要用 CLASS_NAME 搜尋就可以找到它 :
>>> login_btn=browser.find_element(By.CLASS_NAME, 'button-large')
>>> login_btn.get_attribute('type') # 確認此為 Submit 按鈕
'submit'
最後呼叫其 click() 方法登入 :
>>> login_btn.click()
成功登入網站後, 按右上方的登入者姓名會彈出一張選單, 按其中的 "我的借閱" 即可得到目標網頁, 但首先是要找到此登入者姓名按鈕, 在開發者工具的 Element 頁籤搜尋登入者姓名即可找到一個有 user-button 樣式類別的按鈕 :
搜尋 user-button 只有找到一個, 因此具有此樣式類別的元素只有一個, 可以用 By.CLASS_NAME 來定位它, 然後呼叫其 click() 方法來顯示下拉式選單 :
>>> user_btn=browser.find_element(By.CLASS_NAME, 'user-button')
>>> user_btn.click()
到這一步時瀏覽器右上角就出現下拉式選單了 :
接下來要在此下拉式選單網頁中取得 "我的借閱" 按鈕的 WebElement 物件. 在開發者工具的 Element 頁籤搜尋 "我的借閱" 可以找到此 button 元素, 它同樣是沒有 name 或 id 屬性 :
嘗試用它的第一個樣式類別 md-button 去搜尋 (其實它具有多個樣式類別 md-button md-primoExplore-theme md-ink-ripple), 發現有多達 20 個元素使用了此 class 名稱 (我的借閱是其中第 15 個), 雖然也可以用索引 [14] 來定位它, 但這次想改用 XPATH, 用滑鼠左鍵點一下此按鈕, 然後按滑鼠右鍵依序選 "Copy -> Copy Full XPath" :
把它放在一個變數中, 然後呼叫 find_element() 用 XPATH 來定位 "我的借閱" 按鈕, 最後呼叫此按鈕 WebElement 物件之 click() 方法即可得到目標網頁 :
>>> xpath='/html/body/div[3]/md-menu-content/md-menu-item[3]/button'
>>> my_borrow=browser.find_element(By.XPATH, xpath)
>>> my_borrow.click()
只要按右上角的 "全部續借" 鈕就達成 1/2 任務了, 但要先取得此按鈕之 WebElement 物件, 在開發者工具的 Element 頁籤搜尋 "全部續借" 可以找到 4 個 "全部續借" 按鈕, 我們的目標是其中第 3 個不要找錯了 :
同樣用上面的方法複製此按鈕的 XPATH :
儲存至 xpath 變數 :
>>> 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'
取得 "全部續借" 按鈕之 WebElement 物件 :
>>> all_borrow=browser.find_element(By.XPATH, xpath)
>>> all_borrow.get_attribute('class') # 確認是否取得物件
'button-link md-button md-primoExplore-theme'
最後呼叫 click() :
>>> all_borrow.click()
這樣就取得目標網頁了, 如果借閱的書籍無人預約, 那麼全部書籍都會續借成功, 會在書目列表最上面顯示 "所有借閱資料已成功續借" :
如果借閱書籍中有被預約, 則該書無法續借, 這時會顯示 "只有部分借閱資料已成功續借" :
我們必須擷取這個續借結果, 如果是 "所有借閱資料已成功續借", 則爬蟲就完成任務了; 但若是 "只有部分借閱資料已成功續借", 則必須看看哪幾本書被預約, 將其到書名與到期日透過 Line Notify 推播出來.
在 Element 中搜尋 "所有借閱資料已成功續借" 或 "只有部分借閱資料已成功續借" 可以發現續借結果放在一個自訂標籤 prm-alert-bar 裡面的 span 元素內 :
此 span 元素的 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
但此處我們要嘗試另一種元素定位方式, 利用 span 元素的上層節點 prm-alert-bar 的 WebElement 物件來搜尋 span 元素, 在 Element 中搜尋 "prm-alert-bar" 發現網頁中共有兩個 prm-alert-bar 元素, 我們的目標元素位於其中的第一個 :
因此只要呼叫 find_element() 就可以找到它 :
>>> prm_alert_bar=browser.find_element('tag name', 'prm-alert-bar')
>>> prm_alert_bar.get_attribute('ng-if') # 確認有取得 prm-alert-bar 元素
'!$ctrl.isLoadingLoans'
然後用 prm_alert_bar 這元素的 WebElement 物件去搜尋裡面的 span 元素會找到 3 個, 其中索引 0 是包覆索引 1, 而索引 2 則是 "解除" 超連結 :
>>> alert_spans=prm_alert_bar.find_elements('tag name', 'span')
>>> len(alert_spans)
3
>>> alert_spans[0].text
'只有部分借閱資料已成功續借。'
>>> alert_spans[1].text
'只有部分借閱資料已成功續借。'
>>> alert_spans[2].text
'解除'
所以呼叫 find_element() 就可以了 :
>>> alert_span=prm_alert_bar.find_element('tag name', 'span')
>>> alert_span.text
'只有部分借閱資料已成功續借。'
當然直接用 XPATH 亦可 :
>>> 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)
上面操作的完整程式碼如下 :
from selenium import webdriver
from selenium.webdriver.common.by import By
browser=webdriver.Firefox()
url='https://nkust.primo.exlibrisgroup.com/discovery/login?' +\
'vid=886NKUST_INST:86NKUST&lang=zh-tw'
browser.get(url)
browser.implicitly_wait(20)
# 按其他讀者
md_list=browser.find_elements(By.TAG_NAME, 'md-list-item')
md_list[1].click()
# 登入系統
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()
# 按名字顯現選單
user_btn=browser.find_element(By.CLASS_NAME, 'user-button')
user_btn.click()
# 按我的借閱鈕
xpath='/html/body/div[3]/md-menu-content/md-menu-item[3]/button'
my_borrow=browser.find_element(By.XPATH, xpath)
my_borrow.click()
# 按全部續借
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)
all_borrow.click()
# 檢查續借結果
#prm_alert_bar=browser.find_element('tag name', 'prm-alert-bar')
#alert_span=prm_alert_bar.find_element('tag name', 'span')
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:
print('所有借閱資料已成功續借')
else:
print('只有部分借閱資料已成功續借')
# waiting to continue ...
browser.close()
事實上所有的元素定位都可以完全使用 XPATH, 即使要定位之元素有 id 屬性或惟一的 name 或 class 名稱, 都可以用 XPATH 取代.
2024-06-11 補充 :
由於借閱書目以分頁方式呈現, 每頁 10 本書, 如果超過 10 本會在底下出現 "載入更多結果" 按鈕, 而被預約的書會列在所有書目的最後面, 故要取得被預約的書必須按此按鈕到最後一個分頁 :
我最多只能借 30 本, 通常都是借好借滿, 因此要按兩次到第三分頁才能知道哪些書被預約了. 在 Element 中搜尋 "載入更多結果" 可以找到這個按鈕的 HTML 碼 :
可見它具有 class="button-confirm" 樣式類別, 且搜尋 "button-confirm" 只出現一個 :
當進入 "我的借閱" 第一頁時可以用 By.CLASS_NAME 來取得此按鈕 : 到最後一頁時即無此按鈕, 因此可以用 try except 來捕捉是否有此按鈕, 如果沒有表示已到最後一頁.
>>> load_more=browser.find_elements(By.CLASS_NAME, 'button-confirm')
>>> len(load_more)
1
有找到 "載入更多結果" 按鈕表示還有其他借閱書籍, 按此按鈕進入第二頁 :
>>> load_more[0].click()
再次搜尋 "載入更多結果" 按鈕發現還有, 表示有第三頁, 繼續按 :
>>> load_more=browser.find_elements(By.CLASS_NAME, 'button-confirm')
>>> len(load_more)
1
>>> load_more[0].click()
進入到第三頁時就找不到此按鈕了, 表示此為最後一頁 :
>>> load_more=browser.find_elements(By.CLASS_NAME, 'button-confirm')
>>> len(load_more)
0
那些被預約的書就列在此頁表格的最底下幾列 :
因為目前沒有任何借閱書被預約, 因此只能先假設最後一本書被預約, 暫時只取出這本書的書名回傳, 等實際出現被預約書時再來修改.
在 Chrome 開發者工具之 Element 視窗搜尋最後一本書的書名 "Python資料可視化" 發現此表格的每一本書的資訊都是放在自訂標籤 md-list-item 內 :
書名可從其內之 h3 或 a 標籤取得, 但 a 元素裡面的資訊最完整 (包含是否被預約), 因此應從 a 元素中擷取, 書名就在 a 元素的 aria-label 屬性裡 :
在最後一頁 (第三頁) 搜尋 "<a" 發現在滿借 30 本書情況下總共有 38 的 a 元素, 書目資料是從第 9 個 a 元素開始, 可見在最後一頁搜尋 a 元素時實際上是搜尋全部借閱書籍, 而非只有第三頁那 10 本書.
但實際測試卻只找到 37 個 a 元素, 而且第一本書 "Python for Excel" 不見了, 第一本書是在第 11 個 a 元素 (索引 10), 不知道這跟用 "<a" 在 Element 中搜尋差在哪裡 :
>>> a_links=browser.find_elements(By.TAG_NAME, 'a')
>>> len(a_links)
37
>>> a_links[9].get_attribute('aria-label')
'在新分頁開啟RefWorks'
>>> a_links[10].get_attribute('aria-label')
'自然語言處理最佳實務 : 全面建構真正的NLP系統 / Sowmya Vajjala等著 ; 賴屹民譯 ,在新視窗中開啟'
可以在 for 迴圈中用 enumerate() 掃描這些 a 元素 :
>>> for idx, a_link in enumerate(a_links):
print(f"{idx} : {a_link.get_attribute('aria-label')}")
0 : 跳轉到主功能表
1 : 跳轉到我的帳戶總覽
2 : None
3 : None
4 : 圖書館,在新視窗中開啟
5 : 使用說明,在新視窗中開啟
6 : 全部館藏
7 : 期刊查詢
8 : 我的收藏
9 : 在新分頁開啟RefWorks
10 : 自然語言處理最佳實務 : 全面建構真正的NLP系統 / Sowmya Vajjala等著 ; 賴屹民譯 ,在新視窗中開啟
11 : Python3.x機器學習基礎與應用特訓教材 : 軟體設計領域 / 林英志編著 (c.2) ,在新視窗中開啟
12 : PyTorch深度學習攻略 : 核心開發者親授! / Eli Stevens, Luca Antiga, Thomas Viehmann著 ; 黃駿譯 ,在新視窗中開啟
13 : Python資料分析 : 用pandas、numpy和ipython做資料分析 / Wes McKinney原著 ; 張靜雯譯 (c.2) ,在新視窗中開啟
14 : 駕馭Chat GPT 4 : 探索Azure OpenAI與Cognitive Service for Language開發實踐(使用.NET與Node.js) / 柯克(Ko Ko), 陳葵懋(Ian Chen), Ryan Chung著 ,在新視窗中開啟
15 : 一行指令學 Python : 用機器學習掌握人工智慧 / 徐聖訓編著 ,在新視窗中開啟
16 : Python實戰聖經 : 用簡單強大的模組套件完成最強應用 = Python development bible / 文淵閣工作室編著 ,在新視窗中開啟
17 : Python從初學到生活應用超實務 : 讓Python幫你處理日常生活與工作中繁瑣重複的工作 / 陳會安著 (c.3) ,在新視窗中開啟
18 : Deep Learning . 3 . 用Python進行深度學習框架的開發實作 / 斎藤康毅著 ; 吳嘉芳譯 ,在新視窗中開啟
19 : 超圖解Python程式設計入門 = Illustrated Python programmingguide / 趙英傑作 ,在新視窗中開啟
20 : 東京大學資料科學家養成全書 : 使用Python動手學習資料分析 / 塚本邦尊, 山田典一, 大澤文孝著 ; 莊永裕譯 (c.2) ,在新視窗中開啟
21 : Deep Learning 2 : 用Python進行自然語言處理的基礎理論實作 / 斎藤康毅著 ; 吳嘉芳譯 ,在新視窗中開啟
22 : 金融AI : 人工智慧的金融應用 / Yves Hilpisch著 ; 陳仁和譯 ,在新視窗中開啟
23 : PyTorch 自然語言處理 : 以深度學習建立語言應用程式 / Delip Rao, Brian McMahan著 ; 楊尊一譯 ,在新視窗中開啟
24 : 深度學習的16堂課 : CNN.RNN.GAN.DQN.DRL 看得懂、學得會、做得出! / Jon Krohn, Grant Beyleveld, Aglaé Bassens作 ; 黃駿, 哈雷譯 (c.2) ,在新視窗中開啟
25 : 用Excel學Python資料分析 / 張俊紅著 ,在新視窗中開啟
26 : 偏不讓你抓 : 最強Python爬蟲vs反爬蟲大戰實錄 / 韋世東作 ,在新視窗中開啟
27 : 金融人才×機器學習聯手出擊 : 專為FinTech領域打造的機器學習指南 / Jannes Klaas著 ; 彭勝陽譯 (c.2) ,在新視窗中開啟
28 : Python網頁框架超集合 : 在Django、Tornado、Flask、Twisted全面應用 / 劉長龍作 ,在新視窗中開啟
29 : 人工智慧Python基礎課 : 用Python分析了解你的資料 / 陳會安作 ,在新視窗中開啟
30 : 全格局使用PyTorch : 基礎篇 / 深度學習和圖神經網路. 李金洪著 ,在新視窗中開啟
31 : Python資料科學與機器學習 : 從入門到實作必備攻略 / Frank Kane著 ; 陳光欣譯 ,在新視窗中開啟
32 : 區塊鏈生存指南 : 帶你用Python寫出區塊鏈! / 李耕銘著 ,在新視窗中開啟
33 : 全格局使用PyTorch : 實戰篇 / 深度學習和圖神經網路. 李金洪著 ,在新視窗中開啟
34 : 人工智慧 : 概念應用與管理 = Artificial intelligence : concept application and management / 林東清著 ,在新視窗中開啟
35 : 必學!Python 資料科學.機器學習最強套件 : NumPy Pandas Matplotlib OpenCV scikit-learn tf.Keras / 石川聡彦作 ; 劉金讓譯 (c.3) ,在新視窗中開啟
36 : Python資料可視化攻略 / 小久保奈都彌著 ; 許郁文譯 ,在新視窗中開啟
不過也沒差, 反正目前要擷取的是最後一本書的書名 (假設只被預約一本), 所以只要取出索引 [-1] 那本即可 :
>>> a_links[36].get_attribute('aria-label')
'Python資料可視化攻略 / 小久保奈都彌著 ; 許郁文譯 ,在新視窗中開啟'
>>> a_links[-1].get_attribute('aria-label')
'Python資料可視化攻略 / 小久保奈都彌著 ; 許郁文譯 ,在新視窗中開啟'
測試發現在最後一頁搜尋全部借閱書籍時排序不會每次一樣, 因此抓出來的最後一本書是哪一本也不固定, 但如果有書被預約, 則一定放在最後面.
以上單行指令測試都沒問題, 但寫成單一程式執行時卻不是很穩定, 有時可順利執行完, 有時出現錯誤, 通常在點名字要叫出選單按 "我的借閱" 時出現如下例外訊息 :
"Message: Element <button class="md-button md-primoExplore-theme md-ink-ripple" type="button"> is not clickable at point (986,197) because another element <md-backdrop class="md-menu-backdrop md-click-catcher ng-animate ng-enter md-primoExplore-theme ng-enter-active"> obscures it"
似乎是呼叫 click() 時滑鼠位置是在不可按的地方, 查詢找到下面這篇文章 :
此文提出幾種解決方法, 例如先呼叫 maximize_window() 方法將瀏覽器視窗放到最大, 但測試發現還是不穩定, 其次使用隱式等待, 但這也早就已經在用了, 最後是使用滑鼠動作鏈 ActionChains 類別, 經測試有效, 但對於 "載入更多結果" 按鈕卻無效, 可能是書目表格長度會影響該按鈕之座標所致.
修改後的程式碼如下 :
# nkust_lib_3.py
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
browser=webdriver.Firefox()
url='https://nkust.primo.exlibrisgroup.com/discovery/login?' +\
'vid=886NKUST_INST:86NKUST&lang=zh-tw'
browser.get(url)
browser.implicitly_wait(20)
# 按其他讀者
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:
print('所有借閱資料已成功續借')
else:
print('只有部分借閱資料已成功續借')
# 檢查是否有 "載入更多結果" 按鈕
for i in range(3): # 最多 3 頁
load_more=browser.find_elements('class name','button-confirm')
if len(load_more)==0: # 最後一頁: 不用再按
break
else: # 不是最後一頁: 繼續按 "載入更多結果" 按鈕
load_more[0].click() # 按 "載入更多結果" 至下一頁
# 搜尋全部借閱書籍
a_links=browser.find_elements(By.TAG_NAME, 'a')
last_book=a_links[-1].get_attribute('aria-label') # 最後一本書 (暫用)
print(last_book)
browser.close()
執行結果如下 :
>>> %Run nkust_lib_3.py
按其他讀者 ... OK
登入系統 ... OK
按名字顯現選單 ... OK
按我的借閱 ... OK
按全部續借 ... OK
只有部分借閱資料已成功續借
金融AI : 人工智慧的金融應用 / Yves Hilpisch著 ; 陳仁和譯 ,在新視窗中開啟
書名後面固定都會有 "在新視窗中開啟", 這是 aria-label 的屬性值 中本來就有的, 雖然可以用 replace() 去除, 其實直接用 a 元素物件的 text 屬性值即可 :
last_book=a_links[-1].text
三. 寫成爬蟲函式 :
將上面的測試程式改寫為如下的函式 get_ukas :
# nkust_lib_4.py
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
import time
def get_nkust_lib():
try:
browser=webdriver.Firefox()
browser.implicitly_wait(30)
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')
#xpath='/html/body/primo-explore/div/prm-explore-main/div' +\
#'/prm-topbar/div/prm-user-area-expandable/md-menu/button'
#user_btn=browser.find_element(By.XPATH, xpath)
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('class name','button-confirm')
if len(load_more)==0: # 最後一頁
break
else:
load_more[0].click() # 按 "載入更多結果" 至下一頁
# 找尋借閱書籍
a_links=browser.find_elements(By.TAG_NAME, 'a')
last_book=a_links[-1].text
msg += '\n' + last_book
except Exception as e:
print(e)
finally:
browser.close()
return msg
if __name__ == '__main__':
start=time.time()
msg=get_nkust_lib()
print(msg)
end=time.time()
print(f'執行時間:{end-start}')
執行結果如下 :
>>> %Run nkust_lib_4.py
按其他讀者 ... OK
登入系統 ... OK
按名字顯現選單 ... OK
按我的借閱 ... OK
按全部續借 ... OK
只有部分借閱資料已成功續借
Python3.x機器學習基礎與應用特訓教材 : 軟體設計領域 / 林英志編著 (c.2)
執行時間:39.66444277763367
四. 用 Line Notify 推播續借資訊 :
設定 Line Notify 的方法參考下面這篇 :
由於最終是要佈署在 Mapleboard 上用 Chromium 以無頭方式執行, 程式修改如下 :
# nkust_lib_6.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
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_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(30)
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('class name','button-confirm')
if len(load_more)==0: # 最後一頁
break
else:
load_more[0].click() # 按 "載入更多結果" 至下一頁
# 找尋借閱書籍
a_links=browser.find_elements(By.TAG_NAME, 'a')
last_book=a_links[-1].text
msg += '\n' + '❶ ' + last_book
except Exception as e:
print(e)
finally:
browser.close()
return msg
if __name__ == '__main__':
start=time.time()
msg=get_nkust_lib()
token='7CLpVmFpNihuN6GB0bQcc5M1nOhpAtony1966QFMgzz' # 範例權仗
if msg:
now=datetime.now().strftime('%Y-%m-%d %H:%M:%S')
msg='\n' + now + '\n' + msg
code=line_notify(msg, token)
if code==200:
print('Line 訊息發送成功!')
else:
print(f'Line 訊息發送失敗! (code={code})')
print(msg)
end=time.time()
print(f'執行時間:{end-start}')
存檔後用 chmod 更改為可執行檔 :
tony1966@LX2438:~/python$ sudo chmod +x nkust_lib_6.py
[sudo] tony1966 的密碼:
tony1966@LX2438:~/python$ ls -ls
總用量 36
0 lrwxrwxrwx 1 tony1966 tony1966 29 May 25 13:56 geckodriver -> /snap/bin/firefox.geckodriver
4 -rw-rw-r-- 1 tony1966 tony1966 2849 May 26 00:36 ksml_books_1.py
4 -rw-rw-r-- 1 tony1966 tony1966 3090 May 26 00:31 ksml_books_2.py
4 -rw-rw-r-- 1 tony1966 tony1966 2870 May 26 00:19 ksml_books_3.py
4 -rw-rw-r-- 1 tony1966 tony1966 3095 May 26 14:12 ksml_books_4.py
4 -rw-rw-r-- 1 tony1966 tony1966 3095 May 28 20:43 ksml_books_5.py
8 -rwxrwxr-x 1 tony1966 tony1966 6106 Jun 6 19:06 ksml_books_7.py
8 -rwxrwxr-x 1 tony1966 tony1966 4433 Jun 12 23:01 nkust_lib_6.py
先手動執行 :
tony1966@LX2438:~/python$ /usr/bin/python3 /home/tony1966/python/nkust_lib_6.py
按其他讀者 ... OK
登入系統 ... OK
按名字顯現選單 ... OK
按我的借閱 ... OK
按全部續借 ... OK
Line 訊息發送成功!
2024-06-12 23:06:07
❖ 只有部分借閱資料已成功續借
❶ 用Excel學Python資料分析 / 張俊紅著
執行時間:25.12677836418152
結果如下 :
用 crontab -e 修改 Cron table 加入此程式之定時執行設定 :
tony1966@LX2438:~/python$ crontab -e
tony1966@LX2438:~/python$ crontab -l
0 6,16 * * * /usr/bin/python3 /home/tony1966/python/ksml_books_7.py
0 6,16 * * * /usr/bin/python3 /home/tony1966/python/nkust_lib_6.py
參考 :
2024-09-10 補充 :
這兩天終於有書被預約要還, 但似乎書目不對, Line 通知的訊息如下 :
但我登入去查看是這兩本 :
就是上回尚未未收尾部分, 要看看是哪裡有錯.
沒有留言:
張貼留言