この記事について
- Python用のスクレイピングフレームワークであるScrapyを使用します
- Scrapyを使用して、Webサイトをクローリングして、画像を取得、保存する方法のメモです
- 保存するファイル名はSHA1 hashではなく、ちゃんと個別にファイル名を付けて保存します
- 画像保存の方法にフォーカスしたいため、フィルタ設定などはしません。また、以下のような単純なサイトにのみ対応します
- リンクは素直に、htmlファイルに対して
<a>
タグで実装されている - 画像は
<img>
タグで実装されている
- リンクは素直に、htmlファイルに対して
- この記事では、ヤフーニュースのトップからすべてのページ、画像を巡回する例を挙げます
Scrapyプロジェクトを作る
scrapy startproject test_scrapy
cd test_scrapy
Spiderを用意して実装する
下記コマンドで、save_yahoo_image.pyを生成します。
scrapy genspider save_yahoo_image news.yahoo.co.jp
下記のように実装します。実装内容としては、http://https://news.yahoo.co.jp
からすべてのリンクを巡回しながらパース(parse_page
につっこむ)します。
parse_page
では、取得したページ内のすべての<img>
タグのsrc
を取得して画像のURLを取得します。その他、必要な情報を後で用意するImageItemクラスに入れます。
ImageItemクラスには、画像を保存するディレクトリ名(image_directory_name
、ここでは、URLのファイル名の一つ上のアドレスとしている)と、画像のURLリスト(image_urls
)を格納します。ImageItemクラスは後で実装します。
# -*- coding: utf-8 -*-
import scrapy
from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor
from test_scrapy.items import ImageItem
class SaveYahooImageSpider(CrawlSpider):
name = 'save_yahoo_image'
allowed_domains = ["news.yahoo.co.jp"]
start_urls = ["https://news.yahoo.co.jp"]
rules = (
Rule(LinkExtractor(allow=( )), callback="parse_page", follow=True),
)
def parse_page(self, response):
print("\n>>> Parse " + response.url + " <<<")
# print(response.url.rsplit("/", 1)[0])
item = ImageItem()
item["image_directory_name"] = self.start_urls[0].rsplit("/", 1)[1]
item["image_urls"] = []
for image_url in response.xpath("//img/@src").extract():
if "http" not in image_url:
item["image_urls"].append(response.url.rsplit("/", 1)[0] + "/" + image_url)
else:
item["image_urls"].append(image_url)
# print(vars(item))
return item
ImageItemを実装する
items.pyにImageItemクラスを追加します。格納するフィールドを定義しているだけです。
# -*- coding: utf-8 -*-
import scrapy
from scrapy.item import Item, Field
class ImageItem(Item):
image_directory_name = Field()
image_urls = Field()
images = Field()
Imageパイプラインの設定をする
先ほど実装したparse_page
処理が終わった後に、画像を保存するような設定をします。
まず、settings.pyに以下の2行を追加します。後で作成するMyImagesPipelineクラスを使用し、画像の保存先を./savedImagesにするという指定をします。
ITEM_PIPELINES = {'test_scrapy.pipelines.MyImagesPipeline': 1}
# ITEM_PIPELINES = {'scrapy.contrib.pipeline.images.ImagesPipeline': 1}
IMAGES_STORE = './savedImages'
pipelines.pyに画像を保存するための処理を追加します。これは必須ではなく、標準のImagesPipelineを使用しても良いです(そのときは、上述のsettings.pyのコメントアウトを切り替える)。ただし、ImagesPipelineだと保存される画像ファイルのファイル名がSHA1のハッシュ値になってしまうという問題があります。それだとわかりづらいので、指定したディレクトリ下に、URLの最後の部分(XXX.jpg)をファイル名として保存するようにしたクラスMyImagesPipelineを用意して、それを使います。
# -*- coding: utf-8 -*-
import scrapy
from scrapy.pipelines.images import ImagesPipeline
from scrapy.utils.misc import md5sum
# refer: https://stackoverflow.com/questions/31779995/how-to-give-custom-name-to-images-when-downloading-through-scrapy
class MyImagesPipeline(ImagesPipeline):
def get_media_requests(self, item, info):
for image_url in item['image_urls']:
yield scrapy.Request(image_url, meta={'image_directory_name': item["image_directory_name"]})
def image_downloaded(self, response, request, info):
checksum = None
for path, image, buf in self.get_images(response, request, info):
if checksum is None:
buf.seek(0)
checksum = md5sum(buf)
width, height = image.size
filename = request._url.rsplit("/", 1)[1]
path = 'full/%s/%s' % (response.meta['image_directory_name'], filename)
self.store.persist_file(
path, buf, info,
meta={'width': width, 'height': height})
return checksum
実行する
下記コマンドで実行すると、savedImages\full\news.yahoo.co.jp
に画像が保存されていくはずです。
scrapy crawl save_yahoo_image
注意
ヤフーニュースを使用したのは、あくまで例です。本作業はサーバに多大な負荷をかけますので、動作確認出来たら、すぐに停止するようにしてください。本プログラムはフィルタや終了条件を指定していないので、延々とクローリング、ダウンロードを行います。
投稿後に気付いた
何も考えずに記事を書いて、投稿をしてから気づいたのですが、こちらとだいぶ内容がかぶっていました。まあ、本記事は画像の保存方法に特化した内容ということで。。。 (やっぱり皆さん、題材にはヤフーニュースを使うのですね)