14、Python Scrapy Web爬虫框架【2】

1、持久化存储

爬取一页糗事百科数据

1.1、爬虫文件中进行数据解析

spiderName.py

import scrapy


class QiubaiSpider(scrapy.Spider):
    name = 'qiubai'
    start_urls = ['https://www.qiushibaike.com/text/']

    def parse(self, response):
        # 数据解析:解析出段子的作者和内容
        div_list = response.xpath('//*[@id="content"]/div/div[2]/div')
        all_data = []
        for div in div_list:
            author = div.xpath('./div[1]/a[2]/h2/text()').extract_first()
            content = div.xpath('./a/div/span//text()').extract()
            content = ''.join(content)

1.2、将解析的数据存储到item类型的对象

import scrapy
from qiubaipro.items import QiubaiproItem

class QiubaiSpider(scrapy.Spider):
    name = 'qiubai'
    start_urls = ['https://www.qiushibaike.com/text/']

    def parse(self, response):
        div_list = response.xpath('//*[@id="content"]/div/div[2]/div')
        all_data = []
        for div in div_list:
            author = div.xpath('./div[1]/a[2]/h2/text()').extract_first()
            content = div.xpath('./a/div/span//text()').extract()
            content = ''.join(content)
            #解析出来几个字段,就需要在item类中定义几个属性
            # item对象当做是一个字典
            item = QiubaiproItem()
            item['author'] = author
            item['content'] = content

1.3、将item对象提交给管道

import scrapy
from qiubaipro.items import QiubaiproItem

class QiubaiSpider(scrapy.Spider):
    name = 'qiubai'
    start_urls = ['https://www.qiushibaike.com/text/']

    def parse(self, response):
        div_list = response.xpath('//*[@id="content"]/div/div[2]/div')
        all_data = []
        for div in div_list:
            author = div.xpath('./div[1]/a[2]/h2/text()').extract_first()
            content = div.xpath('./a/div/span//text()').extract()
            content = ''.join(content)
            #解析出来几个字段,就需要在item类中定义几个属性
            # item对象当做是一个字典
            item = QiubaiproItem()
            item['author'] = author
            item['content'] = content
            
            yield item

1.4、在管道中接收item,对其进行任意形式的持久化存储

1.4.1存储到txt文件中
class QiubaiproPipeline:

    fp = None
    def open_spider(self,spider):
        print('I am open_spider,打开文件执行一次')
        self.fp = open('./qiubai.txt','w',encoding='utf-8')
    # 会被调用多次
    def process_item(self, item, spider):
        author = item['author']
        content = item['content']
        self.fp.write(author + ':'+content)
        return item

    def close_spider(self,spider):
        print('I am close_spider,关闭文件执行一次')
        self.fp.close()

1.4.2存储到Mysql数据库中
进入数据库终端
mysql -uroot -p123
创建spider数据库
create database spider;
进入spider库
use spider
创建表
create table qiushibaike(author varchar(100),content varchar(10000));
查看详情
desc qiushibaike;
+---------+----------------+------+-----+---------+-------+
| Field   | Type           | Null | Key | Default | Extra |
+---------+----------------+------+-----+---------+-------+
| author  | varchar(100)   | YES  |     | NULL    |       |
| content | varchar(10000) | YES  |     | NULL    |       |
+---------+----------------+------+-----+---------+-------+
import pymysql

class MysqlPipeLine:
    conn = None
    cursor = None

    def open_spider(self, spider):
        self.conn = pymysql.Connect(host='127.0.0.1',port=3306,user='root',password='123',db='spider',charset='utf8')
        print(self.conn)

    # 会被调用多次
    def process_item(self, item, spider):
        author = item['author']
        content = item['content']
        self.cursor = self.conn.cursor()
        sql = 'insert into qiushibaike values("%s","%s")'%(author,content)
        try:
            self.cursor.execute(sql)
            self.conn.commit()
        except Exception as e:
            self.conn.rollback()
        return item

    def close_spider(self, spider):
        self.conn.close()
        self.cursor.close()
1.4.3存储到Redis数据库中
from redis import Redis

class RedisPipeLine:
    conn = None

    def open_spider(self, spider):
        self.conn = Redis(host='127.0.0.1',port=6379,password='foobared')

    # 会被调用多次
    def process_item(self, item, spider):
        self.conn.lpush('qiubaiData',item)

1.5、在配置文件中开启管道

ITEM_PIPELINES = {
   # 300:管道被执行的优先级,数值越小优先级越高,管道类的优先级越高则表示该管道类优先被执行
   'qiubaipro.pipelines.QiubaiproPipeline': 300,
   'qiubaipro.pipelines.MysqlPipeLine': 301,
   'qiubaipro.pipelines.RedisPipeLine': 302,
}

1.6、终端执行命令

scrapy crawl qiubai

1.7、数据查看

Mysql数据库中查询

select * from qiushibaike;

Rdis数据库中查询

问题:什么时候需要用到多个管道类?

​ 实现数据备份的时候。

如何将一组数据持久化存储到不同的载体中呢?

​ 一个管道类表示将一组数据存储到一种形式的载体中。

如果有两个管道类的,爬虫文件提交的item会不会同时提交给这多个管道类?

​ 爬虫文件提交的item只会提交给优先级最高的那一个管道类。

如何将item提交给其他的管道类?
无法实现。却可以将优先级最高的管道类接收到的item对象传递给其他的管道类。
在process_item方法中return item操作表示将item传递给下一个即将被执行的管道类

2、手动请求发送

将多个页码对应的数据进行爬取+解析

  • yield关键字在框架中只会被作用到两个地方
    • 向管道提交item
    • 手动发起请求
      • yield scrapy.Reqeust(url,callback):
