F@N Ad-Tech Blog

株式会社ファンコミュニケーションズ nend・nex8・viidleのエンジニア・技術ブログ

SeleniumのWebDriverがアドレス渡しである話

こんにちは。ファンコミュニケーションズの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になってしまっていたんですね。

結論と修正

結論

  1. SeleniumのWebDriverはアドレス渡しなので、扱いには気をつけましょう。
  2. 親フレームへ戻る時は、switch_to.parent_frameを使いましょう。
  3. 加えて、phantomJSでは、switch_to.parent_frameが使えないので、switch_to.parent_frameの使えるWebDriverを使いましょう。
  4. どうしても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)

ちゃんと回ってますね!

最後に

弊社では随時インターン生を募集しています。詳細は下のページを見てください!

www.wantedly.com