こんにちは!
「女優識別botを作ろう」の2回目となる今回は、画像収集に関して解説していきます。
第1回の記事はこちらです。
>>【LINEbot】Pythonで女優識別botを作ろう Part1【概要説明編】
スクレイピングを行うことで、数10秒程度でフォルダがこのようになります!
実際に解説する内容は以下のようになっています。
今回の内容
- スクレイピングによる画像収集
- スクレイピングコードの解説
では、さっそく見ていきましょう!
追記(2020/09/02)
いつの間にか、yahoo画像のページング機能が廃止されていたため、こちらで紹介しているコードが使えなくなっていました。
こちらで、新しくスクレイピングコードを解説しているので、是非参考にしてください。
画像収集(スクレイピング)
今回は、Yahoo画像のページから、対象女優の画像を集めていきます。
まずは、スクレイピング用のコードから見てみましょう。
このプログラムは、女優1人につき1度実行する必要があるので、今回は「橋本環奈」の画像を取得する例のみを示します。
(画像によって変わる箇所にはコメントを入れています。)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
# ライブラリのインポート import requests import os from bs4 import BeautifulSoup save_dir = "./images/Kanna_Hashimoto" # 画像を保存するディレクトリのパス(対象女優によって変わる) # ディレクトリがない場合は作成する if not os.path.exists(save_dir): os.makedirs(save_dir) # 画像のURLを取得する関数 def get_image_urls(page_url): img_urls = [] r = requests.get(page_url) soup = BeautifulSoup(r.text, features="html.parser") img_tags = soup.find_all("img", attrs={"alt": "「橋本環奈」の画像検索結果"}) # 対象女優によって変わる for img_tag in img_tags: url = img_tag.get("src") img_urls.append(url) return img_urls # 画像をダウンロードする関数 def download_image(url, file_path): r = requests.get(url, stream=True) if r.status_code == 200: with open(file_path, "wb") as f: f.write(r.content) # 複数回ダウンロードを行う関数 def loop_download_image(page_index, img_urls): for idx, url in enumerate(img_urls): file_name = f"{page_index}_{idx}.png" image_path = os.path.join(save_dir, file_name) print(image_path) download_image(url=url, file_path=image_path) if __name__ == "__main__": num = 20 # 集める画像の枚数 for i, n in enumerate(range(1, num, 20)): print(n) # Yahoo画像のURL(対象女優によって変わる) page_url = f"https://search.yahoo.co.jp/image/search?p=%E6%A9%8B%E6%9C%AC%E7%92%B0%E5%A5%88&oq=&ei=UTF-8&b={n}" img_urls = get_image_urls(page_url) loop_download_image(page_index=i+1, img_urls=img_urls) |
このコードはコピペでも動くので、そのまま使ってもらっても構いません。
では、コードの解説をしていきます。
2~4行目では、スクレイピングに必要なライブラリをインポートしています。
2 3 4 |
import requests import os from bs4 import BeautifulSoup |
それぞれの役割はこんな感じです。
os
主にパスに関する操作で使う
requests
Yahoo画像にリクエストを送信して画像をダウンロードする際に使う
BeautifulSoup
requestsによって取得したwebページのソースコードから画像のURLを抜き出す際に使う
7~11行目では、取得してきた画像を保存するディレクトリに関しての操作を行います。
7 8 9 10 11 |
save_dir = "./images/Kanna_Hashimoto" # 画像を保存するディレクトリのパス(対象女優によって変わる) # ディレクトリがない場合は作成する if not os.path.exists(save_dir): os.makedirs(save_dir) |
7行目にある save_dir には、画像を保存するディレクトリのパスを指定します。
10、11行目では、指定したパスにディレクトリが存在しなかった際に、新たに作成するプログラムを書いています。
14~25行目の関数 get_image_urls は、HTMLのコードから画像のURLだけを抜き出し、リストでまとめて返す関数です。
14 15 16 17 18 19 20 21 22 23 24 25 |
# 画像のURLを取得する関数 def get_image_urls(page_url): img_urls = [] r = requests.get(page_url) soup = BeautifulSoup(r.text, features="html.parser") img_tags = soup.find_all("img", attrs={"alt": "「橋本環奈」の画像検索結果"}) # 対象女優によって変わる for img_tag in img_tags: url = img_tag.get("src") img_urls.append(url) return img_urls |
??となった方もいるかもしれませんね。
それでは、Yahoo画像のページの構造を確認してみましょう。
Yahoo画像のページはこのようになっていると思います。
これは、1つのページに複数の画像がまとめられていますね。
つまり、1つ1つの画像は固有のURLをもっているということになります。
F12キーを押して開発者モードに切り替えることで確認することができます。
get_image_urls では、このページ内にある20枚の画像のURLをそれぞれ取得してリストにまとめて返す、という動きをします。
雰囲気をつかめたと思うので、関数内のコードの解説に移ります。
16行目では、URLを格納するリストの初期化を行っています。
17、18行目では、 requests.get で、指定したURL先のデータを取得し、 BeautifulSoup でHTML要素を取得しています。
19行目では、18行目で取得したHTML要素の中から、画像のURLが含まれるすべてのタグを取得しています。
これは、ページ内のすべての画像が共通して持つタグや属性を利用することで実現しています。
HTML要素は、imgタグに画像のURLが記載されています。
しかし、これだけだと、Yahooのロゴなども取得してしまうため、もう少し条件を狭める必要があります。
Yahoo画像では、検索した画像の、imgタグのalt属性に共通の文字列を持ちます。
つまり、これを条件としてタグを取得することで、 img_tags に画像のタグのみを格納しています。
21~23行目では、取得したタグが格納されたリスト img_tags をfor文で回して、src属性から画像のURLを抜き出しています。
最後に、画像のURLをまとめたリストをreturnします。
29~34行目の関数 download_image では、実際に画像のダウンロードを行います。
29 30 31 32 33 34 35 |
# 画像をダウンロードする関数 def download_image(url, file_path): r = requests.get(url, stream=True) if r.status_code == 200: with open(file_path, "wb") as f: f.write(r.content) |
それぞれの引数は、ダウンロードする画像のURLと、画像の保存先のパスとなります。
関数の中身は単純なものなので、簡単に説明します。
requests.get で画像データを取得し、うまく取得できていた場合は保存する、という内容です。
38~43行目の関数 loop_download_image では、上で解説した get_image_urls で取得したすべての画像のURLに対して、 download_image を実行します。
38 39 40 41 42 43 |
def loop_download_image(page_index, img_urls): for idx, url in enumerate(img_urls): file_name = f"{page_index}_{idx}.png" image_path = os.path.join(save_dir, file_name) print(image_path) download_image(url=url, file_path=image_path) |
この関数は、引数としてwebページの番号と、URLが格納されたリストを渡します。
(webページの番号について後述する。)
関数内では、URLのリストをfor文で1つずつ取り出し、 download_image の引数として渡しています。
webページの番号は、保存する画像の名前が被らないようにするために指定しています。
enumerate 関数使う理由も同様のものです。
上の方で見てきた関数を理解していれば、たいして難しいことはしていないとわかると思います。
いよいよ最後のところまでやってきました。
46~53行目では、ページをまたぎながら loop_download_image を実行しています。
46 47 48 49 50 51 52 53 |
if __name__ == "__main__": num = 20 # 集める画像の枚数 for i, n in enumerate(range(1, num, 20)): print(n) # Yahoo画像のURL(対象女優によって変わる) page_url = f"https://search.yahoo.co.jp/image/search?p=%E6%A9%8B%E6%9C%AC%E7%92%B0%E5%A5%88&oq=&ei=UTF-8&b={n}" img_urls = get_image_urls(page_url) loop_download_image(page_index=i+1, img_urls=img_urls) |
またしてもよくわからないですね。
ということで、再度Yahoo画像のページ構造を見てみます。
Yahoo画像では、オレンジで囲まれているところをクリックすることで、ページングが可能な表示になります。
つまりこういうことです。
下の方に1、2、3、、、という風にページングのボタンが出てきました。
実は、ページングが可能だと、スクレイピングが簡単になります。
画像の位置を1ページ目とすると、1ページには20枚の画像が存在します。
このURLを get_image_urls に渡すことで、この20枚の画像のURLを取得できます。
20枚以上画像を集めたいとなると、ページを進める必要がありますね。
いちいちURLをコピペし直して手動でページングさせる方法もありますが、URLを見てみるととある傾向が見えてきます。
1ページ目のURL
https://search.yahoo.co.jp/image/search?p=%E6%A9%8B%E6%9C%AC%E7%92%B0%E5%A5%88&oq=&ei=UTF-8&b=1
2ページ目のURL
https://search.yahoo.co.jp/image/search?p=%E6%A9%8B%E6%9C%AC%E7%92%B0%E5%A5%88&oq=&ei=UTF-8&b=21
3ページ目のURL
https://search.yahoo.co.jp/image/search?p=%E6%A9%8B%E6%9C%AC%E7%92%B0%E5%A5%88&oq=&ei=UTF-8&b=41
URLの最後の部分のみが異なりますね。
つまり、URLの最後の数字のみを1から20ごと変更していくことで、自動でページングすることができます。
48行目のfor文では、まさにこれを行っています。
range を使って1から任意の値まで20ごとに回していることが確認できると思います。
こうすることで、ほぼ自動で欲しい画像を大量に集めることが実現できます。
ここまでがスクレイピングに関する解説です。
おわりに
スクレイピングの解説はどうだったでしょうか。
今回は、「橋本環奈」と検索して出てくる画像のみを集めるプログラムでしたので、各自で他の画像も集めてみてください。
1回で理解できなくても、1つ1つの関数は単純なものなので、自分の手を動かしていけばすぐに理解できるようになると思います。
スクレイピングは魔法みたいで楽しいものなので、是非モノにしてみてください!
では、今回はここまでとします。
お疲れさまでした。
追記:第3回を更新しました。