1 Item Pipeline

当spider爬取到item后,它被发送到我的项目管道(Item Pipeline),通过几个组件按程序进行解决。每一个Item Pipeline是一个实现了简略办法的Python类,它接管到一个item并对其执行一个操作,也要决定该item是否应该持续通过管道,或者被抛弃,不再进行解决。
Item Pipeline典型的用处是:
1.清理HTML数据
2.验证爬取的数据(查看items是否蕴含某些字段)
3.查看正本(并删除它们)
4.将item数据存储在数据库中

1.1 编写本人的Item Pipeline

每个Item Pipeline都是一个Python类,它必须实现以下办法:
process\_item(self, item, spider)
这个办法能够被每个Item Pipeline调用,process\_item()必须是:返回一个字典类型数据、返回一个条目(或任何子类)对象,返回一个 Twisted Deferred 或者DropItem异样,抛弃的item不再由进一步的Item Pipeline解决。
参数含意:
item: Item对象或字典,爬取的item
spider:spider对象,爬取了这个item的spider
此外,他们还能够实现以下办法:
open\_spider(self, spider)
当spider关上时,函数就会被调用,spider参数含意:被关上的spider
close\_spider(self, spider)
当spider敞开是,函数会被调用
from\_crawler(cls, crawler)
如果存在,这个类办法被调用来从一个Crawler创立一个spider实例。它必须返回管道的一个新实例,Crawler对象提供对所有的scrapy外围组件的拜访,比方设置和信号;这是管道拜访它们并将其性能连贯到scrapy的一种形式。

1.2 Pipeline示例

1.2.1 价格验证示例

from scrapy.exceptions import DropItemclass 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将所有通过的我的项目(从所有的spiders)存储到一个item.jl文件中,其中每行以JSON格局序列化:

import jsonclass 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地址和数据库名称在scrapy settings中指定;MongoDB汇合以item类命名。本例的次要目标是展现如何应用from\_crawler()办法以及如何正确地清理资源。

import pymongoclass 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 反复过滤器

用于查找反复items的筛选器,并删除已解决的item,假如咱们的items有一个惟一的id,然而咱们的spider返回的是具备雷同id的多个items:

from scrapy.exceptions import DropItemclass 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 激活Item Pipeline

激活Item Pipeline 必须要在setting中设置ITEM\_PIPELINES,示例如下:

ITEM_PIPELINES = {    'myproject.pipelines.PricePipeline': 300,    'myproject.pipelines.JsonWriterPipeline': 800,}

在这个设置中调配给类的整数值决定了它们运行的程序:item从低到高执行,整数值范畴是0-1000。

2 Feed exports

执行scrapy时最常须要的个性之一就是可能正确地存储爬取出来的数据,scrapy提供了这个性能,容许应用多种序列化格局来生成一个Feed。

2.1 序列化格局

用于序列化scrapy的数据格式次要有以下几种类型:

  • 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 应用办法

进入我的项目目录,执行命令:

scrapy crawl tushu -o tushu.json

通过-o参数前面接要输入的格局即可。

3 下载和解决文件和图像

scrapy提供了可重用的 item pipelines,用于下载与特定item 相干的文件(例如,当你爬取了产品并想要在本地下载它们的图像时),这些pipelines共享一些性能和构造(咱们将它们称为media pipelines),然而通常要么应用Files Pipeline 要么应用 Images Pipeline。
这两个Pipeline都实现了这些个性:

  • 防止从新下载最近下载的媒体
  • 指定存储介质的地位(文件系统目录等)

Image Pipeline有一些额定的性能用于解决图像:

  • 将所有下载的图像转换为通用格局(JPG)和模式(RGB)
  • 生成缩略图
  • 查看图像宽度/高度以确保它们满足最小约束条件

Pipeline为正筹备下载的media url的保留了外部队列,将蕴含雷同媒体的response连贯到该队列,这样能够防止在多个items共享的状况下下载雷同的媒体。

3.1 应用Files Pipeline

应用Files Pipeline典型的工作流程如下:
1.在一个spider中,你将一个item提取并且将所需的urls放入file\_urls字段中;
2.item将从spider返回并进入item pipeline;
3.当item达到FilePipeline,在file\_urls字段中的urls会应用规范scrapy调度器和下载器下载(这意味着调度程序和下装程序中间件被重用),如果优先级更高,会在其余页面被抓取前解决。item会在这个特定的pipline中放弃“locker”状态,晓得实现下载(或因为某些起因未实现下载)。
4.当下载文件时,将应用后果填充另一个字段(files),这个字段将蕴含一个对于下载文件的信息的字典,例如下载门路、原始url(来自file\_urls字段)和文件校验。文件字段列表中的files将保留原来的file\_urls字段的雷同程序,如果有下载失败的文件,谬误将会被记录,而file不会被记录到files字段中。

3.2 应用Images Pipeline

Images Pipeline的应用形式与File Pipeline形式相似,只是默认的字段名称不同,应用image\_urls作为一个item的图片urls,它将填充一个图像image字段,以获取对于下载的图像的信息。
应用ImagesPipeline对于解决image files的长处是,您能够配置一些额定的性能,比方生成缩略图和依据它们的大小过滤图像。
Images Pipeline程序应用Pillow模块格式化图片为JPEG/RGB格局,所以你还须要装置Pillow模块,大多数状况下咱们应用PIL,但家喻户晓,在某些状况下会引起麻烦,所以咱们倡议用Pillow。

3.3 应用Media Pipeline

