プログラミング素人のはてなブログ

技術屋の末端。プログラミングも電気回路も専門外です。 コードに間違いなど見つけられたら、気軽にコメントください。 VC#、python3、ラズパイ始めました。

pythonとseleniumでheadless browserを操作する

wed上の情報を集めるにはスクレイピングという方法がありますが、JavaScriptでリンクが形成されていたりするとソースを解析しただけでは、リンク先のアドレスが分からないので目的の情報にたどり着くことはできません。

そこで使うのがseleniumになります。
seleniumではソースを取得することももちろんできますが、JavaScriptで形成されたリンクを動かすことができます。
これができることでpythonでプログラミングすることで、ブラウザを自動操作できます。

ブラウザーインスタンスを作成

前回のseleniumの記事ではPhantomJSをを使用しましたが、開発も停止しているということで、開発者も他の物をつかってくれと言っているらしいので、今後はfirefoxを使おうと思います(いまのところ)。

s51517765.hatenadiary.jp
s51517765.hatenadiary.jp

PhantomJSはヘッドレスブラウザですが、firefoxは画面あり・なしの両方が使えるので、Debugは画面ありで行い、運用は画面なしでおこなうということも切り替えることができます。

firefoxwindowsでのヘッドレスの使い方は↓を参考にしました。
Python+seleniumでwebdriverとしてheadlessなFirefoxを使う方法 | Sakaki333.com


引数で、ブラウザの種類と画面ありなしを指定します。
以下の関数でブラウザの切り替えをします。
firefoxにのみ最適化されていますので、chromeとPhantomJSは途中でErrorが出ることもあります。

from selenium.webdriver.support.ui import WebDriverWait
from selenium import webdriver
from selenium.webdriver.firefox.options import Options

def browserOpen(driver):
    if driver== "h":
        options = Options()
        options.set_headless(Options.headless)
        geckodriver_path = "C:/ProgramData/Anaconda3/geckodriver.exe" #各自の環境での実行ファイルのパスを設定
        driver = webdriver.Firefox(executable_path=geckodriver_path, options=options)
        print("Headless Firefox")
    elif driver =="f":
        driver = webdriver.Firefox()
        print("Firefox")
    elif driver== "c":
        print("Chrome")
        options = Options()
        driver=webdriver.Chrome()
    else:
        driver = webdriver.PhantomJS()
        print("PhantomJS")
    return driver

seleniumで要素を取得してデータを送る

Amaz〇nにログインするところを例に説明します
https://www.amaz〇n.co.jpのトップページから始めても構いませんが、プログラミングの工数を削減するため直アドレスで入れるところから始めます。
ログインしていない状態で、画面の下のほうに「サインイン」があるので、この先からスタートします。
f:id:s51517765:20180408163843p:plain

ここからスタート
f:id:s51517765:20180408164309p:plain

firefoxの画面上で右クリックすると、「要素を調査」というメニューを起動すると、HTMLが現れて、HTMLのタグをポイントすると、これに対応する画面上の要素がハイライトされます。
f:id:s51517765:20180408164743p:plain

これより、このインプットボックスの要素のタグがinput id ="ap_email"であることが分かり、id ="ap_email"であることが分かります。
HTMLの画面上のタグ上で右クリックで、「コピー¥CSSセレクタ」と選択するとクリップボードに取得できます。
同様のことはchromeでもできます。

ここにメールアドレスを入れたいのでseleniumsend_keyでデータを送ります。

email="aaa@gmail.com"
driver.find_element_by_id("ap_email").send_keys(email)

その次は、「次へ進む」をクリックします。
これも同様に要素を探し、クリックを送ります。

driver.find_element_by_id("continue").click()

これがseleniumの基本と言っていいと思います。
タグのidは名前のようなもので、タグを特定することができます。

idがみつけられれば確実ですが、結構idがないのかどこにあるのかわからないとき、idではどうしてもうまくいかないときはX-pathを使うとうまくいきます。

たとえば、検索ボックスの虫眼鏡マーク。

driver.find_element_by_id("twotabsearchtextbox").send_keys(keyword)
driver.find_element_by_xpath("/html/body/header/div/div[1]/div[3]/div/form/div[2]/div/input").click()

