crawler(七):Scrapy的Request和Response、Files Pipeline、Images Pipeline

请求和响应

Scrapy的RequestResponse对象用于爬网网站。
通常,Request对象在爬虫程序中生成并传递到系统,直到它们到达下载程序,后者执行请求并返回一个Response对象,该对象返回到发出请求的爬虫程序。

爬虫->Request:创建
Request->Response:获取下载数据
Response->爬虫:数据

在这里插入图片描述
两个类Request和Response类都有一些子类,它们添加基类中不需要的功能。这些在下面的请求子类和 响应子类中描述。

class scrapy.http.Request(url[, callback, method='GET', headers, body, cookies, meta, 
encoding='utf-8', priority=0, dont_filter=False, errback])

一个Request对象表示一个HTTP请求,它通常是在爬虫生成,并由下载执行,从而生成Response。
常用参数:

  • url(string) - 此请求的网址
  • callback(callable) - 将使用此请求的响应(一旦下载)作为其第一个参数调用的函数。有关更多信息,请参阅下面的将附加数据传递给回调函数。如果请求没有指定回调,parse()将使用spider的 方法。请注意,如果在处理期间引发异常,则会调用errback。
  • method(string) - 此请求的HTTP方法。默认为’GET’。
  • meta(dict) - 属性的初始值Request.meta。如果给定,在此参数中传递的dict将被浅复制。
  • headers(dict) - 这个请求的头。dict值可以是字符串(对于单值标头)或列表(对于多值标头)。如果 None作为值传递,则不会发送HTTP头。
  • body(str或unicode) - 请求体。如果unicode传递了a,那么它被编码为 str使用传递的编码(默认为utf-8)。如果 body没有给出,则存储一个空字符串。不管这个参数的类型,存储的最终值将是一个str(不会是unicode或None)。
  • cookie(dict或list) - 请求cookie。这些可以以两种形式发送。
  • dont_filter(boolean) - 表示此请求不应由调度程序过滤。当您想要多次执行相同的请求时忽略重复过滤器时使用。小心使用它,或者你会进入爬行循环。默认为False。
  • priority(int) - 此请求的优先级(默认为0)。调度器使用优先级来定义用于处理请求的顺序。具有较高优先级值的请求将较早执行。允许负值以指示相对低优先级。
  • encoding(string) - 此请求的编码(默认为’utf-8’)。此编码将用于对URL进行百分比编码,并将正文转换为str(如果给定unicode)。

Request中meta参数的作用是传递信息给下一个函数,使用过程可以理解成:

把需要传递的信息赋值给这个叫meta的变量,
但meta只接受字典类型的赋值,因此
要把待传递的信息改成“字典”的形式,即:
meta={'key1':value1,'key2':value2}

如果想在下一个函数中取出value1,
只需得到上一个函数的meta['key1']即可,
因为meta是随着Request产生时传递的,
下一个函数得到的Response对象中就会有meta,
即response.meta,
取value1则是value1=response.meta['key1']


class example(scrapy.Spider):
    name='example'
    allowed_domains=['example.com']
    start_urls=['http://www.example.com']
    
    def parse(self,response):
           #从start_urls中分析出的一个网址赋值给url
           url=response.xpath('.......').extract()
           #ExamleClass是在items.py中定义的,下面会写出。
           """记住item本身是一个字典"""
           item=ExampleClass()
           item['name']=response.xpath('.......').extract()
           item['htmlurl']=response.xpath('.......').extract()
           """通过meta参数,把item这个字典,赋值给meta中的'key'键(记住meta本身也是一个字典)。
           Scrapy.Request请求url后生成一个"Request对象",这个meta字典(含有键值'key','key'的值也是一个字典,即item)
           会被“放”在"Request对象"里一起发送给parse2()函数 """
           yield Request(url,meta={'key':item},callback='parse2')
     
     def parse2(self,response):
           item=response.meta['key']
           """这个response已含有上述meta字典,此句将这个字典赋值给item,
           完成信息传递。这个item已经和parse中的item一样了"""
           item['text']=response.xpath('.......').extract()
           #item共三个键值,到这里全部添加完毕了
           yield item

