[Python網頁爬蟲] Scrapy的安裝與使用入門-4

Sean Yeh
Python Everywhere -from Beginner to Advanced
17 min readJun 23, 2022
Pisa, Tuscany, Italy ,photo by Sean Yeh

Scrapy這套開放原始碼框架,定義了完整的爬蟲流程與模組。透過它可以幫助我們快速且簡單的抓取網站的HTML頁面並取得資料,讓我們可以儲存該網頁資料並對資料進行近一步的解析。

我們在上一篇裡面說明了絕對路徑與相對路徑在Scrapy中的表示方式,並且實作了從該單一網頁中的各個超連結延伸出去的內頁,進行資料的爬取。

這裡我們要繼續修改原程式,從該單一網頁中的換頁按鈕一頁頁的爬取下一頁網頁。並且介紹在Scrapy中user-agent的設定方式。

爬取下一頁網頁

所謂Pagination(分頁)用於指向一系列跨頁面的相關內容的分頁,是網頁設計中常見的功能。只要不是太小的網站,一般都可以找到像下圖紅色區塊匡出來的Pagination 換頁功能。

到目前為止,我們還停留在爬取目標網站的頁面(第一個頁面),如果想要讓Scrapy爬蟲順著Pagination分頁區的「下一頁」按鈕逐一的擷取頁面資料,該如何修改目前的程式?

同樣的,我們可以打開Google Chrome的DevTools(可以從Chrome的主選單中選擇「更多工具 > 開發人員工具」或按下滑鼠右鍵單擊頁面元素,然後選擇檢查)來檢視HTML的結構,來找尋換頁Pagination區(如下圖黃色的部分)對應的HTML元素。

找到後,將Pagination換頁區包裹的div 元素,轉換為XPath的表示方式:

//div[@class="page-links"]

並透過response.xpath()處理後,指派給pagination變數。

pagination = response.xpath('//a[@class="nextpostslink"]')

依照同樣方式,可以找到「下一頁」按鈕(如下圖黃色的部分)的位置:

將表示「下一頁」按鈕的HTML元素,轉換為XPath後得到:

//div[@class="page-links"]

並將它指定為next_page_url變數。這裡的XPath我們從pagination的位置出發開始指定到「下一頁」按鈕的位置。並且需要取得的資料是位於href裡面的值也就是url的路徑,因此可以將程式寫成:

next_page_url = pagination.xpath('.//a[@class="nextpostslink"]/@href').get()

再透過if statement判斷下一頁是否存在。如果下一頁的按鈕存在的話,就繼續擷取頁面資料,反之就停止爬蟲程式的執行。

if next_page_url:
yield response.follow(url=next_page_url, callback=self.parse)

設定完畢後,再次執行Scrapy的crawl指令,並輸出為JSON檔案。

$ scrapy crawl advertimes -o advertimes.json

我們可以得到下面的結果。從結果可以發現Scrapy爬到超過300筆的資料:

下面是程式的完整程式碼:

class AdvertimesSpider(scrapy.Spider):
name = 'advertimes'
allowed_domains = ['www.advertimes.com']
start_urls = ['<https://www.advertimes.com/global/>']
def parse(self, response):
articles = response.xpath('//ul[@class="article-list"]/li')

for article in articles:
# 內頁連結
link = article.xpath(".//a/@href").get()
title = article.xpath('.//a/div[@class="article-list-txt"]/h3/text()').get()
content = article.xpath('.//a/div[@class="article-list-txt"]/p/text()').get()
update_date = article.xpath('.//a/div[@class="article-list-txt"]//span[@class="update-date"]/text()').get()

yield {
'link':link,
'title':title,
'content':content,
'update_date':update_date
}


pagination = response.xpath('//div[@class="page-links"]')

next_page_url = pagination.xpath('.//a[@class="nextpostslink"]/@href').get()

if next_page_url:
yield response.follow(url=next_page_url,callback=self.parse)

結合:既爬取內頁、又爬取下一頁

