文章目錄
-
- 爬取動态頁面
-
- (一)Splash 渲染引擎
-
- 1. render.html 端點
- 2. execute 端點
- 3. 常用屬性與方法
-
- (1)Splash 對象的屬性
- (2)Splash 對象的方法
- (二)安裝 Scrapy-Scrapy
-
- 1. 安裝 splash 伺服器
- 2. 安裝 Scrapy-Splash 庫
- 3. plash Lua 腳本
- (三)在 Scrapy 中使用 Splash
-
- 1. 頁面分析
- 2. 建立項目
- 3. 添加配置
- 4. 編寫爬蟲
-
- (1)編寫 quotes.py
- (2)修改 item.py
- (3)修改 pipeline.py
- 5. 運作爬蟲
爬取動态頁面
在之前章節中,我們爬取的都是靜态頁面中的資訊,靜态頁面的内容始終不變,爬取相對容易,但在現實中,目前絕大多數網站的頁面都是動态頁面,動态頁面中的部分内容是浏覽器運作頁面中的 JavaScript 腳本動态生成的,爬取相對困難,這一章來學習如何爬取動态頁面。
先來看一個簡單的動态頁面的例子,
(一)Splash 渲染引擎
Splash 是 Scrapy 官方推薦的 JavaScript 渲染引擎,它是使用 Webkit 開發的輕量級無界面浏覽器,提供基于 HTTP 接口的 JavaScript 渲染服務,支援以下功能:
- 為使用者傳回經過渲染的 HTML 頁面或頁面截圖。
- 并發渲染多個頁面。
- 關閉圖檔加載,加速渲染。
- 在頁面中執行使用者自定義的 JavaScript 代碼。
- 執行使用者自定義的渲染腳本(lua),功能類似于 PhantomJS。
Splash功能豐富,包含多個服務端點,由于篇幅有限,這裡隻介紹其中兩個最常用的端點:
- render.html 提供 JavaScript 頁面渲染服務。
- execute:執行使用者自定義的渲染腳本(lua),利用該端點可在頁面中執行 JavaScript 代碼。
Splash文檔位址:http://splash.readthedocs.io/en/latest/api.html
1. render.html 端點
JavaScript 頁面渲染服務是 Splash 中最基礎的服務,請看下表中列出的文檔:
服務端點 | render.html |
---|---|
請求位址 | http://localhost:8050/render.html |
請求方式 | GET/POST |
傳回類型 | html |
render.html 端點支援的參數如下表所示:
參數 | 是否必選 | 類型 | 描述 |
---|---|---|---|
url | 必選 | string | 需要渲染頁面的URL |
timeout | 可選 | float | 渲染頁面逾時時間 |
proxy | 可選 | string | 代理伺服器位址 |
wait | 可選 | float | 等待頁面渲染的時間 |
images | 可選 | integer | 是否下載下傳圖檔,預設為1 |
js_source | 可選 | string | 使用者自定義的 Javascript 代碼,在頁面渲染前執行 |
這裡僅列出部分常用參數,詳細内容參見官方文檔。
2. execute 端點
在爬取某些頁面時,我們想在頁面中執行一些使用者自定義的 JavaScript 代碼,例如,用 JavaScript 模拟點選頁面中的按鈕,或調用頁面中的 JavaScript 函數與伺服器互動,利用 Splash 的 execute 端點提供的服務可以實作這樣的功能。請看下表中的文檔:
服務端點 | execute |
---|---|
請求位址 | http://localhost:8050/execute |
請求方式 | POST |
傳回類型 | 自定義 |
execute 端點支援的參數如下表所示:
參數 | 是否必選 | 類型 | 描述 |
---|---|---|---|
lua_source | 必選 | string | 使用者自定義的 lua 腳本 |
timeout | 可選 | float | 渲染頁面逾時時間 |
proxy | 可選 | string | 代理伺服器位址 |
我們可以将 execute 端點的服務看作一個可用 lua 語言程式設計的浏覽器,功能類似于PhantomJS。使用時需傳遞一個使用者自定義的 lua 腳本給 Splash,該 lua 腳本中包含使用者想要模拟的浏覽器行為,例如:
- 打開某url位址的頁面
- 等待頁面加載及渲染
- 執行JavaScript代碼
- 擷取HTTP響應頭部
- 擷取Cookie
3. 常用屬性與方法
接下來,看一下splash對象常用的屬性和方法。
(1)Splash 對象的屬性
屬性 | 描述 |
---|---|
splash.args | 使用者傳入參數的表,通過該屬性可以通路使用者傳入的參數,如splash.args.url、splash.args.wait |
splash.js_enabled | 用于開啟/禁止 JavaScript 渲染,預設為 true |
splash.images_enabled | 用于開啟/禁止圖檔加載,預設為 true |
(2)Splash 對象的方法
-
splash:go 方法
調用格式:splash:go{url, baseurl=nil, headers=nil, http_method=“GET”, body=nil, formdata=nil}
方法功能:類似于在浏覽器中打開某 url 位址的頁面,頁面所需資源會被加載,并進行 JavaScript 渲染,可以通過參數指定 HTTP 請求頭部、請求方法、表單資料等。
-
splash:wait 方法
調用格式:splash:wait{time, cancel_on_redirect=false, cancel_on_error=true}
方法功能:設定等待頁面渲染,time 參數為等待的秒數。
-
splash:evaljs 方法
調用格式:splash:evaljs(snippet)
方法功能:在目前頁面下,執行一段JavaScript代碼,并傳回最後一句表達式的值。
-
splash:runjs 方法
調用格式:splash:runjs(snippet)
方法功能:在目前頁面下,執行一段JavaScript代碼,與evaljs方法相比,該函數隻執行 JavaScript 代碼,不傳回值。
-
splash:url 方法
調用格式:splash:url()
方法功能:擷取目前頁面的 url。
-
splash:html 方法
調用格式:splash:html()
方法功能:擷取目前頁面的HTML文本。
-
splash:get_cookies 方法
調用格式:splash:get_cookies()
方法功能:擷取全部Cookie資訊。
(二)安裝 Scrapy-Scrapy
掌握了 Splash 渲染引擎的基本使用後,我們繼續學習如何在 Scrapy 中調用 Splash 服務,Python 庫的 scrapy-splash 是非常好的選擇。
Scrapy-Splash 的安裝分為兩部分:一個是 Splash 服務的安裝,具體通過 Docker 來安裝服務,運作服務會啟動一個 Splash 服務,通過它的接口來實作JavaScript 頁面的加載;另外一個是 Scrapy-Splash 的 Python 庫的安裝,安裝後就可在 Scrapy 中使用 Splash 服務了。
1. 安裝 splash 伺服器
$ docker pull scrapinghub/splash
$ docker run -d --name splash -p 8050:8050 scrapinghub/splash
2. 安裝 Scrapy-Splash 庫
使用 pip 安裝 scrapy-splash:
$ pip install scrapy-splash
3. plash Lua 腳本
在安裝了 splash 伺服器之後,我們需要在 Virtualbox 設定中添加端口轉發:

