Help us understand the problem. What is going on with this article?

【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のくだりは最高じゃないでしょうか。

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

スクリーンショット 2017-06-04 2.07.33.png

この時、ログインからマイページにアクセスするまでのフローは以下の通り。
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】

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

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away