機械学習のデータ集めのためのicrawlerを使いやすくした

#  はじめに
機械学習の画像データ集めにはPythonライブラリのicrawlerが便利で、公式の例では以下のように物凄く簡単にインストールと実装ができる。

#インストール
```
pip install icrawler 
   or 
conda install -c hellock icrawler
```


#実行例（Googleで"cat"で検索し、100枚の画像を取得）
```
from icrawler.builtin import GoogleImageCrawler

google_crawler = GoogleImageCrawler(storage={'root_dir': 'your_image_dir'})
google_crawler.crawl(keyword='cat', max_num=100)
```
このように、たった三行で画像を取得できる。
※現在2020/05/03は、Googleのparser変更により、GoogleImageCrawlerは機能しない。

#使いやすく改良
以下の2点を入れて、より使いやすくした
①. 複数の検索ワードを(外部)指定したい
②. 重複する画像がでないでほしい　　

①については、外部のテキストファイルに検索ワードを行ごとに記述し、これを読み込む形で実装。
②については、ファイル名に画像のURLを記載することで、同じ画像を保存する際に自動でスキップするようにした。
※ただし、長すぎるURLの場合はファイルの書き込みのエラーが出てしまうので、これを解決するために、icrawlerライブラリの"filesystem.py"を以下のように修正する必要がある。

```filesystem.py
class FileSystem(BaseStorage):
    """Use filesystem as storage backend.

    The id is filename and data is stored as text files or binary files.
    """

    def __init__(self, root_dir):
        self.root_dir = root_dir

    def write(self, id, data):
        filepath = osp.join(self.root_dir, id)
        folder = osp.dirname(filepath)
        if not osp.isdir(folder):
            try:
                os.makedirs(folder)
            except OSError:
                pass
        mode = 'w' if isinstance(data, six.string_types) else 'wb'
#        with open(filepath, mode) as fout:
#            fout.write(data)
        try:
            with open(filepath, mode) as fout:
                fout.write(data)
        except  FileNotFoundError: 
                pass
```

#実装
ということで、先に実装を以下に公開する。　

```img_collection.py
import base64
from icrawler import ImageDownloader
from six.moves.urllib.parse import urlparse
from icrawler.builtin import BaiduImageCrawler
from icrawler.builtin import BingImageCrawler
from icrawler.builtin import GoogleImageCrawler
import argparse, os

parser = argparse.ArgumentParser(description='img_collection')
parser.add_argument('--output_dir', default="",type=str, help='')
parser.add_argument('--N',        default=10, type=int, help='')
parser.add_argument('--engine', choices=['baidu',"bing","google"],default="bing",type=str, help='')
args = parser.parse_args()

class Base64NameDownloader(ImageDownloader):
    def get_filename(self, task, default_ext):
        url_path = urlparse(task['file_url'])[2]
        if '.' in url_path:
            extension = url_path.split('.')[-1]
            if extension.lower() not in [
                    'jpg', 'jpeg', 'png', 'bmp', 'tiff', 'gif', 'ppm', 'pgm'
            ]:
                extension = default_ext
        else:
            extension = default_ext
        # works for python 3
        filename = base64.b64encode(url_path.encode()).decode()
        return '{}.{}'.format(filename, extension)
    
def get_crawler(args, dir_name):
    if args.engine == "baidu":
        crawler = BaiduImageCrawler(downloader_cls=Base64NameDownloader,storage={'root_dir': dir_name })
    elif args.engine == "bing":
        crawler = BingImageCrawler(downloader_cls=Base64NameDownloader,storage={'root_dir': dir_name })
    elif args.engine == "google": # dont work
        crawler = GoogleImageCrawler(storage={'root_dir': dir_name })    
    return crawler

if __name__=="__main__":
    # read ini file.
    with open('./setting.txt', mode='r', encoding = "utf_8") as f:
        read_data = list(f)    

    print("SELECTED ENGINE : "+args.engine)        

    for i in range(len(read_data)):
        print("SEARCH WORD : "+read_data[i].replace('\n', ''))
        print("NUM IMAGES  : "+str(args.N))
        dir_name = os.path.join(args.output_dir, read_data[i].replace('\n', '').replace(' ', '_'))
        
        #init crawler
        crawler = get_crawler(args, dir_name)
        crawler.crawl(keyword=read_data[i], max_num=args.N)

```

img_collection.pyと同じ階層にsetting.txtを作成する。これに検索ワードを記述し、下の例では、三種類の検索ワードを指定する形になり、いちいち三つのワードをそれぞれ実行しなくて済む。

```php:setting.txt
猫　cat
猫　大人
猫　子供
```

#使い方
setting.txtに検索ワードを好きなだけ記述し、以下を実行
--N : 取得画像上限（Max1000。実際は通信やページの有無により1000枚も取得できない）
--output_dir : 出力先のディレクトリパス
--engine : 検索エンジン。bing, baiduから選択。Googleは現在動かない。

```
python img_collection.py  --N 1000 --output_dir D:\hogehoge\WebCrawler\out --engine bing
```


#結果

取得枚数1000枚で指定しても、実際には通信のエラーなどで600枚程度が残る感じになる。
とりあえず、これで猫の画像が大量に手に入る。
検索ワードごとにディレクトリを分けているが、一つのディレクトリに画像をまとめてやれば、重複する画像はファイル名が競合するので、基本的にマージされる。

<img width="845" alt="neko.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204810/f2348da2-ff18-5cd1-ebf3-1c173ff88f45.png">

#最後に
Webクローラーは、権利関係がデリケートなので、利用目的に応じて適切に取り扱うようにしましょう。