SAISONカード（Netアンサー）サイトにPythonとSeleniumを使ってログインする

SAISONカード（Netアンサー）のサイトにPythonとSelenium使ってログインする方法を解説します。

※2021/02/26時点の情報です

## 環境
- Windows 10
- Python 3.8
- Selenium 3.141.0

## 全体の処理の流れと注意点
全体の処理の流れについては、下記記事に詳しく書いたのでこちらもあわせて確認してみてください。楽天銀行の例になっていますが、処理の流れは同じです。

https://qiita.com/yoshi2045/items/2fff5bf5f0fa763f7104

また、下記記事では他サイトのサンプル一覧と注意点を解説しているのでこちらも参考にしてみてください。

https://qiita.com/yoshi2045/items/a58a7fbebcb9faad3e15

## SAISONカードサイトでのポイント
全体の処理の流れについては上記記事を参考していただくとして、ここではSAISONカードサイトにおけるポイントを中心に紹介します。

### パスワードパズル問題
SAISONカードサイトの最大の難関は、ログイン時にパズルを完成させる必要がある点です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/459893/43c618ee-432d-6d98-aa7a-985bbd42cdd8.png)

上記画面キャプチャのように、SAISONカードのログイン画面には手動で鍵の絵を動かしパズルを完成させるという操作を行う必要があります。
しかし、これはどう頑張ってもSeleniumでは対処できません。

そこで、まずはこのパズル認証の挙動を改めて確認してみましょう。

### パズル認証結果は保存できる

よく見るとパズル認証の下に、結果を保存するためのチェックボタンがあることがわかります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/459893/e2fd0844-9564-6a2b-e96b-0273baf7f094.png)

初回ログイン時、「パズル認証結果を保存する」にチェックを入れてログインすれば、次回以降パズル認証は不要になります。

試しに初回ログイン時にパズル認証結果を保存し、一度ログアウトしてから再度ログイン画面にアクセスしてみると、次のような画面になります。

![20210226-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/459893/0582d2ec-b31c-5a30-e54c-b360c92de14c.png)

ログインIDが初期値として入力エリアに設定されて（かつ非活性モードで編集負荷状態）、パズル認証欄が非表示になりました。

この状態からであればパスワードのみ入力すればログインできるので、これならSeleniumでも再現できそうです。

### パズル認証結果はCookieに保存されている

では、先程の画面のような状態はどうやって実現しているのでしょうか？

この手の仕組みは一般的に、Cookieを使って実現させています。

Cookieの中身を参照するには、「Cookie Manager」などのアドオン機能を使うと便利です。

