Scrapy框架的使用之Item Pipeline的用法

Item Pipeline是項目管道,本節咱們詳細瞭解它的用法。
html

首先咱們看看Item Pipeline在Scrapy中的架構,以下圖所示。mysql

圖中的最左側即爲Item Pipeline,它的調用發生在Spider產生Item以後。當Spider解析完Response以後,Item就會傳遞到Item Pipeline,被定義的Item Pipeline組件會順次調用,完成一連串的處理過程,好比數據清洗、存儲等。git

Item Pipeline的主要功能有以下4點。github

  • 清理HTML數據。sql

  • 驗證爬取數據,檢查爬取字段。數據庫

  • 查重並丟棄重複內容。json

  • 將爬取結果保存到數據庫。瀏覽器

1、核心方法

咱們能夠自定義Item Pipeline,只須要實現指定的方法,其中必需要實現的一個方法是: process_item(item, spider)
bash

另外還有以下幾個比較實用的方法。微信

  • open_spider(spider)

  • close_spider(spider)

  • from_crawler(cls, crawler)

下面咱們詳細介紹這幾個方法的用法。

1. process_item(item, spider)

process_item()是必需要實現的方法,被定義的Item Pipeline會默認調用這個方法對Item進行處理。好比,咱們能夠進行數據處理或者將數據寫入到數據庫等操做。它必須返回Item類型的值或者拋出一個DropItem異常。

process_item()方法的參數有以下兩個。

  • item,是Item對象,即被處理的Item。

  • spider,是Spider對象,即生成該Item的Spider。

process_item()方法的返回類型概括以下。

  • 若是它返回的是Item對象,那麼此Item會被低優先級的Item Pipeline的process_item()方法處理,直到全部的方法被調用完畢。

  • 若是它拋出的是DropItem異常,那麼此Item會被丟棄,再也不進行處理。

2. open_spider(self, spider)

open_spider()方法是在Spider開啓的時候被自動調用的。在這裏咱們能夠作一些初始化操做,如開啓數據庫鏈接等。其中,參數spider就是被開啓的Spider對象。

3. close_spider(spider)

close_spider()方法是在Spider關閉的時候自動調用的。在這裏咱們能夠作一些收尾工做,如關閉數據庫鏈接等。其中,參數spider就是被關閉的Spider對象。

4. from_crawler(cls, crawler)

from_crawler()方法是一個類方法,用@classmethod標識,是一種依賴注入的方式。它的參數是crawler,經過crawler對象,咱們能夠拿到Scrapy的全部核心組件,如全局配置的每一個信息,而後建立一個Pipeline實例。參數cls就是Class,最後返回一個Class實例。

下面咱們用一個實例來加深對Item Pipeline用法的理解。

2、本節目標

咱們以爬取360攝影美圖爲例,來分別實現MongoDB存儲、MySQL存儲、Image圖片存儲的三個Pipeline。

3、準備工做

請確保已經安裝好MongoDB和MySQL數據庫,安裝好Python的PyMongo、PyMySQL、Scrapy框架。

4、抓取分析

咱們此次爬取的目標網站爲:https://image.so.com。打開此頁面,切換到攝影頁面,網頁中呈現了許許多多的攝影美圖。咱們打開瀏覽器開發者工具,過濾器切換到XHR選項,而後下拉頁面,能夠看到下面就會呈現許多Ajax請求,以下圖所示。

咱們查看一個請求的詳情,觀察返回的數據結構,以下圖所示。

返回格式是JSON。其中list字段就是一張張圖片的詳情信息,包含了30張圖片的ID、名稱、連接、縮略圖等信息。另外觀察Ajax請求的參數信息,有一個參數sn一直在變化,這個參數很明顯就是偏移量。當sn爲30時,返回的是前30張圖片,sn爲60時,返回的就是第31~60張圖片。另外,ch參數是攝影類別,listtype是排序方式,temp參數能夠忽略。

因此咱們抓取時只須要改變sn的數值就行了。

下面咱們用Scrapy來實現圖片的抓取,將圖片的信息保存到MongoDB、MySQL,同時將圖片存儲到本地。

5、新建項目

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

scrapy startproject images360複製代碼

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

scrapy genspider images images.so.com複製代碼

這樣咱們就成功建立了一個Spider。

6、構造請求

接下來定義爬取的頁數。好比爬取50頁、每頁30張,也就是1500張圖片,咱們能夠先在settings.py裏面定義一個變量MAX_PAGE,添加以下定義:

MAX_PAGE = 50複製代碼

定義start_requests()方法,用來生成50次請求,以下所示:

def start_requests(self):
    data = {'ch': 'photography', 'listtype': 'new'}
    base_url = 'https://image.so.com/zj?'
    for page in range(1, self.settings.get('MAX_PAGE') + 1):
        data['sn'] = page * 30
        params = urlencode(data)
        url = base_url + params
        yield Request(url, self.parse)複製代碼

在這裏咱們首先定義了初始的兩個參數,sn參數是遍歷循環生成的。而後利用urlencode()方法將字典轉化爲URL的GET參數,構造出完整的URL,構造並生成Request。