import scrapy
from qiubaipro.items import QiubaiproItem


class QiubaiSpider(scrapy.Spider):
    name = 'qiubai'
    start_urls = ['https://www.qiushibaike.com/text/']
    url_model = 'https://www.qiushibaike.com/text/%d/'
    page_num = 2
    def parse(self, response):
        div_list = response.xpath('//*[@id="content"]/div/div[2]/div')
        all_data = []
        for div in div_list:
            author = div.xpath('./div[1]/a[2]/h2/text()').extract_first()
            content = div.xpath('./a/div/span//text()').extract()
            content = ''.join(content)
            # 解析出来几个字段,就需要在item类中定义几个属性
            # item 对象当做是一个字典
            item = QiubaiproItem()
            item['author'] = author
            item['content'] = content
            yield item
        # 手动请求代码
        if self.page_num < 6:   #结束递归的条件
            url = format(self.url_model % self.page_num)
            self.page_num += 1
            yield scrapy.Request(url=url, callback=self.parse)

start_urls列表如何帮我们自动进行get请求发送

    #父类的一个方法,会被默认执行
    #自动调用了一个start_reqeusts的方法,方法的模拟实现如下:
        def start_requests(self):#模拟实现该方法的原始实现
            for url in self.start_urls:
                yield scrapy.Request(url,callback=self.parse)

如何手动发起post请求:

  • yield scrapy.FormRequest(url,formdata,callback)

  • 如何让start_urls的列表元素发起post请求

    • 重写start_reqeusts方法。

      formdata:请求的参数

    • def start_requests(self):#模拟实现该方法的原始实现
      print('i am start_requests()')
      for url in self.start_urls:
      yield scrapy.FormRequest(url,formdata,callback=self.parse)

3、请求传参

爬取4567前5页电影名称+电影描述

import scrapy
from moviepro.items import MovieproItem


class MovieSpider(scrapy.Spider):
    name = 'movie'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['https://www.4567kan.com/index.php/vod/show/class/%E5%8A%A8%E4%BD%9C/id/1.html']
    url_model = 'https://www.4567kan.com/index.php/vod/show/class/动作/id/1/page/%d.html'
    page_num = 2

    def parse(self, response):
        li_list = response.xpath('/html/body/div[1]/div/div/div/div[2]/ul/li')
        for li in li_list:
            item = MovieproItem()
            title = li.xpath('./div/a/@title').extract_first()
            item['title'] = title
            detail_url = 'https://www.4567kan.com' + li.xpath('./div/a/@href').extract_first()
            # 对详情页的url发起请求
            # meta是一个字典,可以在请求的过程中将meta传递给callback
            yield scrapy.Request(detail_url, callback=self.parse_detail, meta={'item': item})
        if self.page_num < 6:
            new_url = format(self.url_model % self.page_num)
            self.page_num += 1
            yield scrapy.Request(new_url, callback=self.parse)

    # 解析详情页的数据
    def parse_detail(self, response):
        # 接受meta
        item = response.meta['item']
        desc = response.xpath('/html/body/div[1]/div/div/div/div[2]/p[5]/span[2]/text()').extract_first()
        item['desc'] = desc

        # 提交管道
        yield item
  • 作用:可以帮助scrapy实现数据的深度爬取
  • 深度爬取:爬取的数据没有存在于同一张页面
  • 效果:可以让爬虫文件中多个解析的方法公用同一个item对象
  • 传递item:yield scrapy.Request(url,callback,meta={'xxx':xxx})
  • 接受item:response.meta['xxx']
  • 配置文件:
    • CONCURRENT_REQUESTS = 32 #表示框架开启的线程数量,默认开启16个
  • yield关键字在框架中只会被作用到两个地方
    • 向管道提交item
    • 手动发起请求

4、五大核心组件

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

结构流程图:

5、中间件的基本操作

  • 中间件的作用:

    • 批量拦截请求和响应
  • 中间件的分类:

    • 爬虫中间件
    • 下载中间件【重点】
  • 下载中间件:

    • 拦截请求干什么?

          # 拦截请求
          # 参数request就是拦截到所有的请求
          def process_request(self, request, spider):
              print('拦截的请求是:', request.url)
              # 可进行UA伪装
              request.headers['User_Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36'
              # 可设置cookie
              # request.cookie = 'asdfasdgadfavv'
              return None
      
      • 请求头的伪装
        • 在中间件进行UA伪装和在配置文件进行的UA伪装区别:
          • 配置文件的UA伪装是全局,可以将所有的请求设置成一个UA
          • 中间件是可以将每一个请求都设置成不同的UA
      • 代理设置
          # 拦截发生异常的请求对象
          # 参数request就是拦截到异常的请求对象
          # 拦截异常的请求后需要对其修正,让其成为一个正常的请求,让其重新发送
          def process_exception(self, request, exception, spider):
              print('拦截到异常的请求对象为:', request.url)
              # 建议将代理的设置写到方法内部
              # request.meta['proxy'] = 'https://ip:port'
              return request#将修正后的请求进行重新发送
      
    • 拦截响应干什么?

      • 篡改响应数据
          # 拦截响应
          # request拦截到响应对应的请求对象
          # response拦截到的响应对象
          def process_response(self, request, response, spider):
              print('拦截到的响应对象是:', response)
              return response
      
      • 为什么:

        • 如果请求到的数据是不满足需求的数据,则就需要进行响应数据的篡改。

        • 什么是不满足需求的响应数据?

          • 动态加载的数据。

开启下载中间件settings.py中配置

SPIDER_MIDDLEWARES = {
   'middlepro.middlewares.MiddleproSpiderMiddleware': 543,
}
posted @ 2020-06-23 22:34  自己有自己的调调、  阅读(378)  评论(0编辑  收藏  举报