zoukankan      html  css  js  c++  java
  • 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,
    }
    
  • 相关阅读:
    docker安装mtproto及报错解决方案
    Centos7下创建和管理用户
    GitHub项目绑定自己的域名
    navicate远程连接mysql8.0失败
    Java反射
    Spring AOP
    Spring注解
    学习进度笔记20
    学习进度笔记19
    学习进度笔记18
  • 原文地址:https://www.cnblogs.com/remixnameless/p/13184964.html
Copyright © 2011-2022 走看看