由於在前面的擷取「下一頁」資料並不包括每一頁的內頁部分,在此我們可以把兩個程式的功能結合起來,讓它不僅可以擷取目標頁面上的資料外,並且能夠延伸到內頁擷取內頁的資料。當爬蟲程式對目標頁面上所有的資料都擷取完畢後,會再度切換到「下一頁」,重複進行上述擷取的行為,直到網站整個單元的頁面被爬盡為止。

要達成上述目標,在做法上必須先以爬取延伸內頁的程式碼為主,在此基礎上添加上「下一頁的爬取功能」。

因此,我們只需要在 parse 的函式上面進行修改,加上下一頁的爬取功能,即可。而用來爬取延伸內頁的parse_page函式則不需要進行修改。

以下是在 parse 的函式上添加爬取下一頁功能的方法(粗體字部份)。

def parse(self, response):
articles = response.xpath('//ul[@class="article-list"]/li')

for article in articles:
# 內頁連結
link = article.xpath(".//a/@href").get()
title = article.xpath('.//a/div[@class="article-list-txt"]/h3/text()').get()
content = article.xpath('.//a/div[@class="article-list-txt"]/p/text()').get()
update_date = article.xpath('.//a/div[@class="article-list-txt"]//span[@class="update-date"]/text()').get()

yield response.follow(url=link, callback=self.parse_page, meta={'title':title,'content':content,'update_date':update_date})

pagination = response.xpath('//div[@class="page-links"]')
next_page_url = pagination.xpath('.//a[@class="nextpostslink"]/@href').get()

if next_page_url:
yield response.follow(url=next_page_url, callback=self.parse)

從上面的程式碼可以看到parse 函式中有兩個yield:

第一個yield:是原來爬取延伸內頁返回的資料,它順著爬取的連結是內頁中的link連結,並且回呼的函式是parse_page函式,該函式用來爬取延伸內頁。

第二個yield:則是當前面的for迴圈結束之後,依照一個邏輯判斷(判斷頁面上是否有「下一頁」的按鈕)後,如果答案是肯定的話,就順著「下一頁」的按鈕繼續執行parse 函式,parse 函式又會回呼來執行parse_page函式,反之就停止頁面的爬取。

一切設置完畢後,再次執行Scrapy的crawl指令,並輸出為JSON檔案。

$ scrapy crawl advertimes -o advertimes.json

上面影片是在擷取過程錄下來的,擷取後可以得到下面的結果。與上面結果比較可以發現Scrapy爬到的資料一樣是302筆:

若將JSON檔案展開之後,可以看到裡面包括paragraph與lead的資料。這兩個部分的資料位於目標頁面link連結延伸的頁面內。

以下是完整程式碼:

class AdvertimesSpider(scrapy.Spider):
name = 'advertimes'
allowed_domains = ['www.advertimes.com']
start_urls = ['<https://www.advertimes.com/global/>']
def parse(self, response):
articles = response.xpath('//ul[@class="article-list"]/li')

for article in articles:
# 內頁連結
link= article.xpath(".//a/@href").get()
title= article.xpath(
'.//a/div[@class="article-list-txt"]/h3/text()').get()
content= article.xpath(
'.//a/div[@class="article-list-txt"]/p/text()').get()
update_date = article.xpath(
'.//a/div[@class="article-list-txt"]//span[@class="update-date"]/text()').get()

yield response.follow(url=link,
callback=self.parse_page,
meta={
'title':title,
'content':content,
'update_date':update_date}
)

pagination = response.xpath('//div[@class="page-links"]')
next_page_url = pagination.xpath(
'.//a[@class="nextpostslink"]/@href').get()

if next_page_url:
yield response.follow(
url=next_page_url,
callback=self.parse)

def parse_page(self,response):
title = response.request.meta['title']
content = response.request.meta['content']
update_date = response.request.meta['update_date']
articles = response.xpath('//div[@class="entry-txt"]')

for article in articles:
lead = article.xpath( './/div[@class="lead"][1]/text()').get()
paragraph =article.xpath('.//p/text()').getall()

yield {
'title':title,
'content':content,
'update_date':update_date,
'lead':lead,
'paragraph':paragraph
}

User-Agent

