pythonで遊ぼうシリーズ!webスクレイピング編 ~自動で画像を取得してみよう~

pythonで遊ぼう!!webスクレイピング編

pythonで遊ぼう!第2弾です!!

今回はpythonといえばこれ!!という感じのwebスクレイピングです!!

前回は画像処理の実装をしていますので、興味のある方はぜひ見てみてください!

こちら、前回記事です!!

python = 自動化というイメージを持つ方も多いのではないでしょうか?

web操作の自動化である、webスクレイピングを実装していきたいと思います!!

スクレイピングはサーバーへの攻撃になることもあるので、良い子のみんなはほどほどにしようね!!

ではレッツゴー!!

実装準備

ローカル環境の設定などが完了していないと上手くいきませんので、初回記事の実装準備を参考にやってみてくださいね!!

では、今回の実装の準備をしていきましょう!!

コマンドによるライブラリインストール
  • pip3はpipでも大丈夫です!
  • pip3 install beautifulsoup4
  • pip3 install selenium
  • pip3 install lxml

上記のコマンド、実行できたでしょうか?

今回はこれにプラスでもう一つコマンドを実行して頂く必要があります!

pip3 install chromedriver-binary==87.0.4280.88.0

こちらになります!!

これはgoogle chromeを操作するためのライブラリなのですが、

==のあとに何やら数字が羅列していますね!!

この数字、バージョン番号と呼ばれるもので、

バージョン番号を指定しないと正式リリースされていないβ版のバージョン対応でインストールされてしまうみたいなんです…

なので、お使いのchromeのバージョンに合わせて指定する必要があるということですね!

chromeのバージョンはchromeで下記のパスを打ち込むことで確認できます!

chrome://settings/help
chrome バージョン確認画面

画像の赤丸の部分がバージョン番号となります!!

私と同じだった方は、先程のコマンドをコピーして実行していただいて問題ないです!

頭の87が88になっているみたいな方はこちらのサイトから自身のバージョンに適するものを調べてみてください!!

こちらで準備は以上です!!

実行ファイルなど

では今回もまずは概要ざざっと紹介しちゃいます!!

ディレクトリ構造フォルダファイルの違いに注意してくださいね!)

scraping_for_python(親フォルダ) 
    > src(関連フォルダやファイルを入れておくフォルダ)
        > img(取得してきた画像を保存するフォルダ)
        > execute.py(実行コードを書くファイル)

execute.py

# -*- coding: utf-8 -*-
import requests
import os, time, sys
from mimetypes import guess_extension
from bs4 import BeautifulSoup
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import chromedriver_binary

IMAGE_DIR = '../image/'
WAIT_TIME = 1

# 引数を変数に代入
keyword = sys.argv[1]
limit = int(sys.argv[2])


# launch chrome browser
driver = webdriver.Chrome()
# google image search
driver.get('https://www.google.co.jp/imghp?hl=ja&tab=wi&ogbl')
# execute search
driver.find_element_by_name('q').send_keys(keyword, Keys.ENTER)

current_url = driver.current_url
html = requests.get(current_url)
bs = BeautifulSoup(html.text, 'lxml')
images = bs.find_all('img', limit = limit)

image_path = IMAGE_DIR + keyword
if not os.path.exists(image_path):
    os.makedirs(image_path)

for i, img in enumerate(images, 1):
    src = img.get('src')
    if not src.endswith('.gif'):
        print('{}\nダウンロードを開始します。\n'.format(src))
        response = requests.get(src)
        contentType = response.headers['Content-Type']
        ext = guess_extension(contentType.partition(';')[0].strip())
        with open(image_path + '/' + '{num}{ext}'.format(num=i, ext=ext), 'wb') as f:
            f.write(response.content)
            f.close()
            print('ダウンロードが完了しました。\n')
    else:
        print('{}\nは対象外のファイル形式のためスキップします。\n'.format(src))
    time.sleep(WAIT_TIME)

driver.close()
driver.quit()
print('完了!')
sys.exit()

以上です!

execute.pyで処理を実行するんですが、今回はそのときに引数を用います!!

python execute.py xxx(検索キーワード) nn(検索数)

みたいな感じです!!

