Rのこと。

記事は引っ越し作業中。2023年中までに引っ越しを完了させてブログは削除予定

HTTPとAJAXのおさらい

はじめに

ここらへんの知識が足りてないことを痛感することが多いので、ここではHTTPとAJAXのおさらいをまとめておく。参考文献はAutomated Data Collection with R

HTTP

Webサイトからデータを得るということは、webサービスとサーバーが通信していることになる。この通信時の規格がHTTP(Hypertext Transfer Protocol)。クライアントとサーバーの基本的なやり取りをwww.r-datacollection.comlを例にまとめる。このサイトにアクセスすると、ブラウザがHTTPクライアントとして機能し、クライアントDNSサーバーに、渡されているURLを更にわたすことで、www.r-datacollection.comに対応するIPアドレスを受け取る。このIPアドレスを使って、TCP/IP経由でHTTPリクエストを送り、要求されたHTTPサーバーはHTTPレスポンスを返すことで接続を確立する。www.r-datacollection.com.index.htmlであれば、ブラウザはindex.htmlを要求し、これが返されることで、ブラウザの画面に表示される。

HTTPメッセージ

www.r-datacollection.comを例に、HTTPメッセージを見てみる。まず、www.r-datacollection.comIPアドレスに変換し、接続をポート80で確立。その後に、サーバーに対して、リクエストGETを送っている。HTTP/1.1 200 OKの部分から、HTTPレスポンスが返り、コンテンツが返され、Connection #0でクライアントが接続を閉じる。

$ curl -v http://www.r-datacollection.com

