Python爬虫5.1 — scrapy框架简单入门

综述

本系列文档用于对Python爬虫技术的学习进行简单的教程讲解,巩固自己技术知识的同时,万一一不小心又正好对你有用那就更好了。
Python 版本是3.7.4

前面我们学习都是爬虫相关的一些基本知识,学会掌握了前面的技术知识我们可以解决90%爬虫相关的问题。但是我们如何更快更高效的解决这些问题呢(不光是开发快还有请求处理爬取快),这就用到了框架。下面我们开始一步一步学习Scrapy框架。

Scrapy 框架

Scrapy 框架介绍

写一个爬虫,需要做很多的事情。比如:发送网络请求、数据解析、数据存储、反反爬虫机制(更换ip代理、设置请求头等)、异步请求 等。这些工作如果每次都要自己从零开始写的话,比较浪费时间。因此Scrapy把一些基础的东西都封装好了,在它上面开发爬虫可以变得更加的高效(爬取效率和开发效率)。因此真正在公司里,一些上了量的爬虫,都是使用Scrapy框架来解决(关于框架的概念在这里就不再做说明)。

Scrapy 架构图

Scrapy架构图1

Scrapy架构图2

Scrapy 框架模块功能

  1. Scrapy Engine(引擎) : Scrapy框架的核心,负责在SpiderItem PipelineDownloaderScheduler中间通信、传输数据等。
  2. Spider(爬虫) : 发送需要爬取的链接给引擎,最后引擎把其他模块请求回来的数据再发给爬虫,爬虫就去解析想要的数据。这部分是我们开发者自己写的,因为要爬取哪些链接,页面中的哪些数据是我们需要的,都是由程序员自己决定。
  3. Scheduler(调度器) : 复制接收引擎发送过来的请求,并按照一定的方式进行排列和整理,负责调度请求的顺序等。
  4. Downloader(下载器) : 负责接收引擎传过来的下载请求,然后去网络上下载对应的数据在交还给引擎。
  5. Item Pipeline(管道) : 负责将Spider(爬虫)传递过来的数据进行保存,具体保存在哪里,因该看开发者自己的需求。
  6. Downloader Middlewares(下载中间件) : 可以扩展下载器和引擎之间通信功能的中间件。
  7. Spider Middlewares(Spider中间件) : 可以扩展引擎和爬虫之间通信功能的中间件。

Scrapy 执行流程

Scrapy中的数据流由执行引擎控制,其过程如下:

  1. 引擎从Spiders中获取到最初的要爬取的请求(Requests);
  2. 引擎安排请求(Requests)到调度器中,并向调度器请求下一个要爬取的请求(Requests);
  3. 调度器返回下一个要爬取的请求(Requests)给引擎;
  4. 引擎将上步中得到的请求(Requests)通过下载器中间件(Downloader Middlewares)发送给下载器(Downloader ),这个过程中下载器中间件(Downloader Middlewares)中的process_request()函数会被调用到;
  5. 一旦页面下载完毕,下载器生成一个该页面的Response,并将其通过下载中间件(Downloader Middlewares)发送给引擎,这个过程中下载器中间件(Downloader Middlewares)中的process_response()函数会被调用到;
  6. 引擎从下载器中得到上步中的Response并通过Spider中间件(Spider Middlewares)发送给Spider处理,这个过程中Spider中间件(Spider Middlewares)中的process_spider_input()函数会被调用到;
  7. Spider处理Response并通过Spider中间件(Spider Middlewares)返回爬取到的Item及(跟进的)新的Request给引擎,这个过程中Spider中间件(Spider Middlewares)的process_spider_output()函数会被调用到;
  8. 引擎将上步中Spider处理的其爬取到的Item给Item 管道(Pipeline),将Spider处理的Request发送给调度器,并向调度器请求可能存在的下一个要爬取的请求(Requests);
  9. (从第二步)重复直到调度器中没有更多的请求(Requests)。

Scrapy 安装和文档

  1. 安装:通过pip install scrapy命令安装即可。
  2. Scrapy官方文档:http://doc.scrapy.org/en/latest
  3. Scrapy中文文档:https://scrapy-chs.readthedocs.io/zh_CN/latest/index.html (文档版本比较老,建议还是看官方文档)

注意:
1. 在Ubuntu上安装 Scrapy 之前需要先安装以下依赖:
sudo apt-get install python-dev python-pip libxml2-dev libxslt1-dev zliblg-dev libffi-dev libssl-dev然后再通过pip install scrapy安装。
2. 如果在Windows系统下,提示这个错误ModuleNotFoundError:No module named 'win32api',那么使用以下命令 可以解决:pip install pypiwin32

Scrapy 快速入门

创建项目

要使用Scrapy框架创建目录,需要通过命令来创建,首先进入到你想把这个项目存放的目录。然后使用以下命令创建:

scrapy startproject [项目名称]

不一定非要使用命令来创建,也可以按照其项目目录结构进行手动创建,但是手动创建不是很方便并且创建起来很麻烦。

创建爬虫