如果要应用Media Pipeline你必须要在我的项目的setting中减少ITEM\_PIPELINES设置,对于Images Pipeline,应用:

ITEM_PIPELINES = {'scrapy.pipelines.images.ImagesPipeline': 1}

Files Pipeline,应用:

ITEM_PIPELINES = {'scrapy.pipelines.files.FilesPipeline': 1}
留神:Images Pipeline和Files Pipeline能够同时应用。

而后,将指标存储设置配置为一个有效值,该值将用于存储下载的图像。否则即便你配置了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 反对的存储

目前官网惟一反对的是文件系统,然而也反对相似的Amazon S3 and和 Google Cloud Storage.

3.5 示例

1.首先应用media pipline首先要启用它,在setting中配置:

ITEM_PIPELINES = {'scrapy.pipelines.images.ImagesPipeline': 1}

2.而后设置字段images和image\_urls:

import scrapyclass MyItem(scrapy.Item):    # ... other item fields ...    image_urls = scrapy.Field()    images = scrapy.Field()

3.在setting中增加下载门路和字段:

# 图片下载存储门路ITEM_STORE = 'E:\\'

为了防止下载最近下载的文件,能够设置FILES\_EXPIRES或IMAGES\_EXPIRES来配置缓存工夫:

# 120天后过期FILES_EXPIRES = 120# 30天后过期IMAGES_EXPIRES = 30

Images Pipline能够主动创立下载图像的缩略图,在setting中减少IMAGES\_THUMBS参数,参数为一个字典,其中的键是缩略图名称,而值是它们的维数:

IMAGES_THUMBS = {    'small': (50, 50),    'big': (270, 270),}

如果想过滤掉小图片,通过设置IMAGES\_MIN\_HEIGHT和 IMAGES\_MIN\_WIDTH来指定图像大小:

IMAGES_MIN_HEIGHT = 110IMAGES_MIN_WIDTH = 110

这个值的配置不会影响缩略图的生成。
通过下面的配置咱们就能够为咱们的爬虫增加下载图片性能了。

4 小爬虫

下面说了那么多,大家可能感觉曾经一头雾水了,接下来咱们就用一个小我的项目来具体阐明一下,咱们要爬取的网站是(搜房网二手房页面中的各个房源图片)如下图:

获取网页列表中,每个条目标具体页中的图片。

4.1 启用pipeline

setting.py中减少如下内容:

# 新增内容从这里开始################################################################## 启动piplineITEM_PIPELINES = {    # 留神,如果自定义图片名称时,此条内容要正文,不然自定义图片名不失效    'scrapy.pipelines.images.ImagesPipeline': 1,        # 自定义图片名称后,能够勾销正文此条    # 'sp.pipelines.SpDownimagePipeline': 200,}# 图片保留地址IMAGES_STORE = 'E:\\'# 图片过期工夫30天IMAGES_EXPIRES = 30# 设置缩略图# 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-agentUSER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:57.0) Gecko/20100101 Firefox/57.0'# 新增内容从这里完结#################################################################

4.2 配置items

设置要爬取的网页名字段image和爬取网页内的图片链接字段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.htmlimport scrapyclass SpItem(scrapy.Item):    """    定义item字段    """    # 网页名称    image = scrapy.Field()    # 网页内图片链接    image_urls = scrapy.Field()

4.3 spider

咱们的爬虫ftx.py代码如下:

# -*- coding: utf-8 -*-import scrapyfrom sp.items import SpItemclass MyBlog(scrapy.Spider):    name = 'ftx'    start_urls = ['http://esf.fang.com']    def parse(self, response):        """        爬取初始start_urls列表我的项目的url(相对路径),通过response.follow生成        request,作为参数传入回掉函数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函数        :param response: 申请的网页内容        :return: item        """        # 导入item类        item = SpItem()        # 每个集体页面中的图片        # image是每个具体页的题目, image_urls是每个具体页内图片的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.htmlimport scrapyfrom scrapy.pipelines.images import ImagesPipelineclass SpDownimagePipeline(ImagesPipeline):    """    自定义图片下载类    """    def get_media_requests(self, item, info):        """        ImagesPipeline类的办法,必须返回每个图像URL的Request        :param item:获取的item        """        # 从item中获取图片url并发送申请,image_urls就是items.py中定义的字段        for image_url in item['image_urls']:            # meta作用就是能够将item的值传给下一个函数应用,相似于先缓存起来            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是因为图片最初一个字段不是随机数        # 是长乘以宽如:452x340c.jpg,容易重名,所以用的-2,倒数第二个字段        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解决,当实现下载时后果将会以2-元素的元组模式被发送到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,如果呈现问题返回Twisted Failure。

  • url代表文件从哪里下载的,这是从get\_media\_requests返回的request的url
  • path代表文件存储门路
  • checksum代表图像内容的MD5 hash

item\_completed(results, item, info)
当一个独自我的项目中所有图片申请实现时(下载实现或者下载失败),此办法将会被调用,其中results参数为get\_media\_requests下载实现后返回的后果,item\_completed必须返回输入发送到下一个阶段的pipeline。所以你必须返回或删除item,和之前其它pipeline操作一样。
上面的一个示例,咱们将下载的文件门路(在results中传递)存储在file\_path item字段中,如果不蕴含任何文件,则删除该我的项目。

from scrapy.exceptions import DropItemdef 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 scrapyfrom scrapy.pipelines.images import ImagesPipelinefrom scrapy.exceptions import DropItemclass 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