SSブログ
RSS [RSS1.0] [RSS2.0]
共謀罪を含む改悪組織犯罪処罰法は
【「共謀罪」法 衆参両院議員の投票行動(東京新聞 2017/6/16)】

TwitterのAPI 1.1で取得したユーザーのJSONデータからRSSフィードを作成する

 TwitterがツイートのRSSフィードを配信していることを知ってから、ThunderbirdをRSSリーダーとして利用してツイートを読んでいる。SNSというよりミニブログのような状態である。しかし、API version 1API version 1.1に完全移行したらツイッターはRSS配信をやめるらしい。期限は2013/5/8である。
 ツイートをThunderbirdで見る快適さに慣れてしまったので、何とか5/8以降もRSSフィードを取得したい。個人でRSSフィードを作ってくる人もいたが、大勢の人が利用するとAPI制限などで動かなくなるのではと心配になった。ソースを提供してくれているので、自分用をGAEなどのサーバーにアップロードすれば良いのかもしれないが、私の能力不足で失敗した。
 一時期は諦めていたのだが、以前に見て一度は諦めていた【TwitterのAPI 1.1で取得したユーザーのJSONデータからAtomフィードを作成する】をもう一度試してみた。前回失敗したのは次の部分である。

このファイルをcronなどで起動するか、ブラウザから実行すれば、「ユーザー_atom.xml」というAtomファイルを同じディレクトリに出力します。
(TwitterのAPI 1.1で取得したユーザーのJSONデータからAtomフィードを作成する)

 cronが何を指すのか知らなかったし、ブラウザで実行したけれどソースが表示されるだけでAtomファイルを作成してくれなかった。後に、ローカルサーバーを起動して、ローカルサーバーでアクセスできるフォルダーの中にファイル一式を入れて、http://localhost/ で始まるURLにアクセスしなければいけないことを知った。XAMPPというソフトを知ってインストールした後に試したら、実際に動いて、Atomファイルが作られた。

 Atomファイルが作られることは分かったが、毎回XAMPPを起動して作成するのは手間がかかる。そこで、とりあえずPHP対応のサーバーを借りてアップロードして実行した。ディレクトリー内にソフトが書き込めるようアクセス権の変更などが必要だったが、ファイルが作成されることを確認した。ThunderbirdでファイルのURLにアクセスしたら、取得できた。

 次に、AtomだとThunderbirdでは更新日時(Thunderbirdでのダウンロード日時?)が表示されてツイートされた日時が表示されないので、RSSフィードを作成するようにスクリプトを書き換えた。RSSフィードの形式は以前に「独り言」のフィードを「なんちゃってRSS」と呼んで作ったことがあったので、書き換えは容易だった。ついでに、一つのファイルで複数のRSSファイルが作成されるようにスクリプトを書き換えた。これは、小粋空間さんが提供してくれたスクリプトの部分をサブルーチン化(関数化)することで実現できた。スクリプトはこの記事の最後に載せる。

 最後に残った難問はRSSフィードを作成するファイル(プログラム)のURLを自動的に起動する方法である。cronを使えば良いということまでは分かったが、使い方が分からず、cronを使わない方法も検討した。しかし、偶然に【GAEでcronを動かすメモ】というページを見つけて、GAE公式ページの説明よりも分かりやすかったので、そのまま真似して試してみた。成功した。

 小粋空間さんとRe*lierさんには感謝の気持ちでいっぱいである。
 m(_ _)m

 では、最後に小粋空間さんのスクリプトを改造した私のスクリプトを載せておく。

<?php
require_once("twitteroauth-master/twitteroauth/twitteroauth.php");
date_default_timezone_set('Asia/Tokyo');

