2024年5月24日 星期五

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

由於前一篇測試的篇幅過長, 後續測試紀錄於此篇, 本系列之前的筆記參考 : 



四. 以無頭模式執行 Seleniun 爬蟲 :     

在前一篇測試中執行 Selenium 爬蟲程式時, 呼叫 webdriver.Firefox() 或 webdriver.Chrome() 會自動開啟一個瀏覽器視窗, 然後程式碼會模擬手動操作瀏覽器的程序完成網頁資料的擷取, 但這方式有兩個缺點, 一是讓人感到突兀 (雖然不會干擾桌面其他程式作業); 二是開啟實體瀏覽器需要時間, 使爬蟲執行速度變慢, 無頭模式就是用來解決這兩個問題. 

所謂無頭模式即不開啟實體瀏覽器, 而是由執行實例在背景中默默地執行. 由於 Selenium 改版, u以前開啟無頭模式的做法已被廢棄, 新的方法參考下面教學 :


首先要匯入 Options 類別 :

from selenium.webdriver.firefox.options import Options  

如果使用 Chrome :

from selenium.webdriver.chrome.options import Options  

然後建立 Options 物件, 並在呼叫其 add_argument() 方法時傳入 "--headless" 參數 :

options=Options()  
options.add_argument("--headless")  

最後在建立 WebDriver 物件時傳入這個 Options 物件 :

browser=webdriver.Firefox(options=options)  

如果使用 Chrome :

browser=webdriver.Chrome(options=options)    

將上一篇的程式碼修改如下 :

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

def get_books(account, password):
    try:
        # 登入我的書房
        options=Options()   
        options.add_argument("--headless")   
        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()
    account, password='amy08123', '123456'
    borrow_books, reserve_books=get_books(account, password)
    print(f'借閱書籍:\n{borrow_books}')
    print(f'預約書籍:\n{reserve_books}')
    end=time.time()
    print(f'執行時間:{end-start}')

此處黃底色的為新增的部分. 執行結果如下 :

>>> %Run ksml_personal_4.py    
借閱書籍:
[{'book_name': '一行指令學Python : 用機器學習掌握人工智慧', 'due_date': '2024-06-05', 'due_times': '0'}, {'book_name': '一本精通 Python範例應用大全 : Python詳細語法教學&100+個Python範例', 'due_date': '2024-06-05', 'due_times': '0'}]
預約書籍:
[{'book_name': 'AI黃金時期正好學 : TensorFlow 2高手有備而來', 'sequence': '2'}, {'book_name': 'Python3.x網頁資料擷取與分析特訓教材', 'sequence': '1'}, {'book_name': 'Python機器學習錦囊妙計 : 涵蓋預處理到深度學習的實務處方', 'sequence': '4'}, {'book_name': 'PyTorch深度學習攻略 : 核心開發者親授!', 'sequence': '2'}, {'book_name': '用Python學AIoT智慧聯網', 'sequence': '1'}]
執行時間:19.934646129608154

執行過程中確實不會開啟 Firefox 瀏覽器, 而是在背景中默默執行後輸出結果.  


五. 在 Mapleboard 上測試 Seleniun 爬蟲程式 :     

完成上面測試後本來想將其移植到樹莓派 Pi 3 上跑看看, 今年三月時曾於鄉下老家那台 Pi 3 測試 Selenium + Chronium 成功, 參考 : 


但這次不僅使用 Firefox 的驅動程式失敗, 連之前 OK 的 Chromium 也出現錯誤. 無奈只好轉到 Mapleboard 的 Ubuntu Mate 上試試, 經過一番折騰終於成功, 參考 :


由於在 Mapleboard 上安裝的 Selenium 與在 LG Gram 筆電 Win 11 上安裝的版本一樣都是 v4.21.0, 所以語法幾乎一樣, 只有載入 Firefox 驅動程式的方式不同, 要使用 Service 類別來做, 摘要如下 :

