iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 10
0

歡迎來到第十天(三分之一了...),今天要用 Selenium 進行動態爬蟲,首先要先選定一個目標網站進行爬蟲。由於筆者曾經有過到聯合國工作的夢想,因此就決定爬取 UN Career 作為範例。

觀察

依舊,爬蟲的開始從觀察開始,在第一個頁面中我們會發現有五種類型的工作分類分別是:"Professional and higher categories"、"Field services"、"General services and related categories"、"National Professional Officers"、"Internship"

https://ithelp.ithome.com.tw/upload/images/20200923/20128931MKmkJjE7Vj.png

打開 Chrome DevTool 並點擊 "Field services" 會觀察到一個 POST 請求的產生

https://ithelp.ithome.com.tw/upload/images/20200923/20128931i5IIKFSB1d.png

但往下看他的 Form Body 會發現裡面的內容多到眼花撩亂,加上他的規則較難掌握,因此在這個階段會放棄使用模擬 POST 請求的方式拿取所需內容,轉而使用 Selenium 的方式爬取內容。

架構

由於確定策略是使用 Selenium,我們將要完成兩個功能

  • 透過 Seleniunm 操作 Chrome
  • 透過 lxml 讀取 Chrome 所渲染的 HTML,抓取所需資訊

寫扣

由於這次我們要模擬人在瀏覽器上的行為(包含點擊下一頁、點擊下一個分類等)因此必須透過 Selenium 中 getfind_elements_by_xpathclick 的函數。首先比較棘手的問題是要如何模擬人為不斷的點擊下一頁,一直到整個分類的所有頁面都被爬過。透過觀察,會發現在分頁處的 <tr> 標籤有著 pager 的 class name 可以利用,因此可以透過 find_elements_by_xpath("//tr[@class='pager']//table//td/a") 的方式定位出每個分頁按鈕的 <a> 標籤。

from selenium import webdriver

driver = webdriver.Chrome(executable_path = 'Path to webdriver')
url = "https://careers.un.org/lbw/home.aspx?viewtype=SJ&exp=All&level=0&location=All&occup=0&department=All&bydate=0&occnet=0&lang=en-US"
driver.get(url)

elements = driver.find_elements_by_xpath("//tr[@class='pager']//table//td/a") # 定位出 pager

在定位出分頁欄位後,利用一個簡易的 while loop 走過每個分頁。當找出符合條件的 element 時,透過 click 的函數可以模擬點擊該 element 的行為,進而達到前往下一分頁的動作。

page = 1
indexer = 0
elements = driver.find_elements_by_xpath("//tr[@class='pager']//table//td/a")
while True:
    if elements[indexer].text == str(page+1) or (elements[indexer].text == "..." and indexer>3):
        driver.find_elements_by_xpath("//tr[@class='pager']//table//td/a")[indexer].click() # 模擬點擊下一分頁
        elements = driver.find_elements_by_xpath("//tr[@class='pager']//table//td/a") # 重新取得新分頁的分頁列
        page +=1
        indexer = 0
    elif indexer+1 == len(elements):
        break
    indexer += 1

這時候基本上我們已經完成分頁問題,但是!!人生沒有這麼容易,在你執行這套程式碼時應該會發現有時候會出現 IndexError: list index out of range 的錯誤,這是為什麼呢?

Selenium 的特性

在 Selenium 的設定下,只有 get 這個方法會在整個頁面完成讀取後才往下執行下一行程式碼,而在上述程式碼中使用的 click 並不會等到頁面完成讀取才往下繼續執行,因此會發現有時候在頁面還沒完成讀取時就執行了 elements = driver.find_elements_by_xpath("//tr[@class='pager']//table//td/a"),而此時的 elements 即為空集合,因此就會出現 IndexError,該如何避免呢?

此時要利用 WebDriverWaitexpected_conditions 搭配,讓程式碼等待頁面完整讀取後才繼續往下執行。結合了之前的程式碼後就變成這樣

from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By

driver = webdriver.Chrome(executable_path = 'Path to webdriver')
url = "https://careers.un.org/lbw/home.aspx?viewtype=SJ&exp=All&level=0&location=All&occup=0&department=All&bydate=0&occnet=0&lang=en-US"
driver.get(url)

page = 1
indexer = 0
timeout = 5
elements = driver.find_elements_by_xpath("//tr[@class='pager']//table//td/a")
while True:
    if elements[indexer].text == str(page+1) or (elements[indexer].text == "..." and indexer>3):
        driver.find_elements_by_xpath("//tr[@class='pager']//table//td/a")[indexer].click()
        element_p = EC.presence_of_element_located((By.CLASS_NAME, 'pager')) # 條件值
        WebDriverWait(driver, timeout).until(element_p) 
        elements = driver.find_elements_by_xpath("//tr[@class='pager']//table//td/a")
        page +=1
        indexer = 0
    elif indexer+1 == len(elements):
        break
    indexer += 1

到這裡就正式解決了分頁問題!剩下的部份我們明天見!


上一篇
[Day 9] 動態爬蟲 - 1
下一篇
[Day 11] 動態爬蟲 - 3
系列文
資料蒐集與分散式運算 30 天30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言