function fff($name, $filename, $listcount, $connection){

$request = $connection->OAuthRequest('https://api.twitter.com/1.1/statuses/user_timeline.json?screen_name=' . $name, "GET", array("count"=>$listcount));
$results = json_decode($request);

//var_dump($results);

if (isset($results)) {
    $file = fopen($filename, 'w');
    fwrite($file, '<?xml version="1.0" encoding="utf-8"?>' . "\n" . '<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">' . "\n" . '<channel>'. "\n" . '<atom:link href="このスクリプトを入れたディレクトリのURL' . $filename . '" rel="' . $name . '" type="application/rss+xml" />' . "\n");
    fwrite($file, '<title>RSS (@' . $name . ")</title>\n");
    fwrite($file, '<link>' . 'https://twitter.com/' . $name . '</link>' . "\n");
    fwrite($file, '<description>作られるRSSファイルの説明文</description>' . "\n");
    fwrite($file, '<lastBuildDate>');
    foreach ($results as $key => $val) {
        fwrite($file, date('D, d M Y H:i:s +0900', strtotime($results[$key]->created_at)));
        break;
    }
    fwrite($file, '</lastBuildDate>' . "\n");
    fwrite($file, '<language>ja</language>' . "\n\n");
    foreach ($results as $key => $val) {
        fwrite($file, '<item>' . "\n");
        fwrite($file, '<link>' . 'https://twitter.com/' . $name . '/status/' . $results[$key]->id_str . '</link>' . "\n");
        fwrite($file, '<guid isPermaLink="true">' . 'https://twitter.com/' . $name . '/status/' . $results[$key]->id_str . '</guid>' . "\n");
        fwrite($file, '<pubDate>' . date('D, d M Y H:i:s +0900', strtotime($results[$key]->created_at)) . '</pubDate>' . "\n");
        fwrite($file, '<title>' . "\n" . htmlspecialchars($results[$key]->text) . "\n" . '</title>' . "\n");
        fwrite($file, '<description><![CDATA[' . "\n");
        fwrite($file, $results[$key]->text);
        fwrite($file,  "\n" . ']]></description>' . "\n" . '</item>' . "\n");
    }
    fwrite($file, '</channel>' . "\n" . '</rss>' . "\n");
    fclose($file);
}

}

$consumerKey = "xxxxxxxxxxxxxxxxxxxxxx";
$consumerSecret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
$accessToken = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
$accessTokenSecret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";

$Auth = new TwitterOAuth($consumerKey,$consumerSecret,$accessToken,$accessTokenSecret);

fff("self7777", "RSS/self7777.xml", "30", $Auth);
fff("nhk_news", "RSS/nhk_news.xml", "50", $Auth);
fff("nhk_heart", "RSS/nhk_heart.xml", "20", $Auth);
fff("nhk_hensei", "RSS/nhk_hensei.xml", "20", $Auth);
fff("nhk_ohayou", "RSS/nhk_ohayou.xml", "20", $Auth);
fff("NHK_onair", "RSS/NHK_onair.xml", "10", $Auth);
fff("NHK_PR", "RSS/NHK_PR.xml", "20", $Auth);
fff("TwitterDevJP", "RSS/TwitterDevJP.xml", "10", $Auth);

?>

 $consumerKey、$consumerSecret、$accessToken、$accessTokenSecretは小粋空間さんのページで教えて頂いた通りの手法で取得して入力する。
 「fff("self7777", "RSS/self7777.xml", "30", $Auth);」の部分は「fff("取得したいユーザー名(他人でもOK)", "ディレクトリー名/ユーザー名.xml", "取得したいツイートの数", $Auth);」という構成で、並べた数だけRSSファイルが作られる。数が多いとサーバーへの負荷が大きくなり、タイムアウトすることもある。作られるRSSファイルは「ユーザー名.xml」である。変えることもできるが、このままの方が分かりやすいと思われる。取得したいツイートの数は少ない方がサーバーへの負荷が小さいと思われる。
 「ディレクトリー名/」が無い場合は、このスクリプトが置いてあるディレクトリーにRSSファイルが作られる。私はごちゃごちゃすると嫌なので、スクリプトと同じディレクトリーにRSSというディレクトリーを作って、RSSというディレクトリーの中にRSSファイルを格納した。また、小粋空間さんのページでは、「twitteroauthフォルダに、下記の内容をコピーして任意のPHPファイル名で配置してください」と書いてあるが、正しくは「twitteroauthフォルダと同じフォルダに」だろう。私の場合は「twitteroauth-master」フォルダと同じフォルダに上のスクリプトのPHPファイルを配置したので、2行目のrequire_onceの所が小粋空間さんと違っている。理由は「twitteroauth-master」フォルダや「twitteroauth」フォルダに入れると、ダウンロードした「PHP認証ライブラリ」の中に余計なファイルが入って自分が分からなくなるかもしれないからである。JSONデータを利用して別のファィルを作るスクリプトを用意するかもしれないし…。
 それから、上のスクリプトのファイルは、文字コードを「UTF-8N」にして、改行コードを「LF」にして保存した。作られた.xmlファイルも同じ文字コードと改行コードだった。他の文字コードや改行コードではどうなるかは試してない。

 以上が、私が長時間を費やして見つけた「API1.1でもTwitterのRSSフィードを取得する方法」である。同じことをしたい人の参考になれば幸いである。スクリプト以外の細かい所は【TwitterのAPI 1.1で取得したユーザーのJSONデータからAtomフィードを作成する】【GAEでcronを動かすメモ】を真似してほしい。また、スクリプトが正常に動くかどうかアップロードする前にローカルサーバーで実験するのが礼儀なので、XAMPPの使い方などを調べて、試してほしい。