meta是浅复制,必要时需要深复制。

import copy
meta={'key':copy.deepcopy('value')}

meta是一个dict,主要是用解析函数之间传递值,一种常见的情况:在parse中给item某些字段提取了值,但是另外一些值需要在parse_item中提取,这时候需要将parse中的item传到parse_item方法中处理,显然无法直接给parse_item设置而外参数。 Request对象接受一个meta参数,一个字典对象,同时Response对象有一个meta属性可以取到相应request传过来的meta。所以解决上述问题可以这样做:

def parse(self, response):
    # item = ItemClass()
    yield Request(url, meta={'item': item}, callback=self.parse_item)
def parse(self, response):
    item = response.meta['item']
    item['field'] = value
    yield item
request和response之间如何传参

有些时候需要将两个页面的内容合并到一个item里面,这时候就需要在yield scrapy.Request的同时,传递一些参数到一下页面中。这时候可以这样操作。

        request=scrapy.Request(houseurl,method='GET',callback=self.showhousedetail)
        request.meta['biid']=biid
        yield request
 
 
    def showhousedetail(self,response):
        house=HouseItem()
        house['bulidingid']=response.meta['biid']

Items

爬取的主要目标就是从非结构性的数据源提取结构性数据,例如网页。 Scrapy spider可以以python的dict来返回提取的数据.虽然dict很方便,并且用起来也熟悉,但是其缺少结构性,容易打错字段的名字或者返回不一致的数据,尤其在具有多个spider的大项目中。。
为了定义常用的输出数据,Scrapy提供了Item 类。 Item 对象是种简单的容器,保存了爬取到得数据。 其提供了 类似于词典(dictionary-like)的API以及用于声明可用字段的简单语法。
声明Item
Item使用简单的class定义语法以及 Field 对象来声明。例如:

import scrapy

class Product(scrapy.Item):
    name = scrapy.Field()
    price = scrapy.Field()
    stock = scrapy.Field()
    last_updated = scrapy.Field(serializer=str)

Item Pipeline

当Item在Spider中被收集之后,它将会被传递到Item Pipeline,一些组件会按照一定的顺序执行对Item的处理。

每个item pipeline组件(有时称之为“Item Pipeline”)是实现了简单方法的Python类。他们接收到Item并通过它执行一些行为,同时也决定此Item是否继续通过pipeline,或是被丢弃而不再进行处理。

编写你自己的item pipeline

编写你自己的item pipeline很简单,每个item pipiline组件是一个独立的Python类,同时必须实现以下方法:
在这里插入图片描述

pipeline区分传来Items

各个页面都会封装items并将item传递给pipelines来处理,而pipelines接收的入口只有一个就是

def process_item(self, item, spider)函数

spider 对应相应的爬虫,调用spider.name也可区分来自不同爬虫的item

    def process_item(self, item, spider):
        if str(type(item))=="<class 'rishome.items.RishomeItem'>":
            self.saverishome(item)
        if str(type(item))=="<class 'rishome.items.BulidingItem'>":
            self.savebuliding(item)
        if str(type(item))=="<class 'rishome.items.HouseItem'>":
            self.savehouse(item)
        return item  # 必须实现返回

spider 对应相应的爬虫,调用spider.name也可区分来自不同爬虫的item

    def process_item(self, item, spider):
        if spider.name == "XXXX":
        	pass

启用一个Item Pipeline组件

为了启用一个Item Pipeline组件,你必须将它的类添加到ITEM_PIPELINES配置,就像下面这个例子:

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

分配给每个类的整型值,确定了他们运行的顺序,item按数字从低到高的顺序,通过pipeline,通常将这些数字定义在0-1000范围内。

下载及处理文件和图片

