PythonでWebからいろんなデータを集めちゃおう!~スクレイピングの巻~
今回はスクレイピングについてやっていくよ!
スクレイピングできるとWeb上からいろんなデータを集めることができて、調べものが捗ること間違いないよ!
それは楽しみー!
本記事で参考にさせていただいたのは、下記書籍になります。
Pythonを使ってあれこれ便利なことができることについて、具体的な例と共にたくさん掲載されていておススメです。
本内容の理解を深めるために、会話形式で記載してみました。
実際の書籍と比較し、理解を深める一助になれば幸いです!
ガゼルさん「Webスクレイピング?Webをスクレイプする?」
せんぱい「スクレイプ(scrape)はこすり落とす、こすってするなどの意味の英単語。
なんで、Webをこすって・するわけだね。
そこから転じて、プログラムを使用しWebからコンテンツをダウンロードし処理することをいうよ」
ガゼルさん「だいぶ転じましたね。
とにかく、ダウンロードしていろんなデータを集めたりすることができるんですね」
せんぱい「実は、ぼくらが大変お世話になるGoogle先生も多数のWebスクレイピングを実行し、検索エンジン用にWEbページの索引を作成しているんだよ」
ガゼルさん「おお!そんな身近なところにスクレイピングが介在しているとは。
小憎いやつですね。」
せんぱい「小憎い?
では、Webスクレイピングをするのに役立つモジュールを紹介するよ~」
- webbrowser
せんぱい「これはPython付属のモジュール。指定したページを開くことができるよ
その名もWebブラウザ!」 -
Requests
せんぱい「インターネットからファイルやWebページをダウンロードすることができるよ。
これください、とリクエストするわけだね」 -
Beautiful Soup
せんぱい「Webページの記法であるHTMLをパース(文法的に解釈)することができるモジュールだよ」
ガゼルさん「パース?」
せんぱい「パースはコンピュータプログラムの処理の一つだね。プログラムで処理するための工程といういか。
一定の書式や文法に従って記述されたデータを解析して、プログラムの中で扱えるようなデータ構造の集合体に変換することをいうんだよ」
ガゼルさん「最強助っ人外国人選手みたいですね」
せんぱい「バースのこと!?響きだけでしょ!?」
- Selenium
せんぱい「Webブラウザを起動したり制御したりできるよ。Seleniumはブラウザ上のフォームを記入したり、マウスクリックをシミュレートしたりできるんだ。Seleniumは鉱物である、セレン鉱からきているらしいよ」
ガゼル「どのへんが…セレン鉱なんでしょう..?」
せんぱい「分からない!」
プロジェクト:webbrowserモジュールを用いたmaplt.py
せんぱい「webbrowserモジュールのopen()関数を用いると、新たにブラウザを起動し、指定したURLを開くことができるよ。
単純だけど、ちょっとやってみよう!」
import webbrowser
webbrowser.open('https://google.com/')
せんぱい「ででんと!
上記を実行すると、Webブラウザのタブが https://google.com/ というURLを開くよ。
webbrowserモジュールができることは指定したURLをブラウザで開くこと、それだけ」
ガゼルさん「Wow!シンプル!」
せんぱい「けど、open()関数を使用し、いろいろな面白いことができるんだ。
例えば、指定した住所をGoogle Mapsを使用し、開くのは面倒じゃない?」
ガゼルさん「めんどう!」
せんぱい「だしょ?」
ガゼルさん「Yes!」
せんぱい「例えば、やたらとある住所の地図を見たい年頃になったとするじゃない」
ガゼルさん「そ、そんな年頃があるんですか!?」
せんぱい「ある。
そんなとき、クリップボードの内容から自動的に地図を起動する簡単なスクリプトを
書けば面倒な作業をしなくて済む」
ガゼルさん「へー…」
せんぱい「めちゃくちゃテンションがあがっていない!!
住所をクリップボードにコピーし、スクリプトを実行するだけで地図が開くよ。
プログラムに必要な機能は下記の通りだよ」
- コマンドラインやクリップボードから調べたい住所を取得する
- その住所のGoogle MapsのページをWebブラウザで開く
せんぱい「コードは次のように記述する必要があるよ」
- sys.argvからコマンドライン引数(コマンドでプログラムを起動するときに、そのプログラムに渡す値)を読み込む
- クリップボードの内容を読み込む
- webbrowser.open()関数を呼び出してWebブラウザを開く
せんぱい「早速、mapIt.pyというファイルを作成し、記述していくよ」
ガゼルさん「イエッス!サー!!」
ステップ1:URLを検討する!なんか長々とURLに表示されるけど…!
せんぱい「まず、mapIt.pyを次のようなコマンドラインで実行できるように設定してね。
ただ、この方法は別途で」
ガゼルさん「おふ!!別途かーい!!」
C:> mapit 476+5th+Ave,+New+York,+NY+10018+アメリカ合衆国
せんぱい「コマンドライン引数があればそれを用いて、なければクリップボードの内容を使用するように作成していきます」
ガゼルさん「3分クッキングみたいですね」
せんぱい「まず、指定した住所を開くためのURLを検討する必要があります!
ブラウザで http://maps.googles.com を開いて住所を検索すると、アドレスバーには下記のような内容が表示されますね!
https://www.google.com/maps/place/476+5th+Ave,+New+York,+NY+10018+%E3%82%A2%E3%83%A1%E3%83%AA%E3%82%AB%E5%90%88%E8%A1%86%E5%9B%BD/@40.7531823,-73.9844421,17z/data=!3m1!4b1!4m5!3m4!1s0x89c259aa982c98b1:0x82f102a365e99b51!8m2!3d40.7531823!4d-73.9822534
ガゼルさん「長!!」
せんぱい「URLの中に住所が書かれているけど、それ以外にたくさんのテキストが追加されているでしょ?」
ガゼルさん「はい!なんだかながながと…呪文ですか?」
せんぱい「呪文ではないよ」
ガゼルさん「そしたらいらなくないですか!?」
せんぱい「まあまあ!Webサイトの仕組みで、訪問者を追跡したり、サイトをカスタマイズするためにURLにデータを追加したりするんだけど、これがまさにそれだね!」
ガゼルさん「訪問者を追跡…!?我々は追われる身でありますか?」
せんぱい「まあ、最近この追う流れはSafariがITP対応したことをはじめとして、なくなりつつあるけどね~」
ガゼルさん「悪いことをしていないのに、追われたらたまったものではないですからね!」
せんぱい「ところがだよ、試しに、https://www.google.com/maps/place/476+5th+Ave,+New+York,+NY+10018+アメリカ合衆国/ と入力しても地図を開いてくれるのが分かると思う!」
ガゼルさん「ニューヨーク市立図書館!よくここで勉強してたなぁ」
せんぱい「勉強してたの!?」
ガゼルさん「じゃあ!えい! https://www.google.com/maps/place/〒104-8212+東京都中央区銀座4丁目6−16 っと…
あれ?これはうまく開かないですね…」
せんぱい「日本橋・麒麟の像!なぜ!?
日本の住所だとうまくいかないこともあるかもね~…
まぁ、さておき、プログラムでは http://maps.googles.com/map/place/住所 のようにURLに指定してもよさそうだよね!」
ステップ2:コマンドライン引数を処理するために下拵え!
せんぱい「またまた、次のようにコードを書いてみて~」
#! python3
# mapIt.py -コマンドラインやクリップボードに指定した住所の地図を開くぞ!
import webbrowser , sys
if len(sys.argv) > 1:
#コマンドラインから住所を取得する
address = ' '.join(sys.argv[ 1: ])
# TODO:クリップボードから住所を取得する
せんぱい「まず、シバン(#!)の行のあとで、ブラウザを起動するためにwebbrowserモジュールを、コマンドライン引数を読み込むためにsysモジュールをそれぞれインストールする必要があるよ」
ガゼルさん「まずはインストールってことですね!」
せんぱい「sys.argv変数にはプログラムのファイル名とコマンドライン引数のリストが格納されるよ」
ガゼルさん「sys.argv変数ってPythonにおけるコマンドライン引数を取得する際に使用できるやつでしたっけ?
コマンドライン引数は sys モジュールの argv 属性に文字列を要素とするリストとして格納されるという、伝説的な?」
せんぱい「そんな壮大な感じ!?」
つまり、このリストにファイル名以外のものがあれば、len(sys.argv)は1より大きくなり、コマンドライン引数が設定されていることが分かるんだ」
ガゼルさん「ふんふん!1より大きい、つまりコマンドライン引数が格納されているということになるわけですね!?
逆に小さいと、ファイル名しが取得できていないということですね」
せんぱい「そういうこと!コマンドライン引数は通常、スペースにより区切られているけど、今回はすべての引数を文字列として扱いたいわけだよね?」
ガゼルさん「調べたい、住所情報をおくるんですもんね」
せんぱい「sys.argvは文字列のメソッドになる為、join()メソッドに渡せばひとつの文字列を返すんだけど、今回はプログラム名を文字列に含めたくないでしょ?」
ガゼルさん「住所にファイル名はいらないですもんね。単純にjoin()するとギュインとバインになってしまうわけですね」
せんぱい「ちょっと何言ってるか分からない
そんなときは、はい、こう!sys.argvではなく、sys.argv[1:]を渡すと配列の最初の要素を除くことができるんだ」
ガゼルさん「ええ~便利~」
せんぱい「結合後の文字列は変数addressに格納されるよ。
例えば、次のようにコマンドラインに入力してプログラムを実行してみよう」
mapit 870 Valencia St, San Francisco, CA 94110
せんぱい「するとだね、sys.argvには次の値が格納されるんよね」
[‘mapIt.py’, ‘870’, ‘Valencia’, ‘St, ‘, ‘San’, ‘Francisco, ‘, ‘CA’, ‘94110’]
ガゼルさん「ファイル名もしっかり入ってますね!」
せんぱい「変数addressには、sys.argv[1:]でファイル名を除いて格納するから、’870 Valencia St, San Francisco, CA 94110’という文字列が格納されたわけだね」
ステップ3:クリップボードの内容をつかってブラウザを起動させるよ!
せんぱい「はい、次はさっきのコードを下記のように追記して、変更してみよう!」
#! python3
# mapIt.py -コマンドラインやクリップボードに指定した住所の地図を開く
import webbrowser , sys
if len(sys.argv) > 1:
#コマンドラインから住所を取得するぜ!
address = ' '.join(sys.argv[1:])
else
# クリップボードから住所を取得するよ!
adress = pyperclip.paste()
webbrowser.open('https://www.google.com/maps/place/' + address)
ガゼルさん「でましたif・else文!ここではsys.argvが1より大きい場合、コマンドライン引数から住所情報を取得して変数addressに格納するということですね」
せんぱい「そう!そして、もし、コマンドラインに引数がない場合はクリップボードに住所が格納されていると仮定できるわけ。
そこで、その場合はクリップボードの内容を取得できるメソッド、pyperclip.paste()を使ってクリップボードの内容を取得して変数addressに格納してるってわけだね」
ガゼルさん「ペーパークリップ!」
せんぱい「最後に、Google MapsのURLを補って、webbrowser.open()を呼び出し、Webブラウザを起動するというわけ」
ガゼルさん「おお~」
せんぱい「これは膨大な作業を行ってくれるわけではないけれど、数秒のよくある作業を置き換えるだけでも便利なものなんだ
プログラムではなく、実際に行う場合と比較してみるよ」
実際の作業 | maplt.pyでやってみる場合 |
---|---|
住所を選択 | 住所を選択 |
住所をコピー | 住所をコピー |
ブラウザを開く | mapIt.pyを実行 |
https://www.google.com/maps/を開く | |
住所入力欄をクリック | |
住所をペースト | |
Enterを押す |
ガゼルさん「一回だけだと、そんなに変わらないですが何度も何千回も行う場合は効いてきそうですね」
せんぱい「何千回も行ってたら、ちょっと狂気を感じるけれど、そうだね!」
類似プログラムのアイデア
せんぱい「webbrowserモジュールを使うと、ホラ、下記のような作業を簡単にできる気がしてこない?」
- ページ上すべてのリンクをブラウザの別々のタブで開く!
- 自分の地域の天気予報を取得するURLをブラウザで開く!
- 定期的にチェックする複数のSNSサイトを開く!
ガゼルさん「なんか…簡単にできる気がしてきました!!」
せんぱい「でしょ!?」
ガゼルさん「(ほんとはしていないなんて…言えない…)」
requests(リクエスト)モジュールを用いてWebサイトからファイルをダウンロードしてみる!
せんぱい「requestsモジュールを使用すると、ネットワークエラー・接続上の問題・データ圧縮といった複雑な問題を懸念することなく!
Webから簡単にファイルをダウンロードできるよ!」
ガゼルさん「ええ…そんな簡単に!?」
せんぱい「そう!requestsモジュールはPython付属ではない為、ま・ず・はインストールする必要がある!」
ガゼルさん「何はともあれ、インストールと!」
せんぱい「インストールにあたってはコマンドラインから pip install request を実行するんだ!」
ガゼルさん「requestsモジュールって、わりと最初から入っててもよさそうなモジュールなのに、標準ではないんですね~」
せんぱい「そうなんだよ!requestsモジュールがつくられた理由は、Python付属のurllib2モジュールを使用するのがややこしすぎた為なんだ!」
ガゼルさん「ええ…そんなに!?」
せんぱい「だって…urllib2だよ…2だよ…!?」
ガゼルさん「そんな2だよとか言われても!!」
せんぱい「requestsモジュールが正しくインストールされたか確認するのは下記のように入力すればいいよ」
import requests
せんぱい「エラーメッセージが表示されなければrequestsモジュールは正しくインストールされているってことだよ」
requests.get()関数を用いてWebページをダウンロードする!
せんぱい「requests.get()関数はダウンロードするURLを文字列として受け取るんだ。」
ガゼルさん「ダウンロードするURLを…?文字列として…?」
せんぱい「requests.get()の戻り値をtype()で調べてみるとResponseオブジェクトであることが分かるよ」
ガゼルさん「type()ってFFのライブラみたいな感じですね」
せんぱい「そうだね!(ライブラ!?)」
せんぱい「このrequests.get()なオブジェクトにはWebサーバーへのリクエストに対するレスポンス(応答)が格納されているよ。
早速Webサイトからファイルをダウンロードするサンプルプログラムを記述してみよう!」
ガゼルさん「Here We Go!!」
せんぱい「なんてテンションの高さ!!」
requests(リクエスト)モジュールを用いてファイルをダウンロードしてみる!
import requests
res = requests.get('{ここにダウンロードしたいWebページのURLを記述!}') #①
type(res)
#出力結果は <class 'requests.models.Response'>
#Webページへのリクエストが成功したかどうかを調べるため、Responseオブジェクトのstatus_code属性を調べる
res.status_code == requests.codes.ok
#出力結果は True(値が requests.codes.ok なら成功)
#リクエストが成功したらダウンロードしたWebページはResponseオブジェクトのtext属性に文字列として格納される。
#この属性に格納されている文字列の文字数を調べる
len(res.txt)
#出力結果は 174130()
# 冒頭の250文字だけ表示
print(res.txt[:250])
せんぱい「上記のプログラムを実行すると、例えばそれがtxtファイルだったとして、最初の250文字が表示されるよ」
ガゼルさん「ふむふむ」
吾輩はガゼルである。名前はまだ無い。
どこで生れたかとんと見当がつかぬ。何でも薄暗いじめじめした所ビエンビエン泣いていた事だけは記憶している。
吾輩はここで始めてチーターというものを見た。
せんぱい「何この文章?」
ガゼルさん「ガゼル日記」
せんぱい「どこにアップされてるのそれ!?
データをとりたいページへのリクエストが成功したかどうかはResponseオブジェクトのstatus_code属性を調べれら分かるよ。
もし、この値がrequests.code.okだったら成功ってことになる」
ガゼルさん「OK Go!」
せんぱい「ガゼルさんはインターネットやってて、たまに404とか表示されたことない?」
ガゼルさん「あ!あります!」
せんぱい「それは、HTTPプロトコルにおけるステータスコードでNot Foundを表してるんだ」
ガゼルさん「ミスチルの?」
せんぱい「そう!Not Found!ちなみに無事表示された場合のステータスコードは200になるよ」
ガゼルさん「それも、ミスチルの?」
せんぱい「それはミスチルにはない」
エラーをチェックするよ!
せんぱい「Responseオブジェクトにはsatus_codeという属性があり、requests.codes.okと比較することにより、ダウンロードが成功したかどうか調べることができるよ」
ガゼルさん「イチかバチかってやつですね!」
せんぱい「ギャンブル要素はどこにもないけど!?
最も簡単に成功したどうかを調べるにはResponseオブジェクトのraise_for_status ()メソッドを呼び出すのがいいね!
このメソッドはファイルのダウンロードが失敗すれば例外を起こし、成功すれば何もしないんだ」
ガゼルさん「疑わしきは罰せず、ということですね!」
せんぱい「いやいや、疑わしくもなにもないよ!」
res = requests.get('{エラーが発生するようここに存在しないページのURLを設定してみる}')
res.raise_for_status()
Traceback (most recent call last):
File "<pyshell#138>", line 1, in <module>
res.raise_for_status()
File "C:\Python36\lib\site-packages\requests\models.py", line 773, in raise_for_status
raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 404 Clinet Error:Not Found
せんぱい「はい、こんな感じでエラー内容が表示されました!」
ガゼルさん「あじゃじゃ!」
せんぱい「raise_for_statusメソッドはダウンロードが失敗した際に必ずプログラムを停止させるのにいい方法だよ」
ガゼルさん「ダウンロードが失敗した際に…プログラムを停止…させる!?」
せんぱい「そう、それはもう無残に失敗したときにね!
予期せぬエラーが発生した際には、すぐプログラムを止めるようにしようね!
それが人として課された重大な…任務だよ…!涙」
ガゼルさん「ぼく…人じゃないです」
せんぱい「あ、そか。
ま、ダウンロード失敗がプログラムを停止させるほどのものでなければ、raise_for_status()の行をtry/except文で囲んで異常終了させずにエラーを処理することができるよ」
import requests
res = reuests.get('{任意のURL}')
try:
res.raise_for_status()
eccept Exception as exc:
print('問題あり:{}'.format(exc))
せんぱい「上記のように、raise_for_status()メソッドを呼び出すと次のように表示されるよ」
問題あり: 404 Client Error : Not Found
せんぱい「requests.get()を呼び出した場合は必ずraise_for_status()を呼び出すようにするのがポイントだよ。
何でかって言うと、プログラムの実行を継続する前に、実際にダウンロードされたことを確認するほうがいいからね!」
ガゼルさん「実際にきちんと確認するのが大事なんですな!ニンニン!」
せんぱい「え、忍者!?」
ダウンロードしたファイルをハードドライブに保存する
せんぱい「Webページをダウンロードした場合、通常のopen()関数とwrite()メソッドを使用しファイルをハードドライブに保存することができるんだ。しかし、ここで気を付けなければならないことがある…」
ガゼルさん「ごくり…それは一体…!?」
せんぱい「それは、それは…!まず、open()の第2引数に文字列’wp’を渡し、ファイルを「バイナリ書き込み」モードで開く必要があるからなんだ!!だー だー だー(エコー)」
ガゼルさん「…はぁ」
せんぱい「それが、プレーンテキストだったとしてもUNICODEの文字コードを維持する為にテキストモードではなくバイナリモードで保存する必要がある!!」
ガゼルさん「UNICODE…ユニセフ的な!?」
せんぱい「ユニの部分はあってる…!
Unicodeは、文字コードの国際的な業界標準の一つだよ。
世界中の様々な言語の文字を収録して通し番号を割り当てることで、同じコード体系のもとで使用できるようにしたものなんだ。なんなら、古代文字や絵文字まで収録されているよ!」
ガゼルさん「古代文字や絵文字まで…!いります?」
ガゼルさん「Webページをファイルに書き込むのに、Responseオブジェクトのiter_content()メソッドとforループを使用することができる。
import request
res = requests.get('ダウンロードしたいページのURL')
res.raise_for_status()
play_file = open('開く対象のファイル名','wb')
for chunk in res.iter_content(1000000)
play_file_write(chunk)
#出力例: 100000
#出力例: 74130
play_file.close()
せんぱい「iter_content()メソッドはループの繰り返しごとに「チャンク」を返すんだ」
ガゼルさん「ジャック?」
せんぱい「チャンクね。似てるようで全然違ううじゃん…!
データ通信やファイル形式などで、ひとまとまりのデータの塊のことをチャンク(チャンク形式/チャンク構造)といったりするんだよ」
ガゼルさん「塊…魂…!」
せんぱい「塊魂!バンダイナムコゲームの!
では、なくて。
各チャンクはバイトデータ型で、各チャンクに格納する最大バイト数を指定することができるんだ」
ガゼルさん「最大数を指定することができるんですね..!なんて小回りの利くやつ!」
せんぱい「100キロバイトが適切なサイズになる為、iter_content()の引数に100000を渡すよ」
ガゼルさん「100キロバイトは100000バイトということですね~」
せんぱい「するとどうだい?RomeoAndJuice.txtがカレントディレクトリに作成されるよ。
Webサイト上ではrj.txtというファイル名だったけど、ハードドライブ上では異なる名前を付けられることに注意してね」
ガゼルさん「はいな」
せんぱい「requestsモジュールはWebページをダウンロードする処理だけを行うことができるんだ
そして、ページをダウンロードしてもプログラム上ではただのデータにすぎない。
そうただのね..!」
ガゼルさん「何か意味ありげなセリフですね!」
せんぱい「ページをダウンロードした後に、インターネット接続が切れたとしてもページのデータはすべてコンピュータ上に残っている
だってダウンロードしたからね!」
ガゼルさん「ダウンロードして、PCに残っているということですな!」
せんぱい「write()メソッドはファイルに書き込んだバイト数を返すよ。
上記の例では最初のチャンクは100,000バイト、残りは74,130バイトだった」
ガゼルさん「人まとまり毎にそれぞれ、100,000バイト、4,130バイトだったということですね」
せんぱい「まとめると、ファイルはダウンロードして保存する手順は次のようになるよ」
1.requests.get()を呼び出してファイルをダウンロードする!
2.open()に’wb’を渡して、バイナリ書き込みモードで新しいファイルを作成する
3.Responseオブジェクトのiter_content()メソッドを使用しループする
4.各繰り返しでwrite()を使用しファイルに書き込む
5.close()でファイルを閉じる
せんぱい「前記のテキストファイル書き込みでopen()やwrite()やclose()を使ったものに比べ、forループとiter_content()あたりはややこしく感じるかもしれないね」
ガゼルさん「なんか、グルグルさせるとなんだかややこしく感じちゃいますからね」
せんぱい「requestsモジュールは**巨大なファイルをダウンロードしてもメモリーをそれほど消費しないようになっているよ」
ガゼルさん「メモリにやさしい..エコ。容量の大きいファイルをダウンロードしやすくする工夫がされているんですね」
せんぱい「requestsモジュールの他の機能は http://requests.readthedocs.org を参照してね」
~HTML~ Webページにかかせないあいつ
せんぱい「ガゼルさんはHTMLって聞いたことがあるかな?HTMLファイルはプレーンテキストであり、一般に.htmlや.htmという拡張子を持っているよ」
ガゼルさん「HTMLはWeb界隈だと必ず出てきますよね~。柚子胡椒と同じで」
せんぱい「え、柚子胡椒って出てくるっけ?
ここではHTMLについては詳しく触れないけれど、もう少し詳しく見るには下記サイトとか参考にするとよいかも!」
Progate | プログラミングの入門なら基礎から学べるProgate[プロゲート]
プログラミング入門サイト~bituse~|C言語,C++,HTML,JavaScript,PHP,WINAPI,DXライブラリ,MySQL
ちょっと簡単にHTMLについておさらい
せんぱい「ま、ただ簡単にHTMLについてここでおさらいがてら触れてみたいと思います!」
ガゼルさん「はいな!」
せんぱい「HTMLファイルはプレーンテキストで、テキストは < と > で記述されたタグに囲まれている」
ガゼルさん「ああ、グスタフ!」
せんぱい「全然違う!タグはWebページの構成や整形方法をブラウザに伝えます!
開始タグと終了タグで囲むと要素になるよ。
そしてそして、内部テキストは開始タグと終了タグで囲まれた内容を表すよ」
ガゼルさん「なんて抽象的!」
せんぱい「まあ、ちょっと分かりずらいかな?
例えば、ブラウザに“Hello World”と表示させたいとするでしょ?」
ガゼルさん「はい」
せんぱい「その場合は、下記のように記述するよ」
<storong>Hello</storong> world!
せんぱい「<storong><storong/storong>はセットで、それぞれ開始タグと終了タグと言われるよ。
開始タグ<strong>は、囲んだテキストを強調することを意味するよ。
んで、</storong>は強調表示させたいテキストの終わりを表すんだ」
ガゼルさん「終了タグには / がつくんですね!」
せんぱい「HTMLにはさまざまなタグがあるよ。
タグには < と > の間に属性を指定できるものもあるんだ」
ガゼルさん「属性?」
せんぱい「例えば、“<a>”タグはリンクとなる、テキストを囲むやつなのね。
んで、リンク先のURLはhref属性に記述する」
ガゼルさんは <a href="https://">こんな方です</a>
ガゼルさん「おお! こんな方です のところがリンクとなりました!」
せんぱい「要素にはid属性を付けてページ上の要素を識別するのに使用することができる」
ガゼルさん「?」
せんぱい「要素に目印をつけるイメージだね
Webスクレイピングのプログラムを書くときはid属性を用いて要素を探すプログラムを組むことがあるんだ」
ガゼルさん「その印をねらって、取得することができるわけですね!」
せんぱい「そうそう!属性のidを探すにはプラウザの開発者ツール※を用いるのが便利だよ」
※多くのブラウザではF12を押すと出てくる
ドキ!WebページのソースHTMLを見てみよう
せんぱい「プログラムの処理対象になるWebページのソースHTMLをみるにはWebブラウザのページ上で右クリック!
MacだとCtrlキーを押しながらクリックだね。
そして、[ページのソースを表示]というようなメニュー項目を選ぼう!」
ガゼルさん「おお!何やら難しそうな画面が出てきましたね!」
せんぱい「普段みているサイトはこういった内容でできているいるんだよ。
今、この記事の裏側もね!」
ガゼルさん「わあ!メタ的発言!」
せんぱい「スクレイプするにあたってはソースの内容を完全に理解する必要はないよ。
あくまでもサイトからデータを取り出すだめに必要な情報があればOKだからね」
ガゼルさん「ラジャーです!取り出すときの目汁を見つけられるようになればいいんですね!」
せんぱい「目汁になってるよ!目印ね!」
さあ!ブラウザの開発者ツールを開くのだ!
せんぱい「Webページのソースを見るだけじゃなくて、開発者ツールを使用してHTMLを解析してみよう!のコーナーです!」
ガゼルさん「わあ~パチパチパチパチパチパチパチパチパチパチパチパチパチパチパチパチパチパチパチパチ」
せんぱい「拍手多いな!
WindowsのChromeやInternet Exploer、そしてEdgeには開発者ツールが内臓されている!
F12を押すと表示されるよ!」
ガゼルさん「F12ってガンダムの?」
せんぱい「ガンダムにいたっけ?というかいたとしてどうやって押すのさ!?
ファンクションキーのことだよ!キーボードの上の方にある!」
ガゼルさん「ああ、あれのこと!」
せんぱい「あれしかないよ!
表示されたならば、もう一度F12を押すと、また非表示になるよ
もしくは、各ブラウザにてメニューから出すとしたら下記のようになる」
Chromeの場合
[メニュー]→[その他のツール]→[デベロッパーツール]
※Macだと、⌘-Option-Iで開くことができる
Firefoxの場合
- WindowsとLinux
Ctrl-Shift-C を押す - Mac
⌘-Option-C を押す
Safariの場合
[環境設定]→[詳細タブ]→[メニューバーに開発メニューを表示]をクリック
設定後、⌘-Option-I を押すと開発者ツールが表示される
せんぱい「ブラウザの開発者ツールを有効にしり、インストールすると、Webページ上のどんな箇所でも右クリックし[要素の調査]や[検証]メニューを選ぶと対応するHTMLの要素が表示されるんだ」
ガゼルさん「おお~う!何か普段みているページの裏側ではこんなドラマが繰り広げられていたんですね~」
せんぱい「Webスクレイピングするプログラムをつくる際に、HTMLを解析するのに便利だよ~」
### HTMLを解析するのには正規表現を使用しない
せんぱい「一見、HTMLから特定の部分を見つけるのには、正規表現がうってつけのように思わない?」
ガゼルさん「思わないです」
せんぱい「思って!だって特定の文字列パターンから抽出するんだからさ」
でも、正規表現の使用はおすすめできない」
ガゼルさん「ほらー」
せんぱい「くぅ!
まあ、なんで正規表現がおすすめできないかというと…
HTMLの書き方にはさまざまな方法があって、いずれも正しいHTMLとみなされてしまうからなんだ。
可能なバリエーションすべてに対応しようとすると途端に厄介、そして間違いが起こりやすくなっちゃうんだよね」
ガゼルさん「よく言うと、多少書き方が違くても、柔軟に読み解いてくれるんですね」
せんぱい「そう、よくいうとね。
そして、そんなときは、はい、Beautiful SoupのようなHTMLを解析するモジュールを使用すれば、バグになりにくくなって便利です!。」
ガゼルさん「ビビビビューティフル・スープ!?美しい、肉・野菜などの成分を煮出した濃い液状のことということですか!?」
せんぱい「どうしてスープを説明で置き換えたの!?」
では、開発者ツールを用いてHTML要素を検索してみよう!
せんぱい「requestsモジュールを用いてWebページをダウンロードすると、ページのHTMLの内容はひとつの文字列の値になるんだ」
ガゼルさん「一旦、すべて一つになってしまうんですね~
せんぱい「そそ!
次に、必要なのはWebページ上で自分が調べたい情報が対応するのはどの部分か見つけることだ」
ガゼルさん「そーめん そそそ!目印をみつけるわけですね!?」
せんぱい「なんで素麺屋さんのことを!?
はい、ここでブラウザの開発者ツールが役に立ちます!
ここでは例として、 https://weaher.gov から
天気予報データを取り出すプログラムを書いてみよう~!」
ガゼルさん「いえーい!」
せんぱい「まず、コードを書く前に、このページのことを調べてみよう~!
ブラウザでこのサイトを開いて、(郵便番号)94105を探してみると、その地域の天気情報を表示してくれる」
ガゼルさん「Wow!SanFrancisco!」
せんぱい「じゃあ、早速ZIPコードに対応する気温情報を取り出してみよう~!
ページ上の気温の箇所で右クリック!(MacだとCtrlキーを押しながらクリック!)してみて、
メニューから[要素を調査]や[検証]を選択してみよう~!」
ガゼルさん「おお!開発者ツールが開きました!」
せんぱい「そこに、対応するHTMLが表示されている筈!
開発者ツールを見ると、Webページ上で気温に対応するHTMLは <p class=”myforecast-current -lrg”>61° F</p> であることが分かると思う!」
ガゼルさん「class=”myforecast-current -lrg” が目印になるというわけですね~」
せんぱい「そう!気温情報はmyforecast-current -lrgクラスの
要素の中にあるというわけだね~
文字列からその要素を検索するには、BeautifulSoupモジュールが役に立つよ~!」
美しいスープ・BeautifulSoupモジュールを用いてHTMLを解析する
せんぱい「Beautiful SoupはHTMLページから情報を抽出するモジュールなんだ」
ガゼルさん「HTMLページから…つまりWebページからということですね!?」
せんぱい「そうそう!WebページにはHTMLが欠かせないからね!」
ガゼルさん「洋風スープにコンソメが欠かせないようにですね!?」
せんぱい「すごいスープな要素を使おうとしてくる!
でも、まあ、欠かせない感のイメージは近いかな!?」
ガゼルさん「もしくはみそ・スープにおける出汁…!」
せんぱい「スープ感はもういいよ!
Beautiful Soupのモジュール名はbs4(Beautiful Soup バージョン4)。
インストールするには下記のようにコマンドラインに入力するんだ」
pip install beautifulsoup4
せんぱい「beautifulsoup4はインストール時に用いる名前で、インポートするときはimport bs4と書くんだよ」
ガゼルさん「インストールするときと、インポートするときで、通称が違うんですね~何かのこだわりなんでしょうか?」
せんぱい「なんだろうねぇ~
今回の例では、Beautiful Soupではハードドライブ上のHTMLファイルをパースしていくよ。
パースとは部分を識別できるように構文解析することだね~」
ガゼルさん「ぼくもたまーにパースりますね」
せんぱい「そんな銭湯に行く的な感じで!?
では、次なんだけど、下記みたいに入力して、新たにexample.htmlという名前で保存してみて~」
<!-- This is the example.html example file. -->
<html><head><title>The Website Title</title></head>
<body>
<p>Download my <strong>Python</strong> book from <a href="http://inventwithpython.com">my website</a>.</p>
<p class="slogan">Lean Python the easy way!</p>
<p>By <span id ="author">AI Sweigert</span></p>
</body></html>
せんぱい「そして、このHTMLなんだけど。
上のはシンプルなHTMLファイルだけど、さまざまなタグや属性が使用されているでしょ?」
ガゼルさん「
やら
やらですね!」せんぱい「Beautiful Soupを使用すると、HTMLを簡単に扱うことができるんだよ~」
ガゼルさん「ほほう!」
HTMLからBeautifulSoupオブジェクトを生成する・インターネットからもってくる場合とローカル環境にある場合の両方のパターンを見てみるよ!
せんぱい「bs4.BeautifulSoup()関数に、解析したいHTMLを渡しつつ呼び出すと、BeautifulSoupオブジェクトを返してくれるよ
インターネットにつないで、下記のように入力してみて!」
# これはインターネットからWebページの情報をもってくるパターンだよ
import request, bs4
res = requests.get('https://ここに情報が欲しいページのURLを入力する!')
res.raise_for_status()
no_starch_soup = bs4.BeautifulSoup(res.text)
type(no_starch_soup)
せんぱい「はい、無事、変数no_starch_soupにBeautifulSoupオブジェクトを格納できたということだね!」
上記コードはrequests.get()を用いて“No Starch Press”のWebサイトからWebページをダウンロードし、レスポンスのtext属性を bs4.BeautifulSoup()に渡している、と」
ガゼルさん「そして、変数no_starch_soupに格納しているってことですね。スププンと!」
せんぱい「何その擬音!?
で、格納されたものをtype()で調べてみると、はい、bs4.BeautifulSoupオブジェクトだ、ということが分かるということだね~」
ガゼルさん「type()できちっと調べると。この事件の犯人を」
せんぱい「何も事件は起きてないけど!?
まあ、それはさておき、bs4.BeautifulSoup()にFileオブジェクトを渡すことで、ハードドライブ。
つまり、自身のパソコンにあるHTMLファイルを読み込むことができるよ」
# 自身のマシンの中のHTMLファイルを呼び出す例だよ!
example_file = open('example_html')#ちゃんとサンプルのHTMLがある場所でこのプログラムを実行してね!
example_soup = bs4.BeautifulSoup(example_file)
type(example_soup)
せんぱい「はい、ちゃんと example_soup の中にbs4.BeautifulSoupが格納されているのを確認できました~!」
select()メソッドを用いて欲しい要素を見つける!
せんぱい「BeautifulSoupオブジェクトのselectメソッドに、検索したい要素のCSSセレクタを渡して呼び出すと、Webページの要素を取得することができるよ」
ガゼルさん「UFOキャッチャーみたいに!?」
せんぱい「そんなじわじわ狙ってやる感じじゃないかな
もっとしっかり狙う感じかな!?」
ガゼルさん「もっとしっかり…!?」
せんぱい「セレクタはHTMLから検索対象を指定するパターンのことをいうんだよ。
テキストから検索するための正規表現のようなものだね。
もとい正規表現の代わりを担ってくれるということさ…」
ガゼルさん「生まれ変わりのようなものですね…(合掌)」
せんぱい「生まれ変わり、ではない!その一部を簡単に紹介するね!」
セレクタの例
select()に渡すセレクタ | マッチする対象 |
---|---|
soup.select(‘div’) | すべての<div>要素 |
soup.select(‘#author’) | id要素がauthorである要素 |
soup.select(‘.notice’) | class要素がnoticeである全要素 |
soup.select(‘div span’) | <div>要素の中のすべての<span>要素 |
soup.select(‘div > span’) | <div>要素の直下のすべての<span>要素(間に他の要素がない!) |
soup.select(‘input[name]’) | name要素を持つすべての<input要素> |
soup.select(‘input[type=”button”]’) | type属性の値がbuttonであるすべての<input>要素 |
せんぱい「さまざまなセラクタパターンを組み合わせることで、複雑な検索をすることができるんだ
例えば例えば、soup.select(‘p # author’)は、
属性で、且つid属性がauthorである要素にマッチさせることができるんだ」
ガゼルさん「#でidのことなんですね~」
せんぱい「select()メソッドは、Tagオブジェクトのリストを返すんだよね。
TagオブジェクトはBeautiful Soupオブジェクトにおいて、HTML要素の表現になるんだ」
ガゼルさん「表現?…アート?」
せんぱい「アートではない
リストにはBeautiful SoupオブジェクトのHTMLの中でマッチしたすべての要素が含まれている」
ガゼルさん「すべての…要素が含まれる…!?この世の全ての…!?」
せんぱい「そんな壮大な話ではない
Tagオブジェクトをstr()関数に渡すと、それが表現する要素を文字列として取得することができるよ」
そして、Tagオブジェクトにはattrs属性もあり、タグすべての属性を辞書として保持もしてもいるんだ」
ガゼルさん「attrs属性って?」
せんぱい「attrsは要素の属性を辞書として保持するんで、その属性ってことだね」
ガゼルさん「なんと…まるで合わせ鏡みたいな!」
ツトムせんぱいのメモ!
- attrs
attrsはカスタムクラスを作成する際の特殊メソッドの記述を省略できる機能を提供するライブラリ。
pythonのクラス定義の記述方法を変更して、コード量と可読性をあげることができる。 -
type関数
オブジェクトの型を調べることができる。
せんぱい「じゃあ、ここでセレクタを実際につかった例をみてみよう!」
import bs4
example_file = open('example.html')
example_soup = bs4.BeautifulSoup(example_file)
elems = example_soup.select('#author')
type(elems)
#出力結果
#<class 'list'>
len(elems)
#出力結果
#1
type(elems[0])
#出力結果
#<class 'bs4.element.Tag'>
elems[0].getText()
#出力結果
#'Al Swegart'
str(elems[0])
#出力結果
#'<span id="author">Al Sweigart</span>'
elems[0].attrs
#出力結果
#{'id': 'author'}
せんぱい「上のコードはサンプルのHTMLからid=”author”である要素を取り出しているよ。
select(‘#author’)を呼び出すと、id=’author’であるすべての要素のリストが返ってくるんだ」
ガゼルさん「上のをみると、’1’と返ってきてますね」
せんぱい「うん!
このTagオブジェクトのリストをelemsに格納してみて、len(elems)を見るとTagオブジェクトの数は1つ。
つまり、マッチしたのはひとつだけってことだね」
ガゼルさん「id=’author’と設定されたものは一つしかなかったということですね」
せんぱい「そう!要素のgetTextメソッドを呼び出すと、要素の内部テキストを取得できるんだよ
内部テキストは開始タグと終了タグの間の内容ってことになるんだ」
ガゼルさん「
これ→<> これ→</> に、挟まれたやつってことですね。
すると、今回は’Al Sweigart’になるってことなんですね~」
せんぱい「そういうこと!
そして、要素をstrに渡すと、開始タグと終了タグを含む文字列を返すんだ」
ガゼルさん「str(elems[0])を出力すると
になったような感じですね!」せんぱい「そうそう!
最後に、attrsは要素の属性を辞書として保持するよ」
今回の場合は** ‘id’と、その値’author’を持つ辞書になるよ**」
ガゼルさん「idに対応する形でauthorという値をもつんですね」
せんぱい「BeautifulSoupオブジェクトからすべての<p>要素を取り出すこともできるよ!
次のように入力してみて!」
p_elems = example_soup.select('p')
len(p_elems)
#出力結果
#3
str(p_elems[0])
#出力結果
#'<p>Download my <strong>Python</strong> book from <a href="http://inventwithpython.com">my website</p>'
p_elems[0].getText()
#出力結果
'Download my Python book from mya website.'
str(p_elems[1])
#出力結果
'<p class="slogan">Learn Python the easy way!</p>'
p_elems[1].getText()
#出力結果
#'Learn Python the easy way!'
str(p_elems[2])
#出力結果
#'<p>By <span id ="author">Al Awegart</span></p>'
p_elems[2].getText()
#出力結果
#'By Al Sweigart'
せんぱい「今度はselectは、3つの要素からなるリストを返すよ。
これをp_elemsに格納し、p_elems[0]~p_elems[2]をstr()に渡すと各要素を文字列として取得できる」
ガゼルさん「おお~!」
せんぱい「また、getText()を呼び出すと内部テキストを取得できる。」
ガゼルさん「<p></p>の中身を取得することができるんですね~!」
要素の属性からデータを取得することもできるんだ!
せんぱい「Tagオブジェクトのget()メソッドを用いると、要素の属性値を取得するのが簡単にできるよ。
このメソッドを属性名に渡すと、その属性値を返すんだ。試してみるよ!」
import bs4
soup = bs4.BeautifulSoup(open('example.html'))
spam_elem = soup.select('span')[0]
str(spam_elem)
#出力結果
#'<span id="author">Al Sweigart</span>'
spam_elem.get('id')
#出力結果
#'autor'
spam_elem.get('some_nonexistent_addr') == None
#出力結果
#True
spam_elem.attrs
#出力結果
#{'id': 'author'}
せんぱい「ここではselectを使用し、要素を探し最初にマッチした要素をspan_elemに格納している
get()に属性名’id’を渡すと属性値の’author’を返す」
ミッション!Google検索 “I’m Feeling Lucky” 一気に対象としたページを開く!
ガゼルさん「I’m Feeling Lucky?私はいい気分てことですか?」
せんぱい「Googleの検索ボックスの右下に「I’m Feeling Lucky」と書かれたボタンがあるでしょ?
検索ボックスに“ガゼル”と入力して、I’m Feeling Luckyボタンを押してごらん?」
ガゼルさん「わあ!いきなりwikipediaのガゼルのページが開きました!
あ…マサルくん!?」
せんぱい「その写真のガゼルマサルくんていうの!?
それはさておき、何かを調べるとき検索結果でブラウザを開いて、検索し、リンクを一つずつタブで開くという作業ってよく行うよね?」
ガゼルさん「やりますねー」
せんぱい「でも、それって煩わしくない?」
ガゼルさん「煩わC!」
せんぱい「なんかCがでて来た!
うん、煩わしいでしょ?
そこで、コマンドラインから検索したら、自動的にブラウザを開き、上記の検索結果を新しいタブに開くようなプログラムを作成してみよう!」
ガゼルさん「わーい!それはもう人生が変わるほど楽になるんだろうなあ!」
せんぱい「そ、そこまでではない!プログラムの動作は下記だよ」
- 検索キーワードをコマンドライン引数から取得するよ
- 検索結果ページを取得するよ
- 各検索結果についてブラウザのタブを開く
せんぱい「コードでは下記のような動作を行うよ」
- sys.argvからコマンドライン引数を読み込む
- requestsモジュールを用いて検索結果のページを取得する
- 検索結果からリンクを見つける
- webbrowser.open()関数を呼び出しWebブラウザを開く
ステップ1:コマンドライン引数を取得し、検索ページをリクエストする!
せんぱい「コーディングを始める前に、まず検索結果ページのURLを調べておかなきゃいけないよ
Googleの検索結果のページは
https://www.google.com/search?Q=検索ワード
のようになってるよ」
ガゼルさん「?の後ってパラメーターってやつでしたっけ?ページに何らかの情報を持たせたりするときに使えるやつ」
せんぱい「そうそう!
まず、requestsモジュールを用いるとこのページをダウンロードすることができるよね」
ガゼルさん「まず、リクエストするんですね~」
せんぱい「そして、Beautiful Soupを用いればHTMLから検索結果のリンクを見つけることが出来るってわけ」
ガゼルさん「美しいスープから美しい具材を取り出すかの如くですね」
せんぱい「ごめん、ちょっとその例え分からない
最後に、webbrowserモジュールを用いてリンクをブラウザのタブで開いて仕上げだね♪」
ガゼルさん「仕上げはお母さん、と!」
せんぱい「そんなことは言ってないよ!
まあ、さておきさっそくコードをかいていってみよう~」
#! python3
#lucky.py -Google検索結果をいくつか開く
import requests,sys,webbrowser, bs4
print('Googling...') #Googleページをダウンロード中にテキストを表示
res = requests.get('http://google.com/search?q=' + ''.join(sys.argv[1:])
res.raise_for_status()
#TODO:上位の検索結果をいくつか開く/後でかく
#TODO:各結果をブラウザのタブで開く/後でかく
せんぱい「まずはプログラム起動時のコマンドライン引数に、検索ワードを指定するよ。
引数はsys.argvに文字列のリストを格納するんだね~」
ガゼルさん「コマンドライン引数?」
せんぱい「コマンドライン引数はコンピューターのコマンド入力画面から、プログラムを起動する際に指定するパラメーターのことだよ。
プログラムを呼び出すときにもれなくパラメーターをつけておくと、そのパラメーターを受け取っていい感じにしてくれるってことだね」
ガゼルさん「それを、sys.argvで受け取ることができるってことですね~」
せんぱいのメモ!sys.argv
sys.argvはPythonスクリプトに渡されたコマンドライン引数のリスト。
リストの先頭、sys.argv[0]はスクリプトのファイル名になる!
せんぱい「そして、これをjoin()で結合して、requests.get()にGoogle検索のURLとして渡し、ダウンロード結果をresに格納する」
ガゼルさん「もす!」
せんぱい「?」
ステップ2:結果をすべて取得しよう!
せんぱい「次に、ダウンロードした
からBeautiful Soupを用いて検索結果のリンクを抽出するよ」
ガゼルさん「さっそくまた、Beautiful Soupが!」
せんぱい「はい!ここでガゼルさんさんにクイズです!そうするにはどのようなセレクタを指定するとよいでしょう?」
ガゼルさん「はらたいらさんに3000点」
せんぱい「複雑なクイズ形式にしないで!
すべてのリンク、つまり<a>タグを選択すると、余計なリンクまで抽出してしまうよね…」
ガゼルさん「すべてのリンク、という条件だととりあえず全てのリンクをひっぱってしまいますもんね」
せんぱい「そしてそして、ブラウザの開発者ツールを使ってページ上のリンク要素を調べてみたらなんかとても複雑なことになっているのが分かると思う」
<div class="r"><a href="https://things-that-enrich-our-life.site/how-to-beat-a-cheetah/" ping="/url?sa=t&source=web&rct=j&url=https://things-that-enrich-our-life.site/how-to-beat-a-cheetah/&ved=2ahUKEwjc6pG86NfnAhWZUt4KHRyDC0kQFjAGegQIAxAB"><br><h3 class="LC20lb DKV0Md">チーターの倒し方!</h3><div class="TbwUpd NJjxre"><cite class="iUh30 bc tjvcx">things-that-enrich-our-life.site › how-to-beat-a-cheetah</cite></div></a><div class="B6fmyf"><div class="TbwUpd"><cite class="iUh30 bc tjvcx">things-that-enrich-our-life.site › how-to-beat-a-cheetah</cite></div><div class="yWc32e"><span><div class="action-menu ab_ctl"><a class="GHDvEf ab_button" href="#" id="am-b6" aria-label="検索結果のオプション" aria-expanded="false" aria-haspopup="true" role="button" jsaction="m.tdd;keydown:m.hbke;keypress:m.mskpe" data-ved="2ahUKEwjc6pG86NfnAhWZUt4KHRyDC0kQ7B0wBnoECAMQBA"><span class="mn-dwn-arw"></span></a><div class="action-menu-panel ab_dropdown" role="menu" tabindex="-1" jsaction="keydown:m.hdke;mouseover:m.hdhne;mouseout:m.hdhue" data-ved="2ahUKEwjc6pG86NfnAhWZUt4KHRyDC0kQqR8wBnoECAMQBQ"><ol><li class="action-menu-item ab_dropdownitem" role="menuitem"><a class="fl" href="https://webcache.googleusercontent.com/search?q=cache:jwqHvRRTFkoJ:https://things-that-enrich-our-life.site/how-to-beat-a-cheetah/+&cd=7&hl=ja&ct=clnk&gl=jp" ping="/url?sa=t&source=web&rct=j&url=https://webcache.googleusercontent.com/search%3Fq%3Dcache:jwqHvRRTFkoJ:https://things-that-enrich-our-life.site/how-to-beat-a-cheetah/%2B%26cd%3D7%26hl%3Dja%26ct%3Dclnk%26gl%3Djp&ved=2ahUKEwjc6pG86NfnAhWZUt4KHRyDC0kQIDAGegQIAxAG">キャッシュ</a></li></ol></div></div></span></div></div></div>
ガゼルさん「わひゃー!」
せんぱい「そんなときは落ち着いてHTMLの内容を分析してみよう!
例えば下記みたいな共通点を見つけられたとしたら…
(ガゼルさんは何を調べようとしていたんだろう…!)」
- rクラスが検索結果リンクに使われている
ガゼルさん「犯人はヤスじゃなくてr…!」
せんぱい「犯人て何!?しかもヤスて!?
はい、これを取得するために、’.r a’というセレクタを用いてCSSクラスがrである要素の中にあるすべての<a>要素を見つけてみよう!
#TODO!上位の検索結果をいくつか開くのところに下記コードを追記してみて!」
#TODO!上位の検索結果をいくつか開く
soup = bs4.BeautifulSoup(res.text)
link_elems = soup.select('.r a')
ステップ3:検索結果をWebブラウザで開くぜ!
せんぱい「最後にWebブラウザのタブに検索結果を開く処理を記述するよ!
コードに下記の内容を追記して!」
#TODO:各結果をブラウザのタブで開く
num_open = min(5, len(link_elems))
for i in range(num_open):
webbrowser.open('https://google.com + link_elems[i].get('href'))
せんぱい「デフォルトでは検索結果の上位5件を、webbrowserモジュールを使用して新しいタブで開いてくれるよ。
しかし、検索結果が5件に満たないこともあるよね?」
ガゼルさん「ありますね!ちょっと詳しく調べ過ぎたときとか…!
あのチーターの寝込みを襲う方法とか…!」
せんぱい「何を調べようとしたのそれ!?
soup.select()はセレクタ’.r a’にマッチしたすべての要素のリストを返す。
このリストの長さが5件、もしくは少ないほうの数までタブを開くんだ」
ガゼルさん「5件より少なかったら、ひとまずその分を開くんですね~」
せんぱい「Python組み込み関数min()は、渡された整数や浮動小数点の中から最小値を返すんだよ!」
ガゼルさん「つまり、5より多い場合の最小は5になって、5つが開かれるということですね~」
せんぱい「そういうことだね!
min()により、リストの中のリンクが5件未満かどうかを調べて、タブで開くリンクの数をnum_openという変数に格納するよ」
ガゼルさん「num_openに開く数がセットされたと!」
せんぱい「そして、range(num_open)を呼び出しforループを回す」
ガゼルさん「range関数は、指定した長さの整数リストを自動で生成してくれる…と!」
せんぱい「で、繰り返しごとにwebbrowser.open()を使用しブラウザの新しいタブを開く、と!」
検索結果の<a>要素のhref属性の値には、冒頭のhttp://google.com の部分が含まれていない為、補ってるよ」
ガゼルさん「これでコマンドラインから lucky python programing tutorials のように実行すると「Python programing tutoorials」に関するGoogle検索上位5件を開けるようになるわけですね~!
チーター撲滅運動の為の調べものがはかどりそうです!」
せんぱい「何その怖い運動!!」
これに似たプログラム
せんぱい「このアイデアを活かすと、こんなこともできるよ!」
- Amazonなどの通販サイトを検索してすべての商品ページを開く
- ある商品のすべてのレビューへのリンクを開く
- FlickerやImgurなどの写真サイトを検索して、写真へのリンクを開く
ミッション!すべてのXKDCコミックをダウンロードする!
せんぱい「XKCDコミックって知ってる?」
ガゼルさん「バットマンなどコミックですね!」
せんぱい「それDCコミックね!
XKCDコミックはランドール・マンローさんによる、ギークに人気のあるWebコミックだよ!」
ガゼルさん「ほほう、ギースに人気の」
せんぱい「ハワード!ギースじゃなくて、ギークね、ギーク!
ロマンス、皮肉、数学などのウィットに富んだ知的なコミックだよ」
ガゼルさん「棒人間の見た目に騙されるなと」
せんぱい「トップページを見ると、[<Prev](前へ)ボタンがあって、過去のコミックにさかのぼることができるようになってるよ
このすべてのコミックを…すべて、ダウンロードしてみよう」
ガゼルさん「せんぱい…正気ですか」
せんぱい「ほらほら、ガゼルさん。何のためにPythonを学んでいると思ってるんだい?」
ガゼルさん「チーターを…滅殺するため」
せんぱい「ち、違うでしょ!
業務効率を上げる為でしょ!」
ガゼルさん「は!そうでした!」
せんぱい「もう!あと少しでガゼルさんがダークサイドにおちちゃうところだったよ!」
そりゃ、手作業でコミックをダウンロードすると途方もなく時間がかかるから、数分でこの作業をやってくれるスクリプトを作成してみよう!」
ガゼルさん「はーい!」
せんぱい「今回作成するプログラムは、機能として下記を持つ必要があるよ」
- XKDCのホームページを読み込む
- ページ上のコミック画像を保存する
- [<Prev]リンクをたどる
- 最初のコミックに辿り着くまでを繰り返す
せんぱい「コード上では次の動作をする必要があるよ」
- requestsモジュールを使用しページをダウンロードする!
- Beautiful Soupを使用し、ページからコミック画像のURLを見つける!
- iter_content()を使用し、コミック画像をダウンロードしてハードドライブに保存する!
- [<Prev]リンクのURLを見つけて繰り返す
せんぱい「では、さっそく作成していこう!
新たにファイルエディタウィンドウを開いて、downloaadXkcd.pyという名前で保存し、コードを書いていくよ!」
ステップ1:プログラムを設計する
せんぱい「プラウザ開発者ツールを用いてページの要素を検索すると下記のようになっていることがわかるね」
- コミック画像ファイルのURLは要素のhref属性に記載されている
- 要素は
<
div id=”comic”>要素の中にある
– [<Prev]ボタンはprevという値のrel要素をもつ
– 最初のコミックノートパソコン[<Prev]ボタンのリンクは、http://xkdc.com/#というURLになっておりそれ以上過去のページがないことを示している
せんぱい「では、これを受けて…!」
#! python3
# downloadXkdc.py - XKDCコミックをひとつずつダウンロードする
import requests, os, bs4
url = 'https://xkdc.com' # 開始URL
os.makedirs('xkdc', exist_ok=True) # ./xkdcに保存する
while not url.endswith('#'):
# TODO: ページをダウンロードする/後で書く
# TODO: コミック画像のURLを見つける/後で書く
# TODO: 画像のダウンロードする/後で書く
# TODO: 画像を./xkcdに保存する
# TODO: PrevボタンのURLを取得する/後で書く
print('完了')
せんぱい「url = ‘http://xkdc.com’ ではまず、変数urlに開始URLとして https://xkdc.comを 格納しているんだね。
そして、ループの中で現在ページの[<Prev]ボタンのリンクのURLに更新していく、と」
ガゼルさん「while not url.endswith(‘#’): の部分が、urlの最後が#じゃない場合は繰り返すぞ!という宣言ですね」
せんぱい「そう!
繰り返しごとにコミック画像をダウンロードしていき、urlが’#’でおわるとループを終了するんだね!」
ガゼルさん「urlが’#’になった、てことはいちばん最初のコミックにたどり着いたということですね」
せんぱい「YES!
画像はカレントディレクトリのxkcdというフォルダに保存するから、
os.makedirs()で、フォルダを存在させるよ!」
ガゼルさん「メイク・アップ・ディレクトリ!」
せんぱい「exist_ok=Trueというキーワード引数は、フォルダがすでに存在しているときに例外を起こさないようにするためのものなんだ
。
既に存在してたら、“あ、存在してるわ”といって次にいく、と!」
あと、念の為いっておくけど、 # TODO の部分はコメントアウトでひとまず書かなきゃいけない処理のアウトラインを記しているものです!」
ガゼルさん「ヘンゼルとグレーテルのパンくず的なね!」
せんぱい「合ってるような、ないような…!」
ステップ2:まずはページをダウンロードしていくぜ!
せんぱい「次は実際にコミックをダウンロードしていく処理を書いていくよ!」
# TODO: ページをダウンロードする
print('ページをダウンロード中{}...'.format(url))
res = requests.get(url)
res.raise_for_status()
soup = bs4.BeautifulSoup(res.txt)
# TODO: コミック画像のURLを見つける/あとで書く
# TODO: 画像のダウンロードする/あとで書く
# TODO: 画像を./xkcdに保存する/あとで書く
# TODO: PrevボタンのURLを取得する/あとで書く
print('完了')
せんぱい「最初にURLを表示し、プログラムをダウンロードしようとしているURLが分かるようにしている。
そして、requestsモジュールのrequests.get()関数を使用しページをダウンロードする、と」
ガゼルさん「画面には ページをダウンロード中{}…URL みたいに表示される、的な!?」
せんぱい「合わせて、すぐにResponseオブジェクトのraise_for_status()メソッドを呼び出してダウンロードが失敗したら例外をおこしプログラムを終了するようにする」
ガゼルさん「緊急防止ボタン、的な!?」
せんぱい「そして、成功した場合、ダウンロードしたテキストからBeautifulSoupオブジェクトを生成するようにしていく]
ガゼルさん「グッド・ジョブ!…的な!!」
せんぱい「腹立つな…!」
ステップ3:コミック画像を見つけてダウンロードする
せんぱい「続けて、どんどん書いていくよ!また、次のコードを追記してみて!」
# TODO: コミック画像のURLを見つける
comic_elem = soup.select('#comic img')
if comic_elem == []:
print('コミック画像が見つかりませんでした')
else:
comic_url = 'https:' + comic_elem[0].get('src')
# TODO: 画像をダウンロードする
print('画像をダウンロード中 {}...'.format(comic_url))
res = requests.get(comic_url)
res.raise_for_status()
# TODO: 画像を./xkcdに保存する
# TODO: PrevボタンのURLを取得する
print('完了')
せんぱい「XKCDのページを開発者ツールで解析すると、コミック画像の要素は、id属性がcomicである
<
div>要素の中にあることが分かったよね?
だから、’#comic img’というセレクタを使用すれば目的の要素をBeautifulSoupオブジェクトから取得することができるよ」
ガゼルさん「idがcomicの中のimgをんっ、ぐっぐっぐっ!ということですね」
せんぱい「ちょっと言ってる意味が分からない
XKCDのページの中には単純な画像ファイルではないものもある…!」
ガゼルさん「単純な画像ファイルではないもの?」
せんぱい「まあ、今想定しているパターンに該当しない場合というか。
はい、そういった場合、今回はかまわずスキップします!」
ガゼルさん「わお!!大胆!!」
せんぱい「セレクタが要素を見つけることができなければ、soup.select(‘comic img’)は空のリストを返します!
その場合、プログラムはその内容を表示して、画像をダウンロードせずに次のページに遷移します!」
ガゼルさん「else(エルス)!つってぇぇぇ!」
せんぱい「そんな、バルス!みたいに!?
そうでない場合、セレクタは要素がひとつに入ったリストを返すよ。
この要素からsrc属性を取り出して、requests.get()に渡しコミック画像をダウンロードするんだね」
ステップ4:画像を保存し、更に過去のコミックを見つける!
せんぱい「どんどんいくよ!更に次のコードを追記して!」
# TODO: 画像を./xkcdに保存する
image_file = open(os.path.join('xkdc', os.path.basename(comic_url)), 'wb')
for chunk in res.iter_content(100000):
image_file.write(chunk)
image_file.close()
# TODO: PrevボタンのURLを取得する
prev_link = soup.select('a[rel="prev"]')[0]
url = 'https://xkcd.com' + prev_link.get('href')
print('完了')
せんぱい「この時点ではコミック画像はres変数に格納されているから、これをハードドライブのファイルに書き込むよ」
ガゼルさん「res = requests.get(comic_url) の、resの中にあるってことですね」
せんぱい「それには、open()に渡すローカルのファイル名が必要になるよ。
comic_urlには’https://imgs.xkcd.com/comics/heartbleed_explanation.png’ のような値が格納されているけど、ファイルパスのように見えるね?」
ガゼルさん「え?連想ゲーム的な?」
せんぱい「いや、事実ね!
実際、os.path.basename()にcomic_urlを渡すとURLの最後の部分’heartbleed_explanation.png’を返すんだ。
これをハードドライブに保存する際のファイル名として使用することとしよう!」
ガゼルさん「os.path.basename()はファイル名を取得するメソッドでしたっけ?」
せんぱい「そうそう!
xkcdフォルダとファイル名を連結する際に、os.path.join()を用いるとWindowsならバックスラッシュ()、MacやLinuxならスラッシュ(/)をそれぞれ使用し連結してくれるよ」
ガゼルさん「Oh!べんり~!」
せんぱい「はい、これでファイル名が準備できました!
では、この次はopen()にバイナリ書き込みモード’wb’を指定してファイルを開きます!」
ガゼルさん「よ!まってました~!」
せんぱい「Requestsオブジェクトを用いてダウンロードしたファイルを保存する際は、iter_content()メソッドの値を用いてループします!」
ガゼルさん「と、いいますと?」
せんぱい「こうすることで、最大100,000バイトの画像データのチャンク毎に、ファイルに書き込みことができるんだよ!
そして、ラストはファイルを閉じる、と!」
ガゼルさん「image_file.close() で、ですね!」
せんぱい「はい、これで画像データがハードドライブに保存できました!と」
ガゼルさん「おお~!」
せんぱい「そしてそして、’a[rel=”prev”]’というセレクタでrel属性がprevである<a>要素を取り出して、<a>要素のhref属性から前のページのURLを取得し変数urlに格納します!」
ガゼルさん「これにより、次に向かう先を指定するんですね!」
せんぱい「その通り!
で、またwhileループにより前のページのコミック画像をダウンロードする手順全体を開始するんだ!
実行すると…」
ページをダウンロード中 https://xkcd.com...
画像をダウンロード中 https://imgs.xkcd.com/comics/phone_alarm.png...
ページをダウンロード中 https://xkcd.com/1358...
画像をダウンロード中 https://imgs.xkcd.com/comics/nrp.png...
ページをダウンロード中 https://xkcd.com/1357...
画像をダウンロード中 https://imgs.xkcd.com/comics/free_speech.png...
ガゼルさん「おお!どんどんダウンロードしていく~!」
せんぱい「プロジェクトはWebから大量のデータをスクレイピングするために自動的にリンクをたどるプログラムのより実例となっているでしょ?
ページをダウンロードしてリンクを辿るのはWebクローラーのプログラムの基本的挙動といえるよ。
この仕組みを元に、こんなこともできるよね!」
- すべてのリンクをたどり、サイト全体をバックアップする
- Web掲示板からすべてのメッセージをコピーする
-オンラインストアから商品カタログの複製をつくる
ガゼルさん「ページをどんどん辿っていけるといろいろ面白そうなことができそうですね!」
せんぱい「requestsやBeautiflSoupモジュールはrequests.get()に渡すURLを見つけられる場合はとっても便利。
でも、URLを見るけるのが困難な場合もあるんだ」
ガゼルさん「ええ…それってどんなときですか?」
せんぱい「例えば、そのページがログインが必要なときとか。
その場合ってログイン処理が必要なるから、やんなきゃいけないことが複雑になっちゃうんだよね」
ガゼルさん「ただURLにアクセスすればいいってわけにはいかないですもんね」
せんぱい「そうなんだよ。
そんな、複雑な処理ができるようにするにはseleniumモジュールを用いるとよいね!」
ガゼルさん「出た!セレニウムゥゥゥ!!」
せんぱい「ええ?そんなテンションになる!?」
seleniumモジュールを用いてブラウザを制御するんだ!
せんぱい「seleniumモジュールを使うと、プログラムによってリンクをクリックしたり、ログイン情報を入力したりなど、あたかも人とページがやりとりしているようにPythonがブラウザを直接制御可能となるんだ」
ガゼルさん「ガゼルは?ガゼルがやりとりしたようにはできます?」
せんぱい「Seleniumは、RequestsやBeautiful Soupよりも高度な方法でWebページとやりとりすることが可能なんだ」
ガゼルさん「無視しないで!」
せんぱい「ただ、Webブラウザを起動する為、若干スピードが遅く、バックグラウンドで実行するのが困難なところがあるっていうのが玉にキズだね」
Seleniumでブラウザを制御するCSS
せんぱい「「これから提示する例ではWebブラウザとしてFirefoxを制御するよ!」
ガゼルさん「炎の狐…!傷がうずく」
せんぱい「何があったの!?
まず、FirefoxがPCにインストールされていない場合は https://getfirefox.com からダウンロードしよう」
ガゼルさん「ダウンロード…アンドインストゥゥゥル!!」
せんぱい「すごい気合!そして、Firefoxからseleniumを制御するために https://github.com/mozilla/geckodriver
から、OSに合ったgeckodriverをダウンロードし、パスの通ったフォルダに実行ファイルをコピーしておく必要があるよ」
ガゼルさん「パスの通った?」
せんぱい「特定のプログラムをプログラム名だけで実行できるようにすることだよ。
ちょっと詳しくいうと、PATHという環境変数にこのプログラムも名前だけで実行できるようにしてくださいって設定することだよ」
ガゼルさん「うーん…難しい!」
せんぱい「くわしくはこのサイトとか見て!
Seleniumのモジュールをインポートするにはちょっとコツが必要なんだ。」
ガゼルさん「ほう…コツとな?」
せんぱい「それは、import seleniumではなく、from selenium import webdriver とすることだよ」
ガゼルさん「ええ~なんでですか~」
せんぱい「それは大きなる力が働いた結果だからここでは語れないから。」
ガゼルさん「…ごくり。」
せんぱい「ま、これでSeleniumからFirefoxのブラウザを起動することができるようになったよ。
インタラクティブシェルに、次のように入力してみよう~!」
from selenium import webdriver
browser = webdriver.Firefox()
type(browser)
- 出力結果
browser.get('https://yahoo.co.jp')
せんぱい「webdriver.Firefoxが無事、呼び出した際には、Firefoxのブラウザが起動するよ」
ガゼルさん「おお!」
せんぱい「webdriver.Firefox()の戻り値をtype()で調べると…WebDriverというデータ型であることがわかるよ」
ガゼルさん「何やら’WebDriver’とかかいてありますもんね!」
せんぱい「そそ。そして、browser.get(‘https://www.yahoo.co.jp/’)を呼び出すと、ブラウザが https://www.yahoo.co.jp/ を開くよ」
ガゼルさん「わあ!開いたー!!」
そう…ページの要素を見つけるのだ!
せんぱい「WebDriverオブジェクトには、ページの要素を見つけるためのメソッドがいくつかあるんだ」
ガゼルさん「ほうほう」
せんぱい「メソッドは、大きくfind_element_と、find_elements_の2グループに分けることができるよ」
ガゼルさん「あれ…両方ともfindではないですか?」
せんぱい「find_element_メソッドは検索条件にマッチした最初の要素を、ひとつのWebElementオブジェクトとして返すよ
一方、find_elements_メソッドはマッチしたすべての要素をWebElement_*オブジェクトのリストとして返すんだ」
ガゼルさん「ひとつか、すべてかの違いなんですね~!」
せんぱい「そう!find_element_でこんな値がとれるよサンプルを見てみて~!」
要素を検索するSeleniumのWebDriverメソッド
メソッド名 | 返されるWebElementオブジェクトもしくはそのリスト |
---|---|
browser.find_element_by_class_name(name)、browser.find_elements_by_class_name(name) | CSSクラスがnameである要素 |
browser.find_element_by_css_selector(selector)、browser.find_elements_by_css_selector(selector)、 | CSSセレクタにマッチする要素 |
browser.find_element_by_id(id)、browser.find_eleents_by_id(id) | id属性が一致する要素 |
browser.find_element_by_link_text(text)、browser.find_elements_by_link_text(text) | textに完全一致する<a>要素 |
browser.find_element_by_partial_link_test(text)、browser.find_elements_by_partial_link_test(text) | textに部分一致する<a>要素 |
browser.find_element_by_name(name)、browser.find_elements_by_name(name) | name属性が一致する要素 |
browser.find_element_by_tag_name(name)、browser.find_element_by_tag_name(name) | タグ名がnameに一致する要素(大文字と小文字を区別しない為、’a’でも’A’でも'<a>’にマッチする) |
せんぱいの一言メモ!
*_by_tag_name()メソッドを除き、メソッドの引数は大文字と小文字を区別するよ!
せんぱい「目的の要素が見つからないときは、seleniumモジュールはNoSuchElement例外を起こしてしまうから注意してね」
ガゼルさん「NoSuchElement例外?Suchmos(サチモス)的な?」
せんぱい「サチモス的ではない。探した要素が見つからなかったぞ!という例外のことだよ」
ガゼルさん「見つからなかったぞ!ぷんぷん!的なエラーということですか!?」
せんぱい「ぷんぷんとはならないかな?
この例外でプログラムを異常終了させたくない場合は、try/except文で囲むとよいよ」
ガゼルさん「tryとexceptで処理を分けるとよいということですね!」
せんぱい「WebElementオブジェクトを取得できると、下記のような属性を読み出したり、メソッドを呼び出すことができるということですね」
属性、メソッド | 説明 |
---|---|
tag_name | タグ名。例えば<a>要素なら’a’ |
get_nattribute(name) | 要素の属性のnameの値 |
text | 要素の内部テキスト。例えば<span>hello</span>なら’hello’ |
clear() | inputやtextarea要素のテキストを消去する |
is_displayed() | 要素が可視ならTrue、そうでなければFalseを返す |
is_enabled() | input要素が有効ならTrue、無効ならFalseを返す |
is_selected() | chexcboxやradiobutton要素がチェックされていればTrue、されていなければFalseを返す |
location | ‘x’と’y’のキーを持つ辞書で、要素のページ上の座標を表す |
from selenium import webdriver
browser = webdriver.Firefox()
browser.get('http://inventtwithpython.com')
try
elem = browser.find_element_by_class_name('bookcover')
print('そのクラス名を持つ要素<{}>を見つけた'.format(elem.tag_name))
except:
print('そのクラス名を持つ要素は見つからなかった')
せんぱい「上記の例は、まず、Firefoxを起ち上げて、URLを指定しているよ。
browser = webdriver.Firefox()
browser.get('http://inventtwithpython.com')
せんぱい「そして、ページ上でclass名が’bookcover’である要素を探し、見つかればtag_name属性を使用しタグ名を表示するし、
要素が見つからない場合は別のメッセージを表示するってわけ」
ドキ!ページをクリックをシミュレート!
せんぱい「find_element_やfind_elements_メソッドが返すWebElementオブジェクトにはclick()というメソッドがあり、その要素上でマウスをクリックすることをシミュレートすることができるよ」
ガゼルさん「マウスのクリックまでも…!?もはやぼくがやることはない」
せんぱい「あるよ!このメソッドはリンクをたどったり、ラジオボタンを選択したり、[送信]ボタンをクリックしたりするなど要素をマウスでクリックしたときに発生することなら何でも引き起こすことができる」
ガゼルさん「ほんと、こりゃフォームの操作を何でも行うことができそうで便利そうですね!」
せんぱい「では、またまたちょっとコードを書いてみよう~」
from selenium import webdriver
browser = webdriver.Firefox()
browser.get('http://inventwithpython.com')
link_elem = browser.find_element_by_link_text('Read It Online')
type(link_elem)
#出力結果は <class 'selenium.webdiver.remote.webelement.WebElement'>
link_elem.click() # "Read It Online"リンクをたどる
せんぱい「上の例だと、Firefoxで http://inventwithpython.com を開き、内部テキストが「’Read It Online’」である<a>要素に対応にするWebElementを取得し、<a>要素、つまりリンクのクリックをシミュレートしている」
ガゼルさん「んん!?どういうことですか!?」
せんぱい「‘Read It Online’ こんなやつをクリックしたってことだね!」
ドキ!seleniumでフォームを記入し送信する
せんぱい「そしてとうとう…フォームを入力して送信するときがきたよ」
ガゼルさん「とうとうこのときが…!」
せんぱい「Webページ上のテキスト欄にキー入力を送り付けるにはテキスト欄に対応する< input >や< textarea >要素を見つけ、send_keys()メソッドを呼び出すんだ」
from selenium import webdriver
browser = webdriver.Firefox()
#Yahooメールにログインする場合の例だよ!
browser.get('https://mail.yahoo.com')
email_elem = browser.find_element_by_id('login-username')
email_elem.send_keys('not_my_real_email')# ここに本当のユーザー名を入力する
password_elem = browser.find_element_by_id('login-password')
password_elem.send_keys('12345')#ここに本当のパスワードを入力する
password_elem.submit()
せんぱい「はい、上記は一度作成するとYahoo!メールがユーザー名とパスワードidを変更しない限り、上記コードはそれぞれの入力欄に指定したテキストを記入してくれるよ!」
ガゼルさん「サイトの構造が変わってしまうと、またコードの修正が必要になってしまうということですね~」
せんぱい「そう!だから自身でフォームをチェックを書いて、またそれが修正となってしまった場合はこのコードも修正が必要になる!」
ガゼルさん「」
せんぱい「どの要素でもsubmit()を呼び出せばそれが属するフォームの[送信]ボタンを押したのと同じ結果になる。
上記の場合、elem.submit()としても同じ」
特殊なキーを送信する。
せんぱい「Seleniumには文字列の値として記述できない特殊キーのためのモジュールもある」
せんぱい「特殊キーはselenmium.webdriver.common.keysモジュールの属性として記述されている」
せんぱい「モジュール名が長いため、プログラム冒頭でfrom selenium.webdriver.common.keys.keysでなく、keysと書けば済むようになる」
selenmium.webdriver.common.keysモジュールでよく使われる値
属性 | 意味 |
---|---|
Keys.DOWN、Keys.UP、Keys.LEFT、Keys.RIGHT | 矢キー |
Keys.ENTER、Keys.RETURN | Enter、Returnキー |
Keys.HOME、Keys.END、Keys.PAGE_DOEN、KEYS.PAGE_UP | Home、End、PageDown、PageUpキー |
せんぱい「例えば、カーソルがテキスト欄にないとき、HomeとEndキーを押せばそれぞれページの先頭と末尾にスクロールする。
下記を入力するとsend_keys()の呼び出しによりページをスクロールする」
from selenium import webdriver
from selenium.webdriver.common.keys import keys
browser = webdriver.Firefox()
browser.get('http://nostrach.com')
html_elem = browser.find_element_by_tag_name('html')
html_elem.send_keys(Keys.END) #末尾にスクロール
html_elem.send_keys(Keys.HOME) #先頭にスクロール
タグはHTMLファイルの基底となるタグ。
すなわち、HTMLファイルの中身全体はで囲まれている。
browser.find_element_by_tag_name(‘html’)を呼び出すと要素が得られ、一般のWebページではキーを送り付けるのに適切な対象となる。
ページの末尾までスクロールすると新しいコンテンツがロードされるような場合に、上記のような操作は有用。
Seleniumは次のようなメソッドを呼び出しブラウザのさまざまなボタンのクリックをシミュレートする。
- browser.back()
戻るボタンをクリックする -
browser.forward()
[進む]ボタンをクリックする -
browser.refresh()
[再読み込み]ボタンをクリックする -
browser.quit()
[ウィンドウを閉じる]ボタンをクリックする
まとめ
せんぱい「プログラムを使ってWebページをダウンロードできれば、プログラムが処理できる対象はインターネットにも広げることができるでしょ?」
ガゼルさん「ネットの海は…広大だわ…」
せんぱい「Ghost in the Shell!!
requestsモジュールはダウンロードするだけだけど、
とセレクタの基礎知識があればBeautifulSoupモジュールをつかってダウンロードしたページも解析できるってことがわかったでしょ?」
ガゼルさん「うまく使いこなせば、欲しいデータを一瞬で集めることができそうですね!」
せんぱい「そう!それがまさに醍醐味だよね。Google先生も鬼のように集めて今の検索制度を作成しているんだからね…」
ガゼルさん「千里の道も一歩からというやつですな!」
せんぱい「ふふふ…そうかもしれないね
Webベースの作業を完全に自動化するなら、seleniumモジュールを使用し、Webブラウザを直接制御する必要があるよ
seleniumモジュールを使用すると、自動的にWebサイトにログインしフォームを記入したりもできるんだ」
ガゼルさん「鬼のような登録テストのときとかに、力を発揮してくれそうですね!項目を一つづつ確認するときとか!」
せんぱい「Webブラウザはインターネットと情報をやりとりするのに、最も一般的な手段だよね。
プログラマーの道具として使えると、できることがガゼルさんにも分かってもらえたんじゃないかな?」
ガゼルさん「それはあんまり…」
せんぱい「なんでよ!!
とまあ、ここまでスクレイピングでWeb上からデータを集めるあれこれみてきたよ。
他にもではhttps://amzn.to/2vBWy8iにはPythonで業務を効率化できるあれこれがのってるからよかったら見てみてね~」