【Python 網路爬蟲筆記】Selenium Library、爬取 Hackmd 文章專題 - part 4 感謝你點進本篇文章!!我是 LukeTseng,一個熱愛資訊的無名創作者,由於近期大學開設大數據分析程式設計這門課程,裡面談到了爬蟲概念,讓我激起一些興趣,因而製作本系列筆記。
聲明:本篇筆記僅供個人學習用途,斟酌參考。
本篇筆記使用 Jupyter Notebook,搭載 Anaconda 虛擬環境,如需下載者可至該網址:https://www.anaconda.com/download
安裝 Selenium 模組 透過以下指令:
在 Jupyter Notebook(安裝完後記得 Restart Kernel 才會啟用):
什麼是 Selenium? Selenium 是一種開源的網頁瀏覽器自動化工具,可以透過程式碼來模擬 user 在瀏覽器上的各種操作(像人一樣),從而完成自動化測試或網頁爬蟲任務。
Selenium 就是動態爬蟲中應用到最重要的技術。
Selenium 用 WebDriver 來驅動與控制瀏覽器,每種瀏覽器都有專屬的 WebDriver,Chrome 就是用 ChromeDriver、Firefox 用 GeckoDriver、Safari 用 SafariDriver 等等。
總之 WebDriver 是在 Selenium 裡面中最常使用到的技術,因為它可模擬很多 user 的操作像點擊元素、填寫表單、捲動頁面等等。
本篇筆記使用 Chrome 瀏覽器進行操作與編寫程式,要下載 ChromeDriver 可至該網站:https://sites.google.com/chromium.org/driver/downloads
記得要下載目前正使用對應的 Chrome 版本,具體如何看自己的 Chrome 版本?如下圖所示。
不過也可以不用下載,因為有另一個模組就的指令是可以在程式執行時自動下載,會在下一節介紹。
自動安裝 WebDriver 的方法(懶人法) 首先先安裝 webdriver-manager:
1 pip install webdriver-manager
之後在程式中即可使用:
1 2 3 4 5 6 from selenium import webdriverfrom selenium.webdriver.chrome.service import Servicefrom webdriver_manager.chrome import ChromeDriverManagerdriver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))
第一支 selenium 程式 以下是一個小範例,會透過 selenium 自動化程式開啟瀏覽器,並前往指定網站。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from selenium import webdriverfrom selenium.webdriver.chrome.service import Servicefrom webdriver_manager.chrome import ChromeDriverManagerdriver = webdriver.Chrome(service=Service(ChromeDriverManager().install())) driver.get('https://www.google.com' ) import timetime.sleep(5 ) driver.quit()
在當建立 WebDriver 物件的時候,瀏覽器就會開起來了。
當程式執行後,會出現 Chrome 目前受到自動測試軟體控制的字樣,這是正常的,表示程式執行成功。
由於有設定暫停 5 秒的緣故,所以 5 秒後瀏覽器就會關閉了。
元素定位操作 可以使用 .find_element() 方法定位網頁元素,內部的第一個參數主要語法是 By.XXX,而 XXX 則是要定位元素的方法,具體如下範例所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 from selenium import webdriverfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.chrome.service import Servicefrom webdriver_manager.chrome import ChromeDriverManagerdriver = webdriver.Chrome(service=Service(ChromeDriverManager().install())) driver.get('https://www.google.com' ) search_box = driver.find_element(By.ID, "APjFqb" ) search_box = driver.find_element(By.NAME, "q" ) element = driver.find_element(By.CLASS_NAME, "gLFyf" ) search_box = driver.find_element(By.CSS_SELECTOR, "textarea[name='q']" ) search_box = driver.find_element(By.XPATH, "//textarea[@name='q']" )
XPATH 取得的方式很簡單,只要對某個元素按下右鍵 -> Copy -> Copy XPATH 即可取得。
日後要定位元素也建議使用 XPATH 的方式。
常用操作方法 在定位網頁元素後,接下來要做的就是類似像點擊、捲動畫面等操作了,如:
.send_keys() 輸入文字.submit() 提交表單(模擬按下 Enter).click() 按下滑鼠左鍵詳細方法可至 Selenium 官方網站:https://www.selenium.dev/documentation/webdriver/elements/interactions/
然後可到該網站進行一些 Selenium 的測試與學習:https://example.oxxostudio.tw/python/selenium/demo.html
以下是一個小範例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 from selenium import webdriverfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.chrome.service import Servicefrom webdriver_manager.chrome import ChromeDriverManagerdriver = webdriver.Chrome(service=Service(ChromeDriverManager().install())) driver.get('https://example.oxxostudio.tw/python/selenium/demo.html' ) time.sleep(5 ) text_box = driver.find_element(By.XPATH, '//*[@id="show"]' ) text_box.send_keys("Hello World!" ) B = driver.find_element(By.XPATH, '/html/body/button[2]' ) B.click() add_number = driver.find_element(By.XPATH, '//*[@id="add"]' ) add_number.click() time.sleep(10 ) driver.quit()
取得網頁元素內容 方法 說明 範例 .text取得元素的純文字內容 element.text.get_attribute('屬性名')取得元素的指定 HTML 屬性值 element.get_attribute('href').id取得元素的 id element.id.tag_name取得元素的標籤名稱 element.tag_name.size取得元素的尺寸(長寬) element.size.is_displayed()判斷元素是否顯示在頁面上 element.is_displayed().is_enabled()判斷元素是否可用 element.is_enabled().is_selected()判斷元素是否被選取(如 checkbox) element.is_selected().parent取得元素的父元素 element.parent.screenshot('檔名.png')將元素截圖並儲存為圖片 element.screenshot('test.png')
From Selenium 函式庫 - Python 網路爬蟲教學 | STEAM 教育學習網
以下是對於測試網站所做的範例(改自 Selenium 函式庫 - Python 網路爬蟲教學 | STEAM 教育學習網 ):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 from selenium import webdriverfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.chrome.service import Servicefrom webdriver_manager.chrome import ChromeDriverManagerdriver = webdriver.Chrome(service=Service(ChromeDriverManager().install())) driver.get('https://example.oxxostudio.tw/python/selenium/demo.html' ) a = driver.find_element(By.ID, 'a' ) b = driver.find_element(By.CLASS_NAME, 'btn' ) c = driver.find_element(By.CSS_SELECTOR, '.test' ) d = driver.find_element(By.NAME, 'dog' ) h1 = driver.find_element(By.TAG_NAME, 'h1' ) link1 = driver.find_element(By.LINK_TEXT, '我是超連結,點擊會開啟 Google 網站' ) link2 = driver.find_element(By.PARTIAL_LINK_TEXT, 'Google' ) print ("a.id:" , a.id ) print ("b.text:" , b.text) print ("c.tag_name:" , c.tag_name) print ("d.size:" , d.size) print ("link1.href:" , link1.get_attribute('href' )) print ("link2.target:" , link2.get_attribute('target' )) print ("h1.is_displayed:" , h1.is_displayed()) print ("d.is_enabled:" , d.is_enabled()) print ("a.is_selected:" , a.is_selected()) body = driver.find_element(By.TAG_NAME, 'body' ) body.screenshot('./test.png' ) driver.quit()
輸出結果如下:
在外面額外產生了 test.png 的截圖。
專題:爬取 Hackmd 作者頁面的所有文章資訊 以下透過我個人 Hackmd 頁面進行爬取,首先爬取第一頁的每篇文章標題。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from selenium import webdriverfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.chrome.service import Servicefrom webdriver_manager.chrome import ChromeDriverManagerimport timedriver = webdriver.Chrome(service=Service(ChromeDriverManager().install())) driver.get('https://hackmd.io/@LukeTseng' ) time.sleep(3 ) titles = driver.find_elements(By.CSS_SELECTOR, 'span.line-clamp-1.flex-1.text-lg.font-semibold' ) for title in titles: print (title.text) driver.quit()
輸出結果:
如果想要爬取每一頁的內容資訊怎麼辦?可以觀察到 HackMD 沒有下一頁的功能,因此需要自己實作。
從下圖中打開【檢查】,可以分析一下每個頁面的結構長怎樣,這邊實測發現每個頁面按鈕都是一樣的元素所組成,另外我也特別觀察了一下每個元素的 XPATH,他最後面都有特定的規律,如:
//*[@id="hackmd-app"]/section/main/div/div[2]/div/div/div[2]/div/ul/li[1]/a//*[@id="hackmd-app"]/section/main/div/div[2]/div/div/div[2]/div/ul/li[2]/a//*[@id="hackmd-app"]/section/main/div/div[2]/div/div/div[2]/div/ul/li[3]/a上面這些都是頁面 1、2、3 的元素,其他部分都沒有更動,唯一更動的地方只有最後面的 /li[1],他裡面的中括號的數字會動,所以可以考慮使用 string format 去變動。
以下是程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 from selenium import webdriverfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.support.ui import WebDriverWaitfrom selenium.webdriver.support import expected_conditions as ECfrom selenium.webdriver.chrome.service import Servicefrom webdriver_manager.chrome import ChromeDriverManagerimport timedriver = webdriver.Chrome(service=Service(ChromeDriverManager().install())) driver.get('https://hackmd.io/@LukeTseng' ) page_num = 1 for page_num in range (1 , 15 ): print (f"正在爬取第 {page_num} 頁..." ) WebDriverWait(driver, 10 ).until( EC.presence_of_all_elements_located((By.CSS_SELECTOR, 'span.line-clamp-1.flex-1.text-lg.font-semibold' )) ) titles = driver.find_elements(By.CSS_SELECTOR, 'span.line-clamp-1.flex-1.text-lg.font-semibold' ) for title in titles: print (f" - {title.text} " ) if page_num < 14 : try : next_page_button = WebDriverWait(driver, 10 ).until( EC.element_to_be_clickable((By.XPATH, f'//*[@id="hackmd-app"]/section/main/div/div[2]/div/div/div[2]/div/ul/li[{page_num + 1 } ]/a' )) ) next_page_button.click() time.sleep(2 ) except Exception as e: print (f"無法點擊第 {page_num + 1 } 頁: {e} " ) break print ("所有頁面都爬完了!" )driver.quit()
輸出結果:
其中以下程式碼是 Selenium 的顯式等待(Explicit Wait)機制,能自動等待頁面 loading 完成,像是以下就是等待每個標題元素加載完成才繼續執行程式碼。
而其中 WebDriverWait 的第二個參數 10 就是最多等待 10 秒的意思。
until 為持續檢查某個條件,直到條件成立或超時。
EC.presence_of_all_elements_located(...) 是等待的條件,意思是等待「所有符合條件的元素都出現在網頁的 DOM 結構中」。
1 2 3 WebDriverWait(driver, 10 ).until( EC.presence_of_all_elements_located((By.CSS_SELECTOR, 'span.line-clamp-1.flex-1.text-lg.font-semibold' )) )
爬取完標題後,可以來爬取每篇文章的日期、瀏覽量,甚至可以算出所有文章的總瀏覽量:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 from selenium import webdriverfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.support.ui import WebDriverWaitfrom selenium.webdriver.support import expected_conditions as ECfrom selenium.webdriver.chrome.service import Servicefrom webdriver_manager.chrome import ChromeDriverManagerimport timedriver = webdriver.Chrome(service=Service(ChromeDriverManager().install())) driver.get('https://hackmd.io/@LukeTseng' ) page_num = 1 all_views = 0 for page_num in range (1 , 15 ): print (f"正在爬取第 {page_num} 頁..." ) WebDriverWait(driver, 10 ).until( EC.presence_of_all_elements_located((By.CSS_SELECTOR, 'span.line-clamp-1.flex-1.text-lg.font-semibold' )) ) WebDriverWait(driver, 10 ).until( EC.presence_of_all_elements_located( (By.CSS_SELECTOR, 'div.text-text-subtle > span:first-child' ) ) ) WebDriverWait(driver, 10 ).until( EC.presence_of_all_elements_located( (By.XPATH, "//a[i[contains(@class, 'ph-eye')]]" ) ) ) titles = driver.find_elements(By.CSS_SELECTOR, 'span.line-clamp-1.flex-1.text-lg.font-semibold' ) dates = driver.find_elements(By.CSS_SELECTOR, 'div.text-text-subtle > span:first-child' ) views = driver.find_elements(By.XPATH, "//a[i[contains(@class, 'ph-eye')]]" ) for title, date, view in zip (titles, dates, views): print (f" - {title.text} \n 日期:{date.text} \n 瀏覽量:{view.text} \n" ) all_views += int (view.text.replace(',' , '' )) if page_num < 14 : try : next_page_button = WebDriverWait(driver, 10 ).until( EC.element_to_be_clickable((By.XPATH, f'//*[@id="hackmd-app"]/section/main/div/div[2]/div/div/div[2]/div/ul/li[{page_num + 1 } ]/a' )) ) next_page_button.click() time.sleep(2 ) except Exception as e: print (f"無法點擊第 {page_num + 1 } 頁: {e} " ) break print (f"所有文章總瀏覽量:{all_views} " )print ("所有頁面都爬完了!" )driver.quit()
輸出結果:
參考資料 Selenium 函式庫 - Python 網路爬蟲教學 | STEAM 教育學習網
瀏覽器自動化工具Selenium介紹 – CH.Tseng
[DAY7]Selenium簡介 | iT 邦幫忙::一起幫忙解決難題,拯救 IT 人的一天
認識 Selenium · GitBook
Day 15: Selenium - 基本概念和操作 - iT 邦幫忙::一起幫忙解決難題,拯救 IT 人的一天
python一招完美搞定Chromedriver的自动更新 - NewJune - 博客园
Python Selenium 教學筆記 - HackMD
動態網頁爬蟲第一道鎖 - Selenium教學:如何使用Webdriver、send_keys(附Python 程式碼) - 臺灣行銷研究