[Python網頁爬蟲]如何透過Selenium與 Scrapy 擷取JavaScript動態網頁(下)
本篇接續[Python網頁爬蟲]如何透過Selenium與 Scrapy 擷取JavaScript動態網頁(上),繼續說明關於擷取JavaScript動態網頁的相關使用方式。主要內容為在Scrapy中結合Selenium的功能,進行動態網頁擷取的方式。
Scrapy中添加Selenium
雖然Scrapy本身無法擷取JavaScript產生的動態網頁資料,但可以在Scrapy中添加Selemium來協助擷取JavaScript動態網頁。產生一加一大於二的效果。
Scrapy的資料流
開始之前,需要先介紹一下Scrapy的資料流。
Scrapy的文件中提到了Scrapy的架構與資料流。從下圖的中心處可以看到,Scrapy是由ENGINE來負責控制資料流。
A. 首先Scrapy的SPIDERS會向ENGINE發出擷取網頁資料的請求REQUESTS(紅字1的地方);ENGINE收到請求之後,會將請求轉給SCHEDULER(紅字2的地方)進行排程。
B. SCHEDULER會根據排程的時刻表,對ENGINE發出擷取資料的請求(紅字3)。接收到請求的ENGINE,將請求轉給DOWNLOADER(紅字4)。
C. DOWNLOADER收到命令開始下載網頁並擷取資料,然後將取得的資料回給ENGINE(紅字5)。ENGINE則將取得的網頁資料回應給SPIDERS(紅字6)。
D. SPIDERS將網頁資料解析後得到網頁的組成項目,再把組成項目與解析的請求(ITEMS/REQUESTS)回傳給ENGINE(紅字7)。ENGINE再把組成項目(ITEMS)傳給ITEM PIPELINES,已進行資料萃取與儲存等程序。或者是向SCHEDULER請求下一個工作。
重複A到D的步驟,直到完全將SCHEDULER中的工作做完為止。以上就是Scrapy的資料流。
圖片來源:https://docs.scrapy.org/en/latest/topics/architecture.html
Middleware
如上圖紫色的部分,Middleware存在Scrapy資料流的兩個地方,一個是在ENGINE和DOWNLOADER之間,中間的資料傳遞( REQUEST 與 RESPONSE )會經過一系列的 Downloader Middleware。
另一個是在ENGINE與SPIDERS之間的Spider Middleware,它能夠處理SPIDERS輸入(REQUEST)和輸出(ITEMS/RESPONSE)。每個 Middleware都是一個 Python 類別,不需要繼承其他類別。
換言之,Middleware的作用在於當Scrapy發送請求前,以及將回應結果傳給Scrapy網頁爬蟲前,能夠透過Middleware在過程中來進行前置處理。
目標網站
跟上一篇一樣,我們要擷取的目標網站為COVID-19全球疫情地圖中的台灣疫情報告。網站的網址是:https://covid-19.nchc.org.tw/dt_005-covidTable_taiwan.php 。要擷取的是頁面中「COVID-19台灣最新病例、檢驗統計」部分的資料。
安裝套件
想要在Scrapy中使用Selenium套件,必需透過Middleware來使用 Selenium套件,才可以對網頁發送請求並接收回應。因此,若想在Scrapy 中使用Selenium 套件來發送請求與接收回應結果,就需要定義Middleware。
網路上已經有scrapy-selenium Middleware可以使用,可以透過pip指令安裝:
$ pip install scrapy-selenium
安裝完畢之後,就要下載Selenium的驅動程式,安置在專案中。關於Chrome WebDriver 的下載與selenium套件的安裝,可以參考這一篇 — 「用Python控制Chrome瀏覽器 — Selenium初體驗」。
設定settings.py檔案
首先要開啟專案中的settings.py檔案進行設定。
Middleware設定
在settings.py檔案中Middleware的地方加入scrapy-selenium的相關設定,並且設定順序,其方法如下:
DOWNLOADER_MIDDLEWARES = {
'scrapy_selenium.SeleniumMiddleware': 800
}
設定Selenium驅動程式
接著設定Selenium驅動程式的瀏覽器名稱、前面安置的驅動程式路徑,以及啟動的模式。
SELENIUM_DRIVER_NAME = 'chrome' #瀏覽器名稱
SELENIUM_DRIVER_EXECUTABLE_PATH = 'chromedriver' #驅動程式路徑
SELENIUM_DRIVER_ARGUMENTS = ['--headless'] # Headless模式啟動
匯入套件
Scrapy專案中的settings.py檔案設定完畢之後,即開啟同專案內的covid19nchcTW.py檔案。我們需要在檔案中匯入scrapy-selenium的SeleniumRequest套件。
import scrapy
from scrapy_selenium import SeleniumRequest
匯入後還需要在Python 類別Covid19Spider裡面增加一個start_requests函式。我們會將Selenium相關的設定設在此函式裡面。
def start_requests(self):
yield SeleniumRequest(
url=self.url,
callback=self.parse,
wait_time =10)
其中callback參數就是收到網頁的回應結果之後,所要執行的方法(Method),這裡設定為 callback=self.parse
也就是會去執行我們在parse函式裡面寫的爬取網頁資料的程式。另外,我們還設定了10秒的等待時間( wait_time =10
),讓爬蟲在過程中進行10秒的等待,待頁面下載完畢後再開始爬取。
分析目標元素
跟前面使用selenium的時候一樣,我們在每一個<tr>元素包覆的區塊中,找出包括通報日(date)、今日新增確診(confirmed)以及新增確診(七天移動平均)(sevenday)三個「目標資料」的元素,並且分別賦予變數來儲存。
for迴圈取出資料
使用for-in的迴圈將目標資料元素中的資料:通報日(date)、今日新增確診(confirmed)以及新增確診(七天移動平均)(sevenday)取出。也可以在後面增加print函式,在執行過程中將擷取的資料顯示在螢幕上。
for data in datas:
print('===== data in datas =====')
date = data.find_element_by_xpath('./td[1]').text
confirmed = data.find_element_by_xpath('./td[6]').text
sevenday = data.find_element_by_xpath('./td[7]').text
print('日期',date)
print('今日確診人數',confirmed)
print('七日平均人數',sevenday)
yield回傳資料
使用yield回傳date、confirm與sevendays三種資料。
yield {
'date':date,
'confirmed':confirmed,
'sevenday':sevenday,
}
執行爬蟲程式並儲存資料
完成上述爬蟲程式撰寫後,即可在command line 終端機介面中輸入Scrapy crawl指令執行爬蟲程式。在指令後面加上「-o
」,可以指定輸出的檔案與格式,在此我們就輸出CSV檔案吧。
$ scrapy crawl covid19nchcTW -o covid19nchcTW.csv
打開檔案就可以輕易的看到scrapy爬蟲取得的結果。下圖為CSV檔案的部分截圖:
完整程式碼如下:
import scrapy
from scrapy_selenium import SeleniumRequest
import pandas as pdclass Covid19Spider(scrapy.Spider):
name = 'covid19nchcTW'
allowed_domains = ['covid-19.nchc.org.tw']
url = '<https://covid-19.nchc.org.tw/dt_005-covidTable_taiwan.php>'
def start_requests(self):
yield SeleniumRequest(
url=self.url,
callback=self.parse,
wait_time =10) def parse(self, response):
driver = response.request.meta['driver']
datas = driver.find_elements_by_xpath('//*[@id="myTable03"]/tbody/tr')
for data in datas:
print('===== data in datas =====')
date = data.find_element_by_xpath('./td[1]').text
confirmed = data.find_element_by_xpath('./td[6]').text
sevenday = data.find_element_by_xpath('./td[7]').text
print('日期',date)
print('今日確診人數',confirmed)
print('七日平均人數',sevenday)
yield {
'date':date,
'confirmed':confirmed,
'sevenday':sevenday,
}
結論
以上是透過Selenium與 Scrapy 擷取JavaScript動態網頁的方式。其中Scrapy透過整合Selenium套件的功能後,就可以爬取JavaScript動態網頁。並且速度上會比單純使用Selenium套件來得有效率。