下载和处理文件和图像

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

两条管道都实现了以下功能:

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

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

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

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

  • 缩略图生成

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

这些管道还保留当前正在计划下载的媒体URL的内部队列,并将到达的包含相同媒体的响应连接到该队列。这样可以避免在多个项目共享同一媒体时多次下载同一媒体。

使用文件管道

使用时的典型工作流 FilesPipeline 像这样:

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

  2. 该项从spider返回并转到项管道。

  3. 当项目到达 FilesPipeline ,中的URL file_urls 使用标准的Scrapy计划程序和下载程序(这意味着计划程序和下载程序中间软件被重用)来计划下载字段,但具有更高的优先级,在其他页面被抓取之前对其进行处理。该项在特定管道阶段保持“锁定”,直到文件完成下载(或由于某种原因失败)。

  4. 下载文件时,另一个字段 (files )将用结果填充。此字段将包含一个包含有关下载文件的信息的dict列表,例如下载路径、原始的scraped url(取自 file_urls 字段),文件校验和和和文件状态。列表中的文件 files 字段将保留与原始字段相同的顺序 file_urls 字段。如果某些文件下载失败,将记录一个错误,并且该文件不会出现在 files 字段。

使用图像管道

使用 ImagesPipeline 很像使用 FilesPipeline ,但使用的默认字段名不同:您使用 image_urls 对于项目的图像URL,它将填充 images 有关下载图像的信息字段。

使用 ImagesPipeline 对于图像文件,您可以配置一些额外的功能,如生成缩略图和根据图像大小过滤图像。

图像管道需要 Pillow 4.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'

文件命名

默认文件命名

默认情况下,文件使用 SHA-1 hash 文件名的URL。

例如,以下图像URL::

http://www.example.com/image.jpg

谁的 SHA-1 hash 是::

3afec3b4765f8f0a07b78f98c07b83f013567a0a

将使用您选择的内容下载并存储 storage method 和以下文件名:

3afec3b4765f8f0a07b78f98c07b83f013567a0a.jpg

自定义文件命名

您可能希望对保存的文件使用不同的计算文件名。例如,通过在文件名中包含元来对图像进行分类。

通过重写 file_path 您的媒体管道的方法。

例如,具有图像URL::的图像管道

http://www.example.com/product/images/large/front/0000000004166

可以处理为具有压缩哈希的文件名和透视 front ::

00b08510e4_front.jpg

通过重写 file_path 如下所示:

import hashlib
from os.path import splitext

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服务器存储

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'

有关详细信息,请参阅 canned ACLs 在AmazonS3开发者指南中。

您还可以使用其他类似S3的存储。像自托管存储一样的存储 Minios3.scality 。您只需在Scrapy设置中设置端点选项:

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)

谷歌云存储

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

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

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

有关身份验证的信息,请参见 documentation .

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

IMAGES_STORE_GCS_ACL = 'publicRead'

有关详细信息,请参阅 Predefined ACLs 在谷歌云平台开发者指南中。

使用实例

为了使用媒体管道,首先 enable it .

那么,如果蜘蛛返回一个 item object 使用URL字段 (file_urlsimage_urls ,对于文件或图像管道),管道会将结果放在相应的字段下 (filesimages

在使用时 item types 对于预先定义的字段,必须同时定义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'

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

如果有多个图像管道继承自ImagePipeline,并且希望在不同的管道中具有不同的设置,则可以设置以管道类的大写名称开头的设置键。例如,如果您的管道名为mypipeline,并且您希望有自定义图像URL字段,那么您可以定义设置mypipeline图像URL字段,并且将使用自定义设置。

其他功能

文件过期

图像管道避免下载最近下载的文件。要调整此保留延迟,请使用 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天。

如果您有子类filespine的管道,并且希望对其进行不同的设置,则可以设置以大写类名开头的设置键。例如,给定名为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

在哪里?

存储图像文件的示例 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)的图像都将被删除,因为至少有一个维度比约束短。

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

允许重定向

默认情况下,媒体管道忽略重定向,即HTTP重定向到媒体文件URL请求将意味着媒体下载失败。

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

MEDIA_ALLOW_REDIRECTS = True

扩展媒体管道

请参见以下自定义文件管道中可以重写的方法:

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

每个下载的项调用一次此方法。它返回源于指定的 response .

除了……之外 response ,此方法接收原始的 requestinfoitem

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

例如,如果文件URL以常规路径结束(例如 https://example.com/a/b/c/foo.png ,您可以使用以下方法将所有文件下载到 files 文件夹及其原始文件名(例如 files/foo.png ):

import os
from urllib.parse import urlparse

from scrapy.pipelines.files import FilesPipeline

class MyFilesPipeline(FilesPipeline):

    def file_path(self, request, response=None, info=None, *, item=None):
        return 'files/' + os.path.basename(urlparse(request.url).path)

同样,您可以使用 item 根据某些项属性确定文件路径。

默认情况下 file_path() 方法返回 full/<request URL hash>.<extension> .

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 是包含以下键的dict(如果成功是 True 或A Failure 如果有问题的话。

    • url -从中下载文件的URL。这是从 get_media_requests() 方法。

    • path -路径(相对于 FILES_STORE )文件的存储位置

    • checksum -A MD5 hash 图像内容的

    • 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() 方法,将下载的文件路径(传入结果)存储在 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

这个 ImagesPipelineFilesPipeline ,自定义字段名并为图像添加自定义行为。

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

每个下载的项调用一次此方法。它返回源于指定的 response .

除了……之外 response ,此方法接收原始的 requestinfoitem

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

例如,如果文件URL以常规路径结束(例如 https://example.com/a/b/c/foo.png ,您可以使用以下方法将所有文件下载到 files 文件夹及其原始文件名(例如 files/foo.png ):

import os
from urllib.parse import urlparse

from scrapy.pipelines.images import ImagesPipeline

class MyImagesPipeline(ImagesPipeline):

    def file_path(self, request, response=None, info=None, *, item=None):
        return 'files/' + os.path.basename(urlparse(request.url).path)

同样,您可以使用 item 根据某些项属性确定文件路径。

默认情况下 file_path() 方法返回 full/<request URL hash>.<extension> .

2.4 新版功能: 这个 item 参数。

get_media_requests(item, info)

工作方式与 FilesPipeline.get_media_requests() 方法,但对图像URL使用不同的字段名。

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

item_completed(results, item, info)

这个 ImagesPipeline.item_completed() 当一个项目的所有图像请求都已完成时(要么已完成下载,要么由于某种原因失败),将调用方法。

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

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

自定义图像管道示例

下面是图像管道的完整示例,其方法如上图所示:

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
}