note.nkmk.me

PythonでWeb上の画像などのファイルをダウンロード(個別・一括)

Date: 2017-11-21 / Modified: 2019-06-23 / tags: Python, 自動化, Beautiful Soup, スクレイピング

PythonでWeb上の画像やZIP、PDFなどのファイルのURLを指定してダウンロード、ローカルのファイルとして保存する方法について、以下の内容を説明する。

  • URLを指定して画像をダウンロード
    • コード例
    • urllib.request.urlopen()でURLを開く
    • open()のバイナリモードでファイルに書き込み
    • よりシンプルなコード例
  • ZIPファイルやPDFファイルなどのダウンロード
  • Webページの画像のURLを抽出
    • 連番になっている場合
    • Beautiful Soupで抽出
  • URLのリストから複数画像を一括ダウンロード
スポンサーリンク

URLを指定して画像をダウンロード

個別のファイルのURLを指定してダウンロードするのは標準ライブラリのみの使用でOK。追加のインストールは必要ない。

コード例

URLと保存先のパスを指定してファイルをダウンロード・保存する関数とその使い方のコード例を示す。なお、このコードは説明のため若干冗長になっている。シンプルな書き方の例は後述。

import os
import pprint
import time
import urllib.error
import urllib.request

def download_file(url, dst_path):
    try:
        with urllib.request.urlopen(url) as web_file:
            data = web_file.read()
            with open(dst_path, mode='wb') as local_file:
                local_file.write(data)
    except urllib.error.URLError as e:
        print(e)
url = 'https://www.python.org/static/img/python-logo.png'
dst_path = 'data/temp/py-logo.png'
download_file(url, dst_path)

保存先のディレクトリを指定してURLのファイル名で保存する場合は以下の通り。

def download_file_to_dir(url, dst_dir):
    download_file(url, os.path.join(dst_dir, os.path.basename(url)))

dst_dir = 'data/temp'
download_file_to_dir(url, dst_dir)

os.path.basename()でURLからファイル名を抽出し、os.path.join()で指定したディレクトリと結合して保存先のパスを生成している。パス文字列の操作についての詳細は以下の記事を参照。

以下、データの取得部分とファイルとして保存する部分について説明する。

urllib.request.urlopen()でURLを開く

urllib.request.urlopen()でURLを開きデータを取得する。なお、Python 2.6以前のurllib.urlopen()は廃止された。urllib.request.urlretrieve()はまだ廃止されていないが、将来的に廃止される可能性がある。

例外が発生しても止まらないように、try, exceptでエラーを捕捉する。

例ではurllib.errorimportして、urllib.error.URLErrorのみを明示的に捕捉している。ファイルのURLが存在しない場合などにエラーメッセージが表示される。

url_error = 'https://www.python.org/static/img/python-logo_xxx.png'
download_file_to_dir(url_error, dst_dir)
# HTTP Error 404: Not Found

ローカルでの保存時の例外(FileNotFoundErrorなど)も捕捉したい場合は(urllib.error.URLError, FileNotFoundError)のようにする。詳細は以下の記事を参照。

標準ライブラリのurllibではなくサードパーティライブラリのRequestsを使ってurlを開いてデータを取得することも可能。

open()のバイナリモードでファイルに書き込み

urllib.request.urlopen()で取得できるデータはバイト列(bytes型)。

それをそのまま第二引数mode='wb'としたopen()でバイナリとして書き込む。wが書き込み、bがバイナリの意味。

open()によるファイルの読み書きについての詳細は以下の記事を参照。

よりシンプルなコード例

ネストしたwith文はカンマ区切りで一度に書ける。

これを利用すると以下のように書ける。

def download_file(url, dst_path):
    try:
        with urllib.request.urlopen(url) as web_file, open(dst_path, 'wb') as local_file:
            local_file.write(web_file.read())
    except urllib.error.URLError as e:
        print(e)

ZIPファイルやPDFファイルなどのダウンロード

これまでの例は画像ファイルのダウンロード・保存だが、ただ単にweb上のファイルを開いてそれをそのままローカルのファイルとして保存しているだけなので、その他の種類のファイルでも同じ関数が使える。

URLを指定してファイルをダウンロード・保存できる。

url_zip = 'https://github.com/nkmk/python-snippets/raw/master/notebook/data/src/sample_header.csv.zip'
download_file_to_dir(url_zip, dst_dir)

url_xlsx = 'https://github.com/nkmk/python-snippets/raw/master/notebook/data/src/sample.xlsx'
download_file_to_dir(url_xlsx, dst_dir)