Scrapy为下载item中包含的文件(比如在爬取到产品时,同时也想保存对应的图片)提供了一个可重用的item pipelines . 这些pipeline有些共同的方法和结构(我们称之为media pipeline)。一般来说你会使用Files Pipeline或者 Images Pipeline.

使用Files Pipeline

当使用 FilesPipeline ,典型的工作流程如下所示:

  1. 在一个爬虫里,你抓取一个项目,把其中图片的URL放入 file_urls组内。

  2. 项目从爬虫内返回,进入项目管道。

  3. 当项目进入 FilesPipeline,file_urls组内的URLs将被Scrapy的调度器和下载器(这意味着调度器和下载器的中间件可以复用)安排下载,当优先级更高,会在其他页面被抓取前处理。项目会在这个特定的管道阶段保持“locker”的状态,直到完成文件的下载(或者由于某些原因未完成下载)。

  4. 当文件下载完后,另一个字段(files)将被更新到结构中。这个组将包含一个字典列表,其中包括下载文件的信息,比如下载路径、源抓取地址(从file_urls组获得)和图片的校验码(checksum)。 files列表中的文件顺序将和源 file_urls组保持一致。如果某个图片下载失败,将会记录下错误信息,图片也不会出现在 files 组中。

    import scrapy
    
    class MyItem(scrapy.Item):
    
        # ... other item fields ...
        image_urls = scrapy.Field()
        images = scrapy.Field()
    
  5. 在配置文件 settings.py中配置FILES_STORE,这个配置用来设置文件下载下来的路径。

  6. 启动pipeline,
    对于 Files Pipeline, 使用:

    ITEM_PIPELINES = {'scrapy.pipeline.files.FilesPipeline': 1}
    

    对于 Images Pipeline, 使用:

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

    接着IMAGES_STORE设置为一个有效的文件夹,用来存储下载的图片。 否则管道将保持禁用状态,即使你在ITEM_PIPELINES 设置中添加了它。
    对于Files Pipeline, 设置 FILES_STORE

    FILES_STORE = '/path/to/valid/dir'
    

    对于Images Pipeline, 设置 IMAGES_STORE

    IMAGES_STORE = '/path/to/valid/dir'
    

其中:

  • <IMAGES_STORE> 是定义在IMAGES_STORE 设置里的文件夹
  • full是用来区分图片和缩略图(如果使用的话)的一个子文件夹。详情参见 针对图片生成缩略图.
1 传统的Scrapy框架图片下载方式
  1. 创建项目:

  2. 改写settings.py

    • 不遵守robots协议

      # Obey robots.txt rules
      ROBOTSTXT_OBEY = False
      
    • 设置请求头

      # Override the default request headers:
      DEFAULT_REQUEST_HEADERS = {
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
        'Accept-Language': 'en',
      'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36'
      }
      
    1. 开启pipelines
      ITEM_PIPELINES = {
         'bmw.pipelines.BmwPipeline': 300,
      }
      
  3. 改写items.py

    import scrapy
    
    
    class BmwItem(scrapy.Item):
        category = scrapy.Field()
        image_urls = scrapy.Field()
        images = scrapy.Field()
    
    
  4. 改写bmw5.py

    # -*- coding: utf-8 -*-
    import scrapy
    from bmw.items import BmwItem
    
    class Bmw5Spider(scrapy.Spider):
        name = 'bmw5'
        allowed_domains = ['car.autohome.com.cn']
        start_urls = ['https://car.autohome.com.cn/pic/series/65.html']
    
        def parse(self, response):
            # SelectorList -> list
            uiboxs = response.xpath("//div[@class='uibox']")[1:]
            for uibox in uiboxs:
                category = uibox.xpath(".//div[@class='uibox-title']/a/text()").get()
                urls = uibox.xpath(".//ul/li/a/img/@src").getall()
                # for url in urls:
                #     url = response.urljoin(url)
                #     print(url)
                urls = list(map(lambda url:response.urljoin(url),urls))
                item = BmwItem(category=category,image_urls=urls)
                yield item
    
  5. 改写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 os
    from urllib import request
    
    #导入两个库
    from scrapy.pipelines.images import ImagesPipeline
    from bmw import settings
    
    class BmwPipeline(object):
        def __init__(self):
            self.path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'images')
            if not os.path.exists(self.path):
                os.mkdir(self.path)
    
        def process_item(self, item, spider):
            category = item['category']
            urls = item['urls']
    
            category_path = os.path.join(self.path,category)
            if not os.path.exists(category_path):
                os.mkdir(category_path)
            for url in urls:
                image_name = url.split('_')[-1]
                request.urlretrieve(url,os.path.join(category_path,image_name))
            return item
    
  6. 测试py

    from scrapy import cmdline
    cmdline.execute('scrapy crawl bmw5'.split())
    

