玩转 Scrapy 框架 :Item Pipeline 的使用
Posted Amo Xiang
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了玩转 Scrapy 框架 :Item Pipeline 的使用相关的知识,希望对你有一定的参考价值。
目录
Item Pipeline 即项目管道,调用发生在 Spider 产生 Item 之后,当 Spider 解析完 Response,Item 就会被 Engine 传递到 Item Pipeline,被定义的 Item Pipeline 组件会顺次被调用,完成一连串的处理过程,比如数据清洗、存储等。Item Pipeline 的主要功能如下:
一、核心方法
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、def process_item(self, item, spider)。 process_item 是必须实现的方法,Item Pipeline 管道会默认调用这个方法对 Item 进行处理。例如,在这个方法中可以进行数据清洗或将数据写入数据库等操作。该方法必须返回 Item 类型的值或者抛出一个 DropItem 异常。process_item 方法的参数有如下两个:
item: 当前被处理的 Item 对象
spider: 生成 Item 的 Spider对象
process_item 方法根据不同的返回值类型会有不同的行为,返回值类型如下:
Item 对象: 该 Item 会被低优先级的 Item 管道的 process_item 方法处理,直到所有的 process_item 方法被调用完毕
DropItem 异常: 当前 Item 会被丢弃,不再进行处理
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
到现在为止,已经获得了所有必要的信息,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() # 标题
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
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
TupianspiderPipeline 类从 ImagesPipeline 类派生,ImagesPipeline 是 Scrapy 的内建类,用于下载图像文件(FilesPipeline 用于下载文件,如 excel、word等)。ImagesPipeline 类会默认读取 Item 的 image_urls 字段,并认为该字段是一个列表形式,它会遍历 Item 的 image_urls 字段,然后取出每个 URL,并下载该 URL 对应的图片。
在 TupianspiderPipeline 类中重写了 ImagesPipeline 类的一些方法,这些方法的含义如下:
- file_path 方法:request 参数就是当前下载对应的 Request 对象,这个方法用来返回保存的文件名,本例直接将图片链接的最后一部分作为文件名,在实际应用中,也可以用随机的方式产生文件名,或使用其他任何方式产生文件名。
- item_completed 方法:当单个 Item 对象完成下载后调用该方法。因为不是每张图片都会下载成功,所以需要在该方法中分析下载结果,并剔除下载失败的图片。如果某张图片下载失败,那么就不需要将这样的图片保存到本地和数据库中。results 参数是 Item 对应的下载结果,是一个列表形式的值,每一个列表元素是一个元组,其中包含了下载成功或失败的信息。本例遍历下载结果找出所有下载成功的图片(保存到一个列表中)。如果列表为空,那么该 Item 对应的图片就下载失败,随机抛出 DropItem 异常并忽略该 Item,否则返回该 Item,表明此 Item 有效。
- 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"
这里要注意一下调用的顺序。我们需要优先调用 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 基础的读者少走一些弯路,节省时间,并不用做其他用途,如有侵权,联系博主删除即可。感谢您阅读本篇博文,希望本文能成为您编程路上的领航者。祝您阅读愉快!
好书不厌读百回,熟读课思子自知。而我想要成为全场最靓的仔,就必须坚持通过学习来获取更多知识,用知识改变命运,用博客见证成长,用行动证明我在努力。
如果我的博客对你有帮助、如果你喜欢我的博客内容,请点赞
、评论
、收藏
一键三连哦!听说点赞的人运气不会太差,每一天都会元气满满呦!如果实在要白嫖的话,那祝你开心每一天,欢迎常来我博客看看。
编码不易,大家的支持就是我坚持下去的动力。点赞后不要忘了关注
我哦!
以上是关于玩转 Scrapy 框架 :Item Pipeline 的使用的主要内容,如果未能解决你的问题,请参考以下文章
玩转 Scrapy 框架 :Scrapy 架构Request和Response介绍