こんにちは。ファンコミュニケーションズのr_nakayamaです。
急ですが、最近、SeleniumのWebDriverがアドレス渡しであることがわかりました。
【目次】
SeleniumのWebDriverはアドレス渡しと気づく
間違っていたコード
SeleniumのWebDriverがアドレス渡しだと気づくきっかけになった、
最初に書いていて間違っていたコードがこちら(ドライバーはphantomJS)。
phantomJSを使っていた理由は、displayを設置しなくて済むからなどです。
from selenium import webdriver from pyquery import PyQuery as pq UA = 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_3_2 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Chrome/18.0.1025.166 Mobile/13F69 Safari/601.1' cap = webdriver.DesiredCapabilities.PHANTOMJS cap["phantomjs.page.settings.userAgent"] = UA #ユーザーエージェント設定 driver = webdriver.PhantomJS(desired_capabilities=cap, service_args=['--ignore-ssl-errors=true', '--ssl-protocol=any']) url = '***' driver.delete_all_cookies() driver.get(url) def get_iframes(driver, page, layer): """ driver : ドライバー page : フレームのelement layer : どこの層(フレーム)にいるかを表示するための変数 """ ret = [] dom = pq(driver.page_source) cnt = len(dom('iframe')) for idx in range(cnt): try: layer_ = layer+ '{}({})'.format(idx+1,cnt) page_iframe = driver.find_elements_by_tag_name('iframe')[idx] print(layer_) driver.switch_to_frame(page_iframe) ret += get_iframes(driver, page_iframe, layer_) driver.switch_to_frame(page) except: continue return ret page = driver.switch_to_default_content() get_iframes(driver, page, layer = '') driver.quit()
子フレーム操作後に戻ってきて、親フレーム位置にドライバーを戻す、
という方法でフレームの移動を操作していました。
switch_to.parent_frame を使わない理由
そもそも、switch_to.parent_frameがあるのに、なぜ使わなかったのか。
それは、phantomJSでは使えないからです!!!
github.com
driverが例外なく値渡しだと思っていた我がチームは
上のコードを組んだわけですが、結果はこちら
結果
※ 結果の見方
※ A = 何番目のフレームか(そのフレームにある全フレームの数)
※ AAA...
[Out] 1(5) 1(5)1(2) 1(5)1(2)1(1) 1(5)1(2)1(1)1(2) 2(5) 2(5)1(2) 2(5)1(2)1(1) 2(5)1(2)1(1)1(2) 3(5) 3(5)1(2) 3(5)1(2)1(2) 4(5) 4(5)1(2) 5(5) 5(5)1(4)
親フレームだけは回れてますが、子フレームは全部1個目しか潜れていない!
ここでdriverがアドレス渡しかもしれないということに気づきました。
つまり、page_iframe = driver.find_elements_by_tag_name('iframe')[idx]
を行った時点で、page = page_iframeになってしまっていたんですね。
結論と修正
結論
- SeleniumのWebDriverはアドレス渡しなので、扱いには気をつけましょう。
- 親フレームへ戻る時は、switch_to.parent_frameを使いましょう。
- 加えて、phantomJSでは、switch_to.parent_frameが使えないので、switch_to.parent_frameの使えるWebDriverを使いましょう。
- どうしてもphantomJSを使いたい場合は、それを踏まえたコードを書きましょう。
修正したコード
phantomJSをやめ(Chromeにドライバーを変更)、
switch_to.parent_frameで書き直したコードが下。
from selenium import webdriver from selenium.webdriver.chrome.options import Options from pyvirtualdisplay import Display from pyquery import PyQuery as pq display = Display(visible=0) display.start() UA = 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_3_2 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Chrome/18.0.1025.166 Mobile/13F69 Safari/601.1' mobile_emulation = { "userAgent": UA } chrome_options = Options() chrome_options.add_experimental_option("mobileEmulation", mobile_emulation) driver = webdriver.Chrome(chrome_options = chrome_options) driver.set_window_size(400,10000) url = '***' driver.delete_all_cookies() driver.get(url) def get_iframes(driver, layer): ret = [] dom = pq(driver.page_source, parser='html') cnt = len(dom('iframe')) for idx in range(cnt): try: layer_ = layer+ '{}({})'.format(idx+1,cnt) page_iframe = driver.find_elements_by_tag_name('iframe')[idx] print(layer_) driver.switch_to_frame(page_iframe) ret += get_iframes(driver, layer_) driver.switch_to.parent_frame() except: continue return ret driver.switch_to_default_content() get_iframes(driver, layer = '') driver.quit() display.stop()
結果
※ クロールしたサイトは間違っていたコードと同じ時のものです。
※ iframeは動的に展開されるので、前の結果とiframeの数など違うところがあります。
[Out] 1(5) 1(5)1(1) 1(5)1(1)1(2) 1(5)1(1)2(2) 2(5) 2(5)1(1) 2(5)1(1)1(2) 2(5)1(1)2(2) 3(5) 3(5)1(1) 3(5)1(1)1(3) 3(5)1(1)2(3) 3(5)1(1)3(3) 4(5) 4(5)1(1) 4(5)1(1)1(1) 5(5) 5(5)1(3) 5(5)2(3) 5(5)3(3)
ちゃんと回ってますね!