追記(2013/4/14):
 phpファイルのスクリプトで取得したいRSSフィードを並べすぎたせいで、GAEのログにタイムアウトエラーが並んだ。それでも問題なくRSSフィードを取得できていたのだが、気持ち悪いので、phpファイルに並べるIDの数を減らした。タイムアウトエラーは、phpファイルの動作が10秒以内に終わらないと発生するらしい。だから、一つのphpファイルで取得するRSSフィードの数を減らして、一つのGAEアプリから複数のphpファイルにアクセスするようにした。
 具体的には、【GAEでcronを動かすメモ】のpost.pyを変更した。元は次のとおりである。

from google.appengine.api import urlfetch
url = "自分の叩きたいphpのアドレス"
result = urlfetch.fetch(url=url,
method=urlfetch.GET,
headers={'Cache-Control':'max-age=0'},
deadline=10)
if result.status_code == 200:
print result.content

 これを次のように変えた。

from google.appengine.api import urlfetch
urls = ("自分の叩きたいphpのアドレス一つ目",
"自分の叩きたいphpのアドレス二つ目",
"自分の叩きたいphpのアドレス三つ目",
)
for url in urls:
result = urlfetch.fetch(url, deadline=10)
if result.status_code == 200:
print result.content

 これで、問題なく動いているようである。
 一つのpost.pyで複数のphpファイルを叩く方法は、【GAE/Python で HTTPリクエストを並列化 « Stop Making Sense】の「普通の使い方 (同期処理)」の「(複数のURLフェッチを行う)」を参考にした。その記事では、さらに非同期リクエストを行う方法にすることが目的のようで、私も参考にしようと思ったが、非同期にしても、一つのphpファイルの動作が終わるまで10秒以上かかるようだとエラーが発生することは同期の場合と変わらないので、非同期にする必要性を感じず、同期のまま複数のURLフェッチを行うことにした。いまのところ、エラーがほとんど発生しなくなって、綺麗である。ただ、一つのGAEアプリが動作する時間は50秒近くと長くなった。エラーが発生していた時は10秒で強制終了だった。長くなった理由は、一つのpost.pyで叩くphpファイルを分割した以上に増やして、取得するRSSフィードを増やしたからである。たぶん、60秒を超えるようだと、タイムアウトエラーが発生するのだろう。いまところは大丈夫である。

追記(2013/4/17):
 上の追記(2013/4/14)の方法だと、叩いたphpファイルの数が多ければ多いほど時間がかかり、合計で60秒を超えられないので限界があるし、途中のphpファイルを叩いた時にエラーが発生すると、その後に叩く予定だったphpファイルを叩いてくれない。
 そこで、やはり非同期リクエストを行う方法に変更することにした。【GAE/Python で HTTPリクエストを並列化 « Stop Making Sense】の「非同期リクエスト」の「複数のURLフェッチを並列化」のスクリプトをそのまま利用させてもらった。「# 取得結果に対する処理」と「# エラー処理」をどうしたら良いか分からなかったので、適当に決めて、とりあえず次のスクリプトにした。

from google.appengine.api import urlfetch
def handle_result(rpc):
  try:
    result = rpc.get_result()
    if result.status_code == 200:
      text = result.content
  except urlfetch.DownloadError:
    text = url
def create_callback(rpc):
  return lambda: handle_result(rpc)
