下载和处理文件和图像

Scrapy 提供了可重用的 item pipelines 用于下载附加到特定项目的文件(例如,当您抓取产品并希望将其图像下载到本地时)。这些管道共享一些功能和结构(我们称它们为媒体管道),但通常您会使用文件管道或图像管道。

两个管道都实现了这些功能:

  • 避免重新下载最近下载的媒体

  • 指定存储媒体的位置(文件系统目录、FTP服务器、Amazon S3存储桶、Google云存储桶)

图像管道有一些额外的功能用于处理图像:

  • 将所有下载的图像转换为通用格式(JPG)和模式(RGB)

  • 缩略图生成

  • 检查图像的宽度/高度以确保它们满足最小约束

管道还维护一个内部队列,用于存储当前正在安排下载的媒体URL,并将包含相同媒体的响应连接到该队列。这避免了在多个项目共享同一媒体时重复下载。

使用文件管道

使用 FilesPipeline 时的典型工作流程如下:

  1. 在Spider中,您抓取一个项目并将所需URL放入file_urls字段中。

  2. 项目从蜘蛛返回并进入项目管道。

  3. 当项目到达FilesPipeline时,file_urls字段中的URL会使用标准的Scrapy调度器和下载器(这意味着调度器和下载器中间件被重用)进行下载调度,但具有更高的优先级,在其他页面被抓取之前处理它们。项目在该特定管道阶段保持“锁定”状态,直到文件下载完成(或因某些原因失败)。

  4. 当文件下载完成后,另一个字段(files)将会被填充结果。这个字段将包含一个字典列表,其中包含有关下载文件的信息,例如下载路径、原始抓取的URL(取自file_urls字段)、文件校验和以及文件状态。files字段列表中的文件将保留原始file_urls字段的顺序。如果某些文件下载失败,将会记录错误,并且该文件不会出现在files字段中。

使用图片管道

使用 ImagesPipeline 与使用 FilesPipeline 非常相似, 除了使用的默认字段名称不同:你使用 image_urls 来表示项目的图片 URL,它将填充一个 images 字段来存储下载图片的信息。

使用ImagesPipeline处理图像文件的优势在于,你可以配置一些额外的功能,比如生成缩略图和根据图像大小进行过滤。

Images Pipeline 需要 Pillow 7.1.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 hash 是:

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 Pipeline 定义的目录。

  • full 是一个子目录,用于将完整图像与缩略图分开(如果使用)。更多信息请参见 Thumbnail generation for images

  • 是分配给文件的文件名。更多信息请参见 File Naming

FTP服务器存储

版本2.0新增。

FILES_STOREIMAGES_STORE 可以指向一个FTP服务器。 Scrapy 会自动将文件上传到服务器。

FILES_STOREIMAGES_STORE 应以以下形式之一编写:

ftp://username:password@address:port/path
ftp://address:port/path

如果未提供usernamepassword,则分别从FTP_USERFTP_PASSWORD设置中获取。

FTP支持两种不同的连接模式:主动或被动。Scrapy默认使用被动连接模式。要使用主动连接模式,请将FEED_STORAGE_FTP_ACTIVE设置设为True

Amazon S3 存储

如果安装了 botocore >= 1.4.87,FILES_STOREIMAGES_STORE 可以表示一个 Amazon S3 存储桶。Scrapy 将 自动将文件上传到该存储桶。

例如,这是一个有效的 IMAGES_STORE 值:

IMAGES_STORE = "s3://bucket/images"

您可以修改用于存储文件的访问控制列表(ACL)策略,该策略由FILES_STORE_S3_ACLIMAGES_STORE_S3_ACL设置定义。默认情况下,ACL设置为private。要使文件公开可用,请使用public-read策略:

IMAGES_STORE_S3_ACL = "public-read"

有关更多信息,请参阅Amazon S3开发者指南中的canned ACLs

你也可以使用其他类似S3的存储。例如自托管的MinioZenko 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 云存储

