rvest で OAuth 認証してスクレイピング #rstatsj


以前、こんな記事を書いた。

- [rvest でログインしてスクレイピング](http://qiita.com/hoxo_m/items/59bedbf7e4c7c99cbf5a)

しかし最近は Twitter アカウントなどで OAuth を使ってログインできるサイトが増えてきた。
私も Qiita は Twitter ID を紐づけて OAuth でログインしている。

rvest を使って OAuth 認証するのは、上記記事のやり方にひと手間加える必要があるので、ここで紹介しよう。

## Qiita に Twitter ID でログイン

例として、Qiita に Twitter ID でログインしてみる。

```r:R
library(rvest)

# Qiita の Twitter 用認証ページ
login_page <- html_session("https://qiita.com/auth/twitter")

# 認証ページのログインフォームにアカウント名とパスワードを入れる
login_form <- html_form(login_page)[[1]] %>% 
  set_values(`session[username_or_email]`="hoxo_m", `session[password]`="****")

# Submit するとリダイレクトページが表示される
redirect_page <- submit_form(login_page, login_form)
```

まず、Qiita の Twitter 認証ページにアクセスする。
すると、Twitter 側の認証ページへリダイレクトされるので、そこでログインフォームにアカウント名とパスワードを入力する。
これを Submit すると、今度は Qiita 側へ認証情報を返すためにリダイレクトしようとするのだが、rvest では自動的に遷移しない。

したがって、この `redirect_page` ページから、認証情報を返すためのリンクを探してそのリンクをたどらなければならない。

```r:R
redirect_page %>% html_node(xpath='//body')
```

```:結果
(省略)
<div id="bd" role="main">
  <div class="happy notice callback">
    <h2>アプリケーションに戻ります。しばらくお待ちください。</h2>
    <p>自動的に移動しない場合は<a class="maintain-context" href="https://qiita.com/auth/twitter/callback?oauth_token=***&amp;oauth_verifier=***">こちら</a>をクリックしてください</p>
  </div>
</div>
(省略)
```

この「自動的に移動しない場合はこちらをクリックしてください」というのが認証情報を返すためのリンクである。
このリンクを rvest でたどるには、次のようにする。

```r:R
session <- redirect_page %>%
  follow_link(xpath = '//*[@id="bd"]/div/p/a')
```

以上で、Qiita に Twitter ID でログインしたセッションを作成することができた。

あとは通常どおり、ログインした状態でスクレイピングできる。

```r:R
session %>% 
  jump_to("http://qiita.com") %>% 
  html_nodes(xpath='//div[@class="userInfo"]/div/div') %>%
  html_text
```

```:結果
[1] "hoxo_m"          "753Contribution"
```

hoxo_m アカウントでログインできていることが確認できた。

## Qiita の通知情報をスクレイピング

ここからは完全に余談で、Qiita の[通知一覧](http://qiita.com/notifications)のページから、通知情報をスクレイピングしてみる。

```r:R
library(lambdaR)
library(pforeach)

npforeach(i=1:99, .c=rbind)({
  url <- sprintf("http://qiita.com/notifications?page=%d", i)
  message(url)
  
  nodes <- session %>%
    jump_to(url) %>%
    html_nodes(xpath = '//div[@class="notification_actionWrapper"]')
  
  Sys.sleep(1)
  
  nodes %>%
    Map_(node: {
      action <- tryCatch({
        node %>% html_node(xpath='span[@class="action"]') %>% html_text
      }, error = f_(e: {
        # action が「編集」「投稿」の場合
        node %>% html_node(xpath='*/span[@class="action"]') %>% html_text
      }))
      if(action == "ツイート") {
        who <- ""
      } else {
        who <- node %>% html_node(xpath='span[@class="notification_user"]') %>% html_text
      }
      if(action %in% c("フォロー", "ツイート", "Thank!")) {
        url <- ""
      } else if(action %in% c("編集", "投稿")) {
        url <- node %>% html_node(xpath='a') %>% html_attr("href")      
      } else {
        url <- node %>% html_node(xpath='span[@class="action"]/a') %>% html_attr("href")
      }
      if(action == "フォロー") {
        title <- "あなた"
      } else if(action == "採用")　{
        title <- "編集リクエスト"
      } else if(action == "Thank!")　{
        title <- "あなたのコメント"
      } else {
        title <- node %>% html_node(xpath='span[@class="title"]') %>% html_text        
      }
      data.frame(who, action, title, url, stringsAsFactors = FALSE)
    }) %>%
    Reduce_(rbind)
}) -> result

knitr::kable(head(result))
```

|who       |action     |title                                |url                                                       |
|:---------|:----------|:------------------------------------|:---------------------------------------------------------|
|kisy      |ストック   |"rvest すげえ。#rstatsj"             |http://qiita.com/hoxo_m/items/264b45684fcb657cf285        |
|garam     |ストック   |"read.table() でヘッダ..."           |http://qiita.com/hoxo_m/items/b77f1566d3a848e46c92        |
|uri       |編集       |"【gifアニメつき】テキストエディ..." |http://qiita.com/uri/items/a4a6e9abdbe9a913d657/revisions |
|uri       |編集       |"【gifアニメつき】テキストエディ..." |http://qiita.com/uri/items/a4a6e9abdbe9a913d657/revisions |
|dichika   |メンション |"readr::parse_date..."               |http://qiita.com/dichika/items/4255bc4b5949c9b367f1       |
|ddintokyo |ストック   |"RStudio Shiny チュー..."            |http://qiita.com/hoxo_m/items/c8365117f3444fb51df4        |

このデータから、ストック情報だけを取り出して、自分の記事をよくストックするユーザを見つけてみよう。

```r:R
library(dplyr)

stock_ranking <- result %>%
  filter(action == "ストック") %>%
  count(who) %>%
  arrange(desc(n))

knitr::kable(head(stock_ranking, 20))
```

|who             |  n|
|:---------------|--:|
|HirofumiYashima | 47|
|uri             | 39|
|Reds            | 32|
|ytakky          | 26|
|kasumani        | 19|
|cof             | 17|
|horihorio       | 16|
|P_tan           | 12|
|soogie          | 11|
|hiro_matsuno2   | 10|
|kaz-yos         | 10|
|st450           | 10|
|tktz            | 10|
|ginmaru         |  8|
|selious         |  8|
|wakuteka        |  8|
|R_beginner      |  7|
|hayashiyus      |  7|
|heliac2000      |  7|
|ksomemo         |  6|

ここは @uri さんが一位であってほしかった。

Enjoy!

## 関連

- [rvest でログインしてスクレイピング](http://qiita.com/hoxo_m/items/59bedbf7e4c7c99cbf5a)
- [rvest すげえ。](http://qiita.com/hoxo_m/items/264b45684fcb657cf285)
- [ggplot2 に依存しているパッケージ一覧を rvest で取得する](http://qiita.com/hoxo_m/items/c53b6cfbd5ada5fe1642)
- [rvest で声優の男女データをスクレイピング](http://qiita.com/hoxo_m/items/b9002b7b05954e882148)