Scrapy-基础02

摘要

本文内容转自网络,个人学习记录使用,请勿传播

ImagePipeLines的请求传参

  • 环境安装:pip install Pillow

  • 1
    USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.109 Safari/537.36'
  • 需求:将图片的名称和详情页中图片的数据进行爬取,持久化存储。

    • 分析:

      • 深度爬取:请求传参
      • 多页的数据爬取:手动请求的发送
    • 爬虫文件:

    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      import scrapy

      from ..items import DeepimgproItem
      class ImgSpider(scrapy.Spider):
      name = 'img'
      # allowed_domains = ['www.xxx.com']
      start_urls = ['https://pic.netbian.com/4kmeinv/']
      #通用的url模板
      url_model = 'https://pic.netbian.com/4kmeinv/index_%d.html'
      page_num = 2
      def parse(self, response):
      #解析出了图片的名称和详情页的url
      li_list = response.xpath('//*[@id="main"]/div[3]/ul/li')
      for li in li_list:
      title = li.xpath('./a/b/text()').extract_first() + '.jpg'
      detail_url = 'https://pic.netbian.com'+li.xpath('./a/@href').extract_first()
      item = DeepimgproItem()
      item['title'] = title
      #需要对详情页的url发起请求,在详情页中获取图片的下载链接
      yield scrapy.Request(url=detail_url,callback=self.detail_parse,meta={'item':item})
      if self.page_num <= 2:
      new_url = format(self.url_model%self.page_num)
      self.page_num += 1
      yield scrapy.Request(url=new_url,callback=self.parse)
      #解析详情页的数据
      def detail_parse(self,response):
      meta = response.meta
      item = meta['item']
      img_src = 'https://pic.netbian.com'+response.xpath('//*[@id="img"]/img/@src').extract_first()
      item['img_src'] = img_src

      yield item
    • 管道:

    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      # Define your item pipelines here
      #
      # Don't forget to add your pipeline to the ITEM_PIPELINES setting
      # See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html


      # useful for handling different item types with a single interface
      import scrapy
      from itemadapter import ItemAdapter

      from scrapy.pipelines.images import ImagesPipeline
      class DeepimgproPipeline(ImagesPipeline):
      # def process_item(self, item, spider):
      # return item
      def get_media_requests(self, item, info):
      img_src = item['img_src']
      #请求传参,将item中的图片名称传递给file_path方法
      #meta会将自身传递给file_path
      print(item['title'],'保存下载成功!')
      yield scrapy.Request(url=img_src,meta={'title':item['title']})
      def file_path(self, request, response=None, info=None, *, item=None):
      #返回图片的名称
      #接收请求传参过来的数据
      title = request.meta['title']
      return title
      def item_completed(self, results, item, info):
      return item

如何提高scrapy的爬取效率

1
2
3
4
5
6
7
8
9
10
11
12
13
14
增加并发:
默认scrapy开启的并发线程为32个,可以适当进行增加。在settings配置文件中修改CONCURRENT_REQUESTS = 100值为100,并发设置成了为100。

降低日志级别:
在运行scrapy时,会有大量日志信息的输出,为了减少CPU的使用率。可以设置log输出信息为WORNING或者ERROR即可。在配置文件中编写:LOG_LEVEL = ‘ERROR’

禁止cookie:
如果不是真的需要cookie,则在scrapy爬取数据时可以禁止cookie从而减少CPU的使用率,提升爬取效率。在配置文件中编写:COOKIES_ENABLED = False

禁止重试:
对失败的HTTP进行重新请求(重试)会减慢爬取速度,因此可以禁止重试。在配置文件中编写:RETRY_ENABLED = False

减少下载超时:
如果对一个非常慢的链接进行爬取,减少下载超时可以能让卡住的链接快速被放弃,从而提升效率。在配置文件中进行编写:DOWNLOAD_TIMEOUT = 10 超时时间为10s