FILES_STOREIMAGES_STORE 可以表示一个Google云存储桶。Scrapy会自动将文件上传到该桶中。(需要 google-cloud-storage

例如,这些是有效的 IMAGES_STOREGCS_PROJECT_ID 设置:

IMAGES_STORE = "gs://bucket/images/"
GCS_PROJECT_ID = "project_id"

有关身份验证的信息,请参阅此文档

您可以修改用于存储文件的访问控制列表(ACL)策略,该策略由FILES_STORE_GCS_ACLIMAGES_STORE_GCS_ACL设置定义。默认情况下,ACL设置为''(空字符串),这意味着云存储将存储桶的默认对象ACL应用于对象。要使文件公开可用,请使用publicRead策略:

IMAGES_STORE_GCS_ACL = "publicRead"

欲了解更多信息,请参阅Google Cloud Platform开发者指南中的预定义ACL

使用示例

为了使用媒体管道,首先启用它

然后,如果蜘蛛返回一个带有URLs字段的项目对象(分别为文件或图像管道的file_urlsimage_urls),管道将把结果放在相应的字段下(filesimages)。

当使用item types时,如果字段是预先定义的,你必须同时定义URLs字段和结果字段。例如,当使用图像管道时,items必须同时定义image_urlsimages字段。例如,使用Item类:

import scrapy


class MyItem(scrapy.Item):
    # ... other item fields ...
    image_urls = scrapy.Field()
    images = scrapy.Field()

如果你想为URLs键或结果键使用另一个字段名称,也可以覆盖它。

对于文件管道,设置 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"

如果您需要更复杂的内容并希望覆盖自定义管道的行为,请参阅扩展媒体管道

如果您有多个继承自ImagePipeline的图像管道,并且您希望在不同的管道中拥有不同的设置,您可以设置以管道类的大写名称开头的设置键。例如,如果您的管道名为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 Pipeline 将以以下格式创建每个指定大小的缩略图:

<IMAGES_STORE>/thumbs/<size_name>/<image_id>.jpg

其中:

使用smallbig缩略图名称存储的图像文件示例:

<IMAGES_STORE>/full/63bbfea82b8880ed33cdb762aa11fab722a90a24.jpg
<IMAGES_STORE>/thumbs/small/63bbfea82b8880ed33cdb762aa11fab722a90a24.jpg
<IMAGES_STORE>/thumbs/big/63bbfea82b8880ed33cdb762aa11fab722a90a24.jpg

第一个是完整图像,从网站下载的。

过滤掉小图片

在使用图像管道时,您可以通过在IMAGES_MIN_HEIGHTIMAGES_MIN_WIDTH设置中指定允许的最小尺寸来丢弃过小的图像。

例如:

IMAGES_MIN_HEIGHT = 110
IMAGES_MIN_WIDTH = 110

注意

大小限制完全不影响缩略图的生成。

可以只设置一个尺寸约束或同时设置两个。当同时设置两者时,只有满足最小尺寸的图像才会被保存。对于上述示例,尺寸为(105 x 105)、(105 x 200)或(200 x 105)的图像都将被丢弃,因为至少有一个维度小于约束条件。

默认情况下,没有大小限制,因此所有图像都会被处理。

允许重定向

默认情况下,媒体管道会忽略重定向,即对媒体文件URL请求的HTTP重定向将被视为媒体下载失败。

要处理媒体重定向,请将此设置设为 True

MEDIA_ALLOW_REDIRECTS = True

扩展媒体管道

请参阅此处您可以在自定义文件管道中重写的方法:

class scrapy.pipelines.files.FilesPipeline[source]
file_path(self, request, response=None, info=None, *, item=None)[source]

此方法对每个下载的项目调用一次。它返回来自指定 response的文件的下载路径。

除了response,此方法还接收原始的 requestinfoitem

您可以重写此方法以自定义每个文件的下载路径。

例如,如果文件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/ URL hash>.

版本2.4新增:item 参数。

get_media_requests(item, info)[源代码]

如工作流所示,管道将从项目中获取要下载的图像的URL。为了实现这一点,你可以重写get_media_requests()方法,并为每个文件URL返回一个请求:

from 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() 方法,作为一个包含2元素元组的列表。 每个元组将包含 (success, file_info_or_error) 其中:

  • success 是一个布尔值,如果图像下载成功则为 True,如果由于某种原因失败则为 False

  • file_info_or_error 是一个包含以下键的字典(如果 成功是 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)[source]

当单个项目的所有文件请求完成时(无论是下载完成,还是由于某种原因失败),将调用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() 方法返回该项目。

请参阅此处您可以在自定义图像管道中重写的方法:

class scrapy.pipelines.images.ImagesPipeline[source]

ImagesPipelineFilesPipeline 的扩展, 它定制了字段名称并为图像添加了自定义行为。

file_path(self, request, response=None, info=None, *, item=None)[source]

此方法对每个下载的项目调用一次。它返回来自指定 response的文件的下载路径。

除了response,此方法还接收原始的 requestinfoitem

您可以重写此方法以自定义每个文件的下载路径。

例如,如果文件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 hash>.

版本2.4新增:item 参数。

thumb_path(self, request, thumb_id, response=None, info=None, *, item=None)[source]

此方法为每个下载项中的IMAGES_THUMBS项调用。它返回源自指定response的图像的缩略图下载路径。

除了response,此方法还接收原始的 requestthumb_idinfoitem

您可以重写此方法以自定义每个图像的缩略图下载路径。 您可以使用item根据某些项目属性确定文件路径。

默认情况下,thumb_path() 方法返回 thumbs/ name>/ URL hash>.

get_media_requests(item, info)[source]

工作方式与 FilesPipeline.get_media_requests() 方法相同, 但使用不同的字段名称来存储图片URL。

必须为每个图像URL返回一个请求。

item_completed(results, item, info)[source]

当单个项目的所有图片请求完成时(无论是下载完成,还是由于某些原因失败),ImagesPipeline.item_completed() 方法会被调用。

工作方式与 FilesPipeline.item_completed() 方法相同, 但使用不同的字段名称来存储图像下载结果。

默认情况下,item_completed() 方法返回该项目。

自定义图像管道示例

以下是 Images Pipeline 的完整示例,其方法如上所述:

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}