虫眼鏡はid="nav-search-submit-textのようなのですが、これでid指定しても ”is not clickable”というErrorが返ってきました。

ポップアップや子windowに対応する

検索結果画面から、例えば一つ目の結果をクリックしたときは、その後扱うべき画面は2つ目の画面(タブ)になります。
このとき、アクティブなタブを移動しなければなりません。
普段、人間向けのブラウザでは自動的に新しいタブがアクティブになったりしますが、ここでは明示的に遷移しなければなりません。
最初、ここで躓きました。
画面ありfirefoxでデバックしていると、画面は新しいタブに切り替わるのでidが見つからないのが気付きにくいです。
このようなときは、現在のアドレスを表示したり、タブの数をprint()したりするとわかりやすくなります。

検索画面から結果の1つ目をクリックし、新しいタブをアクティブにするには次のようになります。
ここで、新しいタブを立ち上げるには時間がかかることがあるのでsleepを入れるとうまくいくことがあります。

driver.find_element_by_id("result_0").click()
time.sleep(3)
driver.switch_to.window(driver.window_handles[1]) #2つ目のタブに移動、1つ目は[0]
print(len(driver.window_handles)) #今認識されているタブの数を表示 -->2 とでる

時間をおいてもダメなときはスクロールしたり、適当な位置にクリックすると上手くいくという話もある。

driver.execute_script("window.scrollTo(0, document.body.scrollHeight);") #スクロール

ウィンドウの数を見て増えるまで待つ

WebDriverWait(driver, 3).until(lambda d: len(d.window_handles) > 1)

qiita.com

メインプログラム

def main(driver, keyword):
    driver =browserOpen(driver)
    # ログイン情報 login.pyに保存してある情報を読み込む
    email=login.email 
    password=login.password
    twitter=login.twitter #twitter id
    tw_password=login.tw_password #twitter pass

    # ログイン
    url = "長いので省略"
    driver.get(url)
    driver.find_element_by_id("ap_email").send_keys(email)
    driver.find_element_by_id("continue").click()
    driver.find_element_by_id("ap_password").send_keys(password)
    driver.find_element_by_id("signInSubmit").click()
    time.sleep(3)

    driver.find_element_by_id("twotabsearchtextbox").send_keys(keyword)
    driver.find_element_by_xpath("/html/body/header/div/div[1]/div[3]/div/form/div[2]/div/input").click()

    # 検索結果の1つ目
    driver.find_element_by_id("result_0").click()
    time.sleep(3)
    driver.switch_to.window(driver.window_handles[1])
    # 検索結果の2つ目
    # id="result_1"

    #Twiiter shere
    driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
    time.sleep(15)
    driver.find_element_by_xpath('//*[@id="amzn-ss-twitter-share"]/div/a/i').click()
    time.sleep(3)

    #ポップアップが現れるまで待ち、アクティブ画面を遷移する
    WebDriverWait(driver, 3).until(lambda d: len(d.window_handles) > 2)
    driver.switch_to.window(driver.window_handles[2])

    # テキストを取得
    text=driver.find_element_by_id("status").text
    print("Original = "+text)

    driver.find_element_by_xpath('//*[@id="username_or_email"]').send_keys(twitter)
    driver.find_element_by_id("password").send_keys(tw_password)
    screen_shot(driver, 9)
    driver.find_element_by_xpath('/html/body/div[2]/form/div[3]/fieldset[2]/input').click()
    #ログインしてツイート

    text=text.replace("@さんから","")
    text = text.replace("Amazon.co.jp", "")
    text=text +"  //python + seleniumによる自動投稿テスト"
    time.sleep(3)
    driver.find_element_by_id("status").clear()
    print("Shaping = "+text)
    driver.find_element_by_id("status").send_keys(text)
    driver.find_element_by_xpath('/ html / body / div[2] / form / div[3] / fieldset / input').click()

    print("Sucess get Twitter link!")

参考

qiita.com
qiita.com
qiita.com
qiita.com

注意

Amaz○nなど大手のsiteはこのような機械アクセスが禁止されています。最悪アカウント停止などされる危険性もあるので、注意。このエントリーはあくまでもヘッドレスブラウザーのプログラミングの例題として言及しただけです。。