在上一節咱們實現了Scrapy對接Selenium抓取淘寶商品的過程,這是一種抓取JavaScript動態渲染頁面的方式。除了Selenium,Splash也能夠實現一樣的功能。本節咱們來了解Scrapy對接Splash來進行頁面抓取的方式。
html
請確保Splash已經正確安裝並正常運行,同時安裝好Scrapy-Splash庫。
git
首先新建一個項目,名爲scrapysplashtest,命令以下所示:
github
scrapy startproject scrapysplashtest複製代碼
新建一個 Spider,命令以下所示:json
scrapy genspider taobao www.taobao.com複製代碼
能夠參考Scrapy-Splash的配置說明進行一步步的配置,連接以下:https://github.com/scrapy-plugins/scrapy-splash#configuration。
bash
修改settings.py,配置SPLASH_URL
。在這裏咱們的Splash是在本地運行的,因此能夠直接配置本地的地址:服務器
SPLASH_URL = 'http://localhost:8050'複製代碼
若是Splash是在遠程服務器運行的,那此處就應該配置爲遠程的地址。例如運行在IP爲120.27.34.25的服務器上,則此處應該配置爲:dom
SPLASH_URL = 'http://120.27.34.25:8050'複製代碼
還須要配置幾個Middleware,代碼以下所示:異步
DOWNLOADER_MIDDLEWARES = {
'scrapy_splash.SplashCookiesMiddleware': 723,
'scrapy_splash.SplashMiddleware': 725,
'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 810,
}
SPIDER_MIDDLEWARES = {
'scrapy_splash.SplashDeduplicateArgsMiddleware': 100,
}複製代碼
這裏配置了三個Downloader Middleware和一個Spider Middleware,這是Scrapy-Splash的核心部分。咱們再也不須要像對接Selenium那樣實現一個Downloader Middleware,Scrapy-Splash庫都爲咱們準備好了,直接配置便可。scrapy
還須要配置一個去重的類DUPEFILTER_CLASS
,代碼以下所示:ide
DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter'複製代碼
最後配置一個Cache存儲HTTPCACHE_STORAGE
,代碼以下所示:
HTTPCACHE_STORAGE = 'scrapy_splash.SplashAwareFSCacheStorage'複製代碼
配置完成以後,咱們就能夠利用Splash來抓取頁面了。咱們能夠直接生成一個SplashRequest
對象並傳遞相應的參數,Scrapy會將此請求轉發給Splash,Splash對頁面進行渲染加載,而後再將渲染結果傳遞回來。此時Response的內容就是渲染完成的頁面結果了,最後交給Spider解析便可。
咱們來看一個示例,以下所示:
yield SplashRequest(url, self.parse_result,
args={
# optional; parameters passed to Splash HTTP API
'wait': 0.5,
# 'url' is prefilled from request url
# 'http_method' is set to 'POST' for POST requests
# 'body' is set to request body for POST requests
},
endpoint='render.json', # optional; default is render.html
splash_url='<url>', # optional; overrides SPLASH_URL
)複製代碼
這裏構造了一個SplashRequest
對象,前兩個參數依然是請求的URL和回調函數。另外咱們還能夠經過args
傳遞一些渲染參數,例如等待時間wait
等,還能夠根據endpoint
參數指定渲染接口。更多參數能夠參考文檔說明:https://github.com/scrapy-plugins/scrapy-splash#requests。
另外咱們也能夠生成Request對象,Splash的配置經過meta
屬性配置便可,代碼以下:
yield scrapy.Request(url, self.parse_result, meta={
'splash': {
'args': {
# set rendering arguments here
'html': 1,
'png': 1,
# 'url' is prefilled from request url
# 'http_method' is set to 'POST' for POST requests
# 'body' is set to request body for POST requests
},
# optional parameters
'endpoint': 'render.json', # optional; default is render.json
'splash_url': '<url>', # optional; overrides SPLASH_URL
'slot_policy': scrapy_splash.SlotPolicy.PER_DOMAIN,
'splash_headers': {}, # optional; a dict with headers sent to Splash
'dont_process_response': True, # optional, default is False
'dont_send_headers': True, # optional, default is False
'magic_response': False, # optional, default is True
}
})複製代碼
SplashRequest
對象經過args
來配置和Request對象經過meta
來配置,兩種方式達到的效果是相同的。
本節咱們要作的抓取是淘寶商品信息,涉及頁面加載等待、模擬點擊翻頁等操做。咱們能夠首先定義一個Lua腳本,來實現頁面加載、模擬點擊翻頁的功能,代碼以下所示:
function main(splash, args)
args = {
url="https://s.taobao.com/search?q=iPad",
wait=5,
page=5
}
splash.images_enabled = false
assert(splash:go(args.url))
assert(splash:wait(args.wait))
js = string.format("document.querySelector('#mainsrp-pager div.form > input').value=%d;document.querySelector('#mainsrp-pager div.form > span.btn.J_Submit').click()", args.page)
splash:evaljs(js)
assert(splash:wait(args.wait))
return splash:png()
end複製代碼
咱們定義了三個參數:請求的連接url
、等待時間wait
、分頁頁碼page
。而後禁用圖片加載,請求淘寶的商品列表頁面,經過evaljs()
方法調用JavaScript代碼,實現頁碼填充和翻頁點擊,最後返回頁面截圖。咱們將腳本放到Splash中運行,正常獲取到頁面截圖,以下圖所示。
翻頁操做也成功實現,以下圖所示即爲當前頁碼,和咱們傳入的頁碼page
參數是相同的。
咱們只須要在Spider裏用SplashRequest
對接Lua腳本就行了,以下所示:
from scrapy import Spider
from urllib.parse import quote
from scrapysplashtest.items import ProductItem
from scrapy_splash import SplashRequest
script = """ function main(splash, args) splash.images_enabled = false assert(splash:go(args.url)) assert(splash:wait(args.wait)) js = string.format("document.querySelector('#mainsrp-pager div.form > input').value=%d;document.querySelector('#mainsrp-pager div.form > span.btn.J_Submit').click()", args.page) splash:evaljs(js) assert(splash:wait(args.wait)) return splash:html() end """
class TaobaoSpider(Spider):
name = 'taobao'
allowed_domains = ['www.taobao.com']
base_url = 'https://s.taobao.com/search?q='
def start_requests(self):
for keyword in self.settings.get('KEYWORDS'):
for page in range(1, self.settings.get('MAX_PAGE') + 1):
url = self.base_url + quote(keyword)
yield SplashRequest(url, callback=self.parse, endpoint='execute', args={'lua_source': script, 'page': page, 'wait': 7})複製代碼
咱們把Lua腳本定義成長字符串,經過SplashRequest
的args
來傳遞參數,接口修改成execute
。另外,args
參數裏還有一個lua_source
字段用於指定Lua腳本內容。這樣咱們就成功構造了一個SplashRequest
,對接Splash的工做就完成了。
其餘的配置不須要更改,Item、Item Pipeline等設置與上節對接Selenium的方式相同,parse()
回調函數也是徹底一致的。
接下來,咱們經過以下命令運行爬蟲:
scrapy crawl taobao複製代碼
運行結果以下圖所示。
因爲Splash和Scrapy都支持異步處理,咱們能夠看到同時會有多個抓取成功的結果。在Selenium的對接過程當中,每一個頁面渲染下載是在Downloader Middleware裏完成的,因此整個過程是阻塞式的。Scrapy會等待這個過程完成後再繼續處理和調度其餘請求,這影響了爬取效率。所以使用Splash的爬取效率比Selenium高不少。
最後咱們再看看MongoDB的結果,以下圖所示。
結果一樣正常保存到MongoDB中。
本節代碼地址爲:https://github.com/Python3WebSpider/ScrapySplashTest。
所以,在Scrapy中,建議使用Splash處理JavaScript動態渲染的頁面。這樣不會破壞Scrapy中的異步處理過程,會大大提升爬取效率。並且Splash的安裝和配置比較簡單,經過API調用的方式實現了模塊分離,大規模爬取的部署也更加方便。