RからTwitterにポストしてみた
—2010/11/25 追記————————————————————————————————————–
いくつかのバグを修正して github にアップしました.
https://github.com/abicky/osakar4_abicky/blob/master/unix/twitter.R
—2011/11/20 追記————————————————————————————————————–
こちらの記事もどうぞ
OAuthに”対応”したtwitteRを試してみた - あらびき日記
————————————————————————————————————–
Tsukuba.R#8 で @twittroru さんが R の OAuth 認証について発表されたので,どのAPIも使えるようにいじって試してみました.
@twittroru さんの発表資料はこちら.
- スライド: http://www.slideshare.net/tor_ozaki/r-oauth-for-twitter-5232160
- スクリプト: https://gist.github.com/586468
HMAC-SHA1 の計算部分は素晴らしいですね…
Rからつぶやいてみる
1. OAuth.R のダウンロード
github から OAuth.R をダウンロードしてきます.
2. 依存パッケージのインストール
$ sudo R
> install.packages(c("digest", "scrapeR", "RJSONIO"))
3. 以下のコードを test.R という名前ででも保存します.
※access_token, access_token_secret, consumer_key, consumer_secret は自分のものに書き換えてください
source("OAuth.R")
library(RJSONIO)
# authenticated user
auser <- c(token = "access_token", secret = "access_token_secret")
# app
key.consumer <- "consumer_key"
secret.consumer <- "consumer_secret"
makeParams <- function(uri, method, argv, auser) {
nonce <- generateRandomString()
timestamp <- as.integer(Sys.time())
token <- auser['token']
secret <- auser['secret']
keys <- c("oauth_consumer_key",
"oauth_signature_method",
"oauth_timestamp",
"oauth_nonce",
"oauth_version",
"oauth_token")
values <- c(key.consumer, "HMAC-SHA1", timestamp, nonce, "1.0", token)
if (!is.null(names(argv))) {
keys <- c(keys, names(argv))
values <- c(values, argv)
}
params <- data.frame(key=keys, value=values)
signature <- signForOauth(uri, method, params, secret)
rbind(params, data.frame(key="oauth_signature", value=signature))
}
twitterRequest <- function(uri, method = "GET", argv = c(), verbose = FALSE, auser) {
if (missing(auser)) {
auser <- evalq(auser, envir = globalenv())
}
query <- ""
if (!is.null(names(argv))) {
argv <- sapply(argv, function(x) uriEncode(as.character(x)))
query <- paste(names(argv), argv, sep="=", collapse="&")
}
params <- makeParams(uri, method, argv, auser)
if (method == "POST") {
getURL(url = uri,
verbose = verbose,
httpheader = c(Expect = "",
Authorization = generateOauthHeader(params)),
postfields = query)
} else if (method == "GET") {
getURL(url = ifelse(query != "", paste(uri, query, sep="?"), uri),
verbose = verbose,
httpheader = c(Expect = "",
Authorization = generateOauthHeader(params)))
}
}
4. 端末から次のコマンドを打ち込んでみましょう.
> source("test.R")
Loading required package: XML
Loading required package: RCurl
Loading required package: bitops
> # ツイート
> response <- twitterRequest("http://api.twitter.com/1/statuses/update.json", method = "POST",
+ argv = c(status="ありすちゃんとてれすくんが可愛すぎて生きてるのが辛い"))
> statusID <- fromJSON(response)$id_str
> # ツイートを削除
> twitterRequest(sprintf("http://api.twitter.com/1/statuses/destroy/%s.json", statusID), method = "POST")
使い方
twitterRequest(apiURI, method, argv, verbose)
apiURI: Twitter API の URI
method: "GET" か "POST"
argv: API に渡すパラメータ(名前付きのベクトル) c(page = 2, count = 200) など
varbose: HTTPリクエストとリスポンスを出力するかどうか
おまけ
データを取得するのに認証を通すメリットといえば,非公開ユーザのツイートを取得できたり,API制限に引っかかりにくくなったりという程度ですが,データ取得用の関数を作ってみました.
テストは適当にしかしてません.認証が必要なければ twitteR を使うといいと思います.
使い方
getTweets(user = NULL, n = 20, argv = c(), verbose = FALSE)
user: ツイートを取得したいユーザの ID (整数) か screen_name.NULLの場合は自分.
n: 取得するツイート数
argv: その他のパラメータ
varbose: HTTPリクエストとリスポンスを出力するかどうか
getFriendsIDs(user = NULL, argv = c(), verbose = FALSE)
user: フォローのIDを取得したいユーザの ID (整数) か screen_name.NULLの場合は自分.
argv: その他のパラメータ
varbose: HTTPリクエストとリスポンスを出力するかどうか
getFollowersIDs: getFrinedsIDs と同じ仕様
getFriends(user = NULL, argv = c(), verbose = FALSE)
user: フォロー情報を取得したいユーザの ID (整数) か screen_name.NULLの場合は自分.
argv: その他のパラメータ
varbose: HTTPリクエストとリスポンスを出力するかどうか
getFollowers: getFriends と同じ仕様
getFavs(user = NULL, n = 20, argv = c(), verbose = FALSE)
user: お気に入りを取得したいユーザの ID (整数) か screen_name.NULLの場合は自分.
n: 取得するツイート数
argv: その他のパラメータ
varbose: HTTPリクエストとリスポンスを出力するかどうか
ソースコード
source("test.R")
tryRequest <- function(uri, method, argv, verbose, ntry = 3) {
i <- 0
while (i <= ntry) {
i <- i + 1
json <- twitterRequest(uri, method, argv, verbose)
# if receive string like "<!DOCTYPE html>..., try again
# unless number of trials is larger than ntry"
if (substr(json, 1, 1) != "<") {
break
}
json <- FALSE
}
json
}
# unless argv['include_rts'] is set to true or 1, return less than or equal n statuses
getTweets <- function(user = NULL, n = 20, argv = c(), verbose = FALSE) {
uri <- "http://api.twitter.com/1/statuses/user_timeline.json"
argv <- checkArgv(user, argv)
n <- checkUInt(n)
if ("count" %in% names(argv)) {
warning("argv['count'] is ignored!")
}
if ("page" %in% names(argv)) {
warning("argv['page'] is ignored!")
}
if (n > 200) {
argv["count"] <- 200
} else {
argv["count"] <- n
}
argv["page"] <- 1
limit <- 3200 - (as.numeric(argv["page"]) - 1) * 200
if (n > limit) {
warning(sprintf("only return up to %d statuses", limit))
n <- limit
}
# warning message
wmsg <- sprintf("Couldn't get all data! user: %s", user)
json <- rep(NA, n %/% 200 + 1)
i <- 1
while (n > 0) {
tmpjson <- tryRequest(uri, "GET", argv, verbose)
if (is.logical(tmpjson)) {
warning(wmsg)
} else {
if (tmpjson == "[]") {
break
}
tmpjson <- substr(tmpjson, 2, nchar(tmpjson) - 1)
json[i] <- tmpjson
}
argv["page"] <- as.numeric(argv["page"]) + 1
i <- i + 1
n <- n - 200
}
json <- json[!is.na(json)]
json <- sprintf("[%s]", join(",", json))
fromJSON(json)
}
getFriendsIDs <- function(user = NULL, argv = c(), verbose = FALSE) {
getFFIDs("friends", user, argv, verbose)
}
getFollowersIDs <- function(user = NULL, argv = c(), verbose = FALSE) {
getFFIDs("followers", user, argv, verbose)
}
getFriends <- function(user = NULL, argv = c(), verbose = FALSE) {
getFF("friends", user, argv, verbose)
}
getFollowers <- function(user = NULL, argv = c(), verbose = FALSE) {
getFF("friends", user, argv, verbose)
}
getFavs <- function(user = NULL, n = 20, argv = c(), verbose = FALSE) {
n <- checkUInt(n)
limit <- n
if ("id" %in% names(argv)) {
if (!is.null(user)) {
warning("argv['id'] is ignored!")
} else {
user <- argv["id"]
argv <- argv[not(argv, "id")]
}
}
if (is.null(user)) {
uri <- "http://api.twitter.com/1/favorites.json"
wmsg <- "Couldn't get all data!"
} else {
uri <- sprintf("http://api.twitter.com/1/favorites/%s.json", user)
wmsg <- sprintf("Couldn't get all data! user: %s", user)
}
if ("page" %in% names(argv)) {
warning("argv['page'] is ignored!")
}
argv["page"] <- 1
i <- 1
json <- rep(NA, n %/% 200 + 1)
while (n > 0) {
tmpjson <- tryRequest(uri, "GET", argv, verbose)
if (is.logical(tmpjson)) {
warning(wmsg)
} else {
# if there is no more data, return "[]"
if (tmpjson == "[]") {
break
}
tmpjson <- substr(tmpjson, 2, nchar(tmpjson) - 1)
json[i] <- tmpjson
}
argv["page"] <- as.numeric(argv["page"]) + 1
i <- i + 1
n <- n - 20
}
json <- json[!is.na(json)]
json <- sprintf("[%s]", join(",", json))
fromJSON(json)[1:limit]
}
getFF <- function(type, user, argv, verbose) {
uri <- sprintf("http://api.twitter.com/1/statuses/%s.json", type)
argv <- checkArgv(user, argv)
if ("cursor" %in% names(argv)) {
warning("argv['cursor'] is ignored!")
}
users <- list()
argv["cursor"] <- "-1"
while (argv["cursor"] != "0") {
json <- tryRequest(uri, "GET", argv, verbose)
res <- fromJSON(json)
# sometimes 'users' may be '<NA>', so specify the index
#users <- c(users, res$users)
users <- c(users, res[[1]])
argv["cursor"] <- res$next_cursor_str
}
users
}
getFFIDs <- function(type, user, argv, verbose) {
uri <- sprintf("http://api.twitter.com/1/%s/ids.json", type)
argv <- checkArgv(user, argv)
json <- tryRequest(uri, "GET", argv, verbose)
if (is.logical(json)) {
stop("Couldn't get data, please try again later!")
}
unlist(fromJSON(json))
}
not <- function(x, name) {
-which(names(argv) == name)
}
is.int <- function(n) {
if (!is.numeric(n)) {
FALSE
} else {
n %% 1 == 0
}
}
join <- function(collapse, str) {
paste(str, collapse = collapse)
}
checkArgv <- function(user, argv) {
check <- function(argv) {
if ("user_id" %in% names(argv)) {
warning("user_id specified in argv is ignored!")
argv <- argv[not(argv, "user_id")]
}
if ("screen_name" %in% names(argv)) {
warning("screen_name specified in argv is ignored!")
argv <- argv[not(argv, "screen_name")]
}
argv
}
if (is.numeric(user)){
argv <- check(argv)
argv["user_id"] <- user
} else if (is.character(user)) {
argv <- check(argv)
argv["screen_name"] <- user
} else if (!is.null(user)) {
stop("Error: invalid arguments!")
}
argv
}
checkUInt <- function(n) {
nName <- substitute(n)
if (!is.numeric(n)) {
n <- as.numeric(n)
if (is.na(n)) {
stop(sprintf("'%s' is an invalid value!", as.character(nName)))
}
}
if (n <= 0) {
stop(sprintf("'%s' must be a positive value!", as.character(nName)))
} else if (!is.int(n)) {
warning(sprintf("'%s' is casted to integer!", as.character(nName)))
n <- as.integer(n)
}
n
}
もっと適当に作るはずが,止まらなくなってしまってどんどん twitteR に似たような感じになってしまっていったという…
twitteR に OAuth認証を組み込んだ方が良かったかも.
あ,Osaka.R でしょぼい発表のスピーカーやります.