天天看點

爬蟲架構Scrapy(12)爬取動态頁面

文章目錄

    • 爬取動态頁面
      • (一)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 設定中添加端口轉發:

爬蟲架構Scrapy(12)爬取動态頁面

運作 splash 服務後,通過web頁面通路服務的8050端口(可以在VirtualBox虛拟機的浏覽器中通路,也可以在本地浏覽器中通路)

http://127.0.0.1:8050/
           

即可看到其web頁面,如下圖:

爬蟲架構Scrapy(12)爬取動态頁面

上面有個輸入框,預設是http://google.com,我們可以換成想要渲染的網頁如:https://www.baidu.com,然後點選

Render me

按鈕開始渲染。

爬蟲架構Scrapy(12)爬取動态頁面

(三)在 Scrapy 中使用 Splash

這裡我們可以觀察一個典型的供我們練習爬蟲技術的網站:quotes.toscrape.com/js/。

1. 頁面分析

在浏覽器中打開 http://quotes.toscrape.com/js,顯示如下圖所示。頁面中有10條名人名言,每一條都包含在一個

<div class="quote">

元素中。

爬蟲架構Scrapy(12)爬取動态頁面

現在,我們在

scrapy shell

環境下嘗試爬取頁面中的名人名言:

$ scrapy shell http://quotes.toscrape.com/js/
...
>>> response.css('div.quote')
[]
           

從結果看出,爬取失敗了,在頁面中沒有找到任何包含名人名言的

<div class="quote">

元素。這些

<div class="quote">

就是動态内容,從伺服器下載下傳的頁面中并不包含它們(是以我們爬取失敗),浏覽器執行了頁面中的一段 JavaScript 代碼後,它們才被生成出來,如下圖所示:

爬蟲架構Scrapy(12)爬取動态頁面
爬蟲架構Scrapy(12)爬取動态頁面

閱讀代碼可以了解頁面動态生成的細節,所有名人名言資訊被儲存在數組 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

上述文章内容如有錯誤,歡迎各位讀者在評論區留言!