post请求发送

  • 问题:在之前代码中,我们从来没有手动的对start_urls列表中存储的起始url进行过请求的发送,但是起始url的确是进行了请求的发送,那这是如何实现的呢?

    • 解答:其实是因为爬虫文件中的爬虫类继承到了Spider父类中的start_requests(self)这个方法,该方法就可以对start_urls列表中的url发起请求:

    • 1
      2
      3
      def start_requests(self):
      for u in self.start_urls:
      yield scrapy.Request(url=u,callback=self.parse)
    • 【注意】该方法默认的实现,是对起始的url发起get请求,如果想发起post请求,则需要子类重写该方法。

      • yield scrapy.Request():发起get请求
      • yield scrapy.FormRequest():发起post请求
    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      import scrapy
      class FanyiSpider(scrapy.Spider):
      name = 'fanyi'
      # allowed_domains = ['www.xxx.com']
      start_urls = ['https://fanyi.baidu.com/sug']
      #父类中的方法:该方法是用来给起始的url列表中的每一个url发请求
      def start_requests(self):
      data = {
      'kw':'dog'
      }
      for url in self.start_urls:
      #formdata是用来指定请求参数
      yield scrapy.FormRequest(url=url,callback=self.parse,formdata=data)
      def parse(self, response):
      result = response.json()
      print(result)

scrapy的核心组件

  • 从中可以大致了解scrapy框架的一个运行机制
1
2
3
4
5
6
7
8
9
10
- 引擎(Scrapy)
用来处理整个系统的数据流处理, 触发事务(框架核心)
- 调度器(Scheduler)
用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL(抓取网页的网址或者说是链接)的优先队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址
- 下载器(Downloader)
用于下载网页内容, 并将网页内容返回给蜘蛛(Scrapy下载器是建立在twisted这个高效的异步模型上的)
- 爬虫(Spiders)
爬虫是主要干活的, 用于从特定的网页中提取自己需要的信息, 即所谓的实体(Item)。用户也可以从中提取出链接,让Scrapy继续抓取下一个页面
- 项目管道(Pipeline)
负责处理爬虫从网页中抽取的实体,主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据。

中间件

  • scrapy的中间件有两个:

    • 爬虫中间件
    • 下载中间件
    • 中间件的作用是什么?
      • 观测中间件在五大核心组件的什么位置,根据位置了解中间件的作用
        • 下载中间件位于引擎和下载器之间
        • 引擎会给下载器传递请求对象,下载器会给引擎返回响应对象。
        • 作用:可以拦截到scrapy框架中所有的请求和响应。
          • 拦截请求干什么?
            • 修改请求的ip,修改请求的头信息,设置请求的cookie
          • 拦截响应干什么?
            • 可以修改响应数据
  • 中间件重要方法:

  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    # Define here the models for your spider middleware
    #
    # See documentation in:
    # https://docs.scrapy.org/en/latest/topics/spider-middleware.html

    from scrapy import signals

    # useful for handling different item types with a single interface
    from itemadapter import is_item, ItemAdapter

    class MiddleproDownloaderMiddleware:

    #类方法:作用是返回一个下载器对象(忽略)
    @classmethod
    def from_crawler(cls, crawler):
    # This method is used by Scrapy to create your spiders.
    s = cls()
    crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
    return s
    #拦截处理所有的请求对象
    #参数:request就是拦截到的请求对象,spider爬虫文件中爬虫类实例化的对象
    #spider参数的作用可以实现爬虫类和中间类的数据交互
    def process_request(self, request, spider):
    return None
    #拦截处理所有的响应对象
    #参数:response就是拦截到的响应对象,request就是被拦截到响应对象对应的唯一的一个请求对象
    def process_response(self, request, response, spider):
    return response
    #拦截和处理发生异常的请求对象
    #参数:reqeust就是拦截到的发生异常的请求对象
    def process_exception(self, request, exception, spider):
    pass
    #控制日志数据的(忽略)
    def spider_opened(self, spider):
    spider.logger.info('Spider opened: %s' % spider.name)