Scrapy框架提供了两个中间件、下载文件的Files pipeline 和下载图片的Image pipeline

下载文件的Files pipeline

使用步骤:

  1. 定义好一个item,然后定义两个属性file_urlsfiles , file_urls是用来存储需要下载的文件的url链接,列表类型

  2. 当文件下载完成后,会把文件下载的相关信息存储到itemfiles属性中。例如:下载路径,下载url 和文件的效验码

  3. 再配置文件settings.py中配置FILES_STORE,指定文件下载路径

  4. 启动pipeline,在ITEM_PIPELINES中设置scrapy.pipelines.files.FilesPipeline :1

下载图片的Images Pipeline

使用步骤:

  1. 定义好一个item,然后定义两个属性image_urlsimagesimage_urls是用来存储需要下载的文件的url链接,列表类型

  2. 当文件下载完成后,会把文件下载的相关信息存储到itemimages属性中。例如:下载路径,下载url 和文件的效验码

  3. 再配置文件settings.py中配置IMAGES_STORE,指定文件下载路径

  4. 启动pipeline,在ITEM_PIPELINES中设置scrapy.pipelines.images.ImagesPipeline :1

使用Images_pipeline进行图片下载(还是以汽车之家图片为例)
  1. 改写settings.py
    开启自己定义的中间件

    ITEM_PIPELINES = {
    	# 'bmw.pipelines.BmwPipeline': 300,
    	
       #不分组情况下使用的pipeline
       'scrapy.pipelines.images.ImagesPipeline': 1
    }
    
    

    配置IMAGES_STORE,指定文件下载路径

    # 图片下载的路径,供images pipelines使用
    IMAGES_STORE = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'images')
    
  2. 改写pipelines.py

    import os
    from urllib import request
    
    #导入两个库
    from scrapy.pipelines.images import ImagesPipeline
    from bmw import settings
    
    class BmwPipeline(object):
        def __init__(self):
            self.path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'images')
            if not os.path.exists(self.path):
                os.mkdir(self.path)
    
        def process_item(self, item, spider):
            category = item['category']
            urls = item['urls']
    
            category_path = os.path.join(self.path,category)
            if not os.path.exists(category_path):
                os.mkdir(category_path)
            for url in urls:
                image_name = url.split('_')[-1]
                request.urlretrieve(url,os.path.join(category_path,image_name))
            return item
    

以这种方法下载的图片,默认下载到full文件下,能够根据获取的信息将图片进行分类。

