【初心者向け】PythonでURLを指定して画像をダウンロードする

<img src="https://qiita-image-store.s3.amazonaws.com/0/212705/3e18fdfd-4bf9-4549-1363-b2ceff7fba59.png" alt="画像をダウンロードするプログラムの例" title="画像をダウンロードするプログラムの例" width=50%>


<span style="font-size: 120%;"><b>やっほー!　こんにちはー!</b></span>
OthloTechのオザキ([@sena0801masato](https://twitter.com/sena0801masato))です。
OthloTechとは名古屋を中心に活動するIT系学生のコミュニティーです。

今回は、URLを指定して画像をダウンロードするプログラムを作ってみたので紹介したいと思います。
無理やりな部分もあるので、もっとスマートな方法があれば指摘してください。

### この記事の対象者
- Pythonで何かしてみたいプログラミング初心者
- インターネットで画像を保存するとき、左クリックして名前を付けて保存が面倒な人
- 複数の画像をダウンロードしたい人

使った言語は、Python3.6です。使ったパッケージは、requestsです。
まず、URLを指定して画像をダウンロードするのに最低限のプログラムを紹介します。
次に、Google Chromeで画像のURLの調べ方を紹介します。
最後に、僕がURLを指定して画像をダウンロードするときに最低限のプログラムにどのような改良をして使っているか紹介します。

## 画像をダウンロードするプログラム
まず、URLを指定して画像をダウンロードするのに最低限のプログラムを紹介します。

```python:image_download_ver01.py
import requests

url = "ダウンロードしたい画像のURL"
file_name = "保存したいファイル名.jpg"

response = requests.get(url)
image = response.content

with open(file_name, "wb") as aaa:
    aaa.write(image)
```

- requests.getでは、引数にurlを指定し、URLを指定して反応が返ってきて受け取ったデータをresponseに代入しています。
- response.contentでは、受け取った全部のデータの中から、画像データがある部分を選択してimageに代入しています。
- with openでは、任意の名前のファイル(file_name)をwb形式で開いています。ファイルがないときは作ってくれます。今回は、毎回とりあえず中身のないファイルを作り、それに画像データを書き込むということをしています。(つまり、すでにあるファイルの名前にすると上手く動作しないかも)<br>
wb形式とは、openで使われる読み書きに関する形式です。読み出しr、書き込みw、追記a、読み書きr+があり、バイナリデータで操作する場合はbを後ろに加えます。バイナリデータで書き込まないと、エラーになったり、画像として保存されません。なぜならバイナリデータ以外で保存すると、画像データの行末が自動で加工されてしまうみたいです。

無理やりな部分として、ファイル名に拡張子を書くということをしました。
画像の拡張子として.jpgや.pngがあります。保存されたファイルが画像ということをパソコンに認識させるためには、拡張子を.jpgや.pngにするのが手っ取り早いため、書きました。
ただ、本来であれば.jpgと.pngで画像の質が違うのに名前を変えて画像データとしてはどうなっているのか今後調べていきたいです。

例として、この記事の最初にある画像をダウンロードしてみます。URLと保存したいファイル名をプログラムに書きましょう。
(短時間に何度も画像をダウンロードするプログラムを動かすと、Qiitaのサーバに負荷を与えることになるのでやめましょう。1分以上空けて1回とか、普通に記事を閲覧するぐらいの間隔なら大丈夫だと思いますが、私は責任を負いません。)

```python:image_download_ver01.py
url = "https://camo.qiitausercontent.com/42a9852fb9ad88e63f10442b835315424a13c96b/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f3231323730352f33653138666466642d346266392d343534392d313336332d6232636566663766626135392e706e67"
file_name = "Qiitaの画像.jpg"
```

## Chromeで画像のURLを調べる方法
次に、Google Chromeで画像のURLの調べ方を紹介します。(サイトによって著作権等あるので気をつけましょう。)
1. 画像のURLを知りたいページで右クリックし、検証を選択する
<img src="https://qiita-image-store.s3.amazonaws.com/0/212705/84830114-1f34-cb88-c60e-5fdbd76f7d48.png" alt="Chrome検証01" title="Chrome検証01" width=50%>
2. 右側にChromeデベロッパーツール（検証機能）が出てくるので、デベロッパーツールの左上のアイコンをクリックする
<img src="https://qiita-image-store.s3.amazonaws.com/0/212705/9f694e80-af98-d2e4-6766-e3a6fd2c979d.png" alt="Chrome検証02" title="Chrome検証02" width=50%>
3. アイコンが青色になったのを確認し、左側でダウンロードしたい画像をクリックする
<img src="https://qiita-image-store.s3.amazonaws.com/0/212705/724376ed-cf31-9a27-54f9-663dd9ff18e3.png" alt="Chrome検証03" title="Chrome検証03" width=50%>
4. デベロッパーツールに画像に該当するHTMLが選択されるので、URLの部分をダブルクリックする
<img src="https://qiita-image-store.s3.amazonaws.com/0/212705/4d5ad22e-fd94-83f9-fea9-245151f67440.png" alt="Chrome検証04" title="Chrome検証04" width=50%>
5. プログラム等にコピペして使う
6. デベロッパーツールを閉じるときは、デベロッパーツールの右上のバツをクリックする
<img src="https://qiita-image-store.s3.amazonaws.com/0/212705/48eac575-1bba-5568-4bcd-65a230bfcf49.png" alt="Chrome検証05" title="Chrome検証05" width=50%>

これで知りたい画像のURLを調べることができるようになりました。

## 画像をダウンロードするプログラム(改良案)
最後に、僕がURLを指定して画像をダウンロードするときに最低限のプログラムをどのような改良をして使っているか紹介します。
これらの改良をするために使ったパッケージは、datetime、osです。(datetimeとosはPythonに標準でインストールされています。)

<b>改良している点</b>

- なんとなく関数にまとめる
- URLが使えなくなっていたとき [その節へ](#urlが使えなくなっていたとき)
- URLは正常だが、画像のURLではなかったとき [その節へ](#urlは正常だが画像のurlではなかったとき)
- 画像の保存する名前の指定 [その節へ](#画像の保存する名前の指定画像のurlに拡張子が含まれていたとき)
- 画像のURLに拡張子(.jpgや.png)が含まれていたとき
- 画像のURLにパラメータが付いていたとき [その節へ](#画像のurlにパラメータが付いていたとき)
- 保存するときに画像の名前が被らないようにする [その節へ](#保存するときに画像の名前が被らないようにする)
- 毎回プログラムの画像のURLを書き換えるのが面倒だからテキストファイルを読み込む [その節へ](#毎回プログラムの画像のurlを書き換えるのが面倒だからテキストファイルを読み込む)
- 強制終了をさせる [その節へ](#強制終了させる)

<details><summary>僕が使っている改良プログラムはこちら</summary><div>

  ```python:image_download_ver02.py
  import os
  import requests
  import datetime

  url_parameter = {}
  path_base = os.path.dirname(os.path.abspath(__file__))

  # 画像が含まれるHTMLを取得する
  def download_img(url, timeout_sec=10):
      response_date = requests.get(url, params=url_parameter, allow_redirects=False, timeout=timeout_sec)
      if response_date.status_code != 200:
          err = Exception("エラーが起こりました。ステータスコード: " + str(response_date.status_code) + "\n" + url)
          raise err
      content_type = response_date.headers["content-type"]
      if "image" not in content_type:
          err = Exception("エラーが起こりました。HTMLに画像がありません: " + str(content_type))
          raise err
      return response_date.content

  # 保存する場所と名前を編集する
  def make_file_name(dir_base, str_time, url, str01="_画像"):
      url_separate = os.path.splitext(url)
      url_extension = url_separate[1]
      if url_extension == "":
          url_extension = ".png"
      file_name = str_time + str01 + url_extension
      file_path = os.path.join(dir_base, file_name)
      return file_path

  # ダウンロードを保存する
  def save_image(file_name, image):
      with open(file_name, "wb") as img_date:
          img_date.write(image)

  # 日付の書式を加工する
  def edit_datetime():
      now = datetime.datetime.now()
      time_now = "{0:%Y%m%d_%H%M-%S_%f}".format(now)
      return time_now

  def main(image_url, path_base):
      try:
          # 現在の時間を取得
          str_time = edit_datetime()
          str01 = "_画像"
          file_path = make_file_name(path_base, str_time, image_url, str01)
          # HTMLをダウンロードする
          image = download_img(image_url)
          # 画像を保存する
          save_image(file_path, image)
          print("ダウンロード完了")
      except KeyboardInterrupt:
          err = Exception("強制終了させました")
          raise err
      except Exception as err:
          print(str(err))

  if __name__ == "__main__":
      with open("url_list.txt", "r") as tf01:
          for line in tf01:
              line = line.strip()
              image_url = line
              main(image_url, path_base)
  ```
</div></details>
以下で改良プログラムの説明をしていきます。

### URLが使えなくなっていたとき
HTTPのステータスコード(レスポンスの意味を表す3桁の数字)を条件分けし、正常(ステータスコード200)以外ならそのステータスコードを出力させます。同時にエラーになったURLも出力させます。

```python:image_download_ver02.py
if response_date.status_code != 200:
        err = Exception("エラーが起こりました。ステータスコード: " + str(response_date.status_code) + "\n" + url)
        raise err
```
```:ターミナル(例)
Invalid URL '': No schema supplied. Perhaps you meant http://?
エラーが起こりました。ステータスコード: 404
https://以下略
```
### URLは正常だが、画像のURLではなかったとき
HTTPのステータスコードが正常であっても、レスポンスの中身が画像でないと目的に合わないです。そのためレスポンスの中身が画像かどうか、response_date.headers["content-type"]を確認します。

```python:image_download_ver02.py
content_type = response_date.headers["content-type"]
    if "image" not in content_type:
        err = Exception("エラーが起こりました。HTMLに画像がありません: " + str(content_type))
        raise err
```
```:ターミナル(例)
Invalid URL '': No schema supplied. Perhaps you meant http://?
エラーが起こりました。HTMLに画像がありません: text/html; charset=utf-8
```

### 画像の保存する名前の指定、画像のURLに拡張子が含まれていたとき
画像ファイルの名前を作り、保存する場所を指定します。パスを変えればできますが、今回は作業ディレクトリにしました。
画像のURLに拡張子が付いていた場合は、画像ファイルの拡張子をURLに付いていた拡張子に合わせます。付いていなかった場合は.pngにします。(無理やりな部分です)
画像ファイルの名前の例「20180810_0407-56_190618_画像.png」

```python:image_download_ver02.py
# 作業ディレクトリのパス(パソコン内の住所)をpath_baseに代入する
path_base = os.path.dirname(os.path.abspath(__file__))
# 保存する場所と名前を編集する関数
def make_file_name(dir_base, str_time, url, str01="_画像"):
    # 拡張子の前後(.の前後)でURLを分けてurl_separateリストに代入する
    url_separate = os.path.splitext(url)
    # 拡張子の後(.jpgとか)をurl_extensionに代入する
    url_extension = url_separate[1]
    # URLに拡張子がなかった場合.pngにする
    if url_extension == "":
        url_extension = ".png"
    # ファイルの名前を作る
    file_name = str_time + str01 + url_extension
    # 保存する場所のパスとして作業ディレクトリのパスとファイル名を繋げて関数の戻り値とする
    file_path = os.path.join(dir_base, file_name)
    return file_path
```

### 画像のURLにパラメータが付いていたとき
requests.getにはURLのパラメータも一緒に扱うことができます。ほとんど使うことがないためパラメータは無しで設定していますが、必要なときはプログラムを修正してください。

```python:image_download_ver02.py
# パラメータは辞書型で書く
url_parameter = {}
# 画像が含まれるHTMLを取得する
# タイムアウトは時間内(今回は10秒)にレスポンスが返ってこないときにエラーになるように設定する
def download_img(url, timeout_sec=10):
    # allow_redirects=Falseはリダイレクトしない設定
    # params=url_parameterで辞書型のパラメータをURLに加工してくれる
    response_date = requests.get(url, params=url_parameter, allow_redirects=False, timeout=timeout_sec)
```
例:URLが「https://なんとかかんとか.jpg?x=800&q=75」のとき
URLのパラメータは「x=800&amp;q=75」だからurl_parameter = {"x": "800", "q": "75"}と記述します。

### 保存するときに画像の名前が被らないようにする
画像のファイル名が被ると面倒なので、画像ファイル名に時間を使うことにしました。そのとき、ファイル名にする時間の書式を少し加工しました。(年月日\_何時何分-何秒\_コンマ以下の秒)
例：20180810_0407-56_190618　

```python:image_download_ver02.py
# 日付の書式を加工する
def edit_datetime():
    # 現在の時刻を取得する
    now = datetime.datetime.now()
    # 書式を加工する
    time_now = "{0:%Y%m%d_%H%M-%S_%f}".format(now)
    return time_now
```

### 毎回プログラムの画像のURLを書き換えるのが面倒だからテキストファイルを読み込む
画像のURLを同じ作業ディレクトリのurl_list.txtというファイルに書き、それを読み込ませるようにしました。

```python:image_download_ver02.py
# url_list.txtを読み込み専用で読み込む。そのときのデータをtf01とする。
with open("url_list.txt", "r") as tf01:
    # tf01のデータを1行ずつlineに代入する。
    for line in tf01:
        # lineに余計なスペースなどがあると邪魔だから念のため空白を削除する
        image_url = line.strip()
        # 画像URLと作業ディレクトリのパスを渡して画像を保存する
        main(image_url, path_base)
```

# 強制終了させる
一応として実装しましたが、エラーが先に出力されるのでほぼ使いません。Ctrl+Cを同時押ししたときに作動します。

```python:image_download_ver02.py
except KeyboardInterrupt:
        err = Exception("強制終了させました")
        raise err
```

## まとめ
ここまで読んでいただきありがとうございました。
個人的にはいつも毎回名前を付けて保存をしていたので枚数が多くなると、とても時間がかかっていましたが自動化する素晴らしさを体験できました。まだまだコードが美しくなかったり、拡張子を無理やり決めている部分があるので改良していきたいです。また、動画のダウンロードにも挑戦してみたいと思います。
画像をダウンロードするプログラム自体は短いですが、工夫していくと終わりがないです。ぜひ自分なりの改良を加えていってください。

<span style="font-size: 120%;"><b>ではでは!</b></span>

## 参考文献
[requestsを使った画像のダウンロード](https://qiita.com/donksite/items/21852b2baa94c94ffcbe)
[python3のrequestsを使って画像を保存](https://qiita.com/pollenjp/items/0c39c35120cd60575647)

<!--
```言語名:ファイル名
```
URLとは、インターネット上のファイルのパス的な住所
<details><summary>サンプルコード</summary><div>
コードの中身
</div></details>
```python:image_download_ver02.py
```
-->