开发代理中间件

  • request.meta[‘proxy’] = proxy

  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    # Define here the models for your spider middleware
    #
    # See documentation in:
    # https://docs.scrapy.org/en/latest/topics/spider-middleware.html

    from scrapy import signals

    # useful for handling different item types with a single interface
    from itemadapter import is_item, ItemAdapter
    from scrapy import Request

    class MiddleproDownloaderMiddleware:
    #类方法:作用是返回一个下载器对象(忽略)
    @classmethod
    def from_crawler(cls, crawler):
    # This method is used by Scrapy to create your spiders.
    s = cls()
    crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
    return s
    #拦截处理所有的请求对象
    #参数:request就是拦截到的请求对象,spider爬虫文件中爬虫类实例化的对象
    #spider参数的作用可以实现爬虫类和中间类的数据交互
    def process_request(self, request, spider):
    #是的所有的请求都是用代理,则代理操作可以写在该方法中
    request.meta['proxy'] = 'http://ip:port'
    #弊端:会使得整体的请求效率变低
    print(request.url+':请求对象拦截成功!')
    return None
    #拦截处理所有的响应对象
    #参数:response就是拦截到的响应对象,request就是被拦截到响应对象对应的唯一的一个请求对象
    def process_response(self, request, response, spider):
    print(request.url+':响应对象拦截成功!')
    return response
    #拦截和处理发生异常的请求对象
    #参数:reqeust就是拦截到的发生异常的请求对象
    #方法存在的意义:将发生异常的请求拦截到,然后对其进行修正
    def process_exception(self, request, exception, spider):
    print(request.url+':发生异常的请求对象被拦截到!')
    #修正操作
    #只有发生了异常的请求才使用代理机制,则可以写在该方法中
    request.meta['proxy'] = 'https://ip:port'
    return request #对请求对象进行重新发送
    #控制日志数据的(忽略)
    def spider_opened(self, spider):
    spider.logger.info('Spider opened: %s' % spider.name)

开发UA中间件

  • request.headers[‘User-Agent’] = ua

  • 1
    2
    3
    4
    def process_request(self, request, spider):
    request.headers['User-Agent'] = '从列表中随机选择的一个UA值'
    print(request.url+':请求对象拦截成功!')
    return None

开发Cookie中间件

  • request.cookies = cookies

  • 1
    2
    3
    4
    5
    def process_request(self, request, spider):
    request.headers['cookie'] = 'xxx'
    #request.cookies = 'xxx'
    print(request.url+':请求对象拦截成功!')
    return None

selenium+scrapy

  • 需求:将网易新闻中的国内,国际,军事,航空四个板块下的新闻标题和内容进行数据爬取

    • 注意:哪些数据是动态加载的!
    • 技术:selenium,scrapy,中间件
  • 爬虫文件

    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      class WangyiSpider(RedisSpider):
      name = 'wangyi'
      #allowed_domains = ['www.xxxx.com']
      start_urls = ['https://news.163.com']
      def __init__(self):
      #实例化一个浏览器对象(实例化一次)
      self.bro = webdriver.Chrome(executable_path='/Users/bobo/Desktop/chromedriver')

      #必须在整个爬虫结束后,关闭浏览器
      def closed(self,spider):
      print('爬虫结束')
      self.bro.quit()
  • 中间件文件:

    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      from scrapy.http import HtmlResponse    
      #参数介绍:
      #拦截到响应对象(下载器传递给Spider的响应对象)
      #request:响应对象对应的请求对象
      #response:拦截到的响应对象
      #spider:爬虫文件中对应的爬虫类的实例
      def process_response(self, request, response, spider):
      #响应对象中存储页面数据的篡改
      if request.url in['http://news.163.com/domestic/','http://news.163.com/world/','http://news.163.com/air/','http://war.163.com/']:
      spider.bro.get(url=request.url)
      js = 'window.scrollTo(0,document.body.scrollHeight)'
      spider.bro.execute_script(js)
      time.sleep(2) #一定要给与浏览器一定的缓冲加载数据的时间
      #页面数据就是包含了动态加载出来的新闻数据对应的页面数据
      page_text = spider.bro.page_source
      #篡改响应对象
      return HtmlResponse(url=spider.bro.current_url,body=page_text,encoding='utf-8',request=request)
      else:
      return response
  • 配置文件:

    • 1
      2
      3
      4
      DOWNLOADER_MIDDLEWARES = {
      'wangyiPro.middlewares.WangyiproDownloaderMiddleware': 543,

      }
  • 最新方式的全栈数据爬取:CrawlSpider的一个爬虫类

  • 分布式

  • 增量式