[Python網頁爬蟲]如何透過Selenium與 Scrapy 擷取JavaScript動態網頁(下)

Sean Yeh
Python Everywhere -from Beginner to Advanced
10 min readJul 15, 2022

--

Pisa, Tuscany, Italy ,photo by Sean Yeh

本篇接續[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 pd
class 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套件來得有效率。

--

--

Sean Yeh
Python Everywhere -from Beginner to Advanced

# Taipei, Internet Digital Advertising,透過寫作讓我們回想過去、理解現在並思考未來。並樂於分享,這才是最大贏家。