1 Item Pipeline
當(dāng)spider爬取到item后扶关,它被發(fā)送到項目管道(Item Pipeline),通過幾個組件按順序進行處理残吩。每一個Item Pipeline是一個實現(xiàn)了簡單方法的Python類生棍,它接收到一個item并對其執(zhí)行一個操作,也要決定該item是否應(yīng)該繼續(xù)通過管道猪钮,或者被丟棄,不再進行處理胆建。
Item Pipeline典型的用途是:
1.清理HTML數(shù)據(jù)
2.驗證爬取的數(shù)據(jù)(檢查items是否包含某些字段)
3.檢查副本(并刪除它們)
4.將item數(shù)據(jù)存儲在數(shù)據(jù)庫中
1.1 編寫自己的Item Pipeline
每個Item Pipeline都是一個Python類烤低,它必須實現(xiàn)以下方法:
process_item(self, item, spider)
這個方法可以被每個Item Pipeline調(diào)用,process_item()必須是:返回一個字典類型數(shù)據(jù)笆载、返回一個條目(或任何子類)對象扑馁,返回一個 Twisted Deferred 或者DropItem異常涯呻,丟棄的item不再由進一步的Item Pipeline處理。
參數(shù)含義:
item: Item對象或字典腻要,爬取的item
spider:spider對象复罐,爬取了這個item的spider
此外,他們還可以實現(xiàn)以下方法:
open_spider(self, spider)
當(dāng)spider打開時雄家,函數(shù)就會被調(diào)用效诅,spider參數(shù)含義:被打開的spider
close_spider(self, spider)
當(dāng)spider關(guān)閉是,函數(shù)會被調(diào)用
from_crawler(cls, crawler)
如果存在趟济,這個類方法被調(diào)用來從一個Crawler創(chuàng)建一個spider實例乱投。它必須返回管道的一個新實例,Crawler對象提供對所有的scrapy核心組件的訪問顷编,比如設(shè)置和信號;這是管道訪問它們并將其功能連接到scrapy的一種方式戚炫。
1.2 Pipeline示例
1.2.1 價格驗證示例
from scrapy.exceptions import DropItem
class PricePipeline(object):
vat_factor = 1.15
def process_item(self, item, spider):
if item['price']:
if item['price_excludes_vat']:
item['price'] = item['price'] * self.vat_factor
return item
else:
raise DropItem("Missing price in %s" % item)
1.2.2 寫入json文件
下面的Pipeline將所有經(jīng)過的項目(從所有的spiders)存儲到一個item.jl文件中,其中每行以JSON格式序列化:
import json
class JsonWriterPipeline(object):
def open_spider(self, spider):
self.file = open('items.jl', 'w')
def close_spider(self, spider):
self.file.close()
def process_item(self, item, spider):
line = json.dumps(dict(item)) + "\n"
self.file.write(line)
return item
1.2.3 寫入MongoDB
在本例中媳纬,我們將使用pymongo將items寫入MongoDB双肤。MongoDB地址和數(shù)據(jù)庫名稱在scrapy settings中指定;MongoDB集合以item類命名。本例的主要目的是展示如何使用from_crawler()方法以及如何正確地清理資源层宫。
import pymongo
class MongoPipeline(object):
collection_name = 'scrapy_items'
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_DATABASE', 'items')
)
def open_spider(self, spider):
self.client = pymongo.MongoClient(self.mongo_uri)
self.db = self.client[self.mongo_db]
def close_spider(self, spider):
self.client.close()
def process_item(self, item, spider):
self.db[self.collection_name].insert_one(dict(item))
return item
1.2.4 重復(fù)過濾器
用于查找重復(fù)items的篩選器杨伙,并刪除已處理的item,假設(shè)我們的items有一個惟一的id,但是我們的spider返回的是具有相同id的多個items:
from scrapy.exceptions import DropItem
class DuplicatesPipeline(object):
def __init__(self):
self.ids_seen = set()
def process_item(self, item, spider):
if item['id'] in self.ids_seen:
raise DropItem("Duplicate item found: %s" % item)
else:
self.ids_seen.add(item['id'])
return item
1.2.5 激活I(lǐng)tem Pipeline
激活I(lǐng)tem Pipeline 必須要在setting中設(shè)置ITEM_PIPELINES萌腿,示例如下:
ITEM_PIPELINES = {
'myproject.pipelines.PricePipeline': 300,
'myproject.pipelines.JsonWriterPipeline': 800,
}
在這個設(shè)置中分配給類的整數(shù)值決定了它們運行的順序:item從低到高執(zhí)行限匣,整數(shù)值范圍是0-1000。
2 Feed exports
執(zhí)行scrapy時最常需要的特性之一就是能夠正確地存儲爬取出來的數(shù)據(jù)毁菱,scrapy提供了這個功能米死,允許使用多種序列化格式來生成一個Feed。
2.1 序列化格式
用于序列化scrapy的數(shù)據(jù)格式主要有以下幾種類型:
- JSON
- JSON lines
- CSV
- XML
你也可以通過setting中的FEED_EXPORTERS字段來擴展支持的格式贮庞。
JSON
FEED_FORMAT: json
使用的類: JsonItemExporter
JSON lines
FEED_FORMAT: jsonlines
使用的類: JsonLinesItemExporter
CSV
FEED_FORMAT: csv
使用的類: CsvItemExporter
XML
FEED_FORMAT: xml
使用的類: XmlItemExporter
Pickle
FEED_FORMAT: pickle
使用的類: PickleItemExporter
Marshal
FEED_FORMAT: marshal
使用的類: MarshalItemExporter
2.2 使用方法
進入項目目錄峦筒,執(zhí)行命令:
scrapy crawl tushu -o tushu.json
通過-o參數(shù)后面接要輸出的格式即可。
3 下載和處理文件和圖像
scrapy提供了可重用的 item pipelines窗慎,用于下載與特定item 相關(guān)的文件(例如物喷,當(dāng)你爬取了產(chǎn)品并想要在本地下載它們的圖像時),這些pipelines共享一些功能和結(jié)構(gòu)(我們將它們稱為media pipelines)遮斥,但是通常要么使用Files Pipeline 要么使用 Images Pipeline峦失。
這兩個Pipeline都實現(xiàn)了這些特性:
- 避免重新下載最近下載的媒體
- 指定存儲介質(zhì)的位置(文件系統(tǒng)目錄等)
Image Pipeline有一些額外的功能用于處理圖像:
- 將所有下載的圖像轉(zhuǎn)換為通用格式(JPG)和模式(RGB)
- 生成縮略圖
- 檢查圖像寬度/高度以確保它們滿足最小約束條件
Pipeline為正準(zhǔn)備下載的media url的保留了內(nèi)部隊列,將包含相同媒體的response連接到該隊列术吗,這樣可以避免在多個items共享的情況下下載相同的媒體尉辑。
3.1 使用Files Pipeline
使用Files Pipeline典型的工作流程如下:
1.在一個spider中,你將一個item提取并且將所需的urls放入file_urls字段中较屿;
2.item將從spider返回并進入item pipeline隧魄;
3.當(dāng)item到達(dá)FilePipeline卓练,在file_urls字段中的urls會使用標(biāo)準(zhǔn)scrapy調(diào)度器和下載器下載(這意味著調(diào)度程序和下裝程序中間件被重用),如果優(yōu)先級更高购啄,會在其他頁面被抓取前處理襟企。item會在這個特定的pipline中保持“l(fā)ocker”狀態(tài),知道完成下載(或由于某些原因未完成下載)闸溃。
4.當(dāng)下載文件時整吆,將使用結(jié)果填充另一個字段(files),這個字段將包含一個關(guān)于下載文件的信息的字典辉川,例如下載路徑、原始url(來自file_urls字段)和文件校驗拴测。文件字段列表中的files將保留原來的file_urls字段的相同順序乓旗,如果有下載失敗的文件,錯誤將會被記錄集索,而file不會被記錄到files字段中屿愚。
3.2 使用Images Pipeline
Images Pipeline的使用方式與File Pipeline方式類似,只是默認(rèn)的字段名稱不同务荆,使用image_urls作為一個item的圖片urls妆距,它將填充一個圖像image字段,以獲取關(guān)于下載的圖像的信息函匕。
使用ImagesPipeline對于處理image files的優(yōu)點是娱据,您可以配置一些額外的功能,比如生成縮略圖和根據(jù)它們的大小過濾圖像盅惜。
Images Pipeline程序使用Pillow模塊格式化圖片為JPEG/RGB格式中剩,所以你還需要安裝Pillow模塊,大多數(shù)情況下我們使用PIL抒寂,但眾所周知结啼,在某些情況下會引起麻煩,所以我們建議用Pillow屈芜。
3.3 使用Media Pipeline
如果要使用Media Pipeline你必須要在項目的setting中增加ITEM_PIPELINES設(shè)置郊愧,對于Images Pipeline,使用:
ITEM_PIPELINES = {'scrapy.pipelines.images.ImagesPipeline': 1}
Files Pipeline井佑,使用:
ITEM_PIPELINES = {'scrapy.pipelines.files.FilesPipeline': 1}
注意:Images Pipeline和Files Pipeline可以同時使用属铁。
然后,將目標(biāo)存儲設(shè)置配置為一個有效值毅糟,該值將用于存儲下載的圖像红选。否則即使你配置了ITEM_PIPELINES,也是被禁用的。
如果是File Pipeline姆另,在setting中增加FILES_STORE:
FILES_STORE = '/path/to/valid/dir'
如果是Image Pipeline喇肋,在setting中增加IMAGES_STORE:
IMAGES_STORE = '/path/to/valid/dir'
3.4 支持的存儲
目前官方唯一支持的是文件系統(tǒng)坟乾,但是也支持類似的Amazon S3 and和 Google Cloud Storage.
3.5 示例
1.首先使用media pipline首先要啟用它,在setting中配置:
ITEM_PIPELINES = {'scrapy.pipelines.images.ImagesPipeline': 1}
2.然后設(shè)置字段images和image_urls:
import scrapy
class MyItem(scrapy.Item):
# ... other item fields ...
image_urls = scrapy.Field()
images = scrapy.Field()
3.在setting中添加下載路徑和字段:
# 圖片下載存儲路徑
ITEM_STORE = 'E:\\'
為了避免下載最近下載的文件蝶防,可以設(shè)置FILES_EXPIRES或IMAGES_EXPIRES來配置緩存時間:
# 120天后過期
FILES_EXPIRES = 120
# 30天后過期
IMAGES_EXPIRES = 30
Images Pipline可以自動創(chuàng)建下載圖像的縮略圖甚侣,在setting中增加IMAGES_THUMBS參數(shù),參數(shù)為一個字典,其中的鍵是縮略圖名稱间学,而值是它們的維數(shù):
IMAGES_THUMBS = {
'small': (50, 50),
'big': (270, 270),
}
如果想過濾掉小圖片殷费,通過設(shè)置IMAGES_MIN_HEIGHT和 IMAGES_MIN_WIDTH來指定圖像大小:
IMAGES_MIN_HEIGHT = 110
IMAGES_MIN_WIDTH = 110
這個值的配置不會影響縮略圖的生成低葫。
通過上面的配置我們就可以為我們的爬蟲添加下載圖片功能了详羡。
4 小爬蟲
上面說了那么多,大家可能覺得已經(jīng)一頭霧水了嘿悬,接下來我們就用一個小項目來具體說明一下实柠,我們要爬取的網(wǎng)站是(搜房網(wǎng)二手房頁面中的各個房源圖片)如下圖:
獲取網(wǎng)頁列表中,每個條目的詳細(xì)頁中的圖片善涨。
4.1 啟用pipeline
setting.py中增加如下內(nèi)容:
# 新增內(nèi)容從這里開始#################################################################
# 啟動pipline
ITEM_PIPELINES = {
# 注意窒盐,如果自定義圖片名稱時,此條內(nèi)容要注釋钢拧,不然自定義圖片名不生效
'scrapy.pipelines.images.ImagesPipeline': 1,
# 自定義圖片名稱后蟹漓,可以取消注釋此條
# 'sp.pipelines.SpDownimagePipeline': 200,
}
# 圖片保存地址
IMAGES_STORE = 'E:\\'
# 圖片過期時間30天
IMAGES_EXPIRES = 30
# 設(shè)置縮略圖
# IMAGES_THUMBS = {
# 'small': (50, 50),
# 'big': (270, 270),
# }
# 過濾小圖片
# IMAGES_MIN_HEIGHT = 110
# IMAGES_MIN_WIDTH = 110
# 允許重定向
MEDIA_ALLOW_REDIRECTS = True
# 控制時間,下載等待時間3秒
DOWNLOAD_DELAY = 3
# 請求user-agent
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:57.0) Gecko/20100101 Firefox/57.0'
# 新增內(nèi)容從這里結(jié)束#################################################################
4.2 配置items
設(shè)置要爬取的網(wǎng)頁名字段image和爬取網(wǎng)頁內(nèi)的圖片鏈接字段image_urls源内,items.py代碼如下:
# -*- coding: utf-8 -*-
# Define here the models for your scraped items
#
# See documentation in:
# http://doc.scrapy.org/en/latest/topics/items.html
import scrapy
class SpItem(scrapy.Item):
"""
定義item字段
"""
# 網(wǎng)頁名稱
image = scrapy.Field()
# 網(wǎng)頁內(nèi)圖片鏈接
image_urls = scrapy.Field()
4.3 spider
我們的爬蟲ftx.py代碼如下:
# -*- coding: utf-8 -*-
import scrapy
from sp.items import SpItem
class MyBlog(scrapy.Spider):
name = 'ftx'
start_urls = ['http://esf.fang.com']
def parse(self, response):
"""
爬取初始start_urls列表項目的url(相對路徑)葡粒,通過response.follow生成
request,作為參數(shù)傳入回掉函數(shù)parse_item
"""
# 獲取首頁所有二手房的鏈接地址(相對地址)
page_urls = response.css("p.title a::attr(href)").extract()
for page_url in page_urls:
# 相對地址使用response.follow方式拼接url
request = response.follow(page_url, callback=self.parse_item)
# 如果獲取的連接是絕對地址姿锭,用下面這條方法
# request = scrapy.Request(page_url, callback=self.parse_item)
yield request
def parse_item(self, response):
"""
處理item函數(shù)
:param response: 請求的網(wǎng)頁內(nèi)容
:return: item
"""
# 導(dǎo)入item類
item = SpItem()
# 每個個人頁面中的圖片
# image是每個詳細(xì)頁的標(biāo)題塔鳍, image_urls是每個詳細(xì)頁內(nèi)圖片的url
item['image'] = response.css("div.floatl::text").extract_first().strip()
item['image_urls'] = response.css("img.loadimg::attr(data-src)").extract()
yield item
4.4 自定義image pipeline
直接上代碼pipelines.py:
# -*- coding: utf-8 -*-
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: http://doc.scrapy.org/en/latest/topics/item-pipeline.html
import scrapy
from scrapy.pipelines.images import ImagesPipeline
class SpDownimagePipeline(ImagesPipeline):
"""
自定義圖片下載類
"""
def get_media_requests(self, item, info):
"""
ImagesPipeline類的方法,必須返回每個圖像URL的Request
:param item:獲取的item
"""
# 從item中獲取圖片url并發(fā)送請求,image_urls就是items.py中定義的字段
for image_url in item['image_urls']:
# meta作用就是可以將item的值傳給下一個函數(shù)使用呻此,類似于先緩存起來
yield scrapy.Request(image_url, meta={'item': item})
def item_completed(self, results, item, info):
"""
此處沒有做修改轮纫,只是把ImagesPipeline的方法拿過來用,必須返回item
"""
if isinstance(item, dict) or self.images_result_field in item.fields:
item[self.images_result_field] = [x for ok, x in results if ok]
return item
def file_path(self, request, response=None, info=None):
"""
file_path為ImagePipeline自帶的方法焚鲜,這里我們重寫這個方法掌唾,
為了能夠自定義圖片的名稱,如果不重寫忿磅,SHA1 hash格式糯彬,類似full/63bbfea82b8880ed33cdb762aa11fab722a90a24.jpg
"""
# 獲取item,從get_media_requests的Request中獲取
item = request.meta['item']
# 圖片名稱葱她,一版用split(‘/’)分割后取最后一個值也就是-1撩扒,這里沒用-1是因為圖片最后一個字段不是隨機數(shù)
# 是長乘以寬如:452x340c.jpg,容易重名吨些,所以用的-2搓谆,倒數(shù)第二個字段
image_guid = request.url.split('/')[-2] + '.jpg'
# 全名炒辉,包括路徑和圖片名
fullname = "full/%s/%s" % (item['image'], image_guid)
return fullname
對于ImagesPipeline的兩個方法get_media_requests和item_completed這里解釋一下:
get_media_requests(item, info)
pipeline會獲取image的urls從item下載它,因此我們可以重寫get_media_requests方法并且返回每一個url的request:
def get_media_requests(self, item, info):
for file_url in item['file_urls']:
yield scrapy.Request(file_url)
這些請求將由pipeline處理,當(dāng)完成下載時結(jié)果將會以2-元素的元組形式被發(fā)送到item_completed方法泉手,每個元組將包含(success黔寇, file_info_or_error)類似下面這種形式:
[(True,
{'checksum': '2b00042f7481c7b056c4b410d28f33cf',
'path': 'full/0a79c461a4062ac383dc4fade7bc09f1384a3910.jpg',
'url': 'http://www.example.com/files/product1.pdf'}),
(False,
Failure(...))]
success:布爾值,如果下載圖片成功斩萌,返回True,如果下載圖片失敗缝裤,返回False。file_info_or_error:返回的是一個字典颊郎,其中包括憋飞,url、path和checksum,如果出現(xiàn)問題返回Twisted Failure袭艺。
- url代表文件從哪里下載的搀崭,這是從get_media_requests返回的request的url
- path代表文件存儲路徑
- checksum代表圖像內(nèi)容的MD5 hash
item_completed(results, item, info)
當(dāng)一個單獨項目中所有圖片請求完成時(下載完成或者下載失敗)猾编,此方法將會被調(diào)用,其中results參數(shù)為get_media_requests下載完成后返回的結(jié)果升敲,item_completed必須返回輸出發(fā)送到下一個階段的pipeline答倡。所以你必須返回或刪除item,和之前其它pipeline操作一樣驴党。
下面的一個示例瘪撇,我們將下載的文件路徑(在results中傳遞)存儲在file_path item字段中,如果不包含任何文件港庄,則刪除該項目倔既。
from scrapy.exceptions import DropItem
def item_completed(self, results, item, info):
file_paths = [x['path'] for ok, x in results if ok]
if not file_paths:
raise DropItem("Item contains no files")
item['file_paths'] = file_paths
return item
下面是一個完整的自定義Image pipeline示例:
import scrapy
from scrapy.pipelines.images import ImagesPipeline
from scrapy.exceptions import DropItem
class MyImagesPipeline(ImagesPipeline):
def get_media_requests(self, item, info):
for image_url in item['image_urls']:
yield scrapy.Request(image_url)
def item_completed(self, results, item, info):
image_paths = [x['path'] for ok, x in results if ok]
if not image_paths:
raise DropItem("Item contains no images")
item['image_paths'] = image_paths
return item