cucumber flesh

Rを中心としたデータ分析・統計解析らへんの話題をしていくだけ

💮RSSっぽいものを自作してIFTTTに通知を飛ばす

f:id:u_ribo:20150920074308p:plain:w300

日々のあれこれを記録しておきたい性分なせいもあって、最近IFTTTを活用しまくっている。基本的には、各種のサービスをIFTTTを経由してEvernoteに記録する、という過程をとっている。twitterの一日のつぶやきとか、YouTubeのお気に入りなんかをEvernoteに記録している。

問題と解決策

サービスがIFTTTに対応していたり、必要な情報を含んだRSSがあれば良いのだが、世の中そんなに甘くはない。例えば、StackOverflowでは、ユーザーの活動をRSSとして配信してくれているが、お気に入りに登録した投稿についてのRSSを用意していない。そのため通常の方法(RSS -> IFTTT -> Evernote)は適用できない。普段からEvernoteに頼りきっていることも相まって自分でお気に入り登録したものを忘れてしまう or 検索してしまう案件が多発して困っていた。タイトルとURL、日付の情報さえあればRSS自作できるよな、と思い、{rvest}でデータを取得した後に俺々RSSもどきを作ってみた。

まずはパッケージの読み込みから。{pforeach}は @hoxo_m さんによるパッケージだが、CRANには登録されていない。{devtools}を使ってGitHubから適宜ダウンロードする必要がある。

# 他にも使っているけど、とりあえず。
library(xml2)
library(rvest)
library(pforeach)

ページ送り上限の確認

http://stackexchange.com/users/favorites/3993359?page=1&sort=added

このページが私のお気に入り登録した投稿をまとめたものである。新たにお気に入りに追加したもの順に並んでいて、1ページにつき30件表示される。page=1の部分を変更することでページ送りが可能になるので、sprintfを用いて全ページのスクレイピングを行う。

"http://stackexchange.com/users/favorites/3993359?page=1&sort=added" %>% 
  read_html() %>% 
  html_nodes(xpath = '//*[@id="favorites-pager"]/a/span') %>% 
  html_text(trim = TRUE) %>% 
  tidyr::extract_numeric() %>% 
  max(na.rm = TRUE) -> last.page
last.page
## [1] 6

6ページまで読みこめば全ページにアクセスすることができる。

タイトル、urlの取得

では、{rvest}{pforeach}でさくっと必要な情報を取ってくる。ここのコードは親分のものを参考にした。sprintfがミソ。

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

npforeach(i = 1:last.page, .c = rbind)({
  # 空ベクトルを用意しておく
  titles <- vector()
  urls <- vector()
  post.time <- vector()
  
  #iterationに1から6を指定し、sprintfを利用してpage=1からpage=6までにアクセスする
  url <- sprintf("http://stackexchange.com/users/favorites/3993359?page=%d&sort=added", i)
  message(url)

  # タイトルとURL、投稿日を取得し、データフレームにまとめる
  read_html(url) %>% 
    html_nodes(xpath = '//*[@id="favorites-container"]/div/div') %>% {
      html_nodes(., xpath = 'h2/a') %>% {
        titles <<- html_text(., trim = TRUE)
        # trim 引数を指定しないと無駄な部分(改行指定)が含まれる
        urls  <<- html_attr(., "href")
      }
      post.time <<- html_nodes(., xpath = 'div/text()') %>% 
        html_text(., trim = TRUE) %>% 
        .[.!=""]
      # 投稿日の情報。フォーマットが微妙に異なっているので特に必要ではない
    }
  dplyr::data_frame(title = titles, link = urls, pubDate = post.time)
  }) -> res

tbl_dfオブジェクトのデータフレームとして保存。

こんな感じになっている。pubDate列に統一性がなくて気に入らない。

knitr::kable(head(res))
title link pubDate
How to create missing value for repeated measurement data? http://stackoverflow.com/questions/32654706/how-to-create-missing-value-for-repeated-measurement-data yesterday
How does one add a collaborator in Github using the command line? http://stackoverflow.com/questions/13004061/how-does-one-add-a-collaborator-in-github-using-the-command-line Nov 25 '12 at 7:19
Integrating time series graphs and leaflet maps using R shiny http://stackoverflow.com/questions/31814037/integrating-time-series-graphs-and-leaflet-maps-using-r-shiny Sep 1 at 17:12
Parse JSON with R http://stackoverflow.com/questions/2061897/parse-json-with-r Apr 17 at 17:01
How to remove all whitespace from a string? http://stackoverflow.com/questions/5992082/how-to-remove-all-whitespace-from-a-string Feb 10 at 0:38
Show Git version in R Code http://stackoverflow.com/questions/32260956/show-git-version-in-r-code Aug 31 at 4:56

RSSとして機能させる

pubDate列がきちんと日付として機能していればよかったのだけど、工夫しないとRSSとして正常に動作しなそうなので応急処置。大分ダサい。

res$pubDate <- date()
# ダミーの日付を与える。
kulife::write.xml(res, file = "my_so_fav.xml")
# xmlファイルとして保存。
tmp <- readLines("my_so_fav.xml")
tmp <- gsub(pattern = "document>", replacement = "channel> ", x = tmp)
tmp <- gsub(pattern = "row>", replacement = "item> ", x = tmp)
tmp <- append(x = tmp, 
              values = '<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom">', 
              after = 1)
tmp <- append(x = tmp, 
              values = '<title>My StackOverflow Favorite</title>', 
              after = 3)
tmp <- append(x = tmp, 
              values = '<link>https://github.com/uribo/custom_feed/blob/master/my_so_fav.xml</link>', 
              after = 4)
tmp <- append(x = tmp, 
              values = '<atom:link rel="self" type="application/rss+xml" href="https://raw.githubusercontent.com/uribo/custom_feed/master/my_so_fav.xml"/>', 
              after = 5)
tmp[length(tmp) + 1] <- "</rss>"
writeLines(tmp, "my_so_fav.xml")
# 再度保存する

できあがったファイルをGitHubリポジトリに置いておいて、IFTTTでレシピを作って完成。ひとまず思っていたようにEvernoteに記録されたので満足。

日付の部分は上記のコードを実行するたびに変更されるが、新しい投稿が追加されない限りEvernoteには記録されない。この方法を使えばSO以外にも俺々RSSを作ってEvernoteに記録を残せて良い。

課題

  • 日付をなんとかする
  • うまくいかないこともある
  • ルーチンとして自動化できるようにする
  • SO以外にも応用例を考える

改善されたら書く。

Enjoy!