Scrapy框架的使用之Scrapy對接Splash

在上一節咱們實現了Scrapy對接Selenium抓取淘寶商品的過程,這是一種抓取JavaScript動態渲染頁面的方式。除了Selenium,Splash也能夠實現一樣的功能。本節咱們來了解Scrapy對接Splash來進行頁面抓取的方式。
html

1、準備工做

請確保Splash已經正確安裝並正常運行,同時安裝好Scrapy-Splash庫。
git

2、新建項目

首先新建一個項目,名爲scrapysplashtest,命令以下所示:
github

scrapy startproject scrapysplashtest複製代碼

新建一個 Spider,命令以下所示:json

scrapy genspider taobao www.taobao.com複製代碼

3、添加配置

能夠參考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'複製代碼

4、新建請求

配置完成以後,咱們就能夠利用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腳本定義成長字符串,經過SplashRequestargs來傳遞參數,接口修改成execute。另外,args參數裏還有一個lua_source字段用於指定Lua腳本內容。這樣咱們就成功構造了一個SplashRequest,對接Splash的工做就完成了。

其餘的配置不須要更改,Item、Item Pipeline等設置與上節對接Selenium的方式相同,parse()回調函數也是徹底一致的。

5、運行

接下來,咱們經過以下命令運行爬蟲:

scrapy crawl taobao複製代碼

運行結果以下圖所示。

因爲Splash和Scrapy都支持異步處理,咱們能夠看到同時會有多個抓取成功的結果。在Selenium的對接過程當中,每一個頁面渲染下載是在Downloader Middleware裏完成的,因此整個過程是阻塞式的。Scrapy會等待這個過程完成後再繼續處理和調度其餘請求,這影響了爬取效率。所以使用Splash的爬取效率比Selenium高不少。

最後咱們再看看MongoDB的結果,以下圖所示。

結果一樣正常保存到MongoDB中。

6、本節代碼

本節代碼地址爲:https://github.com/Python3WebSpider/ScrapySplashTest。

7、結語

所以,在Scrapy中,建議使用Splash處理JavaScript動態渲染的頁面。這樣不會破壞Scrapy中的異步處理過程,會大大提升爬取效率。並且Splash的安裝和配置比較簡單,經過API調用的方式實現了模塊分離,大規模爬取的部署也更加方便。

相關文章
相關標籤/搜索