Chrome：[CookieManager](https://chrome.google.com/webstore/detail/cookiemanager-cookie-edit/hdhngoamekjhmnpenphenpaiindoinpo?hl=en)
Firefox：[Cookie Quick Manager](https://addons.mozilla.org/ja/firefox/addon/cookie-quick-manager/)

次の画像はFirefoxのCookieQuickManagerで確認した例です。

![noname.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/459893/d5f45a08-969a-f1b2-b5f6-fb4964301b12.png)

SAISONカードのログイン画面のドメインは
api.saisoncard.co.jp
です。
Cookieには多くのサイトの情報が含まれているので、対象ドメインで検索すると探しやすいです。

検索してみた結果、
api.saisoncard.co.jp
のドメイン配下に、以下のようなクッキーが保存されているのを確認できました。

- onetime_PUZZLE_MWkj63o1fRQpwgxEZNCgjsS49dxnH4
- auto_complete
- login_id

※他にもいくつかありましたが、パズル認証・ログインに関わるものはこの3つでした

### Cookie情報を追加する

パズル認証を保存した状態を再現させるには、先程調べたCookie情報をadd_cookie()メソッドで追加すれば可能です。

今回のサンプルでは、クッキーの設定情報は、ID/パスワードと同様に別ファイル（login_info.json）に設定しておくようにしました。

```json:login_info.jsonの抜粋
  "card_saison": {
    "id": "★★★",
    "pass": "★★★",
    "url": "https://api.saisoncard.co.jp/auth/screen/na/authorize?response_type=code&client_id=ZC001&state=&scope=openid",
    "onetime_name": "onetime_PUZZLE_MWkj63o1fRQpwgxEZNCgjsS49dxnH4",
    "onetime_value": "★★★",
    "onetime_path": "/auth/screen/na",
    "onetime_domain": "api.saisoncard.co.jp",
    "auto_comp_name": "auto_complete",
    "auto_comp_value": "\"★★★\"",
    "auto_comp_path": "/auth/screen/na",
    "auto_comp_domain": "api.saisoncard.co.jp",
    "login_id_name": "login_id",
    "login_id_value": "\"★★★\"",
    "login_id_path": "/auth/screen/na",
    "login_id_domain": "api.saisoncard.co.jp"
  }
```

add_cookie()のほうでは、次のように設定内容を読み込んでCookieを追加します。

注意点として、add_cookie()を呼び出す前に一度get()で対象画面を取得しておかないと例外が発生するようです。

```Python
# ログイン画面取得
# add_cookie()呼び出す前に一度get()で対象画面取得する必要あり
browser.get(url_login)

# クッキー情報を追加
browser.add_cookie(
    {"name": login_info[site_name]["onetime_name"],
     "value": login_info[site_name]["onetime_value"],
     "path": login_info[site_name]["onetime_path"],
     "domain": login_info[site_name]["onetime_domain"]
     }
)
browser.add_cookie(
    {"name": login_info[site_name]["auto_comp_name"],
     "value": login_info[site_name]["auto_comp_value"],
     "path": login_info[site_name]["auto_comp_path"],
     "domain": login_info[site_name]["auto_comp_domain"]
     }
)
browser.add_cookie(
    {"name": login_info[site_name]["login_id_name"],
     "value": login_info[site_name]["login_id_value"],
     "path": login_info[site_name]["login_id_path"],
     "domain": login_info[site_name]["login_id_domain"]
     }
)

# クッキー追加後に再度ログイン画面取得
browser.get(url_login)
```


以下、サンプルコードと設定ファイルの内容です。

## サンプルコード全文

```python
#
# セゾンカードサイトへログイン
#

import time
import json
from selenium.webdriver import Firefox, FirefoxOptions
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as ec

# 設定ファイルを取得
login_info = json.load(open("login_info.json", "r", encoding="utf-8"))

# ログインサイト名
site_name = "card_saison"

# ログイン画面URL
url_login = login_info[site_name]["url"]

# ユーザー名とパスワードの指定
USER = login_info[site_name]["id"]
PASS = login_info[site_name]["pass"]

# Firefoxのヘッドレスモードを有効にする
options = FirefoxOptions()
options.add_argument('--headless')

# Firefoxを起動する
browser = Firefox(options=options)

# ログイン画面取得
browser.get(url_login)

# クッキー情報を追加
browser.add_cookie(
    {"name": login_info[site_name]["onetime_name"],
     "value": login_info[site_name]["onetime_value"],
     "path": login_info[site_name]["onetime_path"],
     "domain": login_info[site_name]["onetime_domain"]
     }
)
browser.add_cookie(
    {"name": login_info[site_name]["auto_comp_name"],
     "value": login_info[site_name]["auto_comp_value"],
     "path": login_info[site_name]["auto_comp_path"],
     "domain": login_info[site_name]["auto_comp_domain"]
     }
)
browser.add_cookie(
    {"name": login_info[site_name]["login_id_name"],
     "value": login_info[site_name]["login_id_value"],
     "path": login_info[site_name]["login_id_path"],
     "domain": login_info[site_name]["login_id_domain"]
     }
)

# Cookie情報取得
# 追加したクッキーの情報を参照する場合は以下コード追加
# cookieList = browser.get_cookies()

# ログイン画面取得
browser.get(url_login)

# Cookie設定が反映されているか確認（画面キャプチャ取る）
browser.save_screenshot("../screenshots/card_saison/login.png")

# # 入力
e = browser.find_element_by_id("password")
e.clear()
e.send_keys(PASS)

# ログイン
button = browser.find_element_by_xpath("//input[@value='ログイン']")
button.click()

# ページロード完了まで待機
WebDriverWait(browser, 10).until(
    ec.presence_of_element_located((By.ID, "user_name"))
)

# WebDriverWait()でうまく待機できない場合がある
# その時は代替案としてスリープで強制待機させる
# time.sleep(3)

# ログインできたか確認（画面キャプチャ取る）
browser.save_screenshot("../screenshots/card_saison/home.png")

browser.quit()
```


## ログイン情報設定ファイル
サイトURLやログイン情報（IDやパスワード）は、JSON形式のファイルに保持するようにしています。以下のサンプルでは楽天銀行とSAISONカードの2サイトの情報と設定しています。

"★★★"の値を書き換えればそのまま使えるはずです。

なお、このファイルだけは絶対に漏洩しないよう、管理方法に注意しましょう！

```json:login_info.json
{
  "bank_rakuten": {
    "id": "1234567890",
    "pass": "password",
    "url": "https://fes.rakuten-bank.co.jp/MS/main/RbS?CurrentPageID=START&&COMMAND=LOGIN"
  },
  "card_saison": {
    "id": "★★★",
    "pass": "★★★",
    "url": "https://api.saisoncard.co.jp/auth/screen/na/authorize?response_type=code&client_id=ZC001&state=&scope=openid",
    "onetime_name": "onetime_PUZZLE_MWkj63o1fRQpwgxEZNCgjsS49dxnH4",
    "onetime_value": "★★★",
    "onetime_path": "/auth/screen/na",
    "onetime_domain": "api.saisoncard.co.jp",
    "auto_comp_name": "auto_complete",
    "auto_comp_value": "\"★★★\"",
    "auto_comp_path": "/auth/screen/na",
    "auto_comp_domain": "api.saisoncard.co.jp",
    "login_id_name": "login_id",
    "login_id_value": "\"★★★\"",
    "login_id_path": "/auth/screen/na",
    "login_id_domain": "api.saisoncard.co.jp"
  }
}
```