优化上述的方法
  1. 改写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 os
    from urllib import request
    
    #导入ImagesPipeline
    from scrapy.pipelines.images import ImagesPipeline
    
    #导入settings    引用参数settings.IMAGES_STORE
    from bmw import settings
    
    class BmwPipeline(object):
        def __init__(self):
            self.path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'images')
            if not os.path.exists(self.path):
                os.mkdir(self.path)
    
        def process_item(self, item, spider):
            category = item['category']
            urls = item['urls']
    
            category_path = os.path.join(self.path,category)
            if not os.path.exists(category_path):
                os.mkdir(category_path)
            for url in urls:
                image_name = url.split('_')[-1]
                request.urlretrieve(url,os.path.join(category_path,image_name))
            return item
    
    
    
    #发送下载图片的请求
    '''
        def get_media_requests(self, item, info):
            return [Request(x) for x in item.get(self.images_urls_field, [])]
    '''
    
    # 更改路径,自定义类  继承ImagesPipeline
    class BMWImagesPipeline(ImagesPipeline):
    
        def get_media_requests(self, item, info):
            # 这个方法是在发送下载请求之前调用。
            # 其实这个方法本身就是去发送下载请求的
            #调用父类的get_media_requests() 方法
            request_objs = super(BMWImagesPipeline, self).get_media_requests(item,info)
            for request_obj in request_objs:
                request_obj.item = item
            return request_objs
    
    
        #重写file_path 方法
        def file_path(self, request, response=None, info=None):
            # 这个方法是在图片将要被存储的时候调用,来获取这个图片存储的路径
            path = super(BMWImagesPipeline, self).file_path(request,response,info)
            # path = 'full/%s.jpg'   默认存储的文件路径
    
            category = request.item.get('category')             #得到分类
            images_store = settings.IMAGES_STORE                #存储的文件路径
            category_path = os.path.join(images_store,category) #分类存储的文件路径
    
            #文件夹是否存在,不存在建立相应分类的文件夹
            if not os.path.exists(category_path):
                os.mkdir(category_path)
    
            #得到文件的名字
            image_name = path.replace("full/","")
    
            #文件的绝对路径
            image_path = os.path.join(category_path,image_name)
            return image_path
    
    
  2. 改写settings.py

    ITEM_PIPELINES = {
       # 'bmw.pipelines.BmwPipeline': 300,
    
       #不分组情况下使用的pipeline
       #'scrapy.pipelines.images.ImagesPipeline': 1
    
        #分组情况下使用自己定义的pipeline
       'bmw.pipelines.BMWImagesPipeline': 1
    }
    

在自定义ImagePipeline代码中,作为重要的是要重载get_media_requests(self, item, info)item_completed(self, results, item, info)这两个函数。

    def file_path(self, request, response=None, info=None):
        image_guid = hashlib.sha1(to_bytes(request.url)).hexdigest()
        return 'full/%s.jpg' % (image_guid)
   
    def get_media_requests(self, item, info):
        return [Request(x) for x in item.get(self.images_urls_field, [])]
        #Request返回中 何以传入其它的参数,例如参数含有meta,
    
    def item_completed(self, results, item, info):
        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
  • get_media_requests()。它的第一个参数item是爬取生成的Item对象。我们将它的url字段取出来,然后直接生成Request对象。此Request加入到调度队列,等待被调度,执行下载。
  • file_path()。它的第一个参数request就是当前下载对应的Request对象。这个方法用来返回保存的文件名,直接将图片链接的最后一部分当作文件名即可。它利用split()函数分割链接并提取最后一部分,返回结果。这样此图片下载之后保存的名称就是该函数返回的文件名。
  • item_completed(),它是当单个Item完成下载时的处理方法。因为并不是每张图片都会下载成功,所以我们需要分析下载结果并剔除下载失败的图片。如果某张图片下载失败,那么我们就不需保存此Item到数据库。该方法的第一个参数results就是该Item对应的下载结果,它是一个列表形式,列表每一个元素是一个元组,其中包含了下载成功或失败的信息。这里我们遍历下载结果找出所有成功的下载列表。如果列表为空,那么该Item对应的图片下载失败,随即抛出异常DropItem,该Item忽略。否则返回该Item,说明此Item有效。

get_media_requests用于解析itemimage_urls中指定的url进行爬取,可以通过get_media_requests为每个url生成一个Request。如:

class ImagePipeline(ImagesPipeline):
    def get_media_requests(self, item, info):
        for image_url in item['image_urls']:
            yield scrapy.Request(image_url, meta={"image_name": item['image_name']})

    def file_path(self, request, response=None, info=None):
        file_name = request.meta['image_name'].strip().replace('\r\n\t\t', r'') + ".jpg"
        file_name=file_name.replace('/','_')
        return file_name
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值