下载和处理文件及图片
Scrapy 提供了可重用的项目管道,用于下载附加到特定项目的 文件 (例如,当你抓取产品时,也希望在本地下载它们的图片)。这些管道共享一些功能和结构(我们称之为媒体管道),但通常你会使用文件管道或图片管道。
这两种管道都实现了以下功能
避免重新下载最近已下载的媒体文件
指定媒体文件的存储位置(文件系统目录、FTP 服务器、Amazon S3 存储桶、Google Cloud Storage 存储桶)
图片管道有一些额外的图片处理功能
将所有下载的图片转换为通用格式(JPG)和模式(RGB)
缩略图生成
检查图片的宽度/高度,确保它们符合最小限制
这些管道还会保留一个内部队列,用于存放当前计划下载的媒体 URL,并将包含相同媒体的响应连接到该队列。这样可以避免当多个项目共享同一媒体文件时重复下载。
使用文件管道
使用 FilesPipeline
的典型工作流程如下所示
在爬虫中,抓取一个项目,并将所需文件的 URL 放入
file_urls
字段中。项目从爬虫返回,并进入项目管道。
当项目到达
FilesPipeline
时,file_urls
字段中的 URL 会使用标准的 Scrapy 调度器和下载器(这意味着调度器和下载器中间件被重用)进行下载调度,但优先级更高,会在抓取其他页面之前处理它们。在该特定的管道阶段,项目会一直“锁定”,直到文件下载完成(或因某种原因失败)。文件下载完成后,另一个字段 (
files
) 将填充下载结果。该字段将包含一个字典列表,其中包含有关已下载文件的信息,例如下载路径、原始抓取的 URL(取自file_urls
字段)、文件校验和以及文件状态。files
字段列表中的文件将保留与原始file_urls
字段相同的顺序。如果某些文件下载失败,将记录一个错误,并且该文件不会出现在files
字段中。
使用图片管道
使用 ImagesPipeline
与使用 FilesPipeline
非常相似,只是使用的默认字段名不同:你使用 image_urls
来表示项目的图片 URL,它将填充一个 images
字段来存储关于已下载图片的信息。
使用 ImagesPipeline
处理图片文件的优势在于,你可以配置一些额外功能,例如生成缩略图和根据图片大小进行过滤。
图片管道需要 Pillow 8.0.0 或更高版本。它用于缩略图生成以及将图片标准化为 JPEG/RGB 格式。
启用媒体管道
要启用媒体管道,必须首先将其添加到项目的 ITEM_PIPELINES
设置中。
对于图片管道,使用
ITEM_PIPELINES = {"scrapy.pipelines.images.ImagesPipeline": 1}
对于文件管道,使用
ITEM_PIPELINES = {"scrapy.pipelines.files.FilesPipeline": 1}
注意
你也可以同时使用文件管道和图片管道。
然后,将目标存储设置配置为一个有效值,该值将用于存储已下载的图片。否则,即使你将其包含在 ITEM_PIPELINES
设置中,管道仍将处于禁用状态。
对于文件管道,设置 FILES_STORE
设置
FILES_STORE = "/path/to/valid/dir"
对于图片管道,设置 IMAGES_STORE
设置
IMAGES_STORE = "/path/to/valid/dir"
文件命名
默认文件命名
默认情况下,文件使用其 URL 的 SHA-1 哈希值 作为文件名进行存储。
例如,以下图片 URL
http://www.example.com/image.jpg
其 SHA-1 哈希值
为
3afec3b4765f8f0a07b78f98c07b83f013567a0a
将使用你选择的存储方法和以下文件名进行下载和存储
3afec3b4765f8f0a07b78f98c07b83f013567a0a.jpg
自定义文件命名
你可能希望为保存的文件使用不同的计算文件名。例如,通过在文件名中包含元数据来对图片进行分类。
通过覆盖媒体管道的 file_path
方法来自定义文件名。
例如,一个图片管道,图片 URL 为
http://www.example.com/product/images/large/front/0000000004166
可以处理成一个带有精简哈希值和视角 front
的文件名
00b08510e4_front.jpg
通过像这样覆盖 file_path
方法
import hashlib
def file_path(self, request, response=None, info=None, *, item=None):
image_url_hash = hashlib.shake_256(request.url.encode()).hexdigest(5)
image_perspective = request.url.split("/")[-2]
image_filename = f"{image_url_hash}_{image_perspective}.jpg"
return image_filename
警告
如果你的自定义文件名方案依赖于在不同抓取之间可能变化的元数据,则可能导致意外地使用新文件名重新下载现有媒体文件。
例如,如果你的自定义文件名方案使用产品标题,并且网站在两次抓取之间更改了项目的产品标题,Scrapy 将使用更新的文件名重新下载相同的媒体文件。
有关 file_path
方法的更多信息,请参阅 扩展媒体管道。
支持的存储
文件系统存储
文件系统存储会将文件保存到以下路径
<IMAGES_STORE>/full/<FILE_NAME>
其中
<IMAGES_STORE>
是图片管道在IMAGES_STORE
设置中定义的目录。full
是一个子目录,用于将完整图片与缩略图分开(如果使用)。更多信息请参阅 图片缩略图生成。<FILE_NAME>
是分配给文件的文件名。更多信息请参阅 文件命名。
FTP 服务器存储
Added in version 2.0. (2.0 版本新加入)
FILES_STORE
和 IMAGES_STORE
可以指向一个 FTP 服务器。Scrapy 将自动把文件上传到服务器。
FILES_STORE
和 IMAGES_STORE
应采用以下形式之一
ftp://username:password@address:port/path
ftp://address:port/path
如果未提供 username
和 password
,则分别从 FTP_USER
和 FTP_PASSWORD
设置中获取。
FTP 支持两种不同的连接模式:主动模式或被动模式。Scrapy 默认使用被动连接模式。要改用主动连接模式,请将 FEED_STORAGE_FTP_ACTIVE
设置为 True
。
Amazon S3 存储
如果安装了 botocore >= 1.4.87,FILES_STORE
和 IMAGES_STORE
可以代表一个 Amazon S3 存储桶。Scrapy 将自动把文件上传到存储桶。
例如,这是一个有效的 IMAGES_STORE
值
IMAGES_STORE = "s3://bucket/images"
你可以修改用于存储文件的访问控制列表 (ACL) 策略,该策略由 FILES_STORE_S3_ACL
和 IMAGES_STORE_S3_ACL
设置定义。默认情况下,ACL 设置为 private
。要使文件公开可用,请使用 public-read
策略
IMAGES_STORE_S3_ACL = "public-read"
更多信息,请参阅 Amazon S3 开发人员指南中的 预设 ACL。
你也可以使用其他 S3 兼容存储。例如自托管的 Minio 或 Zenko CloudServer。你只需要在 Scrapy 设置中配置 endpoint 选项即可
AWS_ENDPOINT_URL = "http://minio.example.com:9000"
对于自托管,你可能还会觉得不需要使用 SSL 且不需要验证 SSL 连接
AWS_USE_SSL = False # or True (None by default)
AWS_VERIFY = False # or True (None by default)
Google Cloud Storage
FILES_STORE
和 IMAGES_STORE
可以代表一个 Google Cloud Storage 存储桶。Scrapy 将自动把文件上传到存储桶。(需要 google-cloud-storage)
例如,这些是有效的 IMAGES_STORE
和 GCS_PROJECT_ID
设置
IMAGES_STORE = "gs://bucket/images/"
GCS_PROJECT_ID = "project_id"
有关身份验证的信息,请参阅此文档。
你可以修改用于存储文件的访问控制列表 (ACL) 策略,该策略由 FILES_STORE_GCS_ACL
和 IMAGES_STORE_GCS_ACL
设置定义。默认情况下,ACL 设置为 ''
(空字符串),这意味着 Cloud Storage 会将存储桶的默认对象 ACL 应用于对象。要使文件公开可用,请使用 publicRead
策略
IMAGES_STORE_GCS_ACL = "publicRead"
更多信息,请参阅 Google Cloud Platform 开发人员指南中的 预定义 ACL。
使用示例
要使用媒体管道,首先启用它。
然后,如果爬虫返回一个包含 URL 字段(对于文件管道是 file_urls
,对于图片管道是 image_urls
)的项目对象,管道将把结果放在相应的字段下(files
或 images
)。
使用预先定义了字段的项目类型时,必须同时定义 URL 字段和结果字段。例如,使用图片管道时,项目必须同时定义 image_urls
和 images
字段。例如,使用 Item
类时
import scrapy
class MyItem(scrapy.Item):
# ... other item fields ...
image_urls = scrapy.Field()
images = scrapy.Field()
如果你希望为 URL 键或结果键使用另一个字段名,也可以对其进行覆盖。
对于文件管道,设置 FILES_URLS_FIELD
和/或 FILES_RESULT_FIELD
设置
FILES_URLS_FIELD = "field_name_for_your_files_urls"
FILES_RESULT_FIELD = "field_name_for_your_processed_files"
对于图片管道,设置 IMAGES_URLS_FIELD
和/或 IMAGES_RESULT_FIELD
设置
IMAGES_URLS_FIELD = "field_name_for_your_images_urls"
IMAGES_RESULT_FIELD = "field_name_for_your_processed_images"
如果你需要更复杂的功能并希望覆盖自定义管道行为,请参阅 扩展媒体管道。
如果你有多个继承自 ImagesPipeline 的图片管道,并且希望在不同的管道中使用不同的设置,你可以设置以管道类大写名称为前缀的设置键。例如,如果你的管道名为 MyPipeline,并且你想要自定义 IMAGES_URLS_FIELD,你可以定义设置 MYPIPELINE_IMAGES_URLS_FIELD,然后将使用你的自定义设置。
附加功能
文件过期
图片管道避免下载最近已下载的文件。要调整此保留延迟,请使用 FILES_EXPIRES
设置(或图片管道中的 IMAGES_EXPIRES
),该设置指定延迟的天数
# 120 days of delay for files expiration
FILES_EXPIRES = 120
# 30 days of delay for images expiration
IMAGES_EXPIRES = 30
这两个设置的默认值均为 90 天。
如果你的管道继承自 FilesPipeline,并且希望为其设置不同的过期时间,你可以设置以大写类名作为前缀的设置键。例如,假设管道类名为 MyPipeline,你可以设置键
MYPIPELINE_FILES_EXPIRES = 180
则管道类 MyPipeline 的过期时间将设置为 180 天。
文件最后修改时间用于确定文件的天数年龄,然后将其与设置的过期时间进行比较,以确定文件是否已过期。
图片缩略图生成
图片管道可以自动为下载的图片创建缩略图。
要使用此功能,你必须将 IMAGES_THUMBS
设置为一个字典,其中键是缩略图名称,值是其尺寸。
例如
IMAGES_THUMBS = {
"small": (50, 50),
"big": (270, 270),
}
使用此功能时,图片管道将为每种指定尺寸的缩略图创建以下格式的文件
<IMAGES_STORE>/thumbs/<size_name>/<image_id>.jpg
其中
<size_name>
是在IMAGES_THUMBS
字典键中指定的名称(small
、big
等)<image_id>
是图片 URL 的 SHA-1 哈希值
使用 small
和 big
缩略图名称存储的图片文件示例
<IMAGES_STORE>/full/63bbfea82b8880ed33cdb762aa11fab722a90a24.jpg
<IMAGES_STORE>/thumbs/small/63bbfea82b8880ed33cdb762aa11fab722a90a24.jpg
<IMAGES_STORE>/thumbs/big/63bbfea82b8880ed33cdb762aa11fab722a90a24.jpg
第一个是完整图片,即从网站下载的图片。
过滤小图片
使用图片管道时,可以通过在 IMAGES_MIN_HEIGHT
和 IMAGES_MIN_WIDTH
设置中指定允许的最小尺寸来丢弃过小的图片。
例如
IMAGES_MIN_HEIGHT = 110
IMAGES_MIN_WIDTH = 110
注意
尺寸限制完全不影响缩略图生成。
可以只设置一个尺寸限制或同时设置两个。同时设置两个限制时,只有满足这两个最小尺寸的图片才会被保存。例如,尺寸为 (105 x 105)、(105 x 200) 或 (200 x 105) 的图片都将被丢弃,因为至少有一个维度短于限制。
默认情况下,没有尺寸限制,因此所有图片都会被处理。
允许重定向
默认情况下,媒体管道会忽略重定向,即 HTTP 重定向到媒体文件 URL 的请求将被视为媒体下载失败。
要处理媒体重定向,请将此设置设置为 True
MEDIA_ALLOW_REDIRECTS = True
扩展媒体管道
在这里查看可以在自定义 Files Pipeline 中覆盖的方法
- 类 在 scrapy.pipelines.files 中FilesPipeline[源]
- file_path(self, request, response=None, info=None, *, item=None)[源]
这个方法在每个已下载的项目上被调用一次。它返回源自指定
response
的文件的下载路径。除了
response
外,这个方法还接收原始的request
、info
和item
你可以覆盖这个方法来自定义每个文件的下载路径。
例如,如果文件 URL 像常规路径一样结尾(例如
https://example.com/a/b/c/foo.png
),你可以使用以下方法将所有文件下载到files
文件夹,并保留原始文件名(例如files/foo.png
)from pathlib import PurePosixPath from scrapy.utils.httpobj import urlparse_cached from scrapy.pipelines.files import FilesPipeline class MyFilesPipeline(FilesPipeline): def file_path(self, request, response=None, info=None, *, item=None): return "files/" + PurePosixPath(urlparse_cached(request).path).name
类似地,你可以使用
item
根据某个项目属性来确定文件路径。默认情况下,
file_path()
方法返回full/<request URL hash>.<extension>
。2.4 版本新加入: item 参数。
- get_media_requests(item, info)[源]
如工作流程所示,管道将从项目获取要下载的图片 URL。为此,你可以覆盖
get_media_requests()
方法,并为每个文件 URL 返回一个 Requestfrom itemadapter import ItemAdapter def get_media_requests(self, item, info): adapter = ItemAdapter(item) for file_url in adapter["file_urls"]: yield scrapy.Request(file_url)
这些请求将由管道处理,当它们下载完成后,结果将作为包含两个元素的元组列表发送到
item_completed()
方法。每个元组将包含(success, file_info_or_error)
,其中success
是一个布尔值,如果图片成功下载则为True
,如果因某种原因失败则为False
file_info_or_error
是一个字典,包含以下键(如果 success 为True
)或一个Failure
(如果出现问题)。url
- 下载文件的 URL。这是从get_media_requests()
方法返回的请求的 URL。path
- 文件存储的路径(相对于FILES_STORE
)。checksum
- 图片内容的 MD5 哈希值。status
- 文件状态指示。2.2 版本新加入。
它可以是以下之一
downloaded
- 文件已下载。uptodate
- 未下载文件,因为它根据文件过期策略最近已下载。cached
- 文件已由另一个共享相同文件的项目计划下载。
由
item_completed()
方法接收的元组列表保证保留与get_media_requests()
方法返回的请求相同的顺序。这是
results
参数的一个典型值[ ( True, { "checksum": "2b00042f7481c7b056c4b410d28f33cf", "path": "full/0a79c461a4062ac383dc4fade7bc09f1384a3910.jpg", "url": "http://www.example.com/files/product1.pdf", "status": "downloaded", }, ), (False, Failure(...)), ]
默认情况下,
get_media_requests()
方法返回None
,这意味着该项目没有文件需要下载。
- item_completed(results, item, info)[源]
当单个项目的所有文件请求完成(无论是下载完成还是因某种原因失败)时,会调用
FilesPipeline.item_completed()
方法。item_completed()
方法必须返回将发送到后续项目管道阶段的输出,因此你必须像在任何管道中一样返回(或丢弃)项目。这是一个
item_completed()
方法的示例,我们将下载的文件路径(在 results 中传入)存储到项目的file_paths
字段中,并且如果项目不包含任何文件,则丢弃该项目。from itemadapter import ItemAdapter from scrapy.exceptions import DropItem def item_completed(self, results, item, info): file_paths = [x["path"] for ok, x in results if ok] if not file_paths: raise DropItem("Item contains no files") adapter = ItemAdapter(item) adapter["file_paths"] = file_paths return item
默认情况下,
item_completed()
方法返回该项目。
在这里查看可以在自定义 Images Pipeline 中覆盖的方法
- 类 在 scrapy.pipelines.images 中ImagesPipeline[源]
ImagesPipeline
是FilesPipeline
的扩展,它自定义了字段名并为图片添加了自定义行为。- file_path(self, request, response=None, info=None, *, item=None)[source]
这个方法在每个已下载的项目上被调用一次。它返回源自指定
response
的文件的下载路径。除了
response
外,这个方法还接收原始的request
、info
和item
你可以覆盖这个方法来自定义每个文件的下载路径。
例如,如果文件 URL 像常规路径一样结尾(例如
https://example.com/a/b/c/foo.png
),你可以使用以下方法将所有文件下载到files
文件夹,并保留原始文件名(例如files/foo.png
)from pathlib import PurePosixPath from scrapy.utils.httpobj import urlparse_cached from scrapy.pipelines.images import ImagesPipeline class MyImagesPipeline(ImagesPipeline): def file_path(self, request, response=None, info=None, *, item=None): return "files/" + PurePosixPath(urlparse_cached(request).path).name
类似地,你可以使用
item
根据某个项目属性来确定文件路径。默认情况下,
file_path()
方法返回full/<请求 URL 哈希值>.<扩展名>
。2.4 版本新加入: item 参数。
- thumb_path(self, request, thumb_id, response=None, info=None, *, item=None)[source]
对于通过下载得到的每个 item,
IMAGES_THUMBS
中的每一项都会调用此方法。它返回源自指定response
的图片的缩略图下载路径。除了
response
,此方法还接收原始的request
、thumb_id
、info
和item
。你可以覆盖此方法来自定义每张图片的缩略图下载路径。你可以使用
item
基于某些 item 属性来确定文件路径。默认情况下,
thumb_path()
方法返回thumbs/<尺寸名称>/<请求 URL 哈希值>.<扩展名>
。
- get_media_requests(item, info)[source]
与
FilesPipeline.get_media_requests()
方法的工作方式相同,但使用不同的字段名来处理图片 URL。必须为每个图片 URL 返回一个 Request。
- item_completed(results, item, info)[source]
当单个 item 的所有图片请求都已完成(无论完成下载还是因某些原因失败)时,会调用
ImagesPipeline.item_completed()
方法。与
FilesPipeline.item_completed()
方法的工作方式相同,但使用不同的字段名来存储图片下载结果。默认情况下,
item_completed()
方法返回 item。
自定义图片管道示例
这里是一个完整的图片管道示例,其中包含了上述方法的示例
import scrapy
from itemadapter import ItemAdapter
from scrapy.exceptions import DropItem
from scrapy.pipelines.images import ImagesPipeline
class MyImagesPipeline(ImagesPipeline):
def get_media_requests(self, item, info):
for image_url in item["image_urls"]:
yield scrapy.Request(image_url)
def item_completed(self, results, item, info):
image_paths = [x["path"] for ok, x in results if ok]
if not image_paths:
raise DropItem("Item contains no images")
adapter = ItemAdapter(item)
adapter["image_paths"] = image_paths
return item
要启用你的自定义媒体管道组件,必须将其类导入路径添加到 ITEM_PIPELINES
设置中,如下例所示
ITEM_PIPELINES = {"myproject.pipelines.MyImagesPipeline": 300}