PythonでWeb上の画像などのファイルをダウンロード(個別・一括)
Pythonで、Web上のファイル(画像やZIP、PDFなど)のURLを指定してダウンロード・保存する方法について説明する。
URLを指定してファイルをダウンロード
個別のファイルのURLを指定してダウンロードするのは標準ライブラリのみで実現できる。追加のインストールは必要ない。
サードパーティライブラリRequestsを使う方法については以下の記事を参照。
- 関連記事: Python, Requestsの使い方
urllib.request.urlretrieve()
最も簡単なのはurllib.request.urlretrieve()
を使う方法。
第一引数にURL、第二引数に保存先のパスを指定する。保存先のパスは絶対パスでも相対パスでもよい。
import urllib.request
url = 'https://www.python.org/static/img/python-logo.png'
dst_path = 'data/temp/py-logo.png'
urllib.request.urlretrieve(url, dst_path)
なお、urllib.request.urlretrieve()
はPython2のurllib
モジュールから移植された関数で、将来的に廃止される可能性がある。
レガシーインターフェースを避けたい場合は、次に紹介するurllib.request.urlopen()
を使う。
urllib.request.urlopen()とopen()を利用
urllib.request.urlopen()
で、URLを開いてデータをバイト列(bytes
)で取得できる。
取得したデータを組み込み関数open()
でバイナリとして書き込み、保存する。第二引数mode
を'wb'
とすればよい。w
が書き込み、b
がバイナリの意味。
def download_file(url, dst_path):
with urllib.request.urlopen(url) as web_file:
with open(dst_path, 'wb') as local_file:
local_file.write(web_file.read())
url = 'https://www.python.org/static/img/python-logo.png'
dst_path = 'data/temp/py-logo.png'
download_file(url, dst_path)
ネストしたwith文はカンマ区切りで一度に書けるので、以下のように書くことも可能。
def download_file(url, dst_path):
with urllib.request.urlopen(url) as web_file, open(dst_path, 'wb') as local_file:
local_file.write(web_file.read())
download_file(url, dst_path)
保存先のディレクトリを指定、URLのファイル名で保存
保存先のディレクトリを指定して、URLのファイル名で保存するには、以下のようにする。ここでは上で定義した関数を使っているが、urllib.request.urlretrieve()
でも同様。
import os
def download_file_to_dir(url, dst_dir):
download_file(url, os.path.join(dst_dir, os.path.basename(url)))
url = 'https://www.python.org/static/img/python-logo.png'
dst_dir = 'data/temp'
download_file_to_dir(url, dst_dir)
os.path.basename()
でURLからファイル名を抽出し、os.path.join()
で指定したディレクトリと結合して保存先のパスを生成している。パス文字列の操作についての詳細は以下の記事を参照。
例外処理
urllib.request.urlretrieve()
やurllib.request.urlopen()
では、存在しないURLを指定するとHTTPError
が送出される。
url_error = 'https://www.python.org/static/img/python-logo-abc.png'
dst_path = 'data/temp/py-logo.png'
# download_file(url_error, dst_path)
# HTTPError: HTTP Error 404: Not Found
これを捕捉するには、urllib.error
をインポートして使う。
import urllib.error
try:
download_file(url_error, dst_path)
except urllib.error.HTTPError as e:
print(e)
# HTTP Error 404: Not Found
urllib.error.HTTPError
はurllib.error.URLError
のサブクラスなので、urllib.error.URLError
を指定してもよい。
ローカルでの保存時の例外も捕捉したい場合は、except
にタプルを指定する。例えば存在しないディレクトリを含むパスを指定するとFileNotFoundError
となる。
url = 'https://www.python.org/static/img/python-logo.png'
dst_path_error = 'data/not_exist/py-logo.png'
try:
download_file(url, dst_path_error)
except (urllib.error.HTTPError, FileNotFoundError) as e:
print(e)
# [Errno 2] No such file or directory: 'data/not_exist/py-logo.png'
例外処理の詳細は以下の記事を参照。
URL指定の注意点
これまでの例は画像ファイルをダウンロード・保存したが、ただ単にWeb上のファイルを開いてそのままローカルに保存しているだけなので、その他の種類のファイルでも同じ関数が使える。
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_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
のソースがダウンロードされてしまう。
ファイルの実体(raw file)へのリンクは以下のURL。ファイルをダウンロード・保存したい場合はこちらを指定する必要がある。
また、ユーザーエージェントやリファラなどでアクセスが制限されていてダウンロードできない場合もある。すべてのファイルのダウンロードを保証するものではない。
ユーザーエージェントなどリクエストヘッダの変更・追加はRequestsを使うと簡単。
- 関連記事: Python, Requestsの使い方
複数のURLからファイルを一括ダウンロード
複数のURLからファイルをダウンロードしたい場合は、各URLに対して個別にurllib.request.urlretrieve()
や上述のurllib.request.urlopen()
を使った関数を呼び出せばよい。
例えばURLのリストがあれば、for
ループで回せばよい。仮のURLリストのため、ここでは関数呼び出しはコメントアウトしている。
import time
url_list = ['url_0', 'url_1', 'url_2', 'url_3', 'url_4']
dst_dir = 'data/temp'
sleep_time_sec = 1
for url in url_list:
# download_file_to_dir(url, dst_dir)
time.sleep(sleep_time_sec)
サーバーに負担をかけないように、ファイルを1つダウンロードするごとにtime.sleep()
で待機させている。単位は秒なので、上の例では1秒ずつスリープ。time
モジュールをインポートして使う。
Webページの画像のURLを抽出し、一括ダウンロード
ファイルを一括ダウンロードする具体的な例として、Webページ内の画像の一括ダウンロードを考える。
連番になっている場合
ダウンロードしたい画像のURLが単純な連番になっている場合は簡単。連番に限らず何らかの規則性があれば、後述のBeautiful Soupなどでスクレイピングをするより、規則に従ってURLを生成するほうが楽。
dst_dir = 'data/temp'
sleep_time_sec = 1
for i in range(5):
url = f'https://example.com/dir/base_{i:03}.jpg'
print(url)
# download_file_to_dir(url, dst_dir)
time.sleep(sleep_time_sec)
# https://example.com/dir/base_000.jpg
# https://example.com/dir/base_001.jpg
# https://example.com/dir/base_002.jpg
# https://example.com/dir/base_003.jpg
# https://example.com/dir/base_004.jpg
上の例では{i:03}
で3桁の0埋め連番としている。0埋めの必要が無い場合は{i}
、3桁ではなく5桁の場合は{i:05}
とすればよい。f文字列については以下の記事を参照。
Beautiful Soupで抽出
Webページの画像URLを一括で抽出するには、スクレイピングのライブラリであるBeautiful Soupを使う方法もある。
以下の例では、Yahoo!ニュースのトピックスのサムネイル画像のURLを抽出している。ユーザーエージェントによってレスポンスが異なるため、Chromeのユーザーエージェントを指定している。
from bs4 import BeautifulSoup
url = 'https://news.yahoo.co.jp/list/'
ua = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) '\
'AppleWebKit/537.36 (KHTML, like Gecko) '\
'Chrome/116.0.0.0 Safari/537.36'
req = urllib.request.Request(url, headers={'User-Agent': ua})
html = urllib.request.urlopen(req)
soup = BeautifulSoup(html, "html.parser")
dst_dir = 'data/temp/yahoo'
sleep_time_sec = 1
for img in soup.find(class_='newsFeed_list').find_all('img'):
url = img.get('src')
download_file_to_dir(url, dst_dir)
time.sleep(sleep_time_sec)
Webページによって構成は異なるが、基本的には以下のような流れになる。
- ダウンロードしたい画像が含まれるブロックの
class
やid
を指定して、その中の<img>
タグのオブジェクトを抽出soup.find(class_='newsFeed_list').find_all('img')
の部分soup.find_all('img')
とすると、ページ内のすべての<img>
タグが抽出される(ロゴなどの不要な画像も含まれる)
<img>
タグのsrc
要素やdata-src
要素から画像のURLを取得img.get('src')
の部分
なお、上のサンプルコードはあくまでも一例であり動作を保証するものではない。Yahoo!のページ構成が変更されると動かなくなる可能性があるので注意。