ファイルと画像のダウンロードおよび処理

Scrapyは、特定のアイテムに添付されたファイルをダウンロードするための再利用可能な アイテム・パイプライン を提供します(たとえば、製品をスクレイピングし、画像をローカルにダウンロードする場合)。 これらのパイプラインは少しの機能と構造を共有します(媒体パイプラインと呼びます)が、通常はファイル・パイプラインまたは画像パイプラインを使用します。

両方のパイプラインは以下の機能を実装しています:

  • 最近ダウンロードした媒体の再ダウンロードを避ける

  • メディアの保存場所の指定(ファイル・システム・ディレクトリ、Amazon S3バケット、Google Cloud Storageバケット)

画像パイプラインには、画像を処理するためのいくつかの追加機能があります:

  • ダウンロードしたすべての画像を共通の形式(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 を使用することとよく似ています。あなたがアイテムの画像URLに image_urls を使用すると、ダウンロードした画像に関する情報が images フィールドに入力されます。

画像ファイルに ImagesPipeline を使用する利点は、サムネイルの生成やサイズに基づいた画像のフィルタリングなどの追加機能を設定できることです。

画像パイプラインは、画像をJPEG/RGB形式にサムネイル化および正規化するために Pillow を使用するため、使用するにはこのライブラリをインストールする必要があります。 Python Imaging Library (PIL)もほとんどの場合に動作するはずですが、セットアップによっては問題を引き起こすことが知られているため、PILの代わりに Pillow を使用することをお勧めします。

あなたの媒体パイプラインを有効にする

媒体パイプラインを有効にするには、まずプロジェクトに ITEM_PIPELINES 設定を追加する必要があります。

画像パイプラインを使うには:

ITEM_PIPELINES = {'scrapy.pipelines.images.ImagesPipeline': 1}

ファイル・パイプラインを使うには:

ITEM_PIPELINES = {'scrapy.pipelines.files.FilesPipeline': 1}

注釈

ファイルと画像のパイプラインの両方を同時に使用することもできます。

次に、ダウンロードした画像の保存に使用される有効な値でターゲット・ストレージ設定を構成(configure)します。 そうしないと、パイプラインは ITEM_PIPELINES 設定に含めても無効のままになります。

ファイル・パイプラインの場合、 FILES_STORE 設定を設定します:

FILES_STORE = '/path/to/valid/dir'

画像パイプラインの場合、 IMAGES_STORE 設定を設定します:

IMAGES_STORE = '/path/to/valid/dir'

サポートされるストレージ

現在、ファイル・システムは公式にサポートされている唯一のストレージですが、 Amazon S3Google Cloud Storage にファイルを保存することもサポートされています。

ファイル・システム・ストレージ

ファイルは、ファイル名のURLから生成する SHA1 hash を使用して保存されます。

たとえば、次の画像URL:

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

SHA1 hash は:

3afec3b4765f8f0a07b78f98c07b83f013567a0a

ダウンロードされ、以下のファイルに保存されます:

<IMAGES_STORE>/full/3afec3b4765f8f0a07b78f98c07b83f013567a0a.jpg

ここで:

  • <IMAGES_STORE> は、画像パイプラインの IMAGES_STORE 設定で定義されているディレクトリです。

  • `` full`` は、(使用する場合)サムネイルから完全な画像を分離するためのサブディレクトリです。詳細は 画像のサムネイル生成 を参照。

Amazon S3 ストレージ

FILES_STOREIMAGES_STORE はAmazon S3バケットを表すことができます。 Scrapyは自動的にファイルをバケットにアップロードします。

たとえば、以下は有効な IMAGES_STORE 値です:

IMAGES_STORE = 's3://bucket/images'

あなたは FILES_STORE_S3_ACLIMAGES_STORE_S3_ACL 設定によって定義される、保存されたファイルに使用されるアクセス制御リスト(ACL)ポリシーを変更できます。デフォルトでは、ACLは private に設定されています。ファイルを公開するには、 public-read ポリシーを使用します:

IMAGES_STORE_S3_ACL = 'public-read'

詳細については、Amazon S3開発者ガイドの canned ACLs を参照してください。

Scrapyは boto / botocore を内部で使用するため、他の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)

Google Cloud ストレージ

FILES_STOREIMAGES_STORE は、Google Cloud Storageバケットを表すことができます。Scrapyは自動的にファイルをバケットにアップロードします( google-cloud-storage が必要です)。

