スクレイピングやWebサイト自動操作に使われるSelenium。 便利な関数がいろいろ揃っています。
しかし、Seleniumの操作には数多くの「コツ」が数存在します。
「コツ」を知らないと、「正しい」コードを書いているのに動きません。
うまくタイミングが合わないとか、なぜここでWebDriverExceptionが発生するのか分からないとかいうことになり、デバッグに大変時間を費やします。
なにせ、コードは「正しい」のですから。
通常のプログラムと違い、正攻法ではデバッグできません。 そこで、前編と後編に分けて、Seleniumの「コツ」を詳しく解説していきます。
あわせてスクレイピングの法的規制についても触れます。
要素の特定の仕方
要素を操作するには、まず要素を特定しなければなりません。
JavaScriptのDOM操作を知っている方であれば基本は同じなので関数の働きはすぐ分かるでしょうが、でも、タイミングを待つ方法、サイトのDOM構造が変わっても通用するコードの書き方など、Selenium独自の作法があります。
それらから3つピックアップして解説します。
wait.untilを使うとき
find_element(s)_by_XXXを使った方がいいのか、wait.untilを使った方がいいのか迷うときがあります。
ここでの選択のポイントは、「次に特定しようとしている要素がDOMに表れる/表示される/クリック可能になるのが、未来か否か」です。
例えば、親をチェックしたらVisibleになるような要素を特定する場合、親をチェックしてからサイトの処理が動き要素がVisibleになるまでは時間がかかります。
人間の目には同時でも、Seleniumはプログラムです。
サイトの処理よりSeleniumの処理が速かった場合、要素がVisibleにならずにその要素への操作は失敗します。このような場合には、必ずwait.untilを使います。
要素が既に現れていることが分かっている場合には、find_element(s)_by_XXXを使います。こちらの方が処理が幾分高速なようです。
ID、クラス、XPATH
IDで特定したらいいのか、クラスで特定したらいいのか、XPATHを使うべきか、迷うことがあります。
一般的には、IDが割り振られていたら迷うことなくIDを使います。
IDはHTML上でユニークで、かつ、変わることは(一般的には)ありません。 クラスで特定するのも、そのクラスがHTML上でユニークそうである場合は有効です。
XPATHは最後の手段と心得てください。
理由は、XPATHは一般的にDOMの構造に依存するため、サイトのDOMが変更されたら(それはしばしば起こることです)、XPATHが変わり、無効になるからです。
サイトのDOM構造が変わっても通用する、堅牢なシステムを開発するべきです。 このようにID、クラス、XPATHを使い分けます。
相対位置で要素を特定
find_element_by_XXXなどで特定した要素に、更にfind_element_by_XXXを続けると、最初の要素の配下の要素を特定します。DOM全体に対してではありません。
これを使い、どんどん範囲を狭め、要素を特定できることがあります。 例えば、
color = driver.find_element_by_class_name("product-sku").find_element_by_tag_name("div").find_element_by_tag_name("div").find_element_by_tag_name("div").find_element_by_tag_name("span")
ページによってDOMの構造が変わる複数のページをスクレイピングするために書いたコードです。
クラス product-sku はユニークだったので、そこから追いかけました。 このようなコードを書かなければならないこともあります。
要素を操作
「正しい」コードを書いているのに、要素がうまく操作できないことがあります。
ここでも、「コツ」があります。例えばタイミングが少しでもずれると要素への操作は失敗します。
Seleniumはプログラムなので、タイミングはぴったりと合わさなければなりません。 要素の操作の仕方から、4つピックアップして解説します。
visibility_of_element_locatedとelement_to_be_clickable
wait.untilで要素を待つとき、visibility_of_element_locatedとelement_to_be_clickableのどちらを使うかは、きちんとした決まりがあります。
その要素から属性値やinnerHTMLを取得する場合にはvisibility_of_element_locatedを使います。 ところが、visibility_of_element_locatedで取得出来ても、その瞬間にはクリックは出来ません。
クリックしたい場合はelement_to_be_clickableを使います。
element_to_be_clickableを使わないと、クリックは失敗します。
リンクをクリック
クリックできるのは、インスペクターでeventが設定されていることが分かっている要素のみです。
aタグでのリンクにはeventは設定されていません。
だから、aタグをそのままクリックしても、遷移しません。
aタグをクリックするには、find_element_by_link_textでリンクのテキスト文を指定し、取得出来た要素をクリックします。
send_keysとperform
テキストフィールドに文字を書き込むとき、send_keysを使います。
ところが、うまく送れないときや、またクリックしたいけどその要素の特定が難しいときがあります。
例えばモーダルダイアログを閉じたいとき、performを使ってEnterキーを画面にそのまま送ると処理が簡潔になる場合があります。 例えば、
#ポップアップを閉じる wait.until(expected_conditions.presence_of_element_located((By.ID, "popup-notice-area"))) webdriver.ActionChains(driver).send_keys(Keys.ENTER).perform()
このようにperformを使います。
このコードは、ポップアップが出現するのを待ってはいますが、何か特定の要素にEnterを送っている訳ではありません。
画面全体にEnterを送ることにより、ポップアップを閉じています。
Select
コンボボックスの中から特定の要素を選択するときはSelectを使います。
ただ、これはSelectオブジェクトを作らなければならないので注意してください。 サンプルコードは以下です。
element = driver.find_element_by_xpath("/html/body/div[1]/div/section[2]/form/div[1]/section[2]/div[7]/div[2]/select") select = Select(element) select.select_by_index(5)
このコードでは、6番目のインデックスを選択しています。インデックスは0から始まりますので、ここも注意してください。
ウィンドゥの切り替え
捜査対象のウィンドゥを切り替えるには、以下のコードを書きます。
driver.switch_to.window(driver.window_handles[1])
switch_to_window ではなく switch_to.window というところに注意してください。 window_handlesは0からインデックスが始まります。
マウスオーバー
よく、何かにマウスをオーバーさせると大きな画像が切り替わるサイトがあります。
その場合、何かをクリックしても大きな画像は切り替わりません。
クリックとマウスオーバーは全く別のイベントだからです。
このようなサイトをスクレイピングする際、どうすればいいのでしょうか。
答えは簡単です。実際にマウスをオーバーさせれば良いのです。
下記のようなコードになります。
actions = ActionChains(driver) actions.move_to_element(element).perform()
このコードはマウスをオーバーさせますが、ヘッドレスでないブラウザを使っていても、実際のマウスは動きません。
Seleniumの内部的にオーバーさせるということです。
JavaScriptを使うとき
Seleniumでは捜査対象のページでJavaScriptを実行させることができます。 execute_script です。
どうしてもJavaScriptを実行させないと、処理が実行できないことがあります。 その例を3つ紹介します。
その前に、JavaScriptを実行させる際のデバッグの注意点として、重大なポイントがあります。
それは、「全てのJavaScriptのエラーがWebDriverExeptionで返ってくる」です。
書いたJavaScriptのコードのどこにバグがあるのか、エラーコードから判別できないのです。
正直、ここの作りはどうにかならないものかと思いますが、言語仕様上仕方ないのかもしれません。
だから、JavaScriptのコードは細心の注意をもって書いてください。
スクロール
画面のスクロールはJavaScriptを実行させないとできません。
次のコードは、画面の上部にある要素の縦方向のサイズを取得し、その合計分だけページを縦にスクロールさせるコードです。
e1 = driver.find_element_by_id("topAd") e2 = driver.find_element_by_id("top-lighthouse") e3 = driver.find_element_by_id("header") e4 = driver.find_element_by_class_name("store-header") e5 = driver.find_element_by_class_name("product-main") offset = e1.rect["height"] + e2.rect["height"] + e3.rect["height"] + e4.rect["height"] + e5.rect["height"] + 200 driver.execute_script("window.scrollTo(0, window.pageYOffset + " + str(offset) + ");")
一点注意点として、offsetを渡すとき、JavaScriptのコードとして渡すので、Pythonのコードとしてはstr型に変換して連結してください。
ここを書き間違えるとWebDriverExceptionになり、極めてデバッグしにくいことになってしまいます。先述した通り、どこが間違っているのかエラーコードから判別できません。
「画面の最下部までスクロールする」は次のコードです。
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
クリックでチェックできない
稀に、クリックしてもチェックできないチェックボックスがあります。
そんなときはJavaScriptの出番です。
driver.execute_script('document.getElementById("shipping_other_check1").click();')
でチェックできます。
‘と”
JavaScriptの中に”や’が入っている場合、それはどちらかに統一しましょう。
そして、Pythonの方で、使っていない方でJavaScriptコードを囲うのです。
これは極めて分かりにくいバグになりますので注意してください。
スクレイピングの規制について
スクレイピングは、対象となるWebサイトの動作に支障を来さない範囲で行う必要があります。
高頻度のアクセスでサイトが落ちるなどの被害を与えると、罪に問われる可能性があります。
日本でのスクレイピングの事件として有名なのは、岡崎市立中央図書館事件です。
2010年3月、岡崎市立中央図書館の蔵書検索システムにシステム障害が発生しました。
これは、ある人がスクレイピングをかけていて高負荷をかけたことが原因でした。
その人は偽計業務妨害罪で逮捕されましたが、起訴猶予で釈放されました。
そのスクレイピングシステムのアクセス頻度は1秒に1回程度でした。
しかし、この程度でも、逮捕される可能性はあるのです。
それから10年が経過し、現在のWebシステムはより堅牢になっていますが、Webシステムが同種の被害を受ける可能性は原理的にはあります。
何秒に1回のアクセスなら大丈夫とは確かな判例はないので言えないですが、常識的な範囲のアクセスに留めるようにしましょう。
逮捕とまではいかなくても、捜査対象のサイトからあなたのIPアドレスがアクセス禁止にされたりします。
そうなると、システム開発できません。
スクレイピングシステムを開発する人は、この法的規制に十分留意するようにしてください。
まとめ
前編後編に分けて、Seleniumの実践編を解説してきました。かなり具体的な事柄を解説しました。
スクレイピングの法的規制についても触れました。
スクレイピングはうまく扱えば強力な道具です。有用なスクレイピングシステムを開発し、クライアントの便宜を計りましょう。
またその際、自分でもWebシステムを開発したことがあると、よりスクレイピングしやすいかもしれません。作ったことがある人でないと構造は分からないものです。
それでは、Seleniumの実践に入ってみてください。