*   Trying 64.111.125.36:80...
* TCP_NODELAY set
* Connected to www.r-datacollection.com (64.111.125.36) port 80 (#0)
> GET / HTTP/1.1
> Host: www.r-datacollection.com
> User-Agent: curl/7.65.3
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Sun, 15 Mar 2020 01:49:21 GMT
< Server: Apache
< Upgrade: h2
< Connection: Upgrade
< Vary: Accept-Encoding,User-Agent
< Transfer-Encoding: chunked
< Content-Type: text/html; charset=UTF-8
< 

<!DOCTYPE html>
<html>
    <head>

【略】

</html>
* Connection #0 to host www.r-datacollection.com left intact

HTTPメッセージのテンプレ

HTTPメッセージのリクエストはこうなっている。 f:id:AZUMINO:20200315105156p:plain

HTTPメッセージのレスポンスはこうなっている。 f:id:AZUMINO:20200315105159p:plain

HTTPのリクエス

下記が代表的なHTTPのリクエスト。

リクエス 内容
GET サーバーからリソースを取得
POST メッセージボディを使って、データないし、ファイルを送り、サーバーからリソースを取得
HEAD GET リクエストと同じ。開始行とヘッダーのみ取得
PUT リクエストメッセージのボディをサーバーに保存
DELETE サーバーからリソースを削除
TRACE メッセージがサーバーに届く経路を取得
CONNECT ネットワークの接続を確立
OPTIONS サポートされるHTTPメソッドを取得

最も代表的なのはGRTとPOSTで、ボディの指定が異なる。GETはボディに何も指定しないが、POSTはデータを送信するためにボディを利用する。HTMLドキュメントなどを貰う場合はGETで間に合うが、フォームの入力が必要な場合などはPOSTでデータを送信する。

HTTPを扱うRパッケージ

代表的なHTTPを扱うRパッケージはRCurlhttrパッケージの2つ。RでWebの通信をできる背景にはCで書かれているlibcurlライブラリの存在が大きい。libcurlライブラリをRで使えるようにしているのが、RCurlパッケージ。このおかげで、HTTPヘッダの指定、URLエンコードの解釈、webサーバーのデータストリーム処理、SSL接続、プロキシへの接続、ハンドル認証などができる。

GETメソッド

RCurlパッケージのget**()で、HTTPリクエストのGETを利用できる。バイナリデータが対象であれば、getBinaryURL()を使えばいい。

library(RCurl)

cat(RCurl::getURL("http://www.r-datacollection.com/materials/http/helloworld.html"))
<html>
<head><title>Hello World</title></head>
<body><h3>Hello World</h3>
</body>
</html>

bin_png <- RCurl::getBinaryURL("http://www.r-datacollection.com/materials/http/sky.png")
bin_png %>% head(, 10)
[1] 89 50 4e 47 0d 0a

writeBin(bin_png, "sky.png")

コンテンツによっては、下記のようにフォームに値を入れて送信して、データを取得する場合がある。

f:id:AZUMINO:20200315124636p:plain

HTMLはこうなっており、<form action="GETexample.php" method="get">にあるように、GETexample.phpというファイルに送信される。

<!DOCTYPE HTML>
<html><head><title>HTTP GET Example</title></head> <body>
<h3>HTTP GET Example</h3>
<form action="GETexample.php" method="get">
    Name: <input type="text"   name="name"  value="Anny Omous"><br>
    Age:  <input type="number" name="age"   value="23"><br><br>
    <input type="submit" value="Send Form and Evaluate"><br><br>
    <input type="submit" value="Send Form and Return Request" name="return">
</form>
</body></html>

何も値が入っていないと、[http://www.r-datacollection.com/materials/http/GETexample.php]は下記のように返してくる。

Please specify your name! Please specify your age!

getForm()を使って、phpにわたす値をname = Tanaka, age = 30として渡すと下記が返ってくる。

getForm(url, name = "Tanaka", age = 30)

[1] "Hello Tanaka!\nYou are 30 years old.\n"
attr(,"Content-Type")
                charset 
"text/html"     "UTF-8" 

get**()では、HTTPメソッドを扱う引数がある。

cat(RCurl::getURL("http://www.r-datacollection.com/materials/http/helloworld.html", .opts = "HEAD", header = TRUE))
HTTP/1.1 200 OK
Date: Sun, 15 Mar 2020 03:59:09 GMT
Server: Apache
Upgrade: h2
Connection: Upgrade
Vary: Accept-Encoding,User-Agent
Transfer-Encoding: chunked
Content-Type: text/html; charset=UTF-8

<html>
<head><title>Hello World</title></head>
<body><h3>Hello World</h3>
</body>
</html>

AJAX(Asynchronous JavaScript + XML)

HTML/HTTPに欠けている「動的なコンテンツ提供」を行うためにはAJAXを使う。AJAXを使うことで、画像の遷移のない通信である非同期通信を行うことが出来る。そのため、同期処理のように一瞬画面が白くなる画面切替は発生しない。HTML/HTTPには下記が欠けている。

  • ブラウザ側でユーザーの行動イベントを検知する仕組み
  • そのようなイベントに対する応答を明確に記述するスクリプトエンジン
  • 非同期的に情報を取得するためのデータリクエスト方式

JavaScript

AJAXは、XMLHttpRequestJavaScript、DOM、XMLの技術要素をまとめた言葉なので、理解するためにはこれらを知っておく必要がある。例えば、JavaScriptをはじめjQueryというDOM操作を用意にするJavaScriptライブラリなどを理解する必要がある。JavaScriptが使われているかどうかはHTMLをみればわかる。例えば、<script>タグにJavaScriptが記述できたり、<script>のsrc属性にファイルパスを渡したりすることでJavaScriptが使われているかどうかわかる。いずれの方法もDOMを構築することになる。

DOM(Document Object Model)は、プログラムからHTMLやXMLを操作する仕組みのこと。例えば、ブラウザに表示される文字色などの情報を変更する際に、何もしていない状態のHTMLファイルではJavaScriptからは操作できないため、特定の部分に印を付けてJavaScriptにわかるようにする「取り決め」がDOMの役割。DOMを取り決めることで、ブラウザは動的に振る舞うことが出来る。参考文献のこのページがわかりよい。ソースは下記の通り。

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html>

<script type="text/javascript" src="jquery-1.8.0.min.js"></script>
<script type="text/javascript" src="script1.js"></script>

<head>
<title>Collected R wisdoms</title>
</head>

<body>
<div id="R Inventor" lang="english" date="June/2003">
  <h1>Robert Gentleman</h1>
  <p><i>'What we have is nice, but we need something very different'</i></p>
  <p><b>Source: </b>Statistical Computing 2003, Reisensburg</p>
</div>

<div lang="english" date="October/2011">
  <h1>Rolf Turner</h1>
  <p><i>'R is wonderful, but it cannot work magic'</i> <br><emph>answering a request for automatic generation of 'data from a known mean and 95% CI'</emph></p>
  <p><b>Source: </b><a href="https://stat.ethz.ch/mailman/listinfo/r-help">R-help</a></p>
</div>

<address><a href="http://www.r-datacollection.com"><i>The book homepage</i></a></address>
</body>
</html>

script1.jsはこんな感じ。$(document)はDOM中の要素を選択するためのjQueryのメソッド。readyはHTML要素が完全に揃うまで待つもので、サイトが巨大になるとDOMの構築が終わらないままjavaScriptが実行されるかもしれない。$("p")でpノードだけを指定し、hide()メソッドを実行するように指定。click(function()ハンドラでクリックのイベントをもとにアクションを引き起こす。$(this).nextAll()でクリックされた要素に続くすべての要素を選択し、300msの速度でフェードを実行する。

$(document).ready(function() {
    $("p").hide();
    $("h1").click(function(){
    $(this).nextAll().slideToggle(300);
    });
});

XHR

XHRは、XMLHttpRequestの略で、ブラウザとwebサーバー間の連続的な情報交換をする仕組みのこと。XHRによるユーザー・サーバー間の通信は下記のように行われる。

  • AJAXリクエストはブラウザでクリック(Javascriptが認識可能イベント)することで開始される。
  • AJAXリクエストが開始されると、JavascriptはXHRオブジェクトをインスタンス化する。このインスタンスは、リクエストを作成するものとして使われ、取得したデータをどう扱うかをコールバック関数によって定義する。
  • XHRオブジェクトは、指定されたファイルに対するリクエスト(HTTP or HTTPS)をサーバーに送る。
  • サーバー側では、リクエストを受信して、処理して、XHRオブジェクトを通じてクライアント、ブラウザに送る。
  • ブラウザでレスポンスデータ受信し、そのイベントをイベントハンドラが受け取る。内容によってはコールバックイベントハンドラが処理して、ブラウザに表示する。

このサイトはThe book homepageしか表示されず、"Quotes.html was fetched."というアラートが出てくる。これをOKすることで、内容が別のドキュメントから読み込まれる。

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html>

<script type="text/javascript" src="jquery-1.8.0.min.js"></script>
<script type="text/javascript" src="script2.js"></script>

<head>
<title>Collected R wisdoms</title>
</head>

<body>
<address><a href="http://www.r-datacollection.com"><i>The book homepage</i></a></address>
</body>
</html>

script2.jsはこんな感じ。jQueyのload()メソッドでquotes/quotes.htmlの情報を収集している。load()メソッドはXHRオブジェクトを生成し、HTMLドキュメントの情報を反映する。

$(document).ready(function() {
    $("body").load("quotes/quotes.html", function() {
    alert("Quotes.html was fetched.");
    });
});

quotes/quotes.htmlの内容はこれ。

<div id="R Inventor" lang="english" date="June/2003">
  <h1>Robert Gentleman</h1>
  <p><i>'What we have is nice, but we need something very different'</i></p>
  <p><b>Source: </b>Statistical Computing 2003, Reisensburg</p>
</div>

<div lang="english" date="October/2011">
  <h1>Rolf Turner</h1>
  <p><i>'R is wonderful, but it cannot work magic'</i> <br><emph>answering a request for automatic generation of 'data from a known mean and 95% CI'</emph></p>
  <p><b>Source: </b><a href="https://stat.ethz.ch/mailman/listinfo/r-help">R-help</a></p>
</div>

この仕組を見てわかるように、スクレイピングしようとすると、すべての情報が反映されておらず、取得することができないため、何らかのアクションでjQueyを動かして、情報を反映させてからスクレイピングする必要がある。

AJAXを調べる

Chormeの開発者ツールでAJAXを調べることが出来る。Chormeの開発者ツールであれば、AJAXと関わりの深いものはElementsとNetworkの2つ。Networkパネルをみてみる。先程のサイトを通信のやり取りは画像のようになっている。fortunes2.htmljquery-1.8.0.min.jsscript2.jsquotes.htmlが読み込まれている。

f:id:AZUMINO:20200315151526p:plain

Rに渡すためのURLは、quotes.htmlをクリックし、その中のリクエストURLに記載されているhttp://www.r-datacollection.com/materials/ch-6-ajax/fortunes/quotes/quotes.htmlである。このサイトにはjQueyで動かした後の情報が記載されている。

f:id:AZUMINO:20200315151950p:plain

Previweでファイルの中身を確認できる。

f:id:AZUMINO:20200315152209p:plain

RのgetURL()にこのアドレスを渡す必要がある。

cat(RCurl::getURL("http://www.r-datacollection.com/materials/ch-6-ajax/fortunes/quotes/quotes.html"))
<div id="R Inventor" lang="english" date="June/2003">
  <h1>Robert Gentleman</h1>
  <p><i>'What we have is nice, but we need something very different'</i></p>
  <p><b>Source: </b>Statistical Computing 2003, Reisensburg</p>
</div>

<div lang="english" date="October/2011">
  <h1>Rolf Turner</h1>
  <p><i>'R is wonderful, but it cannot work magic'</i> <br><emph>answering a request for automatic generation of 'data from a known mean and 95% CI'</emph></p>
  <p><b>Source: </b><a href="https://stat.ethz.ch/mailman/listinfo/r-help">R-help</a></p>
</div>

AJAXWebサービスの利便性を遥かに向上させてくれる一方でスクレイピングを目的とすると、少しややこしくなるが、仕組みを理解しておけばなんとかなる。AJAXにリクエストされる情報は、メインファイルがある同じページのどこかにあるので開発者ツールでなんとかできる。