運作 splash 服務後,通過web頁面通路服務的8050端口(可以在VirtualBox虛拟機的浏覽器中通路,也可以在本地浏覽器中通路)
http://127.0.0.1:8050/
即可看到其web頁面,如下圖:
上面有個輸入框,預設是http://google.com,我們可以換成想要渲染的網頁如:https://www.baidu.com,然後點選
Render me
按鈕開始渲染。
(三)在 Scrapy 中使用 Splash
這裡我們可以觀察一個典型的供我們練習爬蟲技術的網站:quotes.toscrape.com/js/。
1. 頁面分析
在浏覽器中打開 http://quotes.toscrape.com/js,顯示如下圖所示。頁面中有10條名人名言,每一條都包含在一個
<div class="quote">
元素中。
現在,我們在
scrapy shell
環境下嘗試爬取頁面中的名人名言:
$ scrapy shell http://quotes.toscrape.com/js/
...
>>> response.css('div.quote')
[]
從結果看出,爬取失敗了,在頁面中沒有找到任何包含名人名言的
<div class="quote">
元素。這些
<div class="quote">
就是動态内容,從伺服器下載下傳的頁面中并不包含它們(是以我們爬取失敗),浏覽器執行了頁面中的一段 JavaScript 代碼後,它們才被生成出來,如下圖所示:
閱讀代碼可以了解頁面動态生成的細節,所有名人名言資訊被儲存在數組 data 中,最後的 for 循環疊代 data中的每項資訊,使用 document.write 生成每條名人名言對應的
<div class="quote">
元素。
上面是動态網頁中最簡單的一個例子,資料被寫死于 JavaScript 代碼中,實際中更常見的是 JavaScript 通過HTTP 請求跟網站動态互動擷取資料(AJAX),然後使用資料更新 HTML 頁面。爬取此類動态網頁需要先執行頁面中的 JavaScript 代碼渲染頁面,再進行爬取。是以,我們進行爬取頁面資訊之前首先需要用 splash 渲染頁面。
2. 建立項目
在項目環境中講解 scrapy-splash 的使用,建立一個 Scrapy 項目,取名為scrapy_splash:
$ scrapy startproject scrapy_quotes
然後,在 scrapy_splash 項目目錄下使用
scrapy genspider
指令建立Spider:
$ cd scrapy_quotes/scrapy_quotes
$ scrapy genspider quotes quotes.toscrape.com
在 scrapy.cfg 同級目錄,建立 bin.py,用于啟動 Scrapy 項目,内容如下:
# 在項目根目錄下建立:bin.py
from scrapy.cmdline import execute
# 第三個參數是爬蟲程式名
execute(['scrapy', 'crawl', 'quotes',"--nolog"])
建立好的項目樹形目錄如下:
$ tree
.
├── bin.py
├── scrapy.cfg
└── scrapy_quotes
├── __init__.py
├── items.py
├── middlewares.py
├── pipelines.py
├── settings.py
└── spiders
├── __init__.py
└── quotes.py
3. 添加配置
在項目配置檔案 settings.py 中對 scrapy-splash 進行配置,添加内容如下:
# Splash伺服器位址
SPLASH_URL = 'http://127.0.0.1:8050/'
# 開啟Splash的兩個下載下傳中間件并調整HttpCompressionMiddleware的次序
DOWNLOADER_MIDDLEWARES = {
'scrapy_splash.SplashCookiesMiddleware': 723,
'scrapy_splash.SplashMiddleware': 725,
'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 810,
}
# 設定去重過濾器
DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter'
# 用來支援cache_args(可選)
SPIDER_MIDDLEWARES = {
'scrapy_splash.SplashDeduplicateArgsMiddleware': 100,
}
DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter'
HTTPCACHE_STORAGE = 'scrapy_splash.SplashAwareFSCacheStorage'
ITEM_PIPELINES = {
'scrapy_quotes.pipelines.ScrapyQuotesPipeline': 300,
}
編寫 Spider 代碼過程中,使用 scrapy_splash 調用 Splash 服務非常簡單,scrapy_splash 中定義了一個 SplashRequest 類,使用者隻需使用
scrapy_splash.SplashRequest
(替代
scrapy.Request
)送出請求即可。下面是 SplashRequest 構造器方法中的一些常用參數:
參數 | 功能 |
---|---|
url | 與 scrapy.Request 中的 url 相同,也就是待爬取頁面的 url |
headers | 與 scrapy.Request 中的 headers 相同 |
cookies | 與 scrapy.Request 中的 cookies 相同 |
args | 傳遞給 Splash 的參數(除url以外),如wait、timeout、images、js_source等 |
cache_args | 如果 args 中的某些參數每次調用都重複傳遞并且資料量較大,此時可以把該參數名填入cache_args 清單中,讓 Splash 伺服器緩存該參數,如 SplashRequest(url, args = {‘js_source’: js, ‘wait’: 0.5}, cache_args = [‘js_source’]) |
endpoint | Splash 服務端點,預設為 ‘render.html’ ,即 JavaScript 頁面渲染服務,該參數可以設定為 ‘render.json’、‘render.har’、‘render.png’、‘render.jpeg’、‘execute’ 等 |
splash_url | Splash 伺服器位址,預設為 None,即使用配置檔案中 SPLASH_URL 的位址 |
4. 編寫爬蟲
(1)編寫 quotes.py
在這個案例中,我們隻需使用 Splash 的 render.html 端點渲染頁面,再進行爬取即可實作 QuotesSpider,代碼如下:
import scrapy
from scrapy_splash import SplashRequest
from ..items import QuotesItem
class QuotesSpider(scrapy.Spider):
name = 'quotes'
allowed_domains = ['quotes.toscrape.com']
start_urls = ['http://quotes.toscrape.com/']
def start_requests(self):
for url in self.start_urls:
yield SplashRequest(url, args={'images': 0, 'timeout': 60})
def parse(self, response):
authors = response.css('div.quote small.author::text').extract()
quotes = response.css('div.quote span.text::text').extract()
item = QuotesItem()
item['authors'] = authors
item['quotes'] = quotes
yield from (dict(zip(['author', 'quote'], item)) for item in zip(authors, quotes))
next_url = response.css('ul.pager li.next a::attr(href)').extract_first()
if next_url:
url = response.urljoin(next_url)
yield SplashRequest(url, args={'images': 0, 'timeout': 60})
上述代碼中,使用 SplashRequest 送出請求,在 SplashRequest 的構造器中無須傳遞 endpoint 參數,因為該參數預設值便是
'render.html'
。使用
args
參數禁止
Splash
加載圖檔,并設定渲染逾時時間。
另外,上述代碼除了以上這種寫法,還可以進行如下編寫:
class QuotesSpider(scrapy.Spider):
name = "quotes"
allowed_domains = ["quotes.toscrape.com"]
start_urls = ['http://quotes.toscrape.com/js/']
def start_requests(self):
for url in self.start_urls:
yield SplashRequest(url, args={'images': 0, 'timeout': 8})
def parse(self, response):
for sel in response.css('div.quote'):
quote = sel.css('span.text::text').extract_first()
author = sel.css('small.author::text').extract_first()
item = QuotesItem()
item['authors'] = author
item['quotes'] = quote
yield item
next_url = response.css('li.next > a::attr(href)').extract_first()
if href:
url = response.urljoin(next_url)
yield SplashRequest(url, args={'images': 0, 'timeout': 8})
(2)修改 item.py
import scrapy
class QuotesItem(scrapy.Item):
authors = scrapy.Field()
quotes = scrapy.Field()
(3)修改 pipeline.py
import json
class ScrapyQuotesPipeline:
def __init__(self):
self.f = open("quotes.json", 'wb')
def process_item(self, item, spider):
# 讀取item中的資料 并換行處理
content = json.dumps(dict(item), ensure_ascii=False) + ',\n'
self.f.write(content.encode('utf=8'))
return item
def close_spider(self, spider):
# 關閉檔案
self.f.close()
5. 運作爬蟲
運作 bin.py,等待 1 分鐘,就會生成檔案
quotes.json
。
上述文章内容如有錯誤,歡迎各位讀者在評論區留言!