ファイルと画像のダウンロードおよび処理¶
Scrapyは、特定のアイテムに添付されたファイルをダウンロードするための再利用可能な アイテム・パイプライン を提供します(たとえば、製品をスクレイピングし、画像をローカルにダウンロードする場合)。 これらのパイプラインは少しの機能と構造を共有します(媒体パイプラインと呼びます)が、通常はファイル・パイプラインまたは画像パイプラインを使用します。
両方のパイプラインは以下の機能を実装しています:
最近ダウンロードした媒体の再ダウンロードを避ける
メディアの保存場所の指定(ファイル・システム・ディレクトリ、Amazon S3バケット、Google Cloud Storageバケット)
画像パイプラインには、画像を処理するためのいくつかの追加機能があります:
ダウンロードしたすべての画像を共通の形式(JPG)とモード(RGB)に変換する
サムネイル生成
画像の幅/高さをチェックして、最小の制約を満たしていることを確認します
パイプラインは、現在ダウンロードがスケジュールされている媒体URLの内部キューを保持し、到着したレスポンスのうち、同じ媒体を含むレスポンスをそのキューに接続します。これにより、複数のアイテムで共有されている場合に同じ媒体を複数回ダウンロードすることがなくなります。
ファイル・パイプラインの使用¶
FilesPipeline
を使用する場合の典型的なワークフローは次のようになります:
Spiderでは、アイテムをスクレイプし、目的のURLを
file_urls
フィールドに入れます。アイテムはスパイダーから返され、アイテム・パイプラインに送られます。
アイテムが
FilesPipeline
に到達すると、file_urls
フィールドのURLは標準のScrapyスケジューラーとダウンローダー(スケジューラーとダウンローダーのミドルウェアが再利用されることを意味します)を使用してダウンロード用にスケジュールされます。しかし、他のページの処理より高い優先度で、他のページがスクレイピングされる前にダウンロード用の処理を行います。ファイルのダウンロードが完了する(または何らかの理由で失敗する))まで、アイテムはその特定のパイプライン・ステージでロックされたままになります。ファイルがダウンロードされると、別のフィールド(
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 S3 と Google 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_STORE
と IMAGES_STORE
はAmazon S3バケットを表すことができます。 Scrapyは自動的にファイルをバケットにアップロードします。
たとえば、以下は有効な IMAGES_STORE
値です:
IMAGES_STORE = 's3://bucket/images'
あなたは FILES_STORE_S3_ACL
と IMAGES_STORE_S3_ACL
設定によって定義される、保存されたファイルに使用されるアクセス制御リスト(ACL)ポリシーを変更できます。デフォルトでは、ACLは private
に設定されています。ファイルを公開するには、 public-read
ポリシーを使用します:
IMAGES_STORE_S3_ACL = 'public-read'
詳細については、Amazon S3開発者ガイドの canned ACLs を参照してください。
Scrapyは boto
/ botocore
を内部で使用するため、他のS3のようなストレージも使用できます。自己ホスト型の Minio や s3.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_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'
認証については、この 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
辞書キーで指定されたもの(small
、big
など)です<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
に加えて、このメソッドは元のrequest
とinfo
を受け取ります。このメソッドをオーバーライドして、各ファイルのダウンロード・パスをカスタマイズできます。
たとえば、ファイルの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
は、(successTrue
の場合)以下のキーを含む辞書です。(successFalse
の場合)問題が発生した場合は 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
¶ ImagesPipeline
はFilesPipeline
の拡張であり、フィールド名をカスタマイズして画像のカスタム動作を追加します。-
file_path
(request, response, info)¶ このメソッドは、ダウンロードされたアイテムごとに1回呼び出されます。 指定された
response
から始まるファイルのダウンロード・パスを返します。response
に加えて、このメソッドは元のrequest
とinfo
を受け取ります。このメソッドをオーバーライドして、各ファイルのダウンロード・パスをカスタマイズできます。
たとえば、ファイルの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