還須要引入scrapy.Request和urllib.parse模塊,以下所示:

from scrapy import Spider, Request
from urllib.parse import urlencode複製代碼

再修改settings.py中的ROBOTSTXT_OBEY變量,將其設置爲False,不然沒法抓取,以下所示:

ROBOTSTXT_OBEY = False複製代碼

運行爬蟲,便可以看到連接都請求成功,執行命令以下所示:

scrapy crawl images複製代碼

運行示例結果以下圖所示。

全部請求的狀態碼都是200,這就證實圖片信息爬取成功了。

7、提取信息

首先定義一個Item,叫做ImageItem,以下所示:

from scrapy import Item, Field
class ImageItem(Item):
    collection = table = 'images'
    id = Field()
    url = Field()
    title = Field()
    thumb = Field()複製代碼

在這裏咱們定義了4個字段,包括圖片的ID、連接、標題、縮略圖。另外還有兩個屬性collectiontable,都定義爲images字符串,分別表明MongoDB存儲的Collection名稱和MySQL存儲的表名稱。

接下來咱們提取Spider裏有關信息,將parse()方法改寫爲以下所示:

def parse(self, response):
    result = json.loads(response.text)
    for image in result.get('list'):
        item = ImageItem()
        item['id'] = image.get('imageid')
        item['url'] = image.get('qhimg_url')
        item['title'] = image.get('group_title')
        item['thumb'] = image.get('qhimg_thumb_url')
        yield item複製代碼

首先解析JSON,遍歷其list字段,取出一個個圖片信息,而後再對ImageItem賦值,生成Item對象。

這樣咱們就完成了信息的提取。

8、存儲信息

接下來咱們須要將圖片的信息保存到MongoDB、MySQL,同時將圖片保存到本地。

MongoDB

首先確保MongoDB已經正常安裝而且正常運行。

咱們用一個MongoPipeline將信息保存到MongoDB,在pipelines.py裏添加以下類的實現:

import pymongo

class MongoPipeline(object):
    def __init__(self, mongo_uri, mongo_db):
        self.mongo_uri = mongo_uri
        self.mongo_db = mongo_db

    @classmethod
    def from_crawler(cls, crawler):
        return cls(
            mongo_uri=crawler.settings.get('MONGO_URI'),
            mongo_db=crawler.settings.get('MONGO_DB')
        )

    def open_spider(self, spider):
        self.client = pymongo.MongoClient(self.mongo_uri)
        self.db = self.client[self.mongo_db]

    def process_item(self, item, spider):
        self.db[item.collection].insert(dict(item))
        return item

    def close_spider(self, spider):
        self.client.close()複製代碼

這裏須要用到兩個變量,MONGO_URIMONGO_DB,即存儲到MongoDB的連接地址和數據庫名稱。咱們在settings.py裏添加這兩個變量,以下所示:

MONGO_URI = 'localhost'
MONGO_DB = 'images360'複製代碼

這樣一個保存到MongoDB的Pipeline的就建立好了。這裏最主要的方法是process_item()方法,直接調用Collection對象的insert()方法便可完成數據的插入,最後返回Item對象。

MySQL

首先確保MySQL已經正確安裝而且正常運行。

新建一個數據庫,名字仍是images360,SQL語句以下所示:

CREATE DATABASE images360 DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci複製代碼

新建一個數據表,包含id、url、title、thumb四個字段,SQL語句以下所示:

CREATE TABLE images (id VARCHAR(255) NULL PRIMARY KEY, url VARCHAR(255) NULL , title VARCHAR(255) NULL , thumb VARCHAR(255) NULL)複製代碼

執行完SQL語句以後,咱們就成功建立好了數據表。接下來就能夠往表裏存儲數據了。

接下來咱們實現一個MySQLPipeline,代碼以下所示:

import pymysql

class MysqlPipeline():
    def __init__(self, host, database, user, password, port):
        self.host = host
        self.database = database
        self.user = user
        self.password = password
        self.port = port

    @classmethod
    def from_crawler(cls, crawler):
        return cls(
            host=crawler.settings.get('MYSQL_HOST'),
            database=crawler.settings.get('MYSQL_DATABASE'),
            user=crawler.settings.get('MYSQL_USER'),
            password=crawler.settings.get('MYSQL_PASSWORD'),
            port=crawler.settings.get('MYSQL_PORT'),
        )

    def open_spider(self, spider):
        self.db = pymysql.connect(self.host, self.user, self.password, self.database, charset='utf8', port=self.port)
        self.cursor = self.db.cursor()

    def close_spider(self, spider):
        self.db.close()

    def process_item(self, item, spider):
        data = dict(item)
        keys = ', '.join(data.keys())
        values = ', '.join(['%s'] * len(data))
        sql = 'insert into %s (%s) values (%s)' % (item.table, keys, values)
        self.cursor.execute(sql, tuple(data.values()))
        self.db.commit()
        return item複製代碼

如前所述,這裏用到的數據插入方法是一個動態構造SQL語句的方法。

這裏又須要幾個MySQL的配置,咱們在settings.py裏添加幾個變量,以下所示:

MYSQL_HOST = 'localhost'
MYSQL_DATABASE = 'images360'
MYSQL_PORT = 3306
MYSQL_USER = 'root'
MYSQL_PASSWORD = '123456'複製代碼

這裏分別定義了MySQL的地址、數據庫名稱、端口、用戶名、密碼。

這樣,MySQL Pipeline就完成了。

Image Pipeline

Scrapy提供了專門處理下載的Pipeline,包括文件下載和圖片下載。下載文件和圖片的原理與抓取頁面的原理同樣,所以下載過程支持異步和多線程,下載十分高效。下面咱們來看看具體的實現過程。

官方文檔地址爲:https://doc.scrapy.org/en/latest/topics/media-pipeline.html。

首先定義存儲文件的路徑,須要定義一個IMAGES_STORE變量,在settings.py中添加以下代碼:

IMAGES_STORE = './images'複製代碼

在這裏咱們將路徑定義爲當前路徑下的images子文件夾,即下載的圖片都會保存到本項目的images文件夾中。

內置的ImagesPipeline會默認讀取Item的image_urls字段,並認爲該字段是一個列表形式,它會遍歷Item的image_urls字段,而後取出每一個URL進行圖片下載。

可是如今生成的Item的圖片連接字段並非image_urls字段表示的,也不是列表形式,而是單個的URL。因此爲了實現下載,咱們須要從新定義下載的部分邏輯,即要自定義ImagePipeline,繼承內置的ImagesPipeline,重寫幾個方法。

咱們定義ImagePipeline,以下所示:

from scrapy import Request
from scrapy.exceptions import DropItem
from scrapy.pipelines.images import ImagesPipeline

class ImagePipeline(ImagesPipeline):
    def file_path(self, request, response=None, info=None):
        url = request.url
        file_name = url.split('/')[-1]
        return file_name

    def item_completed(self, results, item, info):
        image_paths = [x['path'] for ok, x in results if ok]
        if not image_paths:
            raise DropItem('Image Downloaded Failed')
        return item

    def get_media_requests(self, item, info):
        yield Request(item['url'])複製代碼

在這裏咱們實現了ImagePipeline,繼承Scrapy內置的ImagesPipeline,重寫下面幾個方法。

  • get_media_requests()。它的第一個參數item是爬取生成的Item對象。咱們將它的url字段取出來,而後直接生成Request對象。此Request加入到調度隊列,等待被調度,執行下載。

  • file_path()。它的第一個參數request就是當前下載對應的Request對象。這個方法用來返回保存的文件名,直接將圖片連接的最後一部分看成文件名便可。它利用split()函數分割連接並提取最後一部分,返回結果。這樣此圖片下載以後保存的名稱就是該函數返回的文件名。

  • item_completed(),它是當單個Item完成下載時的處理方法。由於並非每張圖片都會下載成功,因此咱們須要分析下載結果並剔除下載失敗的圖片。若是某張圖片下載失敗,那麼咱們就不需保存此Item到數據庫。該方法的第一個參數results就是該Item對應的下載結果,它是一個列表形式,列表每個元素是一個元組,其中包含了下載成功或失敗的信息。這裏咱們遍歷下載結果找出全部成功的下載列表。若是列表爲空,那麼該Item對應的圖片下載失敗,隨即拋出異常DropItem,該Item忽略。不然返回該Item,說明此Item有效。

如今爲止,三個Item Pipeline的定義就完成了。最後只須要啓用就能夠了,修改settings.py,設置ITEM_PIPELINES,以下所示:

ITEM_PIPELINES = {
    'images360.pipelines.ImagePipeline': 300,
    'images360.pipelines.MongoPipeline': 301,
    'images360.pipelines.MysqlPipeline': 302,
}複製代碼

這裏注意調用的順序。咱們須要優先調用ImagePipeline對Item作下載後的篩選,下載失敗的Item就直接忽略,它們就不會保存到MongoDB和MySQL裏。隨後再調用其餘兩個存儲的Pipeline,這樣就能確保存入數據庫的圖片都是下載成功的。

接下來運行程序,執行爬取,以下所示:

scrapy crawl images複製代碼

爬蟲一邊爬取一邊下載,下載速度很是快,對應的輸出日誌以下圖所示。

查看本地images文件夾,發現圖片都已經成功下載,以下圖所示。

查看MySQL,下載成功的圖片信息也已成功保存,以下圖所示。

查看MongoDB,下載成功的圖片信息一樣已成功保存,以下圖所示。

這樣咱們就能夠成功實現圖片的下載並把圖片的信息存入數據庫。

9、本節代碼

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

10、結語

Item Pipeline是Scrapy很是重要的組件,數據存儲幾乎都是經過此組件實現的。請讀者認真掌握此內容。


本資源首發於崔慶才的我的博客靜覓: Python3網絡爬蟲開發實戰教程 | 靜覓

如想了解更多爬蟲資訊,請關注個人我的微信公衆號:進擊的Coder

weixin.qq.com/r/5zsjOyvEZ… (二維碼自動識別)

相關文章
相關標籤/搜索