【Python3】ログイン機能付サイトでスクレイピング【requests】【BeautifulSoup】

# Pythonによるスクレイピング&機械学習
[Pythonによるスクレイピング&機械学習 開発テクニック BeautifulSoup,scikit-learn,TensorFlowを使ってみよう](https://www.amazon.co.jp/Python%E3%81%AB%E3%82%88%E3%82%8B%E3%82%B9%E3%82%AF%E3%83%AC%E3%82%A4%E3%83%94%E3%83%B3%E3%82%B0-%E9%96%8B%E7%99%BA%E3%83%86%E3%82%AF%E3%83%8B%E3%83%83%E3%82%AF-BeautifulSoup-scikit-learn-TensorFlow%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%81%BF%E3%82%88%E3%81%86/dp/4802610793)より
学習ログ。
第二章。
# 前回
この本の第一章では、

+ APIの基本的な呼び方
+ Beautifulsoupを用いた、基本的なWebページでのスクレイピングの仕方

について記述してありました。
ので、Beautifulsoupの基本的な使用法に関しては、当記事では書きません。

# この章で得られるもの
2章では、高度なスクレイピングを学びます。
主に**JavaScriptを使用したサイト**（動的に情報を取得してるようなサイト）や、
**ログインが必要なサイト**からデータを取得するのに必要なテクニックを学んでいきます。
今回は**後者のみ**です。

# 環境
Python 3.6.0

# コード
[こちら(Git)](https://github.com/shunyooo/scraping_ML_DL/tree/master/Section2)にて

# 高度なスクレイピング

# 2-1 ログインの必要なサイトからダウンロード
基本的にWebページはステートレスです。これは、HTTP通信との親和性、Web製作における簡潔さや、ユーザビリティにおいて貢献していると考えられます。  

ただ、それだけでは少し不便です。**Webサイト側から見て、そのユーザが何回目の訪問なのか、ログインしているのか**、などを知りたい場合、困ります。**そこでCookieという手法が使えます。**（最近はjsで管理できるWebStorage,LocalStorageなどもありますが）

Cookieというのは、**HTTP通信の際に、サーバー側から発行し、ユーザ(ブラウザ)側で一時的にデータを保存できる手法**です。Cookieを付与されたユーザは、そのサーバへのリクエスト時にCookieの値をヘッダーに付けてリクエストを行います。

サーバ側では、そのCookieを読み込み、ユーザの特定や、どの状態にあるかなどを判定することができるというわけです。Cookieを更新する時は、レスポンスに更新する命令と値を入れておく感じです。

Cookieで保存できるデータは4096バイトらしい（わずか4KB!）。これ豆知識。あと最大期限が３ヶ月ぐらいだったはずです。

## ログイン機能におけるCookie
Cookieでログイン機能がどう実現されているか考えてみましょう。
まず考えられるのは、CookieにユーザーIDやパスワードなど、ユーザーが入力する時の値をそのまま入れておくやり方。この方法でも実現はできます。ただ、悪意ある他人がそのブラウザを覗いた時（簡単に覗けます）、とても怖いですね。

ので、**セッション**という仕組みがしばしば利用されています。ユーザーはログイン時にIDとパスワードをサーバに送信します。この時**サーバ側では、そのユーザに対して一時的に紐づけられるセッションIDという乱数を発行します**。ユーザはこのセッションIDをCookieに保存することで、安全にログイン状態を保持できるわけです。
Cookieが無効になれば、もっかいログインを促すようなフローになります。身に覚えがあると思います。

自分の開発現場では同じニュアンスで**(アクセス)トークン**と言ってます。おんなじ？ API方式だからでしょうか？

## requestを使ってみる
さっそくrequestを使って、ログイン系のやつを実装してみます。
教科書の例に沿います。

## サイト選択
[作詞掲示板サイト](http://uta.pw/sakusibbs/)でマイページに表示されている、お気に入りの一覧を取得するプログラムを作って見ます。

余談ですがこのサイト、ちょっと覗いてみると、

```
人って 
なんで 
生きて 
いるのかな？ 

ボクが 
いるって 
意味がある 
ことなのかな？ 
```

自分の黒歴史がフラッシュバックで襲ってきました。中二の夏、モバゲーで小説書いていた頃が思い出されて、なんともいえません。

ただ、この作者の方のをもう少し見てみると、「遅刻しそうなときの歌」というタイトルで、以下のような作詞が。

```
5分だけ　5分だけ 
猶予を欲する　傲慢な怠惰 
永遠の刻から　堕ちる勇気だけが 
自らを律するのか 
5分だけ　5分だけ 
数分先に　視得る惨劇に 
指先で触れた　未来にダイヴする 
Oh My Study Time 
```
センスが感じられますね。Oh My Study Timeのくだりは最高じゃないでしょうか。

話を戻します。
まず、アカウントを作成して、ログインしてみます。そして、適当にお気に入りを作成。マイページにアクセスすると、お気に入りが確認できます。

<img width="1025" alt="スクリーンショット 2017-06-04 2.07.33.png" src="https://qiita-image-store.s3.amazonaws.com/0/180836/e9f3a404-9f9c-ab2b-a00c-b35d73b5a1f8.png">



この時、ログインからマイページにアクセスするまでのフローは以下の通り。
1. 作詞掲示板のログインページを開く。
2. フォームにユーザーマイとパスワードを入力して、フォームを送信。
3. ログイン後のページが表示されるのでマイページのリンクをクリック。

ここで重要になるのが、**フォームのパラメータ名、フォーム送信時のリンク**になります。
これは、ソースを見て確認しましょう。ログインページのフォームは以下のようです。

```html

<form action="users.php?action=login&amp;m=try" method="post">
	<table>
		<tr><td>ユーザー名</td><td><input id="user" name="username_mmlbbs6" type="text" size="12" value=""/></td></tr>
		<tr><td>パスワード</td><td><input id="pass" name="password_mmlbbs6" type="password" size="12" /></td></tr>
		<tr><td></td><td><input type="submit" value="ログイン" size="8" /></td></tr>
	</table>
	<input type="hidden" name="back" value="index.php" />
	<input type="hidden" name="mml_id" value="0" />
</form>
```

パラメータ名に関して
ユーザー名は```username_mmlbbs6```
パスワードは```password_mmlbbs6```

フォーム送信のリンクに関して、
```このページのリンク+"users.php?action=login&amp;m=try"```

であることが確認できました。これを見て、pythonで実装していきます。

## 実装
###1.ログイン

```python

import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin

# メールアドレスとパスワードの指定
USER = "山田太郎"
PASS = "password"

# セッションを開始
session = requests.session()

# ログイン
login_info = {
    "username_mmlbbs6":USER,
    "password_mmlbbs6":PASS,
    "back":"index.php",
    "mml_id":"0"
}

# action
url_login = "http://uta.pw/sakusibbs/users.php?action=login&m=try"
res = session.post(url_login, data=login_info)
res.raise_for_status() # エラーならここで例外を発生させる

print(res.text)
```

実行結果

``` html

<!DOCTYPE html>
<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=utf-8" >
<meta name="viewport" content="width=960, initial-scale=1, minimum-scale=1">
<link rel="stylesheet" href="resource/konawiki3.css" type="text/css" >
<meta name="keywords" content="作詞,歌,歌詞,作詞掲示板,掲示板,アーティスト,同人,短歌,ポエム,言葉,ことば">
<meta name="description" content="作詞掲示板は自作の詞を投稿してみんなで楽しむ掲示板です。">
<title>作詞掲示板(uta.pw)</title>
<meta name="viewport" content="width=device-width">

<!-- facebook -->
<meta property="og:title" content="作詞掲示板(uta.pw)" />
<meta property="og:type" content="website"/>
<meta property="og:url" content="http://uta.pw/sakusibbs/index.php" />
<meta property="og:image" content="http://uta.pw/sakusibbs/resource/image-sakusibbs.png" />
<meta property="og:description" content="自作の詞を投稿できる作詞掲示板" />
<meta property="og:site_name" content="作詞掲示板(uta.pw)" />
<!-- /facebook -->

</head>

<body>
<div id="bbsheader">
  <div style="text-align:center;">
    <div style="width:960px; margin-left:auto; margin-right:auto;">
      <div id="site-descript">
        作詞掲示板「uta.pw」は自作の詞を投稿してみんなで楽しむ掲示板です。
      </div>
      <div id="header_menu">
        <a href="http://uta.pw/">トップ</a> |
        <a href="http://uta.pw/sakusibbs/">掲示板トップ</a> |
        <span class="islogin"><a href='users.php?user_id=241'>[山田太郎さんのマイページ]</a></span>
[<a href="./users.php?action=logout">ログアウト</a>]

...(省略)
```
うまいことログイン後のトップページが取れ、ログインの成功が確認できました。

### 2.マイページへ遷移
まず、先ほど得られたhtmlから、マイページに遷移できるようなURLを取得します。
どこを取ればいいのかは、先ほどのようにソースを確認するか、chromeだと検証で楽に確認できたりします。

該当箇所は以下になります。

```html
<span class="islogin">
<a href="users.php?user_id=241">[山田太郎さんのマイページ]</a>
</span>
```
つまり、isloginクラス要素内のaタグを取得すれば良いのです。


```py

# マイページのURLをピックアップする
soup = BeautifulSoup(res.text,"html.parser")
a = soup.select_one(".islogin a")# isloginクラス要素内のaタグ
if a is None:
    print("マイページが取得できませんでした")
    quit()

# 相対URLを絶対URLに変換
url_mypage = urljoin(url_login, a.attrs["href"])
print("マイページ=", url_mypage)
```
実行結果

```py
マイページ= http://uta.pw/sakusibbs/users.php?user_id=241
```

とれました。
あとは普通に遷移するだけです。

```py
res = session.get(url_mypage)
res.raise_for_status()
print(res.text)
```

実行結果

```html
<!DOCTYPE html>
<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=utf-8" >
<meta name="viewport" content="width=960, initial-scale=1, minimum-scale=1">
<link rel="stylesheet" href="resource/konawiki3.css" type="text/css" >
<meta name="keywords" content="作詞,歌,歌詞,作詞掲示板,掲示板,アーティスト,同人,短歌,ポエム,言葉,ことば">
<meta name="description" content="作詞掲示板は自作の詞を投稿してみんなで楽しむ掲示板です。">
<title>山田太郎 さんのページ - 作詞掲示板(uta.pw)</title>
<meta name="viewport" content="width=device-width">

...(中略)...

<h2>最近お気に入りの作品</h2><div><ul id='favlist'><li><a href='post.php?mml_id=303'>迷路<span class='comment'> by しゅん</span></a><span style='color:orange'>**</span><span class='desc'>人生という名の迷路を歩き続け..</span></li>
<li><a href='post.php?mml_id=300'>自分が決めた道<span class='comment'> by Western･Castle</span></a><span style='color:orange'>***</span><span class='desc'></span></li>
<li><a href='post.php?mml_id=278'>遅刻しそうなときの歌<span class='comment'> by Dj Hino</span></a><span style='color:orange'>****</span><span class='desc'>ハッと目が覚めて二度寝してう..</span></li>
<li><a href='post.php?mml_id=302'>僕の足<span class='comment'> by Homary</span></a><span style='color:orange'>*********</span><span class='desc'>変わった所で何かが変わる訳で..</span></li>
</ul></div>
</div>    
```

マイページ遷移後のhtmlも取れました。お気に入り作品の情報も確認できます。

### 3.お気に入りのタイトル取得
上記の結果から、id=favlist内のリストのaタグのコンテンツを取れば良いことがわかります。

```py

# お気に入りの詩のタイトルとリンクを列挙
soup = BeautifulSoup(res.text,"html.parser")
links = soup.select("#favlist li > a")
for a in links:
    href = urljoin(url_mypage, a.attrs["href"])
    title = a.get_text()
    print("- {} > {}".format(title,href))
```

実行結果

```
- 迷路 by しゅん > http://uta.pw/sakusibbs/post.php?mml_id=303
- 自分が決めた道 by Western･Castle > http://uta.pw/sakusibbs/post.php?mml_id=300
- 遅刻しそうなときの歌 by Dj Hino > http://uta.pw/sakusibbs/post.php?mml_id=278
- 僕の足 by Homary > http://uta.pw/sakusibbs/post.php?mml_id=302
```

いい感じですね。とれました。

このコードは[こちら](https://github.com/shunyooo/scraping_ML_DL/blob/master/Section2/2-1%20login-getfav.ipynb)から

# requestsモジュールのメソッドについて
HTTP通信を行ってくれるrequestsモジュールの主要なメソッド紹介。
といっても、HTTPメソッドがそのままという感じらしい。
POSTの時のデータは、文字列辞書式。

```py

# GET
r = requests.get("http://google.com")
# POST
formdata = {"key1":"valiue1","key2":"value2"}
r = requests.post("http://example.com",data=formdata)

# PUT
r = requests.put("http://httpbin.org/put")
# DELETE
r = requests.delete("http://httpbin.org/delete")
# HEAD
r = requests.head("http://httpbin.org/head")
```

そして、戻り値のtextやcontentプロパティで、取得したデータの詳細を取得できます。

# requests, BeautifulSoup によるスクレイピングの欠点（追記:2017/12/20）
最近bitcoin流行りで、スクレイピング需要が多いのか、若干いいねが増えてきているので、タイトル修正のついでに追記しておきます。

requests, BeautifulSoupによるスクレイピングは、初回レスポンスのHTMLをパースしているに過ぎないので、静的なページにしか対応していません。
したがって、**動的なページには弱いです。**
動的なページとはAjax(jsにより後からデータを取得する技術)によってページがあとからウニョウニョ更新されるようなページです。

たとえば、このようなページ。
こいつは requests じゃ対応できません。
> https://inagoflyer.appspot.com/btcmac


そこで使えるのが、**Selenium**というツールです。
これを次回紹介していきます。[実際に上記のサイトのスクレイプも行います。](https://qiita.com/shunyooo/items/09cc636344212112a6fc#appendix%E8%BF%BD%E8%A8%9820171220)
以下より。
> [【Python3】ブラウザを経由したスクレイピング【動的なページなど】【Selenium】](https://qiita.com/shunyooo/items/09cc636344212112a6fc)

重たくなってきたので、このあたりで。