由於大部分網站會加上檢查機制,導致您的爬蟲程式可能被拒絕於門外。為避免這種狀況,一般會透過在程式碼中自訂 HTTP Header的User-Agent(使用者代理)屬性讓爬取網站的行為與一般人為的瀏覽器操作行為接近。在Scrapy中也可以對User-Agent進行設定。

接下來,先觀察一下目前Scrapy使用的User-Agent,其值為何?

目前的User-Agent

要觀察目前Scrapy使用的User-Agent,可以進入Scrapy Shell裡面。首先,在終端機的提示文字下輸入:

$scrapy shell

進入Scrapy Shell介面。接著在Shell裡面輸入:

In [1]: request.headers

按下enter鍵之後,可以得到下面的結果:

我們看到一組對應的Key-Value。從這裡可以得知目前Scrapy使用的User-Agent為 Scrapy/2.6.1 (+https://scrapy.org)

b'User-Agent': b'Scrapy/2.6.1 (+https://scrapy.org)',

Chrome的User-Agent

我們再來觀察一下我們平常使用的Chrome瀏覽器,它的User-Agent為何?

打開Google Chrome的DevTool,點選「Network」標籤進入後,再選擇「Headers」標籤並向下捲動,可以找到如下面的紅色的區塊。

在這裡可以知道目前使用的Chrome瀏覽器所用的User-Agent。

修改Scrapy的User-Agent

為了避免網站阻擋爬蟲的活動,讓爬蟲行為看起來比較像是「人類」的行為,改變Scrapy使用的User-Agent為其方法之一。

首先,打開專案中的settings.py檔案,我們將要在這裡修改User-Agent。修改settings.py檔案的User-Agent有三種方式:

#第一種方式

打開檔案後,尋找下面這一段被註解掉的程式碼。

# Crawl responsibly by identifying yourself (and your website) on the user-agent
#USER_AGENT = 'google_trends (+http://www.yourdomain.com)'

修改這個程式碼為粗體字部份:

# Crawl responsibly by identifying yourself (and your website) on the user-agent
USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36'

#第二種方式

另一種方式是修改settings.py檔案中這一段被註解掉的程式碼。

# Override the default request headers:
#DEFAULT_REQUEST_HEADERS = {
# 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
# 'Accept-Language': 'en',
#}

修改這個程式碼為粗體字部份:

# Override the default request headers:
DEFAULT_REQUEST_HEADERS = {
'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36'
}

#第三種方式

還有一個方式可以修改User-Agent,這個方式不需要修改到settings.py檔案,可以直接在爬蟲程式中修改。

在advertimes.py中將原本的start_urls刪除:

start_urls = ['https://www.advertimes.com/global/']

並且建立start_requests函式。其中透過url參數取代上面的start_urls變數;callback參數的值為parse函式(self.parse)。最後加上第三個參數header。

def start_requests(self):
yield scrapy.Request(
url='https://www.advertimes.com/global/',
callback=self.parse,
header={'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36'})

在header參數的值為一個字典,我們在此可以將User-Agent的鍵值放入字典中(如上面粗體字部份)。

if next_page_url條件式的yield中也要加上User-Agent的鍵值。原本的程式碼(如下)

if next_page_url:
yield response.follow(
url=next_page_url,
callback=self.parse)

加上User-Agent的鍵值後改成(如下):

if next_page_url:
yield response.follow(
url=next_page_url,
callback=self.parse,
header={'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36'})

結論

Scrapy是一套用Python語法寫的大型網路爬蟲框架,提供了多種工具,我們不僅可以透過Scrapy來擷取資料,還可以用來處理和儲存資料。可以說是提供了網路爬蟲所需的所有功能,透過它我們可以管理HTTP請求、Session和輸出管道等等,更可以剖析爬取的內容。

然而,網頁爬蟲程式的利用,通常只是資料分析的第一步,透過爬蟲取得資料後,通常還需要對資料進行清理與分析,然後再使用機器學習等各種模型進行預測工作。不過若在整個流程的第一步就使用如此強大的工具,那麼資料分析工作已經比平常輕鬆一半了。

--

--

Sean Yeh
Python Everywhere -from Beginner to Advanced

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