第1引数に検索キーワード、半角スペース入れて第2引数に検索数です!!

で、処理の結果取得できた画像は、

imgフォルダ内の指定した検索キーワード名で作成されたフォルダ内に保存されると言った感じです!!

コード解説

まずは理解しやすいように処理順序を確認していきましょう!

処理順序
  1. ライブラリのimport
  2. 引数を取得
  3. chrome起動
  4. chromeでキーワード画像検索
  5. 指定した数の画像を取得
  6. ローカルに保存

ライブラリのimport

import requests
import os, time, sys
from mimetypes import guess_extension
from bs4 import BeautifulSoup
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import chromedriver_binary

インストールしたもの以外にも、インストールなしで使えるライブラリをいくつかimportしております!!

importは以上です!

引数を取得

# 引数を変数に代入
keyword = sys.argv[1]
limit = int(sys.argv[2])

はい!たったこれだけです!!

もしできる方は引数ごとにバリデーション入れてみてくださいね!!

例えば、

第1引数は必須、第2引数は数値であるプラス指定がない場合はデフォルトで10を指定するとかですね!!

chrome起動

# launch chrome browser
driver = webdriver.Chrome()

はいー!これもたったこれだけです!!

ライブラリ様様ですね!!

キーワードで画像検索

# google image search
driver.get('https://www.google.co.jp/imghp?hl=ja&tab=wi&ogbl')
# execute search
driver.find_element_by_name('q').send_keys(keyword, Keys.ENTER)

こちらもたった2行でございます!!

まずは画像検索を指定して、その後にキーワードを入れてENTERって感じですね!!

画像取得

current_url = driver.current_url
html = requests.get(current_url)
bs = BeautifulSoup(html.text, 'lxml')
images = bs.find_all('img', limit = limit)

ちょっと長いですねー、でも逃げないで!!

順番に見ていけばなんてことないですからね!

まずは、

current_url = driver.current_url

こちらは変数名の通り、現在開いているページのURLを取得しています!!

続いての、

html = requests.get(current_url)

こちらは指定したURLのhtmlを取得しています!

current_urlを指定しているので、現在開いているページのhtmlを取得したってことですね!

bs = BeautifulSoup(html.text, 'lxml')

こちらは、最初にインストールしたbeautifulsoup4というライブラリの機能を使ってhtmlの要素を抽出しているようなイメージです!!

htmlタグごと抽出してくれたりするので、このあとの画像取得で役立つというわけですね!!

最後!

images = bs.find_all('img', limit = limit)

先程抽出したものの中で、imgタグだけを引数で指定した数値分探して全部もってこい!!という命令をしています!!

これでimagesという変数に必要な情報を入れられましたね!

ローカルに保存


for i, img in enumerate(images, 1):
    src = img.get('src')
    if not src.endswith('.gif'):
        print('{}\nダウンロードを開始します。\n'.format(src))
        response = requests.get(src)
        contentType = response.headers['Content-Type']
        ext = guess_extension(contentType.partition(';')[0].strip())
        with open(image_path + '/' + '{num}{ext}'.format(num=i, ext=ext), 'wb') as f:
            f.write(response.content)
            f.close()
            print('ダウンロードが完了しました。\n')
    else:
        print('{}\nは対象外のファイル形式のためスキップします。\n'.format(src))
    time.sleep(WAIT_TIME)

こちらも割と長いですねー!あー逃げないで!!ちゃんと説明するから!!

一行目わかりそうでわからなそうな感じですね!

for i, img in enumerate(images, 1):

enumerateってなんやねん!!

簡単に説明するとforで回している要素に番号をつけられるって感じですね!

なぜ今回番号付ける必要があるかというと、保存する画像のファイル名をこの番号にしたいからですね!

まあ元の画像のファイル名を持ってきても良いんですけど、enumerateの練習ってことで!

第2引数の1は番号を1からスタートして欲しいときに指定する感じです!

指定しないと0から始まりますので!

最初のforで先程取得したimgタグたちを一個ずつ回していく、そしてついでに何番目の画像かも分かるようにしているって感じです!!

続いて、

src = img.get('src')

こちらはなんとなくわかりますかね?

imgタグのsrcという属性の中身を取得していますね!

srcに画像パスが記載してありますからね!

