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のくだりは最高じゃないでしょうか。
話を戻します。
まず、アカウントを作成して、ログインしてみます。そして、適当にお気に入りを作成。マイページにアクセスすると、お気に入りが確認できます。
この時、ログインからマイページにアクセスするまでのフローは以下の通り。
- 作詞掲示板のログインページを開く。
- フォームにユーザーマイとパスワードを入力して、フォームを送信。
- ログイン後のページが表示されるのでマイページのリンクをクリック。
ここで重要になるのが、フォームのパラメータ名、フォーム送信時のリンクになります。
これは、ソースを見て確認しましょう。ログインページのフォームは以下のようです。
<form action="users.php?action=login&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&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 じゃ対応できません。
そこで使えるのが、Seleniumというツールです。
これを次回紹介していきます。実際に上記のサイトのスクレイプも行います。
以下より。
重たくなってきたので、このあたりで。