urls = ("自分の叩きたいphpのアドレス一つ目",
        "自分の叩きたいphpのアドレス一つ目",
        "自分の叩きたいphpのアドレス一つ目",
)
rpcs = []
for url in urls:
  rpc = urlfetch.create_rpc(deadline=10)
  rpc.callback = create_callback(rpc)
  urlfetch.make_fetch_call(rpc, url, headers={'Cache-Control':'max-age=0'})
  rpcs.append(rpc)
for rpc in rpcs:
  rpc.wait()

 この変更により、追記(2013/4/14)のスクリプトで8つのphpファイルを叩いて44秒かかっていた処理が、8秒前後で済むようになった。一つのphpファイルが10秒以内で済むようにしてあるので、どんなに叩くファイルを増やしてもエラーが発生するファイルが無い限り10秒以内で収まるのだろう。
 ところで、どのphpファイルにどのくらいの時間がかかっているかログが欲しかったので【GAE/Python で HTTPリクエストを並列化 « Stop Making Sense】の「非同期リクエストを試す」の「テスト用GAEアプリのソースコード一式」を使って試してみたが、うまくいかなかった。app.yamlを次のように書き換えて、Python 2.5 のアプリだったのをPython 2.7 に対応させたからかもしれない。

application: Application名
version: 1
runtime: python27
api_version: 1
threadsafe: false

handlers:
- url: /cron/post
  script: post.py
  login: admin

 「threadsafe: true」にして「post.py」を「post.app」にしても動くようにする方法は、まだ分からない。

追記(2013/5/18):
 追記(2013/4/17)の方法で順調に8分間隔でRSSフィードが更新できていたのだが、今日は90分近くもRSSフィードが更新されない現象が起こった。←解決。次の追記参照。
 午前中にどのような間隔で更新されるか記録してグラフにした。

RSSフィードの更新頻度
(図をクリックすれば拡大できます)

 RSSフィードを作成している60アカウントの状況を確認したのだが、同じphpファイルで作成するRSSファイルの更新間隔は同じだった。更新時刻はphpサーバーで表示される時刻を記録した。更新されないRSSフィード用のphpファイルにブラウザのアドレス欄から直接アクセスしたら、RSSファイルは更新された。このことから、GAEがphpサーバーのphpファイルを叩いてないのかもしれない。すなわち、cronが正常に機能してないのかもしれない。
 GAEのログでは確認できず、原因不明である。幸い、今日はツイート数が少なかったので、取得漏れは生じてなさそうである。

追記(2013/5/18):
 上の追記の問題は【GAEでcronを動かすメモ】にヒントが書いてあった。

・5行目はキャッシュコントロールです。
GAEはどうもfetchしたURLの中身をキャッシュするらしく、それではデプロイしてからしばらく経つとphpを動かしてくれなくなります。
(GAE側はSuccessと出ている、phpも直接手動なら叩けるのにどうも動かない場合はこれを疑うと良いかと)
この1行を入れておくことで、キャッシュすることなく毎回きちんと叩いてくれるようになります。
GAEでcronを動かすメモ | Re*lier

 phpファイルの頭にアクセス直後にテキストファイルを作るスクリプトを入れてGAEからphpにアクセスされているのか確認したところ、テキストファイルが作られないことがあったので、phpファイルにアクセスしてない可能性が浮かんだ。そこで、上記のキャッシュの話を思い出した。GAEはキャッシュにアクセスしていてphpファイルにはアクセスしていなかったのかもしれない。
 そこで、post.pyに上の赤文字のように「, headers={'Cache-Control':'max-age=0'}」を追加したところ、RSSフィードが8分間隔で更新されるようになった。

追記(2013/11/10):
 GAE内でJSONをRSSフィードに変換できるようになったので、続きを書いた。
 【Twitterで取得したユーザーのJSONデータをRSSフィードに変換する(GAEを使う)】


タグ:Twitter RSS PHP JSON
nice!(2)  コメント(0)  トラックバック(0) 
カテゴリー:ブログを読んで
共通テーマ:パソコン・インターネット

読者の反応

nice! 2

sonet-asin-area

コメント 0

コメントを書く 

お名前:[必須]
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。
captcha

トラックバック 0

トラックバックの受付は締め切りました

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。