SeleniumでNCD検索システムから全経験手術症例を1つのCSVファイルに抽出する
はじめに
とある事情により、自分の経験症例数を算出する必要がありました。一般外科医であれば、自分の登録症例はすべてNCDに登録されていると思います。自分の経験症例を検索するにはNCD検索システムにアクセスする必要があります。
手術症例数の件数表示は、施設別、術式別、術者別に一覧表示できるのですが、ここで大きな問題が。
- 施設・術者・術式の複数を組み合わせたフィルタリングができない
- 手術は20件ずつ表示されて、それ以上の設定はできない
というクソ仕様になっていました。NCDには術者助手に関わらず、自分が入った手術がカウントされていると思いますが、自分が術者として執刀したそれぞれの手術のカウントをするには、20件ずつクリックしてカウント、もしくはExcelにコピペしていなきゃいけないということになります。1500件近くあったので、この作業を75回も繰り返すのは嫌だ!
たまらずNCDの事務局にCSVなどで自分の経験症例を一括ダウンロードできないものかと問い合わせましたが、秒でだめですと返信が返ってきました。というわけで、せっかく勉強したのでPythonとスクレイピングしてやりましょう。
CSVでリストが手に入れば後は、Rでいかようにも料理できますので。
ChromeでNCD検索システムを立ち上げる
import time from selenium import webdriver import chromedriver_binary # PATHを通す driver = webdriver.Chrome() driver.get('https://user.ncd.or.jp/member/memberLogin.html') # ページを立ち上げる time.sleep(3) # 読み込み時間を考慮して5秒待つ
ログインIDとパスワードを入力する
ChromeでDevelopper Tools
を眺めてもコードがよくわからないのです。これがJavaScriptってやつでしょうか?素人にはよくわかりません。
driver.page_source
を実行すると、大量の文字列が取得でき、各要素がどんな風に表現されているかわかります。すると
# ログインID <input type="text" name="user" style="width:120px;" maxlength="10"> # パスワード <input type="password" name="password" style="width:116px;" maxlength="50">
となっていることがわかります。name="user"
とname="password"
でそれぞれの入力フォームが規定されているので、find_element_by_name()
で指定してあげます。send_keys
というのがフォームに文字列を入力する方法みたいですね。
user_box = driver.find_element_by_name("user") # "user"という名前でフォームを認識する user_box.send_keys('自分のログインID') pass_box = driver.find_element_by_name("password") pass_box.send_keys('自分のパスワード')
driver.find_element_by_xpath('button')
ログインボタンをクリックする
# ログインボタン <input type="button" value="ログイン" onclick="return goLogin();">
ログインボタンは上記のように規定されているのですが、value="ログイン"
で探してあげる方法を探すのに難渋しましたが、この記事が参考になりました。
driver.find_element_by_xpath("//input[@value='ログイン']").click()
これを実行すると次の画面にジャンプすると思います
手術症例一覧をクリックする
この画面に来たら次は、手術症例一覧
をクリックするようにコードを書きます。
driver.page_source
でじっくり探すと、手術症例一覧のボタンは下記であることがわかります。
<input type="button" onclick="return goShujyuShoureiIchiran();" value="手術症例\u3000一覧">
なので、このように入力すれば次のページに遷延します。
driver.find_element_by_xpath("//input[@value='手術症例\u3000一覧']").click()
Table情報を取得する
さてようやくスクレイピングしたい手術リストのページに到達することができました。driver.page_source
でソースコードを探すと、
<table border="0" cellspacing="0" cellpadding="3" width="96%">
というのが手術症例の表のtableなのですが、valueもnameもついていないので、このままでは指定できません。このサイトによると
id、class、nameがちゃんと設置されてないページでも、CSSセレクタやXPathをゴリゴリ書けば基本的になんでも要素を取得できます。CSSセレクタやXPathはディベロッパーツールで指定の要素を右クリックすればまるごとコピーできます。
なので、ChromeでView
からDevelopper Tools
を開き、右クリックからCopy XPath
をすると、メインのテーブルは
/html/body/form/div[3]
に存在することがわかります。なので
table = driver.find_element_by_xpath("/html/body/form/div[3]")
で表を丸ごと抽出してtable
に格納してあげます。これを各列ごとの要素に分解するには
from selenium.webdriver.common.by import By rows = table.find_elements(By.TAG_NAME, "tr")
とすると表のデータが列ごとのリストとしてrows
に格納されます。どの行にどの情報が入っているのか1つ1つ試していくと、
print(rows[9].text) # 列名 print(rows[10].text) # 手術名最初 print(rows[29].text) # 手術名最後
となっていることがわかります。11列目から30列目が必要な情報 = 手術のリストになっていることがわかります。これをそれぞれ分解してデータフレームに格納していきましょう。
手術リストをデータフレームに格納する
正直、ここが一番時間を喰った箇所でした。Rでは一瞬でできる操作がPythonだとすごく面倒。R万歳!最終的にはこちらの記事を参考にさせていただきました。
1行抜き出し、各項目を列の値として格納してdf
に格納するという作業です。
# 結果を入れる配列を用意 facility = [] ncd = [] date = [] method = [] operator = [] status = [] df = [] # 1行の各項目を抽出してデータフレームにする for i in range(10,30): cols = rows[i].find_elements(By.TAG_NAME, "td") facility += [cols[0].text] ncd += [cols[1].text] date += [cols[2].text] method += [cols[3].text] operator += [cols[4].text] status += [cols[5].text] df = pd.DataFrame(data={'Facility': facility, 'NCD': ncd, 'Date': date, 'Method': method, 'Operator': operator, 'Status': status}, columns=['Facility', 'NCD', 'Date', 'Method', 'Status'])
len(df)
で確認しておくと、20と返ってくるので、1ページ内の手術をすべて抽出できていることになります。
次ページをクリックする
次ページをクリックすることで次の20件のリストのページに遷延することができます。driver.page_source
をつぶさに見ていくと、
<a href="javascript:void(0);" onclick="return pageJump(2);">次ページ</a>
が次ページのリンクとなっています。リンクを文字列で認識してクリックするには、
driver.find_element_by_link_text(u"次ページ").click()
と書けばいいようです。このu
って何だ?って話しですが気にしない。これをページ数分繰り返せばいいわけですね。
for loopで回す
これをページ数分繰り返せばいいわけです。表の下の方にページ数が書いてあり、これを取得する方法もないわけじゃないのですが、面倒だったので、手入力してしまいました。
facility = [] ncd = [] date = [] method = [] operator = [] status = [] df = [] for k in range('手動でページ数を入力する'): for i in range(10,30): table = driver.find_element_by_xpath("/html/body/form/div[3]") rows = table.find_elements(By.TAG_NAME, "tr") cols = rows[i].find_elements(By.TAG_NAME, "td") facility += [cols[0].text] ncd += [cols[1].text] date += [cols[2].text] method += [cols[3].text] operator += [cols[4].text] status += [cols[5].text] df = pd.DataFrame(data={'Facility': facility, 'NCD': ncd, 'Date': date, 'Method': method, 'Operator': operator, 'Status': status}, columns=['Facility', 'NCD', 'Date', 'Method', 'Operator', 'Status']) # 次ページをクリック driver.find_element_by_link_text(u"次ページ").click() time.sleep(2)
これを実行するとdf
の中に手術リストが格納されていると思います。
csvファイルにして出力
df.to_csv('~/Desktop/NCD.csv', encoding='utf_8_sig')
encoding
を指定しないと文字化けするので注意してください。これで自分の経験症例をすべてCSVファイルとして手に入ると思います。時間にして2秒×ページ数!今回、やってみてPythonは業務効率化にものすごいインパクトがあるんだなと思いました。実際の仕事にどう活かせるかはおいといて。