进入到项目所在的路径,执行命令:

scrapy genspider [爬虫名称] [爬虫的域名]

(注意:爬虫的名称不能和项目名称一样)。

目录介绍

下面为项目主要的文件目录及文件作用:

project_folder -- 项目文件夹名称
|
├──project_name -- 该项目的python模块,一般和项目文件夹名称相同
|  |
|  ├──spider -- 放置spider代码的包,以后所有的爬虫,都存放在这个里面
|  |
|  ├──items.py -- 用来存放爬虫怕写来的数据的模型
|  |
|  ├──middlewares.py -- 用来存放各种中间件的文件
|  |
|  ├──pipelines.py -- 用来对items里面提取的数据做进一步处理,如保存到本地磁盘等
|  |
|  ├──settings.py -- 本爬虫的一些配置信息(如请求头、多久发送一次请求、ip代理池等)
|
├──scrapy.cfg -- 项目的配置文件

使用Scrapy框架爬取糗事百科

使用命令创建糗百爬虫

创建一个名字叫做qsbk的爬虫,并且能爬取的网页只会限制在qiushibaike.com这个域名下(注意:爬虫的名称不能和项目名称一样)。

scrapy genspider qsbk_spider 'qiushibaike.com'

爬虫代码解析

  1. items.py文件代码
    import scrapy
    
    class QsbkItem(scrapy.Item):
        # define the fields for your item here like:
    
        # 定义item数据字段
        author = scrapy.Field()
        content = scrapy.Field()
  1. qsbk_spider.py文件代码
    import scrapy
    
    from qsbk.items import QsbkItem
    
    
    class QsbkSpiderSpider(scrapy.Spider):
        name = 'qsbk_spider'
        allowed_domains = ['qiushibaike.com']
        start_urls = ['https://www.qiushibaike.com/text/page/1/']
    
        def parse(self, response):
            # SelectorList
            # 解析页面
            content_left = response.xpath('//div[@id="content-left"]/div')
            # 提取数据
            for dz_div in content_left:
                # Selector
                author = dz_div.xpath(".//h2/text()").get().strip()
                content_tmp = dz_div.xpath(".//div[@class='content']//text()").getall()
                content = ''.join(content_tmp).strip()
                item = QsbkItem(author=author, content=content)
                # 使用yield返回给pipeline
                yield item
  1. pipelines.py文件代码
    import json
    
    
    class QsbkPipeline(object):
        def __init__(self):
            """
            打开文件,也可放在open_spider中
            """
            self.fp = open('duanzi.json', 'w', encoding='utf-8')
    
        def open_spider(self, spider):
            """
            爬虫被打开的时候执行
            :param spider:
            :return:
            """
            print("爬虫开始....")
    
        def process_item(self, item, spider):
            """
            爬虫有item传过来的时候会被调用
            :param item:
            :param spider:
            :return:
            """
            item_json = json.dumps(dict(item), ensure_ascii=False)
            # 数据写入文件
            self.fp.write(item_json + '\n')
            return item
    
        def close_spider(self, spider):
            """
            爬虫关闭的时候被调用
            :param spider:
            :return:
            """
            # 关闭文件
            self.fp.close()
            print("爬虫结束....")

运行爬虫

运行爬虫只要我们运行命令即可:

scrapy crawl qsbk_spider

每次运行爬虫我们都需要执行命令,所以我们可以在项目根目录下创建一个文件start.pyl里面放置需要执行的命令,文件代码如下:

from scrapy import cmdline

cmdline.execute("scrapy crawl qsbk_spider".split())

# execute里面需要传递列表数据,等价于
# cmdline.execute(["scrapy", "crawl", "qsbk_spider"])

运行爬虫会打印很多log信息,我们可以在setting.py文件中进行设置仅打印WARNING等级以上的错误。在文件中增加LOG_LEVEL = 'WARNING'配置即可。

糗事百科Scrapy爬虫笔记

  1. response是一个scrapy.http.response.html.HtmlResponse对象,可以执行xpathcss语法来提取数据。
  2. 提取出来的数据是一个Selector或者SelectorList对象,如果想要获取其中的字符串,那么应该执行getall()或者get()方法。
  3. getall()方法:获取Selector中所有文本,返回的是一个列表。
  4. get()方法:获取的是Selector中的第一个文本,返回德是一个string类型。
  5. 如果数据解析回来,要传给pipeline处理,那个可以使用yield来返回。或者是收集所有的item,最后统一使用return返回。
  6. item:建议在items.py中定义好模型,以后就不要使用字典。
  7. pipeline:这个是专门用来保存诗句的,其中三个方法是会经常使用的:
    • open_spider(self,spider):当爬虫被打开的时候执行;
    • process_item(self,item,spider):当爬虫有item传过来的时候会被调用;
    • close_spider(self,spider):当爬虫关闭的时候被调用。
    • 要激活pipeline,应该在setting.py中,设置ITEM_PIPELINES。示例如下:
    ITEM_PIPELINES = {
        'qsbk.pipelines.QsbkPipeline': 300,
    }
    

