• Scrapy 框架之 Item Pipeline 的使用


    Item Pipeline 即项目管道,调用发生在 Spider 产生 Item 之后,当 Spider 解析完 Response,Item 就会被 Engine 传递到 Item Pipeline,被定义的 Item Pipeline 组件会顺次被调用,完成一连串的处理过程,比如数据清洗、存储等。Item Pipeline 的主要功能如下:

    1. 数据清理,主要清理 HTML 数据
    2. 校验抓取的数据,检查抓取的字段数据
    3. 查重并丢弃重复的内容
    4. 数据存储,也就是将抓取的数据保存到数据库中(如:SQLite、MySQL、MongoDB 等) 中。

    一、核心方法

    Item Pipeline 管道是一个普通的 Python 类,需要在 pipelines.py 文件中定义。与中间件类一样,也需要实现一些方法,大多数方法是可选的,但必须实现 def process_item(self, item, spider) 方法,其他几个可选的方法如下:

    def open_spider(self, spider: Spider)
    def close_spider(self, spider: Spider)
    def from_crawler(cls, crawler)
    
    • 1
    • 2
    • 3

    下面分别对这些方法进行详细的描述。

    1、def process_item(self, item, spider)。 process_item 是必须实现的方法,Item Pipeline 管道会默认调用这个方法对 Item 进行处理。例如,在这个方法中可以进行数据清洗或将数据写入数据库等操作。该方法必须返回 Item 类型的值或者抛出一个 DropItem 异常。process_item 方法的参数有如下两个:

    item: 当前被处理的 Item 对象
    spider: 生成 Item 的 Spider对象
    
    • 1
    • 2

    process_item 方法根据不同的返回值类型会有不同的行为,返回值类型如下:

    Item 对象: 该 Item 会被低优先级的 Item 管道的 process_item 方法处理,直到所有的 process_item 方法被调用完毕
    DropItem 异常: 当前 Item 会被丢弃,不再进行处理
    
    • 1
    • 2

    2、def open_spider(self, spider: Spider)。 open_spider 方法是在 Spider 开启时自动调用的。在这个方法中可以做一些初始化操作,如打开数据库连接、初始化变量等。其中 spider 就是被开启的 Spider 对象。

    3、def close_spider(self, spider: Spider)。 close_spider 方法是在 Spider 关闭时自动调用的。在该方法中可以做一些收尾工作,如关闭数据库连接、删除临时文件等。其中 spider 就是被关闭的 Spider 对象。

    4、def from_crawler(cls, crawler)。 from_crawler 是一个类方法,用 @classmethod 标识,是一种依赖注入的方式。参数 cls 的类型是 Class,最后会返回一个 Class 实例。通过参数 crawler 可以拿到 Scrapy 的所有核心组件,如全局配置的每个信息,然后创建一个 Pipeline 实例。

    二、实战:获取图片(仅学习使用)

    2.1 简单分析

    本例抓取某个网站中的 1920x1080 壁纸,并将这些壁纸的相关信息(如 URL、标题等) 保存在 MySQL 数据库和 MongoDB。

    通过浏览器输入链接 aHR0cHM6Ly93d3cudHVwaWFuemouY29tL2luZGV4Lmh0bWw= 进入网站首页(推荐使用 Chrome 浏览器),然后在导航栏中选择壁纸图,如下图所示:
    在这里插入图片描述
    进入壁纸图页面之后,向下拉动浏览器滚动条,找到 电脑壁纸分辨率,并选择 1920x1080壁纸,如下图所示:
    在这里插入图片描述
    进入页面,如下图所示:
    在这里插入图片描述然后在页面右键菜单中单击 检查 命令,会打开浏览器开发者工具。切换到 Network 标签,刷新页面,会发现 Network 标签下方显示了很多 URL,如下图所示:
    在这里插入图片描述
    单击第一个 URL 时,在右侧的 Preview 标签页中显示的返回信息正好是页面上的信息,展开这些信息,会看到下图所示的内容,很明显,里面包含了所有需要的信息,包括图形 URL、标题等。
    在这里插入图片描述
    对该网站的初步分析已经结束,找到了 URL,下一步就是需要分析 URL 中的参数了,单击上图所示的 Headers 标签,会看到下图所示的 General 部分,其中 Request URL 就是完整URL。
    在这里插入图片描述
    单击下一页,观察 URL 发生何种变化,如下图所示:
    在这里插入图片描述
    多试几次发现规律,URL 如下:

    第1页url https://www.tupianzj.com/bizhi/1920x1080/list_65_1.html
    第2页url https://www.tupianzj.com/bizhi/1920x1080/list_65_2.html
    第3页url https://www.tupianzj.com/bizhi/1920x1080/list_65_3.html
    第4页url https://www.tupianzj.com/bizhi/1920x1080/list_65_4.html
    第5页url https://www.tupianzj.com/bizhi/1920x1080/list_65_5.html
    第6页url https://www.tupianzj.com/bizhi/1920x1080/list_65_6.html
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    到现在为止,已经获得了所有必要的信息,URL 中 list_65_ 前面的是固定不变的,后面的数字代表某页的数据,第1页就是1,第2页就是2,以此类推。现在万事具备,只差写程序完成壁纸的抓取了。

    2.2 编码

    首先创建一个名为 TupianSpider 的爬虫项目,创建命令如下:
    在这里插入图片描述
    用 Pycharm 打开创建好的爬虫项目,目录结构如下图所示:
    在这里插入图片描述
    1、首先在 settings.py 文件中进行如下设置:
    在这里插入图片描述
    因为我们要将图片信息保存到 MySQL 数据库和 MongoDB 数据库中。要往数据库中写数据,需要连接数据库的信息,如域名或IP、数据库名、用户名、密码等。这些信息也可以直接在 settings.py 文件中进行配置,代码如下:
    在这里插入图片描述
    2、在 items.py 文件中定义要抓取的字段,示例代码如下:

    import scrapy
    
    
    class TupianspiderItem(scrapy.Item):
        # define the fields for your item here like:
        # name = scrapy.Field()
        # 定义MySQL表名和MongoDB数据集合名
        collection = table = "images"  # 表名和集合名
        idx = scrapy.Field()  # 编号
        url = scrapy.Field()  # 链接
        title = scrapy.Field()  # 标题
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    3、接下来在 picture.py 文件中编写抓取图片的爬虫程序,示例代码如下:

    import scrapy
    from TupianSpider.items import TupianspiderItem
    
    
    class PictureSpider(scrapy.Spider):
        name = 'picture'
        # 图片下载地址域名不再该域名下
        # allowed_domains = ['www.tupianzj.com']
        url_template = "https://www.tupianzj.com/bizhi/1920x1080/list_65_{}.html"
        cookies = {
           填写你自己的cookie信息
        }
    
        def start_requests(self):
            # 通过for-in循环向服务端请求MAX_PAGE参数指定的次数
            # 测试的时候可以先将 MAX_PAGE 改为较小值
            for page in range(1, self.settings.get("MAX_PAGE") + 1):
                # 组成每一页URL并发送请求
                yield scrapy.Request(url=self.url_template.format(page), cookies=self.cookies)
    
        def parse(self, response, **kwargs):
            # 解析
            # print(response.text)
            li_list = response.xpath("//ul[@class='list_con_box_ul']/li")
            for li in li_list:
                item = TupianspiderItem()
                img_src = li.xpath("./a[1]/img/@src").extract_first()
                item["idx"] = img_src.split("/")[-1].replace(".jpg", "")
                item["title"] = li.xpath("./a[1]/@title").extract_first()
                item["url"] = 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
    • 28
    • 29
    • 30
    • 31

    4、在 pipelines.py 文件中编写3个 Item 管道类,分别用来将图片保存到本地、保存到 MySQL 数据库和保存到 MongoDB 数据库,示例代码如下:

    import pymysql
    import pymongo
    from scrapy import Request
    from scrapy.pipelines.images import ImagesPipeline
    from scrapy.exceptions import DropItem
    
    
    # 将图片信息保存到MongoDB数据库
    class MongoPipeline:
        def __init__(self, mongo_uri, mongo_db):
            # 传入连接MongoDB数据库必要的信息
            self.mongo_uri = mongo_uri
            self.mongo_db = mongo_db
    
        @classmethod
        def from_crawler(cls, crawler):
            # 这个cls就是MongoPipeline本身,这里创建了MongoPipeline类的实例
            return cls(mongo_uri=crawler.settings.get("MONGO_URI"),
                       mongo_db=crawler.settings.get("MONGO_DB"))
    
        # 开启Spider时调用
        def open_spider(self, spider):
            # 连接MongoDB数据库
            self.client = pymongo.MongoClient(self.mongo_uri)
            self.db = self.client[self.mongo_db]
    
        # 处理Item对象
        def process_item(self, item, spider):
            print(item)
            # 获取数据集的名字(本例是images)
            name = item.collection
            # 向images数据集插入文档
            self.db[name].insert_one(dict(item))
            return item
    
        def close_spider(self, spider):
            # 关闭MongoDB数据库
            self.client.close()
    
    
    # 将图片信息保存到MySQL数据库中
    class MysqlPipeline:
        def __init__(self, host, database, user, password, port):
            self.host = host
            self.database = database
            self.user = user
            self.password = password
            self.port = port
    
        @classmethod
        def from_crawler(cls, crawler):
            # 创建MysqlPipeline类的实例
            return cls(
                host=crawler.settings.get("MYSQL_HOST"),
                database=crawler.settings.get("MYSQL_DATABASE"),
                user=crawler.settings.get("MYSQL_USER"),
                password=crawler.settings.get("MYSQL_PASSWORD"),
                port=crawler.settings.get("MYSQL_PORT"),
            )
    
        def open_spider(self, spider):
            # 连接数据库
            print(1111)
            self.db = pymysql.connect(host=self.host, user=self.user, password=self.password, database=self.database,
                                      # 注意这里的编码有坑,不要写utf-8 否则会报错
                                      charset="utf8", port=self.port)
            self.cursor = self.db.cursor()
    
        def close_spider(self, spider):
            # 关闭数据库
            self.db.close()
    
        def process_item(self, item, spider):
            print(item["title"])
            data = dict(item)
            keys = ", ".join(data.keys())
            values = ", ".join(['%s'] * len(data))
            sql = "insert into %s (%s) values (%s)" % (item.table, keys, values)
            # 将与图片相关的数据插入MySQL数据库的images表中
            self.cursor.execute(sql, tuple(data.values()))
            self.db.commit()
            return item
    
    
    class TupianspiderPipeline(ImagesPipeline):
        # 返回对应本地图像文件的文件名
        # def file_path(self, request, response=None, info=None):
        def file_path(self, request, response=None, info=None, *, item=None):
            url = request.url
            # 注意: split 不要写成了 spilt
            file_name = url.split("/")[-1]
            print(file_name)
            return file_name
    
        # 过滤不符合条件的图片
        def item_completed(self, results, item, info):
            images_paths = [x['path'] for ok, x in results if ok]
            print(images_paths)
            if not images_paths:
                # 抛出异常,删除当前下载的图片
                raise DropItem("Image Download Failed")
            return item
    
        def get_media_requests(self, item, info):
            print(item["url"])
            # 根据当前url创建Request对象,并返回该对象,Request对象会加到调度队列中准备下载该图像
            yield Request(item["url"])
            # return 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
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108

    TupianspiderPipeline 类从 ImagesPipeline 类派生,ImagesPipeline 是 Scrapy 的内建类,用于下载图像文件(FilesPipeline 用于下载文件,如 excel、word等)。ImagesPipeline 类会默认读取 Item 的 image_urls 字段,并认为该字段是一个列表形式,它会遍历 Item 的 image_urls 字段,然后取出每个 URL,并下载该 URL 对应的图片。

    在 TupianspiderPipeline 类中重写了 ImagesPipeline 类的一些方法,这些方法的含义如下:

    1. file_path 方法:request 参数就是当前下载对应的 Request 对象,这个方法用来返回保存的文件名,本例直接将图片链接的最后一部分作为文件名,在实际应用中,也可以用随机的方式产生文件名,或使用其他任何方式产生文件名。
    2. item_completed 方法:当单个 Item 对象完成下载后调用该方法。因为不是每张图片都会下载成功,所以需要在该方法中分析下载结果,并剔除下载失败的图片。如果某张图片下载失败,那么就不需要将这样的图片保存到本地和数据库中。results 参数是 Item 对应的下载结果,是一个列表形式的值,每一个列表元素是一个元组,其中包含了下载成功或失败的信息。本例遍历下载结果找出所有下载成功的图片(保存到一个列表中)。如果列表为空,那么该 Item 对应的图片就下载失败,随机抛出 DropItem 异常并忽略该 Item,否则返回该 Item,表明此 Item 有效。
    3. get_media_requests 方法:item 参数是抓取生成的 Item 对象。将它的 url 字段取出来,然后直接生成 Request 对象。这个 Request 对象会加入调度队列,等待被调用,并下载 URL 对应的图像文件。

    5、最后在 settings.py 文件中声明前面编写的3个管道类,并为 TupianspiderPipeline 类指定图像下载的本地路径,在默认情况下,ImagesPipeline 类会自动读取 settings.py 文件中名为 IMAGES_STORE 的变量值作为存储图片文件的路径。

    ITEM_PIPELINES = {
        'TupianSpider.pipelines.TupianspiderPipeline': 300,
        'TupianSpider.pipelines.MongoPipeline': 301,
        'TupianSpider.pipelines.MysqlPipeline': 302,
    }
    # 指定存储图片文件的路径
    IMAGES_STORE = "./images"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    这里要注意一下调用的顺序。我们需要优先调用 TupianspiderPipeline 对 Item 做下载后的筛选,下载失败的 Item 就直接忽略,它们不会保存到 MongoDB 和 MySQL 里。随后再调用其他两个存储的 Pipeline,这样就能确保存入数据库的图片都是下载成功的。

    现在运行爬虫,会看到 Pycharm 输出类似下图所示的日志信息(前提是注释了 LOG_LEVEL = "ERROR" ),表明爬虫正在抓取图片,并将这些图片保存到本地以及数据库中。
    在这里插入图片描述
    在抓取图片的过程中,会看到在项目目录下多了一个 images 目录,这就是保存本地图片的目录。我们可以打开该目录,会看到 images 目录中有很多图像文件,如下图所示:
    在这里插入图片描述
    我们也可以打开 MySQL 数据库的 images 表,如果 images 表中包含类似下图所示的信息,表明图片数据插入成功。
    在这里插入图片描述
    MongoDB 中查看图片信息:

    Scrapy 提供了专门处理下载的 Pipeline,包括文件下载和图片下载。下载文件的原理和图片的原理与抓取页面的原理一样,因此下载过程支持异步和多线程,十分高效。官方文档地址为:https://doc.scrapy.org/en/latest/topics/media-pipeline.html

    补充:如果是下载文件,由继承 ImagesPipeline 改为继承 FilesPipeline 即可,并将 IMAGES_STORE 改为 FILES_STORE,其他操作不变。

    三、总结

    Item Pipeline 是 Scrapy 非常重要的组件,数据存储几乎都是通过此组件实现的,请认真掌握此内容。

    至此今天的案例就到此结束了,笔者在这里声明,笔者写文章只是为了学习交流,以及让更多学习 Python 基础的读者少走一些弯路,节省时间,并不用做其他用途,如有侵权,联系博主删除即可。感谢您阅读本篇博文,希望本文能成为您编程路上的领航者。祝您阅读愉快!


    在这里插入图片描述

        好书不厌读百回,熟读课思子自知。而我想要成为全场最靓的仔,就必须坚持通过学习来获取更多知识,用知识改变命运,用博客见证成长,用行动证明我在努力。
        如果我的博客对你有帮助、如果你喜欢我的博客内容,请 点赞评论收藏 一键三连哦!听说点赞的人运气不会太差,每一天都会元气满满呦!如果实在要白嫖的话,那祝你开心每一天,欢迎常来我博客看看。
     编码不易,大家的支持就是我坚持下去的动力。点赞后不要忘了 关注 我哦!

  • 相关阅读:
    Java基础:设计模式之原型模式
    python调用c++代码《从C++共享链接库编译到python调用指南》
    中国核动力研究设计院使用 DolphinDB 替换 MySQL 实时监控仪表
    基于STM32的智能小车--电机驱动设计
    MySQL驱动包下载
    MySQL数据库八股文
    Pytorch训练深度强化学习时CPU内存占用一直在快速增加
    互融云工业品电商系统简介
    nginx的性能调优
    界面组件DevExpress WPF v23.2 - 全新升级的数据编辑器、流程图组件
  • 原文地址:https://blog.csdn.net/xw1680/article/details/126687311