画像まとめてダウンローダー作った

ちょっと必要になったから、ウェブページのアドレスを渡すとそのページ中にある画像をダウンロードしてくれるスクリプトを作った。 やっつけ仕事。

基本的に img タグを追ってるけど、 img タグが a タグの中にいて、かつその a タグの href 属性に指定されてる URL が画像の場合はそっちをダウンロードしてきてくれる( img タグにサムネイルを指定してクリックすると大きくなるページを想定)。

使ってるマシンの CPU コア数分だけプロセス作って、そいつらで並列に画像をダウンロードするからそれなりに速いと思う。 コアあたりスレッド2つで動くなら、 CPU の数 * 2でやるともっと速いかもね。

#-*- coding: utf-8 -*-

import os
import re
import urllib
import argparse
from urlparse import urljoin
from BeautifulSoup import BeautifulSoup
from multiprocessing import (
        Pool,
        cpu_count,
    )


IMAGE_EXTS = [u'jpg', u'jpeg', u'png', u'gif']


def get_args():
    parser = argparse.ArgumentParser()
    parser.add_argument(u'url', type=str,
        help=u'ダウンロードしたい画像があるウェブページのURL')
    parser.add_argument(u'directory', type=str,
        help=u'ダウンロードした画像を入れるディレクトリ')
    parser.add_argument(u'-e', u'--ext', action=u'append', default=[],
        help=u'jpg, jpeg, png, gifの他にダウンロードしたい画像拡張子 [複数指定可能]')
    parser.add_argument(u'-d', u'--domain', action=u'append', default=[],
        help=u'ダウンロードする画像があるドメインを制限する [複数指定可能]')

    return parser.parse_args()


def get_html_source(url):
    entry = urllib.urlopen(url)
    if 200 < entry.code < 300:
        raise Exception()

    return unicode(entry.read())


def get_pictures_list(url, is_valid_img):
    pictures = []

    soup = BeautifulSoup(get_html_source(url))
    for img in soup.findAll('img'):
        if is_valid_img(img[u'src']):
            if img.parent.name == u'a'\
            and is_valid_img(img.parent[u'href']):
                pictures.append(urljoin(url, img.parent[u'href']))
            else:
                pictures.append(urljoin(url, img[u'src']))

    return pictures


def get_img_validator(exts=[], domains=[]):
    patterns = []

    if len(exts) > 0:
        patterns.append(re.compile(ur'\.(%s)$' % ur'|'.join(exts), re.UNICODE))

    if len(domains) > 0:
        domains = [domain.replace(u'.', u'\.') for domain in domains]
        patterns.append(re.compile(
            ur'^((https?://([^.]+\.)*%s/)|(?!http))' % ur'|'.join(domains),
            re.UNICODE))

    def is_valid_img(url):
        for pattern in patterns:
            if pattern.search(url) is None:
                break
        else:
            return True

        return False

    return is_valid_img


def downloader(arg):
    url, savepath = arg
    urllib.urlretrieve(url, os.path.join(savepath, url.split(u'/')[-1]))


def save_pictures(pictures, savepath):
    if not os.path.isdir(savepath):
        raise Exception

    pool = Pool(cpu_count())
    pool.map(downloader, [(url, savepath) for url in pictures])
    pool.close()
    pool.join()


def main():
    args = get_args()

    pictures = get_pictures_list(
        args.url, get_img_validator(IMAGE_EXTS + args.ext, args.domain))

    if len(pictures) > 0:
        save_pictures(pictures, os.path.abspath(args.directory))


if __name__ == '__main__':
    main()