优化实例爬虫数据存储(Scrapy导出器)

上面例子我们在进行存储数据的时候需要先将字典转换成json,然后再做一些其他的存储处理操作,这样有点麻烦。我们可以使用Scrapy框架自带的Exporter导出器scrapy.exporters,其中就有一个JSON的导出模块JsonItemExporter

修改pipelines.py文件如下:

# 引入JsonItemExporter类库
from scrapy.exporters import JsonItemExporter


class QsbkPipeline(object):
    def __init__(self):
        """
        打开文件,也可放在open_spider中
        """
        self.fp = open('duanzi1.json', 'wb')
        # 初始化导出器
        # 导出文件必须以二进制打开
        self.exporter = JsonItemExporter(self.fp, ensure_ascii=False, encoding='utf-8')

    def open_spider(self, spider):
        """
        爬虫被打开的时候执行
        :param spider:
        :return:
        """
        print("爬虫开始....")

    def process_item(self, item, spider):
        """
        爬虫有item传过来的时候会被调用
        :param item:
        :param spider:
        :return:
        """
        # 进行导出
        self.exporter.export_item(item)
        return item

    def close_spider(self, spider):
        """
        爬虫关闭的时候被调用
        :param spider:
        :return:
        """
        # 完成导出
        self.exporter.finish_exporting()
        # 关闭文件
        self.fp.close()
        print("爬虫结束....")

我们在使用JsonItemExporter导出器的时候,它是把所有的数据都当成列表中的一项进行导出。这种方式有一个缺陷,它的导出过程是先把你传回来的所有字典保存在一个列表当中,在最后执行finish_exporting()的时候再统一写到json文件中。那么这样的话如果传入的json数据比较大的话就不是很好了,它会把所有要导出的json先放到内存中,如果内存不够大的话就对内存的使用不是很友好。

所以我们可以使用另外一种导出器的使用JsonLinesItemExporter,我们使用这种就像第一次数据处理时的效果,它会将json每行写入到文件中。

修改pipelines.py文件如下:

# 引入JsonLinesItemExporter类库
from scrapy.exporters import JsonLinesItemExporter


class QsbkPipeline(object):
    def __init__(self):
        """
        打开文件,也可放在open_spider中
        """
        self.fp = open('duanzi2.json', 'wb')
        # 初始化导出器
        # 导出文件必须以二进制打开
        self.exporter = JsonLinesItemExporter(self.fp, ensure_ascii=False, encoding='utf-8')

    def open_spider(self, spider):
        """
        爬虫被打开的时候执行
        :param spider:
        :return:
        """
        print("爬虫开始....")

    def process_item(self, item, spider):
        """
        爬虫有item传过来的时候会被调用
        :param item:
        :param spider:
        :return:
        """
        # 进行导出
        self.exporter.export_item(item)
        return item

    def close_spider(self, spider):
        """
        爬虫关闭的时候被调用
        :param spider:
        :return:
        """
        # 关闭文件
        self.fp.close()
        print("爬虫结束....")

Scrapy导出器不光有JSON格式导出,还有XML、CSV、PICK等方式格式化数据导出。

JsonItemExporter和JsonLinesItemExporter

保存Json数据的时候,可以使用这两个类,让操作变得更简单:

  1. JsonItemExporter : 这个时每次把数据添加到内存中,最后统一写入到磁盘中,好处时存储的数据是一个满足json规则的数据,坏处是如果数据量比较大,那么比较耗内存。
  2. JsonLinesItemExporter : 这个每次调用export_item的时候就把这个item存储到硬盘中。患处是每一个字典是一行,整个文件不是一个满足json格式的文件,好处是每次处理数据的时候就直接存储到了硬盘中,这样就不会耗内存。数据也比较安全。

爬取多页数据

修改qsbk_spider.py文件如下即可:

import scrapy

from qsbk.items import QsbkItem


class QsbkSpiderSpider(scrapy.Spider):
    name = 'qsbk_spider'
    allowed_domains = ['qiushibaike.com']
    start_urls = ['https://www.qiushibaike.com/text/page/1/']
    base_url = 'https://www.qiushibaike.com'

    def parse(self, response):
        # SelectorList
        # 解析页面
        content_left = response.xpath('//div[@id="content-left"]/div')
        # 提取数据
        for dz_div in content_left:
            # Selector
            author = dz_div.xpath(".//h2/text()").get().strip()
            content_tmp = dz_div.xpath(".//div[@class='content']//text()").getall()
            content = ''.join(content_tmp).strip()
            item = QsbkItem(author=author, content=content)
            # 使用yield返回给pipeline
            yield item
        # 获取下一页地址
        next_url = response.xpath('//ul[@class="pagination"]/li[last()]/a/@href').get()
        if not next_url:
            # 没有下一页地址结束爬虫
            return
        else:
            # 将下一页请求返回给调度器
            yield scrapy.Request(self.base_url + next_url, callback=self.parse)

日志设置

需另写文章进行记录日志学习

其他博文链接

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章