簡単な画像収集のスクレイピングが上手くできなかった話


Qiitaで面白そうな内容のものがあったので、コードを試してみました。もともと機械学習とWeb開発に興味があったので、こんなことできたらいいなと思えるとても良い記事でした。
[齋藤飛鳥と玉森裕太の顔分類 WEB アプリ開発](https://qiita.com/shota_seki/items/c8cf40f9c7764f43496c)

同じ人物だとつまらないので、King & Princeの平野紫耀とBTSのテテにしました。（二人ともかっこよすぎる、、）


しかしエラーとなる部分が出てきてしまって、10分くらいいじろうと思っていたのが、気づいたら2, 3時間経っていました。

コード自体は動くはずなのになぜか上手くいかない、、
そんなエラーに出会ってから解決するまでを記録します。

## 読者の対象者

- スクレイピングをやりたい
- エラーがないコードを動かしてみたい
- エラーを解決する過程が知りたい
- 他人がどのくらいエラーでつまづくものなのか知りたい

といった人になるかと思います。

## とりあえずコードを実行してみる
コードを実行すると、以下のようになった。
```
ImportError: No module named 'icrawler'
```
icrawlerとは、Webクローラーのミニフレームワークです。

これがインポートできないのかと思い、とりあえずicrawlerをインストールします。無事インストールは完了しました。（よくインストールでもつまづく、、）
そして、再び実行します。
```
ImportError: No module named 'icrawler'
```
ん？　同じエラーやん、、

ググったら、以下の記事を見つけました。
[【python】ImportError: No module named '***'の対処法](https://www.haya-programming.com/entry/2018/09/09/202711)

僕の場合は、単に`python scraping.py`で実行するのではなく、`python3 scraping.py`で実行すればいいだけでした。
大事なのは、**3をつけるかどうかではなく、icrawlerがどのpythonに紐づいているか**です。

もし、このようなエラーを起こしたくないなら
```
python -m pip install **
```
でインストールすれば良いのだとわかりました。

## 新たなエラーとの出会いと解決まで

エラーが一つ解決すると、新しいエラーが現れました。
```
OSError: cannot write mode RGBA as JPEG
```
見たことがなさそうなエラーなのでとりあえずエラー文をコピペしてググります。すると以下の記事が見つかりました。
[Pillow (Python Imaging Library)でPNGファイルを扱う時の注意](https://ugcj.com/pillow-python-imaging-library%E3%81%A7png%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%92%E6%89%B1%E3%81%86%E6%99%82%E3%81%AE%E6%B3%A8%E6%84%8F/)

要するに、openしているとこの後ろに`.convert('RGB')`を付ければ良いということです。
```
img=Image.open(img_path)
```
これを以下のように書き加える。

```
img=Image.open(img_path).convert('RGB')
```

## 解決できなそうなエラーは例外処理を使う

次に出会ったエラーは、画像ファイルが開けないという内容のエラーでした。
[【Python】画像の破損をチェックするコード例【Pillow】](https://srbrnote.work/archives/5717)

実際にその画像を見てみるとたしかに破損したファイルになってしまっているようです。この画像ファイルを消せばいいのですが、これが何枚もあるとその作業も面倒です。

そこで、例外処理で処理をスキップすることにしました。

```python
for img_path in img_paths:
        try:
        #画像ファイルに変換
        img=Image.open(img_path).convert('RGB')
        #指定したサイズでリサイズをする
        img_resized=img.resize((w,h))
        
        #リサイズした画像を上書き保存、同じパスを指定
        img_resized.save(img_path)
        img.close()
```
上のコードを以下のように例外処理する感じに書き換えます。

```python
for img_path in img_paths:
        try:
            #画像ファイルに変換
            img=Image.open(img_path).convert('RGB')
            #指定したサイズでリサイズをする
            img_resized=img.resize((w,h))

            #リサイズした画像を上書き保存、同じパスを指定
            img_resized.save(img_path)
            img.close()
        except Exception as e:
            print("Error:", img_path)
            msg = getattr(e, 'msg', None)
            print(f'(message) {msg}')
        else:
            # エラーは検出できなかった。
            print('ok!', img_path)

```

try文がもとの処理したい内容、except文がエラーが起きたときの処理内容 (ただprintしてチェックしたいだけ)となっています。

## コード全文

このまま人にレビューしてもらったら

「`if __name__ == "__main__":`を書きましょう」
「よく変える変数はコマンド引数にしましょう」
「関数の引数はtypeが分かるようにしましょう」

など返ってきそうですが、とりあえず動けばいいのでそのままにします。
（今度、コードを読みやすくするポイントについても書きたい）

```python
from icrawler.builtin import BingImageCrawler
from PIL import Image
import glob


# 画像を収集するメソッド
# 引数は画像を保存するパスpath、検索ワードkeyword、収集する枚数num

def scraping(path, keyword, num):

    bing_crawler=BingImageCrawler(
    downloader_threads=4,
    storage={'root_dir': path}
    )

    #検索ワードにkeywordを入れたときに得られる画像をnum枚収集
    bing_crawler.crawl(
        keyword=keyword,
        max_num=num
    )
    print(f'{keyword}: scraping completed!')


#ファイルの形式はjpegなので、ファイル名には必ず拡張子.jpgがつく
asuka_path='./images/figure0/*.jpg'
tama_path='./images/figure1/*.jpg'

keywords=['平野紫耀','BTS V']
num=600

scraping('./images/figure0/', keywords[0], num)
scraping('./images/figure1/', keywords[1], num)

# """
# 画像をリサイズするメソッド
# 引数は保存したいパスpath=フォルダ名+フォーマット名、変更後のサイズの幅と高さw,h
#
# *リサイズしたい画像はパスで指定される
# """

def resize_image(path, w, h):
    img_paths=glob.glob(path)

    for img_path in img_paths:
        try:
            #画像ファイルに変換
            img=Image.open(img_path).convert('RGB')
            #指定したサイズでリサイズをする
            img_resized=img.resize((w,h))

            #リサイズした画像を上書き保存、同じパスを指定
            img_resized.save(img_path)
            img.close()
        except Exception as e:
            print("Error:", img_path)
            # e に msg 属性があれば取得、無ければ None にしておきました。
            msg = getattr(e, 'msg', None)
            print(f'(message) {msg}')
        else:
            # エラーは検出できなかった。
            print('ok!', img_path)
    print(f'{path}: resized!')

#サイズは300x300で指定
width=300
height=300

resize_image(asuka_path, width, height)
resize_image(tama_path, width, height)
```

## 最後に
多分もっとこうしたらいいみたいなのが見つかるのかもしれないですが、とりあえず動けばいいの精神で進めました。
色々な分野に興味があるならその分、スピード感も大事にしていきたいです。


## 参考記事

[齋藤飛鳥と玉森裕太の顔分類 WEB アプリ開発](https://qiita.com/shota_seki/items/c8cf40f9c7764f43496c)

[【python】ImportError: No module named '***'の対処法](https://www.haya-programming.com/entry/2018/09/09/202711)

[Pillow (Python Imaging Library)でPNGファイルを扱う時の注意](https://ugcj.com/pillow-python-imaging-library%E3%81%A7png%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%92%E6%89%B1%E3%81%86%E6%99%82%E3%81%AE%E6%B3%A8%E6%84%8F/)

[【Python】画像の破損をチェックするコード例【Pillow】](https://srbrnote.work/archives/5717)