Splash渲染引擎
? Splash是Scrapy官方推薦的JavaScript渲染昵慌,它是使用WebKit開發(fā)的輕量級無界面瀏覽器梦抢,提供基于HTTP接口的JavaScript渲染服務(wù),支持以下功能:
- 為用戶返回經(jīng)過渲染的HTML頁面或頁面截圖氛琢。
- 并發(fā)渲染多個頁面
- 關(guān)閉圖片加載,加速渲染
- 在頁面中執(zhí)行用戶自定義的JavaScript代碼。
- 執(zhí)行用戶自定義的渲染腳本(lua)白热,功能類似于PhantomJS。
首先安裝Splash粗卜,通過Docker安裝屋确。
$ docker run -p 8050:8050 -p 8051:8051 scrapinghub/splash
或者通過docker-compose.yml
:
splash:
container_name: splash
image: scrapinghub/splash
restart: always
ports:
- "8050:8050"
- "8051:8051"
詳情可見:http://www.reibang.com/p/1ab7f03f4e5a
安裝完成后,在本機(jī)的8050和8051端口開啟Splash服務(wù)续扔。
? Splash功能豐富乍恐,包含多個服務(wù)端點(diǎn)。這里只介紹兩個最常用的端點(diǎn):
- render.html:提供JavaScript頁面渲染服務(wù)
- execute:執(zhí)行用戶自定義的渲染腳本(lua)测砂,利用該端點(diǎn)可在頁面中執(zhí)行JavaScript代碼茵烈。
? Splash文檔地址:http://splash.readthedocs.io/en/latest/api.html
render.html端點(diǎn)
? JavaScript頁面渲染服務(wù)是Splash中最基礎(chǔ)的服務(wù)。
服務(wù)端點(diǎn) | render.html |
---|---|
請求地址 | http://localhost:8050/render.html |
請求方式 | GET/POST |
返回類型 | html |
? render.html端點(diǎn)支持的參數(shù)如下表所示砌些。
參數(shù) | 是否必選 | 類型 | 描述 |
---|---|---|---|
url | 必選 | string | 需要渲染頁面的url |
timeout | 可選 | float | 渲染頁面超時時間 |
proxy | 可選 | string | 代理服務(wù)器地址 |
wait | 可選 | float | 等待頁面渲染的時間 |
images | 可選 | integer | 是否下載圖片呜投,默認(rèn)為1 |
js_source | 可選 | string | 用戶自定義JavaScript代碼,在頁面渲染前執(zhí)行 |
? 這里僅列出部分常用參數(shù)存璃,詳細(xì)內(nèi)容參見官方文檔仑荐。
? 下面是使用requests庫調(diào)用render.html端點(diǎn)服務(wù)對頁面:http://quotes.toscrape.com/js/進(jìn)行渲染的示例代碼。
>>> import requests
>>> from scrapy.selector import Selector
>>> splash_url = 'http://localhost:8050/render.html'
>>> args = {'url':'http://quotes.toscrape.com/js', 'timeout':'5', 'image':0}
>>> response = requests.get(splash_url, params=args)
>>> sel = Selector(response)
>>> texts = sel.css('div.quote span.text::text').extract()
>>> for text in texts:
... print text
...
“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”
“It is our choices, Harry, that show what we truly are, far more than our abilities.”
“There are only two ways to live your life. One is as though nothing is a miracle. The other is as though everything is a miracle.”
“The person, be it gentleman or lady, who has not pleasure in a good novel, must be intolerably stupid.”
“Imperfection is beauty, madness is genius and it's better to be absolutely ridiculous than absolutely boring.”
“Try not to become a man of success. Rather become a man of value.”
“It is better to be hated for what you are than to be loved for what you are not.”
“I have not failed. I've just found 10,000 ways that won't work.”
“A woman is like a tea bag; you never know how strong it is until it's in hot water.”
“A day without sunshine is like, you know, night.”
? 在上述代碼中纵东,依據(jù)文檔中的描述設(shè)置參數(shù)url粘招、timeout、images偎球,然后發(fā)送HTTP請求到服務(wù)接口地址洒扎。從運(yùn)行結(jié)果看出,頁面渲染成功衰絮,我們爬取到了頁面中的10條名人名言袍冷。
execute端點(diǎn)
? 在爬取某些頁面時,我們想在頁面中執(zhí)行一些用戶自定義的JavaScript代碼猫牡,例如胡诗,用JavaScript模擬點(diǎn)擊頁面中的按鈕,或調(diào)用頁面中的JavaScript函數(shù)與服務(wù)器交互,利用Splash的execute端點(diǎn)提供的服務(wù)可以實(shí)現(xiàn)這樣的功能煌恢。
服務(wù)端點(diǎn) | execute |
---|---|
請求地址 | http://localhost:8050/execute |
請求方式 | POST |
返回類型 | 自定義 |
execute端點(diǎn)支持的參數(shù)如下表所示骇陈。
參數(shù) | 必選/可選 | 類型 | 描述 |
---|---|---|---|
lua_source | 必選 | string | 用戶自定義的lua腳本 |
timeout | 可選 | float | 渲染頁面超時時間 |
proxy | 可選 | string | 代理服務(wù)器地址 |
? 我們可以將execute端點(diǎn)的服務(wù)看做一個可用lua語言編程的瀏覽器,功能類似于PhantomJS瑰抵。使用時需傳遞一個用戶自定義的lua腳本給Splash缩歪,該lua腳本中包含用戶想要模擬的瀏覽器行為,例如:
- 打開某URL地址的頁面
- 等待頁面加載及渲染
- 執(zhí)行JavaScript代碼
- 獲取HTTP響應(yīng)頭部
- 獲取Cookie
下面是使用requests庫調(diào)用execute端點(diǎn)服務(wù)的示例代碼:
>>> import requests
>>> import json
>>> lua_script = '''
... function main(splash)
... splash:go("http://example.com")
... splash:wait(0.5)
... local title = splash:evaljs("document.title")
... return {title=title}
... end
... '''
>>> splash_url = 'http://localhost:8050/execute'
>>> headers = {'content-type':'application/json'}
>>> data = json.dumps({'lua_source':lua_script})
>>> response = requests.post(splash_url, headers=headers, data=data)
>>> response.content
'{"title": "Example Domain"}'
>>> response.json()
{u'title': u'Example Domain'}
? 用戶自定義的lua腳本中必須包含一個main函數(shù)作為程序入口谍憔,main函數(shù)被調(diào)用時會傳入一個splash對象(lua中的對象)匪蝙,用戶可以調(diào)用該對象上的方法操縱Splash。例如习贫,在上面的例子中逛球,先調(diào)用go方法打開某頁面,再調(diào)用wait方法等待頁面渲染苫昌,然后調(diào)用evaljs方法執(zhí)行一個JavaScript表達(dá)式颤绕,并將結(jié)果轉(zhuǎn)換為相應(yīng)的lua對象,最終Splash根據(jù)main函數(shù)的返回值構(gòu)造HTTP響應(yīng)返回給用戶祟身,main函數(shù)的返回值可以是字符串奥务,也可以是lua中的表(類似于Python字典),表會被編碼成json串袜硫。
? 接下來氯葬,看一下splash對象常用的屬性和方法。
- splash.args屬性:用戶傳入?yún)?shù)的表婉陷,通過該屬性可以訪問用戶傳入的參數(shù)帚称,如splash.args.url、splash.args.wait秽澳。
- splash.js_enabled屬性:用于開啟/禁止JavaScript渲染闯睹,默認(rèn)為True。
- splash.images_enabled屬性:用于開啟/禁止圖片加載担神,默認(rèn)為True楼吃。
- splash:go方法:splash:go{url, baseurl=nil, headers=nil, http_method="GET", body= nil, formdata=nil}類似于在瀏覽器中打開某url地址的頁面,頁面所需資源會被加載妄讯,并進(jìn)行JavaScript渲染孩锡,可以通過參數(shù)指定HTTP請求頭部、請求方法捞挥、表單數(shù)據(jù)等浮创。
- splash:wait方法:splash:wait{time, cancel_on_redirect=false, cancel_on_error=true}等待頁面渲染,time參數(shù)為等待的秒數(shù)砌函。
- splash:evajs方法:splash:evajs(snippet)在當(dāng)前頁面下,執(zhí)行一段JavaScript代碼,并返回最后一句表達(dá)式的值讹俊。
- splash:runjs方法:splash:runjs(snippet)在當(dāng)前頁面下垦沉,執(zhí)行一段JavaScript代碼,與evajs方法相比仍劈,該函數(shù)只執(zhí)行Javasc代碼厕倍,不返回值。
- splash:url方法:splash:url()獲取當(dāng)前頁面的url贩疙。
- splash:html方法:splash:html()獲取當(dāng)前頁面的HTML文本讹弯。
- splash:get_cookies方法:splash:get_cookies()獲取全部Cookie信息
在Scrapy中使用Splash
? 掌握了Splash渲染引擎的基本使用后,我們繼續(xù)學(xué)習(xí)如何在Scrapy中調(diào)用Splash服務(wù)这溅,Python庫的scrape-splash是非常好的選擇组民。
? 使用pip安裝scrape-splash。
$ pip install scrapy-splash
? 在項(xiàng)目環(huán)境中講解scrapy-splash的使用悲靴,創(chuàng)建一個Scrapy項(xiàng)目臭胜,取名為splash_examples:
$ scrapy startproject splash_examples
? 首先在項(xiàng)目配置文件settings.py
中對scrappy-splash進(jìn)行配置,添加內(nèi)容如下:
# Splash服務(wù)器地址
SPLASH_URL = 'http://localhost:8050'
# 開啟Splash的兩個下載中間件并調(diào)整HttpCompressionMiddleware的次序
DOWNLOADER_MIDDLEWARES = {
'scrapy_splash.SplashCookiesMiddleware': 723,
'scrapy_splash.SplashMiddleware': 725,
'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 810,
}
# 設(shè)置去重過濾器
DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter'
# 用來支持cache_args(可選)
SPIDER_MIDDLEWARES = {
'scrapy_splash.SplashDeduplicateArgsMiddleware': 100,
}
? 編寫Spider代碼過程中癞尚,使用scrapy_splash調(diào)用Splash服務(wù)非常簡單耸三,scrapy_splash中定義了一個SplashRequest類,用戶只需要使用scrapy_splash.SplashRequest(替代scrapy.Request)提交請求即可浇揩。下面是SplashRequest構(gòu)造器方法中的一些常用參數(shù)仪壮。
- url:與scrapy.Request中的url相同,也就是待爬取頁面的URL(注意胳徽,不是Splash服務(wù)器地址)
- headers:與scrapy.Request中的headers相同睛驳。
- cookies:與scrapy.Request中的cookies相同。
- args:傳遞給Splash的參數(shù)(除URL以外),如wait膜廊、timeout乏沸、images、js_source等爪瓜。
- cache_args:如果args中的某些參數(shù)每次調(diào)用都重復(fù)傳遞并且數(shù)據(jù)量較大(例如一段JavaScript代碼)蹬跃,此時可以把該參數(shù)名填入cache_args列表中,讓Splash服務(wù)器緩存該參數(shù)铆铆,如(SplashRequest(url,args={'js_source':js, 'wait':0.5}, cache_args=['js_source']))蝶缀。
- endpoint:Splash服務(wù)端點(diǎn),默認(rèn)為'render.html',即JavaScript頁面渲染服務(wù)薄货,該參數(shù)可以設(shè)置為'render.json'翁都、'render.har'、'render.png'谅猾、'render.jpeg'柄慰、'execute'等鳍悠,有些服務(wù)端點(diǎn)的功能我們沒有講解,詳細(xì)內(nèi)容可以查閱文檔坐搔。
- splash_url:Splash服務(wù)器地址藏研,默認(rèn)為None,即使用配置文件中的SPLASH_URL的地址概行。
? 現(xiàn)在蠢挡,大家已經(jīng)對如何在Scrapy中使用Splash渲染引擎爬取動態(tài)頁面有了一定了解,接下來我們在已經(jīng)配置了Splash使用環(huán)境的splash_examples項(xiàng)目中完成兩個實(shí)戰(zhàn)項(xiàng)目凳忙。
項(xiàng)目實(shí)戰(zhàn):爬取toscrape中的名人名言
項(xiàng)目需求
? 爬取網(wǎng)站http://quotes.toscrape.com/js中的名人名言信息业踏。
頁面分析
? 該網(wǎng)站的頁面已在本章開頭部分分析過,大家可以看相關(guān)內(nèi)容涧卵。
編碼實(shí)現(xiàn)
? 首先勤家,在splash_examples項(xiàng)目目錄下使用scrape gensipder命令創(chuàng)建Spider:
$ scrapy genspider quotes quotes.toscrape.com
? 在這個案例中,我們只需使用Splash的render.html端點(diǎn)渲染頁面艺演,再進(jìn)行爬取即可實(shí)現(xiàn)QuotesSpider却紧,代碼如下:
# -*- coding: utf-8 -*-
import scrapy
from scrapy_splash import SplashRequest
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': 3})
pass
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()
yield {'quote': quote, 'author': author}
href = response.css('li.next>a::attr(href)').extract_first()
if href:
url = response.urljoin(href)
yield SplashRequest(url, args={'images': 0, 'timeout': 3})
pass
? 上述代碼中,使用SplashRequest提交請求胎撤,在SplashRequest的構(gòu)造器中無須傳遞endpoint參數(shù)晓殊,因?yàn)樵搮?shù)默認(rèn)值便是'render.html'。使用args參數(shù)禁止Splash加載圖片伤提,并設(shè)置渲染超時時間巫俺。
? 運(yùn)行爬蟲,觀察結(jié)果:
$ scrapy crawl quotes -o quotes.csv
成功爬取了10個頁面中的100條名人名言肿男。