たとえば、以下は有効な IMAGES_STORE および GCS_PROJECT_ID 設定です:

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

認証については、この documentation を参照してください。

FILES_STORE_GCS_ACL および IMAGES_STORE_GCS_ACL 設定によって定義される、保存されたファイルに使用されるアクセス制御リスト(ACL)ポリシーを変更できます。デフォルトでは、ACLは '' (空の文字列)に設定されます。これは、Cloud Storageがバケットのデフォルト・オブジェクトACLをオブジェクトに適用することを意味します。ファイルを公開するには、 publicRead ポリシーを使用します:

IMAGES_STORE_GCS_ACL = 'publicRead'

詳細については、Google Cloud Platform Developer Guide の Predefined ACLs を参照してください。

使用例

媒体パイプラインを使用するには、まず、 媒体パイプラインを有効にする を行います。

次に、スパイダーがURLキー(ファイルまたは画像パイプラインの場合はそれぞれ file_urls または image_urls )を含む辞書を返すと、パイプラインは結果をそれぞれのキー( files または images )の値として返します。

Item を使用する場合は、以下のImages Pipelineの例のように、必要なフィールドを持つカスタム・アイテムを定義します:

import scrapy

class MyItem(scrapy.Item):

    # ... other item fields ...
    image_urls = scrapy.Field()
    images = scrapy.Field()

URLキーまたは結果キーに別のフィールド名を使用する場合は、それをオーバーライドすることもできます。

ファイル・パイプラインでは、 FILES_URLS_FIELD and/or FILES_RESULT_FIELD 設定を設定します:

FILES_URLS_FIELD = 'field_name_for_your_files_urls'
FILES_RESULT_FIELD = 'field_name_for_your_processed_files'

画像パイプラインでは、 IMAGES_URLS_FIELD and/or 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で、カスタムFIlE_EXPIERSが必要な場合は、以下のように設定すると、カスタム設定が使用されます。

MYPIPELINE_FILES_EXPIRES = 180

こうすると、パイプライン・クラスMyPipelineの有効期限は180に設定されます。

画像のサムネイル生成

画像Pipelineは、ダウンロードした画像のサムネイルを自動的に作成できます。

この機能を使用するには、 IMAGES_THUMBS を辞書に設定する必要があります。ここで、キーはサムネイル名であり、値はその寸法です。

例えば:

IMAGES_THUMBS = {
    'small': (50, 50),
    'big': (270, 270),
}

この機能を使用すると、画像パイプラインは指定された各サイズのサムネイルをこの形式で作成します:

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

ここで:

  • <size_name>IMAGES_THUMBS 辞書キーで指定されたもの( smallbig など)です

  • <image_id> は画像のURLの SHA1 hash です

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

注釈

サイズの制約は、サムネイルの生成にはまったく影響しません。

1つのサイズ制約または両方を設定することができます。両方を設定すると、両方の最小サイズを満たす画像のみが保存されます。上記の例では、サイズが(105×105)または(105×200)または(200×105)の画像はすべて削除されます。これは、少なくとも1つの寸法が制約よりも短いためです。

デフォルトでは、サイズの制限はないため、すべての画像が処理されます。

リダイレクトを許可する

デフォルトでは、媒体パイプラインはリダイレクトを無視します。つまり、媒体アファイルURLリクエストへのHTTPリダイレクトは、媒体のダウンロードが失敗したと見なされることを意味します。

媒体のリダイレクトを処理するには、この設定を True に設定します:

MEDIA_ALLOW_REDIRECTS = True

媒体パイプラインの拡張

ここで、カスタム・ファイル・パイプラインでオーバーライドできるメソッドを参照してください:

class scrapy.pipelines.files.FilesPipeline
file_path(request, response, info)

このメソッドは、ダウンロードされたアイテムごとに1回呼び出されます。 指定された response から始まるファイルのダウンロード・パスを返します。

response に加えて、このメソッドは元の requestinfo を受け取ります。

このメソッドをオーバーライドして、各ファイルのダウンロード・パスをカスタマイズできます。