はい次!

if not src.endswith('.gif'):

こちらは画像パスの最後が.gifで終わっていないものという意味のif文ですね!

今回はjpgやpngなどの静止画を取得したかったので、gifファイルは処理が走らないように弾いているというわけですね!

次!

print('{}\nダウンロードを開始します。\n'.format(src))

はいただのメッセージ!!終わり!w

あ、ちなみにこういう処理スタートとかのメッセージって重要ですからね!

メッセージ出力がきれいに実装されていると、どこでどういうエラーが起きたとかがわかりやすいので書いてみてくださいね!(今回の処理はエラー処理していないんですけどねww)

はい次は一気に行きます!

response = requests.get(src)
        contentType = response.headers['Content-Type']
        ext = guess_extension(contentType.partition(';')[0].strip())
        with open(image_path + '/' + '{num}{ext}'.format(num=i, ext=ext), 'wb') as f:
            f.write(response.content)
            f.close()

こちらですね!

こちらは画像データ取得して保存してます!以上!!

はい嘘です!ちゃんと説明します!

response = requests.get(src)

こちらは、画像データを取得しています!

最初細かく考え出すとわけわからなくなるので、画像の色々なデータを取得しているんだなというくらいでいいと思います!!

contentType = response.headers['Content-Type']
ext = guess_extension(contentType.partition(';')[0].strip())

こちらは画像ファイルの拡張子を取得しています!

jpgとかpngとか拡張子が複数あるのでそれを特定している感じです!!

with open(image_path + '/' + '{num}{ext}'.format(num=i, ext=ext), 'wb') as f:

で、こいつですね!

まずopen()というのは、ファイルの読み書きなどをするための関数です!

open(ファイル名(パス)、オプション、文字エンコード)

みたいな感じで引数を指定できます!

なので、今回も第1引数にはファイル名含めた画像パス、第2引数にはwbというオプションを指定しています!

wbというのはバイナリファイルへの書き込みをするときに使用するオプションです!

バイナリファイルっていうのは簡単に言うと、テキストファイル以外のファイルのことです!今回は画像ファイルなのでバイナリファイルですね!

'{num}{ext}'.format(num=i, ext=ext)

こちらはformat 関数を使ってファイル名を生成している感じです!

numには番号をextには先に取得してある拡張子を、入れてそれをつなぎ合わせてファイル名にしているというわけですね!

f.write(response.content)
f.close()

ラストはこちらですね!

こちらはシンプルに画像データをopenで指定したパスに書き込みを行い、処理を閉じているって感じです!

これでコードの解説は以上となります!!

バババッと書いてしまったので、一度で理解できなかった方は何度も読み直して関数を調べたりして、一歩ずつ理解していきましょう!!

プロのエンジニアも実装するときは検索する時間が殆どを占めていて、詰まるときなんかは平気で一日なにも進まないこともありますからね!!

ゆっくり地道にいいんです!!すべての関数を暗記する必要はないんです!あーなんかこういう事できるやついたなーくらいでいいんです!!

まとめ

ここまでお読みいただきありがとうございます!

前回より、コードもライブラリの数も増えているので、プログラム書いたぞって感じになりましたかね??

こうやって色々組み合わせながら実装していくのって楽しいですよね!!

コマンド実行して自動で画像保存できたときのあの感動!!これだからプログラミングはやめられないですね!!

いや俺はもっと複雑なモノつくりたいんだよ!!こんな子供だましに付き合っている暇はないって方も落ち着いてください!!

どんな複雑で大きなプログラムも小さな部品が組み合わさることでできているんです!!

こういうシンプルな機能を作れるということは案外大事だったりするんですね!!

余談ですが、このシリーズは

エンジニアでない方にもプログラムを書いて動かすということの楽しさを知ってほしい

という思いがあります!なので、今までプログラミングに触れたことのない人にもやってほしいんです!!

周りにエンジニアの方がいるのがベストですけど、ネットにゴロゴロ情報載っているので独力でも全然可能です!!

今回も前回も環境さえあれば概要をコピーすれば動きます!!

暇つぶし感覚でぜひやってみてください!!

それではみなさん、素敵なwebスクレイピングライフをお送りください〜!!