Python3 requestsを使ってログインする

#問題
Webサイトへのログイン/ログアウトを自動化したいが、
訳あってSeleniumが使えない。
[Robobrowser](https://robobrowser.readthedocs.io/en/latest/)や[mechanicalsoup](https://mechanicalsoup.readthedocs.io/en/stable/)だとログアウトのリンクが選択できない。
まあ、制約などがなければ普通はSeleniumを使うよね。
さて、困った。
その時の備忘録。

ググってみると、requestsモジュールを使用して
必要なフォームパラメータをpostしてやるとログインできるぜ！
っといった記事を多く目にした。
よし！参考になる！以前自作したWebアプリで実験。

##試した環境など
・Python 3.6.7
・CentOS7
・[requestsモジュールのインストール](http://jp.python-requests.org/en/latest/)

#ログイン
まずは対象のログインフォームのソースを確認。

```login.html
<form action="/login" accept-charset="UTF-8" method="post">
     <input name="utf8" type="hidden" value="✓">
     <input type="hidden" name="authenticity_token" value="SmPba8H6cRcKKH/hK5DAyyONn/LGWD7vPIMM4eJDQH3FIzu55qhYsJXOnub0xQaVguMH4O4qI5a0hAtyHSkZUg==">

      <div class="form-group">
        <label for="session_email">メールアドレス</label>
        <input class="form-control" type="email" name="session[email]" id="session_email">
      </div>

      <div class="form-group">
        <label for="session_password">パスワード</label>
        <input class="form-control" type="password" name="session[password]" id="session_password">
      </div>

      <input type="submit" name="commit" value="ログイン" class="btn btn-primary btn-block" data-disable-with="ログイン">
</form>
```



pythonのソースはこんな感じ。

```login.py
import requests
import time

session = requests.session()
session.get(url)

login_data = {
   'UTF-8': '✓',
   'session[email]': メールアドレス,
   'session[password]': パスワード,
}

login = session.post(login url, data=login_data)
time.sleep(2) # 少し時間を置いてみる
print(login.text)

```

ムム、ログイン出来ない。
ん？フォームにはauthenticity_tokenというのがある。
これも必要かな？
ちなみに[Chromeデべロッパーツール](https://www.tsukimi.net/chrome-developer-tools_post.html)でもpost時にフォームから何が送られているか確認できる。
ただ、authenticity_tokenはサイトアクセス毎にランダムで変わるね。
さて、どうやってこの隠れキャラを取得しようか？

##requests以外に入れたモジュール
・[BeautifulSoup4](https://www.crummy.com/software/BeautifulSoup/bs4/doc/)
  Webスクレイピングなんかでよく使われているらしい。

以下の様してみた。

```login.py
import requests
import time
from bs4 import BeautifulSoup

session = requests.session()
response = session.get(url)

# BeautifulSoupオブジェクト作成(token取得の為)
bs = BeautifulSoup(response.text, 'html.parser')

login_data = {
   'UTF-8': '✓',
   'session[email]': メールアドレス,
   'session[password]': パスワード,
}

# tokenの取得
authenticity_token = bs.find(attrs={'name':'authenticity_token'}).get('value')

# 取得したtokenをpostするパラメータに追加
login_data['authenticity_token'] = authenticity_token

login_data = session.post(login url, data=login_data)
time.sleep(2)
print(login.text)
```

あれ？ダメでした。。
Chromeデべロッパーツールを確認するとcookie内にセッションIDがあるみたい。
こいつのチェックに弾かれている訳ですな。
じゃあ、cookieを取得してpostしてみよう。

```login.py
import requests
import time
from bs4 import BeautifulSoup

session = requests.session()
response = session.get(url)

bs = BeautifulSoup(response.text, 'html.parser')

login_datadata = {
   'UTF-8': '✓',
   'session[email]': メールアドレス,
   'session[password]': パスワード,
}

authenticity_token = bs.find(attrs={'name':'authenticity_token'}).get('value')

login_data['authenticity_token'] = authenticity_token

# cookieの取得
response_cookie = response.cookies
print(response_cookie)
#<RequestsCookieJar[<Cookie _hogehoge_session=長いので省略

# post時にcookieを追加
login = session.post(login url, data=login_data, cookies=response_cookie)
time.sleep(2)
print(login.text)
```

おお！ログイン出来た。

# ログアウト

ログアウトのhtmlはどうなっているか確認。
うん、フォームですらない。

```logout.html
<a rel="nofollow" data-method="delete" href="/logout">ログアウト</a>
```
とりあえすhref="/logout"にフォーカスを当てたらログアウト時のURLがわかるので、
こいつにpostしてみる。
post時に送信されるデータをChromeデべロッパーツールで確認する。

```login.py

# ログアウト時に送られるデータ
logout_data = {
    '_method': 'delete',
    'authenticity_token': なにかのtoken 
}

```

またtokenかよ。
htmlを眺めているとheadタグ内に以下のtokenが埋め込まれてた。

```logout.html
<meta name="csrf-token" content="TTAEq+OCmvxSwoj98XrC08g8XtUMOLVc25xzojfu6SqKRCmNeERFgMKMJcD7pILtWvgxwhk2bueQo1ATouh/UA==">
```
これかな？
とりあえず手動でログアウトしてみてその際にauthenticity_tokenに入っている
tokenと照らし合わせてみると一致していた。
いや〜頼りになるなぁ〜Chromeデべロッパーツール。
なので、こいつをBeautifulSoupで取得してpostしてみる。

```login.py
# metaに埋め込まれているtokenを取得
csrf_token = bs.find(attrs={'name': 'csrf-token'}).get('content')

# ログアウト時に送られるデータ
logout_data = {
    '_method': 'delete',
    'authenticity_token': csrf_token # 取得したtokenをセット 
}

logout = session.post(logout url, data=logout_data)
time.sleep(2)
print(logout.text)
```
おお！ログアウト出来た！
cookieはいらない様です。

# 全ソース
```login.py
import requests
import time
from bs4 import BeautifulSoup

# セッションの作成
session = requests.session()
response = session.get(url)　# ログインしたいurlを引数に入れる

# token取得用にBeautifulSoupを作成
# ちなみにパーサは'html.parser'より'lxml'の方が高速らしいが別途installが必要
bs = BeautifulSoup(response.text, 'html.parser')

# ログイン時にpostするデータを定義
# ボタン要素は無くても問題ない
login_data = {
   'UTF-8': '✓',
   'session[email]': メールアドレス,
   'session[password]': パスワード,
}

# tokenを取得
authenticity_token = bs.find(attrs={'name':'authenticity_token'}).get('value')

# 取得したtokenをログイン時にpostするデータに追加する
login_data['authenticity_token'] = authenticity_token

# cookieの取得
response_cookie = response.cookies

# ログインurl, 送信するデータ、cookieをセットしてpostする
login = session.post(login url, data=login_data, cookies=response_cookie)
time.sleep(2)

# ログイン後のソースを取得
print(login.text)

# ログアウト処理
# ログアウト時に必要なtokenを取得
csrf_token = bs.find(attrs={'name': 'csrf-token'}).get('content')

# ログアウト時にpostするデータを定義
logout_data = {
    '_method': 'delete',
    'authenticity_token': csrf_token 
}

# ログアウトurl, 送信するデータをセットしてpostする
logout = session.post(logout url, data=logout_data)
time.sleep(2)

# ログアウト後のソースを取得
print(logout.text)
```

そんな訳で初投稿 & 汚いソースではありますが、
requests + (BeautifulSoup)でもログイン、ログアウトが実現できる様です。
ですが正直なところ、セキュアなサイトだと出来ないかもしれませんね。