たとえば、ファイルのURLが通常のパスのように終わる場合(例 https://example.com/a/b/c/foo.png )、次のアプローチを使用して、全てのファイルを元のファイル(例 files/foo.png)で files フォルダーにダウンロードできます:

import os
from urllib.parse import urlparse

from scrapy.pipelines.files import FilesPipeline

class MyFilesPipeline(FilesPipeline):

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

デフォルトでは file_path() メソッドは full/<request URL hash>.<extension> を返します。

get_media_requests(item, info)

ワークフローにあるように、パイプラインはアイテムからダウンロードする画像のURLを取得します。これを行うには、 get_media_requests() メソッドをオーバーライドして、各ファイルURLのリクエストを返すことができます:

def get_media_requests(self, item, info):
    for file_url in item['file_urls']:
        yield scrapy.Request(file_url)

これらのリクエストはパイプラインによって処理され、ダウンロードが完了すると、結果が item_completed() メソッドに2要素タプルのリストとして送信されます。各タプルには (success, file_info_or_error) が含まれます:

  • success は、画像が正常にダウンロードされた場合は True であるブール値であり、何らかの理由で失敗した場合は False です

  • file_info_or_error は、(success True の場合)以下のキーを含む辞書です。(success False の場合)問題が発生した場合は Twisted Failure を含みます。

    • url - ファイルのダウンロード元のURL。これは get_media_requests() メソッドから返されるリクエストのURLです。

    • path - ファイルが保存されたパス( FILES_STORE への相対パス)

    • checksum - 画像コンテンツのMD5ハッシュ(MD5 hash)

item_completed() が受け取るタプルのリストは、 get_media_requests() メソッドから返されたリクエストと同じ順序を保持することが保証されています。

以下は results 引数の典型的な値です:

[(True,
  {'checksum': '2b00042f7481c7b056c4b410d28f33cf',
   'path': 'full/0a79c461a4062ac383dc4fade7bc09f1384a3910.jpg',
   'url': 'http://www.example.com/files/product1.pdf'}),
 (False,
  Failure(...))]

デフォルトでは、 get_media_requests() メソッドは None を返します。これは、アイテムにダウンロードするファイルがないことを意味します。

item_completed(results, item, info)

FilesPipeline.item_completed() メソッドは、1つのアイテムに対するすべてのファイルリクエストが完了した(ダウンロードが完了したか、何らかの理由で失敗した)ときに呼び出されます。

item_completed() メソッドは、後続のアイテム・パイプライン・ステージに送信される出力を返す必要があるため、パイプラインの場合と同様に、アイテムを返す(またはドロップする)必要があります。

以下は item_completed() メソッドの例で、(結果で渡された)ダウンロードしたファイル・パスを file_paths アイテム・フィールドに保存し、ファイルが含まれていない場合はアイテムをドロップします:

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")
    item['file_paths'] = file_paths
    return item

デフォルトでは、 item_completed() メソッドはアイテムを返します:

カスタム画像パイプラインでオーバーライドできるメソッドをご覧ください:

class scrapy.pipelines.images.ImagesPipeline

ImagesPipelineFilesPipeline の拡張であり、フィールド名をカスタマイズして画像のカスタム動作を追加します。

file_path(request, response, info)

このメソッドは、ダウンロードされたアイテムごとに1回呼び出されます。 指定された response から始まるファイルのダウンロード・パスを返します。

response に加えて、このメソッドは元の requestinfo を受け取ります。

このメソッドをオーバーライドして、各ファイルのダウンロード・パスをカスタマイズできます。

たとえば、ファイルのURLが通常のパスのように終わる場合(例 https://example.com/a/b/c/foo.png )、次のアプローチを使用して、全てのファイルを元のファイル(例 files/foo.png)で files フォルダーにダウンロードできます:

import os
from urllib.parse import urlparse

from scrapy.pipelines.images import ImagesPipeline

class MyImagesPipeline(ImagesPipeline):

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

デフォルトでは file_path() メソッドは full/<request URL hash>.<extension> を返します。

get_media_requests(item, info)

FilesPipeline.get_media_requests() メソッドと同じように機能しますが、画像のURLに異なるフィールド名を使用します。

各画像URLのリクエストを返す必要があります。

item_completed(results, item, info)

ImagesPipeline.item_completed() メソッドは、1つのアイテムに対するすべての画像リクエストが完了した(ダウンロードが完了したか、何らかの理由で失敗した)ときに呼び出されます。

FilesPipeline.item_completed() メソッドと同じように機能しますが、画像のダウンロード結果を保存するために異なるフィールド名を使用します。

デフォルトでは、 item_completed() メソッドはアイテムを返します:

カスタム画像パイプライン例

上記の例示されたメソッドでの画像パイプラインの完全な例を以下に示します:

import scrapy
from scrapy.pipelines.images import ImagesPipeline
from scrapy.exceptions import DropItem

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")
        item['image_paths'] = image_paths
        return item