url_pdf = 'https://github.com/nkmk/python-snippets/raw/master/notebook/data/src/pdf/sample1.pdf'
download_file_to_dir(url_pdf, dst_dir)

なお、この関数で指定するURLはファイルそのものへのリンクでなければならない。

例えば、GitHubのレポジトリのファイルの場合、以下のURLは拡張子がpdfだが実際はhtmlのページ。上の関数でこのURLを指定すると、htmlのソースがダウンロードされてしまう。

ファイルの実体へのリンクは以下のURL。ファイルをダウンロード・保存したい場合はこちらを指定する必要がある。

また、ユーザーエージェントやリファラなどでアクセスが制限されていてダウンロードできないといった場合もある。すべてのファイルのダウンロードを保証するものではない。

ユーザーエージェントなどリクエストヘッダの変更・追加はRequestsを使うと簡単。

Webページの画像のURLを抽出

ページ内の画像を一括でダウンロードするには、まず画像のURLを抽出してリストを作成する。

連番になっている場合

ダウンロードしたい画像のURLが単純な連番になっている場合は簡単。連番に限らず何らかの規則性があれば、後述のBeautiful Soupなどでスクレイピングをするより、規則に従ってURLのリストを作ってしまったほうが楽。

リスト内包表記を使う。

url_list = ['https://example.com/basedir/base_{:03}.jpg'.format(i) for i in range(5)]
pprint.pprint(url_list)
# ['https://example.com/basedir/base_000.jpg',
#  'https://example.com/basedir/base_001.jpg',
#  'https://example.com/basedir/base_002.jpg',
#  'https://example.com/basedir/base_003.jpg',
#  'https://example.com/basedir/base_004.jpg']

上の例では{:03}で3桁の0埋め連番としている。0埋めの必要が無い場合は{}、3桁ではなく例えば5桁の場合は{:05}とすればよい。文字列strformatメソッドについては以下の記事を参照。

また、ここでは出力を見やすくするためにpprintを使っている。

Beautiful Soupで抽出

Webページの画像URLを一括で抽出するにはBeautiful Soupを使う。

import os
import time
import urllib.error
import urllib.request

from bs4 import BeautifulSoup

url = 'https://news.yahoo.co.jp/list/'
ua = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) '\
     'AppleWebKit/537.36 (KHTML, like Gecko) '\
     'Chrome/55.0.2883.95 Safari/537.36 '

req = urllib.request.Request(url, headers={'User-Agent': ua})
html = urllib.request.urlopen(req)

soup = BeautifulSoup(html, "html.parser")

url_list = [img.get('data-src') for img in soup.find(class_='list').find_all('img')]

例ではYahoo!ニュースのトピックスのサムネイル画像のURLを抽出している。

Webページによって構成は異なるが、基本的には、

  • ダウンロードしたい複数の画像が含まれるブロックのclassidなどを指定して、<img>タグのオブジェクトのリストを取得
    • soup.find(class_='list').find_all('img')の部分
  • <img>タグのsrc要素やdata-src要素から画像のURLを取得
    • img.get('data-src')

という流れになる。

なお、上のサンプルコードはあくまでも一例であり動作を保証するものではない。Yahoo!のページ構成が変更されると動かなくなる可能性がある。

Beautiful Soupについては以下の記事も参照。

URLのリストから複数画像を一括ダウンロード

URLのリストがあれば、forループで回して、最初に示したURLを指定してファイルをダウンロード・保存する関数を呼び出すだけ。仮のURLリストのため、ここでは関数呼び出しdownload_image_dir()はコメントアウトしている。

download_dir = 'data/temp'
sleep_time_sec = 1

for url in url_list:
    print(url)
#     download_file_dir(url, download_dir)
    time.sleep(sleep_time_sec)
# https://example.com/basedir/base_000.jpg
# https://example.com/basedir/base_001.jpg
# https://example.com/basedir/base_002.jpg
# https://example.com/basedir/base_003.jpg
# https://example.com/basedir/base_004.jpg

サーバーに負担をかけないように、画像を1枚ダウンロードするごとにtime.sleep()で待機時間をつくっている。単位は秒なので、上の例では1秒ずつスリープ。timeモジュールをimportして使う。

例は画像ファイルの場合だが、その他の種類のファイルでもリストになっていれば同様にまとめてダウンロード可能。

スポンサーリンク
シェア
このエントリーをはてなブックマークに追加

関連カテゴリー

関連記事