URLを記載したテキストファイルから画像をダウンロード

# はじめに
- 実は、`$ wget -i urls.txt` の様な感じで、画像を次々ダウンロードしてくれます。
- ただし、画像が存在しないと、`.html` や `.txt` がダウンロードされたりします。
- 今回は、`Content-Type` の確認、画像データの確認、一律 `.jpeg` へ変換等を実施します。
- 前回の記事は、[Google カスタム検索エンジンで画像リンクを取得する](https://qiita.com/maeda_mikio/items/bf6e0686db489a4bf67b) です。
- ソース一式は [ここ](https://github.com/maedamikio/public/tree/master/abe_or_ishihara/learning) です。


# ライブラリのインストール
- URL から画像をダウンロードする `requests`
- 画像データの確認、変換する `pillow`

```
$ pip install pillow requests
```


# 設定ファイル config.py
- 下記の様に、URLファイルは、`CLASSES` と `LINK_PATH` を元に参照されます。
- また、`DOWNLOAD_PATH` に画像をダウンロードします。
- 詳しくは、前回の記事を確認して頂ければと思います。

```
$ cat config.py


CLASSES = [
    '安倍乙',
    '石原さとみ',
    '大原優乃',
    '小芝風花',
    '川口春奈',
    '森七菜',
    '浜辺美波',
    '清原果耶',
    '福原遥',
    '黒島結菜'
]


BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
DATA_PATH = os.path.join(BASE_PATH, 'data')
LINK_PATH = os.path.join(DATA_PATH, 'link')
DOWNLOAD_PATH = os.path.join(DATA_PATH, 'download')
```

# URL を記載したテキストファイル
- 以下の様なファイルです。

```
$ head 黒島結菜.txt
http://cm-watch.net/wp-content/uploads/2018/03/b22dc3193fd35ebb1bf7aa4e74c8cffb.jpg
https://www.crank-in.net/img/db/1165407_650.jpg
https://media.image.infoseek.co.jp/isnews/photos/hwchannel/hwchannel_20191107_7062003_0-small.jpg
https://i.pinimg.com/originals/3e/3c/61/3e3c61df2f426a8e4623b58d84d94b40.jpg
http://yukutaku.net/blog/wp-content/uploads/wordpress-popular-posts/253-100x100.jpg
http://gratitude8888.biz/wp-content/uploads/2017/03/cb1175590da467bef3600df48eabf770.jpg
https://www.cinemacafe.net/imgs/p/ATDRThl-6oWF9fpps9341csCOg8ODQwLCgkI/416673.jpg
https://s3-ap-northeast-1.amazonaws.com/moviche-uploads/wp-content/uploads/2019/10/IMG_2547.jpg
https://scontent-frx5-1.cdninstagram.com/vp/05d6926fed565f82247879638771ee46/5E259FCC/t51.2885-15/e35/67735702_2288175727962135_1310736136046930744_n.jpg?_nc_ht=scontent-frx5-1.cdninstagram.com&_nc_cat=103&se=7&ig_cache_key=MjEyMzM1MTc4NDkyMzQ4NzgxMg%3D%3D.2
http://moco-garden.com/wp-content/uploads/2016/05/kurosimayuina.jpg
```

# 画像のダウンロード、チェックと保存

## URL を記載したテキストファイルの読み込み
- 前回作成した URL を改行で記載したファイルを読み込みます。

```
def download(query):
    """データのダウンロード、データのチェック、画像の保存."""

    linkfile = os.path.join(LINK_PATH, '{}.txt'.format(query))
    if not os.path.isfile(linkfile):
        print('no linkfile: {}'.format(linkfile))
        return

    with open(linkfile, 'r') as fin:
        link_list = fin.read().split('\n')[:-1]
```

## 画像のダウンロードと Content-Type の確認
- 上記で読み込んだ URL のリストデータを元に、次々ダウンロードをします。
- `Content-Type` が `image/` で始まっている事を確認します。
- `image/` は、`jpeg` `png` `gif` `bmp` だったりしますね。

```
    for num, link in enumerate(link_list, start=1):

        try:
            result = requests.get(link)
            content = result.content
            content_type = result.headers['Content-Type']
        except Exception as err:
            print('err: {}, link: {}'.format(err, link))
            continue

        if not content_type.startswith('image/'):
            print('err: {}, link: {}'.format(content_type, link))
            continue
```

## pillow での画像読み込み設定
- 以下を設定すると、サイズの大きい画像も読み込んでくれる様になります。

```
ImageFile.LOAD_TRUNCATED_IMAGES = True
```


## 画像データのチェック
- `pillow` で画像データを読み込みます。
- 読み込めなければ、画像データは壊れている確率が高いです。

```
        try:
            image = Image.open(io.BytesIO(content))
        except Exception as err:
            print('err: {}, link: {}'.format(err, link))
            continue
```

## 画像データを .jpeg へ変換
- 後工程の事を考えると、いちいち `.png` や `.bmp` の場合を考慮しながら処理をするのは面倒だと思ってます。
- そこで、一律 `.jpeg` へ変換します。
- `RGBA` 等の場合があるので、`.jpeg` の `RGB` に変換します。

```
        if image.mode != 'RGB':
            image = image.convert('RGB')
        data = io.BytesIO()
        image.save(data, 'jpeg', optimize=True, quality=95)
        content = data.getvalue()
```

## 画像の保存
- 設定フィアルに記載の `DOWNLOAD_PATH` に従い、`0001.jpeg` `0002.jpeg` などの様なファイル名で保存します。
- URL の末尾を利用してファイル名にする事は無いと思います。
- また、URL のテキストファイルの行数とファイル名の数字が一致しているので、お互いに参照がしやすいと思います。

```
        filename = os.path.join(DOWNLOAD_PATH, query, '{:04d}.jpeg'.format(num))
        with open(filename, 'wb') as fout:
            fout.write(content)
        print('query: {}, filename: {}, link: {}'.format(query, os.path.basename(filename), link))
```

## ダウンロード、ファイル処理時のエラーの例
- URL は、だいたい `6,000` 位でした。その内、約 `180` がエラーでした。
- エラーは、以下の様な感じです。
- 画像データではなく、`html` だったりします。
- ただし、`Content-Type` の `application/octet-stream` や `binary/octet-stream` は、画像データとして保存出来るはずですが、今回は数も少ないので省きました。

```
$ awk '{print $2}' err.txt | sort | uniq -c | sort -nr
  47 text/html;
  31 text/plain,
  30 ('Connection
  27 text/html,
  18 'content-type',
  10 cannot
   5 application/octet-stream,
   2 application/xml,
   1 images
   1 binary/octet-stream,
   1 UserWarning:
   1 HTTPSConnectionPool(host='jpnews24h.com',
   1 HTTPSConnectionPool(host='host-your-site.net',
   1 HTTPSConnectionPool(host='gamers.co.jp',
   1 HTTPConnectionPool(host='youtube.dojin.com',
   1 HTTPConnectionPool(host='nosh.media',
   1 HTTPConnectionPool(host='arukunews.jp',
   1 Exceeded
```


# おわりに
- `$ wget -i urls.txt` では、ちょっと手が届かないかゆい部分に対応しました。
- 次回は、画像から顔認識を実施する予定です。