from selenium.webdriver.chrome.service import Service    # 匯入 Service 類別
driverpath='./geckodriver'                     # 指定驅動程式連結的路徑 
firefox_service=Service(driverpath)     # 建立 Service 物件

若要用無頭模式, 則在呼叫 webdriver() 時就要傳入 Options 與 Service 物件了 :

browser=webdriver.Firefox(options=options, service=firefox_service)   

將上面的程式修改如下 :

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.firefox.options import Options
from selenium.webdriver.firefox.service import Service  
import re
import time

def get_books(account, password):
    try:
        # 登入我的書房
        options=Options() 
        options.add_argument("--headless") 
        driverpath='./geckodriver' 
        service=Service(driverpath) 
        browser=webdriver.Firefox(options=options, service=service)  # 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()
    account, password='amy08123', '123456'
    borrow_books, reserve_books=get_books(account, password)
    print(f'借閱書籍:\n{borrow_books}')
    print(f'預約書籍:\n{reserve_books}')
    end=time.time()
    print(f'執行時間:{end-start}')

黃底色部分就是比上面在 Win11 上跑的多出來的部分, 執行結果 OK, 同樣是在背景執行, 不會跳出 Firefox 視窗. 注意, 呼叫 webdriver.Firefox() 時 options 必須是第一參數.  

%Run ksml_books_1.py   
借閱書籍:
[{'book_name': '一行指令學Python : 用機器學習掌握人工智慧', 'due_date': '2024-06-05', 'due_times': '0'}, {'book_name': '一本精通 Python範例應用大全 : Python詳細語法教學&100+個Python範例', 'due_date': '2024-06-05', 'due_times': '0'}]
預約書籍:
[{'book_name': 'AI黃金時期正好學 : TensorFlow 2高手有備而來', 'sequence': '2'}, {'book_name': 'Python3.x網頁資料擷取與分析特訓教材', 'sequence': '1'}, {'book_name': 'Python機器學習錦囊妙計 : 涵蓋預處理到深度學習的實務處方', 'sequence': '4'}, {'book_name': 'PyTorch深度學習攻略 : 核心開發者親授!', 'sequence': '2'}, {'book_name': '用Python學AIoT智慧聯網', 'sequence': '1'}]
執行時間:47.18455147743225

也可以使用 Mapleboard 內建的 Chromium 瀏覽器來測試, 作法參考 : 


將上面的 Firefox 版程式改成 Chromium 版如下 : 

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
import time

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()
    account, password='amy08123', '123456'
    borrow_books, reserve_books=get_books(account, password)
    print(f'借閱書籍:\n{borrow_books}')
    print(f'預約書籍:\n{reserve_books}')
    end=time.time()
    print(f'執行時間:{end-start}')

執行結果如下 :

>>> %Run ksml_books_3.py  
借閱書籍:
[{'book_name': '一行指令學Python : 用機器學習掌握人工智慧', 'due_date': '2024-06-05', 'due_times': '0'}, {'book_name': '一本精通 Python範例應用大全 : Python詳細語法教學&100+個Python範例', 'due_date': '2024-06-05', 'due_times': '0'}]
預約書籍:
[{'book_name': 'AI黃金時期正好學 : TensorFlow 2高手有備而來', 'sequence': '2'}, {'book_name': 'Python3.x網頁資料擷取與分析特訓教材', 'sequence': '1'}, {'book_name': 'Python機器學習錦囊妙計 : 涵蓋預處理到深度學習的實務處方', 'sequence': '4'}, {'book_name': 'PyTorch深度學習攻略 : 核心開發者親授!', 'sequence': '2'}, {'book_name': '用Python學AIoT智慧聯網', 'sequence': '1'}]
執行時間:22.50754141807556

哇, Mapleboard 實在令我驚艷, 本想添購 Pi 4 或 Pi 5 的打算漸漸消融了. 我反覆測試上面 Firefox 與 Chromium 版的程式, 發現 Chromium 跑得比 Firefox 快一倍, 所以正式佈署上線時應該採用 Chromium 來跑較好. 

沒有留言 :