Edited at

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

More than 1 year has passed since last update.


Pythonによるスクレイピング&機械学習

Pythonによるスクレイピング&機械学習 開発テクニック BeautifulSoup,scikit-learn,TensorFlowを使ってみようより

学習ログ。

第二章。


前回

この本の第一章では、


  • APIの基本的な呼び方

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

について記述してありました。

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


この章で得られるもの

2章では、高度なスクレイピングを学びます。

主にJavaScriptを使用したサイト(動的に情報を取得してるようなサイト)や、

ログインが必要なサイトからデータを取得するのに必要なテクニックを学んでいきます。

今回は後者のみです。


環境

Python 3.6.0


コード

こちら(Git)にて


高度なスクレイピング


2-1 ログインの必要なサイトからダウンロード

基本的にWebページはステートレスです。これは、HTTP通信との親和性、Web製作における簡潔さや、ユーザビリティにおいて貢献していると考えられます。

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

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

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

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


ログイン機能におけるCookie

Cookieでログイン機能がどう実現されているか考えてみましょう。

まず考えられるのは、CookieにユーザーIDやパスワードなど、ユーザーが入力する時の値をそのまま入れておくやり方。この方法でも実現はできます。ただ、悪意ある他人がそのブラウザを覗いた時(簡単に覗けます)、とても怖いですね。

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

Cookieが無効になれば、もっかいログインを促すようなフローになります。身に覚えがあると思います。

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


requestを使ってみる

さっそくrequestを使って、ログイン系のやつを実装してみます。

教科書の例に沿います。


サイト選択

作詞掲示板サイトでマイページに表示されている、お気に入りの一覧を取得するプログラムを作って見ます。

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

人って 

なんで
生きて
いるのかな?

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

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

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

5分だけ 5分だけ 

猶予を欲する 傲慢な怠惰
永遠の刻から 堕ちる勇気だけが
自らを律するのか
5分だけ 5分だけ
数分先に 視得る惨劇に
指先で触れた 未来にダイヴする
Oh My Study Time

センスが感じられますね。Oh My Study Timeのくだりは最高じゃないでしょうか。

話を戻します。

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

この時、ログインからマイページにアクセスするまでのフローは以下の通り。

1. 作詞掲示板のログインページを開く。

2. フォームにユーザーマイとパスワードを入力して、フォームを送信。

3. ログイン後のページが表示されるのでマイページのリンクをクリック。

ここで重要になるのが、フォームのパラメータ名、フォーム送信時のリンクになります。

これは、ソースを見て確認しましょう。ログインページのフォームは以下のようです。


<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.ログイン


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)

実行結果


<!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だと検証で楽に確認できたりします。

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

<span class="islogin">

<a href="users.php?user_id=241">[山田太郎さんのマイページ]</a>
</span>

つまり、isloginクラス要素内のaタグを取得すれば良いのです。


# マイページの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)

実行結果

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

とれました。

あとは普通に遷移するだけです。

res = session.get(url_mypage)

res.raise_for_status()
print(res.text)

実行結果

<!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タグのコンテンツを取れば良いことがわかります。


# お気に入りの詩のタイトルとリンクを列挙
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

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

このコードはこちらから


requestsモジュールのメソッドについて

HTTP通信を行ってくれるrequestsモジュールの主要なメソッド紹介。

といっても、HTTPメソッドがそのままという感じらしい。

POSTの時のデータは、文字列辞書式。


# 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というツールです。

これを次回紹介していきます。実際に上記のサイトのスクレイプも行います。

以下より。


【Python3】ブラウザを経由したスクレイピング【動的なページなど】【Selenium】


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