twitterに投稿されたゲレンデ写真を纏めたサイト+その他の技術情報

twitterに投稿された(略 をどうやって作成したかという技術メモ。
### 作成手順
以下の3ステップで作成しています。
1. twitterAPIを利用して#snowJPの検索結果を収集する。
2. 収集した検索結果を加工してRSS化する。
3. 指定したRSSを自動投稿するプラグインをwordpressに導入し、2で加工したRSSを登録する。
1、2で使ってるプログラムは最後に紹介するとして。3はFeedWordPressというプラグインを利用します。
[FeedWordPressでマルチブログする – [Mi]みたいなもの](http://d.hatena.ne.jp/mitaina/20090711/1247273446)
これでRSSさえ作ってしまえば、最後のアウトプット先をWordPressにすることができます。普通こういうWebサービスは自分で"ガワ"をデザインするのですが
* 自分のデザイン能力の低さ
* wordpressの使いやすさ
* 携帯やiPhone対応への手間(wordpressには携帯・iPhoneビュー用のプラグインもある)
など考えると、wordpressを使うのがベストかな、と判断しました。
ということで、後はRSSを作るだけ!スクレイピングもRSS作成も得意だからすぐだよね!…とはいかず。
### 苦労した点
#### 1. twitterAPIのパース
twitterではスクレイピングを規約で明示的に禁止しているので、APIを使って情報を取得します。提供形式はjson、atomの2種類で慣れているatomを選んでrubyのrssライブラリでパースしよー♪…と思いきや、なぜか対応できないと跳ねられる。しかたないのでREXMLを使ってXML文章としてパースしたんだけど、**REXMLの使い方をすっかり忘れていて**パースするだけなのにめちゃ苦労しました。
#### 2. RSS1.0と2.0の仕様の違い
1.で情報は取得できたので、それをRSSに流し込むだけ。…流し込むまでは良かったのですが**RSS1.0では画像の展開ができない**という制限を知らなかったので、RSS1.0で作ってしまい画像が表示されませんでしたとさ
#### 3. FeedWordPressとrubyのrss/makerの仕様制限
RSS2.0で作成してFeedWordPressに読み込ませた!…のだけど、今度は改行が反映されないorz 調べたところ、どうやらFeedWordPressがRSS2.0を読み込む場合、
1. 「content:encoded」があれば本文として使う。
2. 「content:encoded」がなければ「description」として使う。ただし改行は反映されない。
仕様らしく、僕が作成したRSS2.0は「description」しか作ってなかったので改行コードが反映されなかったと。んじゃ「content:encoded」を作ればいいじゃん、というところですが、rss/makerライブラリの仕様でRSS2.0作成時には「content:encoded」を利用できない!
仕方ないのでRSS2.0を作成し文字列として読み込んだ後、「description」の下に1行挿入し「content:encoded」とする超泥臭い作り方で乗り切りました。うーん、RSSライブラリは完璧じゃないよなぁ…。
### ということでコード公開

[ruby]
require 'kconv’
require 'open-uri’
require 'uri’
require 'kconv’
require 'cgi’
require 'rss/maker’
require 'rexml/document’
include REXML
#
# 0.初期設定
#
# プログラムルートパス
#
path = Dir.pwd + '/’
#
# ———————————————————————————————-
# 1.twitterAPIを使用してRSS保存
# ———————————————————————————————-
#
=begin
# 検索例(twitter公式)
# http://search.twitter.com/search.atom?q=%23snowJP&locale=ja
# http://search.twitter.com/search.json?q=%23snowJP&locale=ja
# 検索例(twitter検索)
# http://pcod.no-ip.org/yats/search?query=%23snowJP&rss
# http://pcod.no-ip.org/yats/search?query=%23snowJP&json
=end
#
# 検索語句を配列で指定
serachWord = ['スノーボード’,’イエティ’,’軽プリ’]
#
# 全てをURLエンコード
serachWordTemp = Array.new
serachWord.each do |text|
serachWordTemp << CGI.escape(text)
end
serachWord = serachWordTemp
#
# ファイル保存。ファイル名は検索語句+日時
#
#=begin
# ★APIへのアクセス制限を回避するため必要な時だけコメントオフにする
time = Time.now.strftime("%Y%m%d%H%M%S") #=> 20091014204219
serachWord.each do |text|
url = 'http://search.twitter.com/search.atom?q=’ + text + '&locale=ja’
filename = text + '_’ + time + '.atom’
File.open(path + filename, 'wb’) do |f|
f.write open(url).read
end
end
#=end
#
# ★検証用
#time = '20091015223148’
#
# ———————————————————————————————-
# 2-1.保存したRSSを重複なしで一つのファイルに纏める(タブ区切り、先頭は日時)
# ———————————————————————————————-
#
tempText = 'main_snow_text.txt’
File.open(path + tempText, 'wb’) do |f|
serachWord.each do |text|
filename = text + '_’ + time + '.atom’
doc = REXML::Document.new File.open(path + filename,"r")
entrySize = doc.root.elements["openSearch:itemsPerPage"].text.to_i
unless entrySize == "" or entrySize == 0
1.upto(entrySize) do |i|
entry = doc.root.elements["entry[#{i}]"]
# 指定した検索ヒット数よりヒットしたpostが少なかった場合対応
break if entry == nil
# f.write entry.text("id")
# f.write entry.text("title")
# f.write entry.text("updated")
f.write entry.text("published") + "\t"
f.write entry.text("content").gsub(/\n/,"") + "\t"
f.write entry.text("twitter:source") + "\t"
f.write entry.text("author/name") + "\t"
f.write entry.text("author/uri") + "\t"
entry.each_element_with_attribute("type","text/html") do |elem|
f.write elem.to_s.gsub(/.*http/,’http’).gsub(/’ rel=’.*/,") + "\t"
end
entry.each_element_with_attribute("type","image/png") do |elem|
f.write elem.to_s.gsub(/.*http/,’http’).gsub(/’ rel=’.*/,") + "\t"
end
f.write "\r\n"
end
end
end
end
#
# ———————————————————————————————-
# 2-2.纏めたファイルから順番に読み出し配列に入れる+フィルタリング
# ———————————————————————————————-
#
margeArr = []
#
File.open(path + tempText) do |f|
while line = f.gets
margeArr << line
end
end
#
margeArr.sort!
margeArr.uniq!
#
# フィルタリング
=begin
margeArr.each do |line|
case line
when %r[<a href="http://movapic.com/pic/\w+]
margeArrFilter << line
when %r[<a href="http://twitpic.com/\w+]
margeArrFilter << line
when %r[http://f.hatena.ne.jp/twitter/\w+]
margeArrFilter << line
else
end
end
=end
#
# 最新の15個を取り出し
rssNum = 200
margeArr.slice!(0, margeArr.size – rssNum) if margeArr.size – rssNum > 0
# ———————————————————————————————-
# 2-3.画像のみのRSSを生成する。画像パスは展開する
# ———————————————————————————————-
#
snowRssFile = 'snowJP_All.rss’
#snowRssFile = 'snowJP_newComer.rss’
rssArr = margeArr
f_rss = File.open(path + snowRssFile, "wb")
#
# RSSのバージョンを指定してRSSオブジェクトを生成します
rss = RSS::Maker.make("2.0") do |maker|
#
#適用するスタイルシートを指定します
xss = maker.xml_stylesheets.new_xml_stylesheet
xss.href = "./rdf.xsl"
#
# 記事を配信しているページに関する情報を設定します
maker.channel.about = "http://prius.cc/twit_snow/"
maker.channel.title = "snowJP"
maker.channel.description = "snowJP"
maker.channel.link = "http://prius.cc/twit_snow/"
#
# RSSのitemを更新日が新しい順番にソートする機能を有効にする
maker.items.do_sort = true
#
# 以下では記事に関する情報を指定します
rssArr.each do |elem|
elemArr = elem.split(/\t/)
item = maker.items.new_item
# 各種形式変換ここから——————–
formatTime = elemArr[0].gsub('T’,’ ').gsub('Z’,")
# もし画像パスがあれば展開する。
case elemArr[1]
when %r[<a href="http://movapic.com/pic/\w+]
# パーマリンクへのURL取得
parmlink = elemArr[1].slice(%r[http://movapic.com/pic/\w+])
# サムネイル用URL生成
thumnail = 'http://image.movapic.com/pic/s_’ + parmlink.slice(%r[http://movapic.com/pic/(\w+)], 1) + '.jpeg’
when %r[<a href="http://twitpic.com/\w+]
# パーマリンクへのURL取得
parmlink = elemArr[1].slice(%r[http://twitpic.com/\w+])
# サムネイル用URL生成
thumnail = 'http://twitpic.com/show/thumb/’ + parmlink.slice(%r[http://twitpic.com/(\w+)], 1)
when %r[http://f.hatena.ne.jp/twitter/\w+]
# パーマリンクへのURL取得
parmlink = elemArr[1].slice(%r[http://f.hatena.ne.jp/twitter/\w+])
# サムネイル用URL生成
hatenaParmlink = parmlink.slice(%r[http://f.hatena.ne.jp/twitter/(\w+)], 1)
hatenaDateTime = hatenaParmlink[0,8]
thumnail = 'http://img.f.hatena.ne.jp/images/fotolife/t/twitter/’ + hatenaDateTime + '/’ + hatenaParmlink + '_120.jpg’
else
parmlink = ""
end
unless parmlink == ""
descriptionStr = '<p>’ + elemArr[1] + '</p>’ + '<p><a href="’ + parmlink + '" target="_blank"><img src="’ + thumnail + '"></a></p>’
else
descriptionStr = '<p>’ + elemArr[1] + '</p>’
end
#
# 各種形式変換ここまで——————–
item.title = elemArr[3] + 'の’ + formatTime + 'の投稿’
item.link = elemArr[5]
item.description = '<![CDATA[' +
descriptionStr +
'<p>’ + '<img src="’ + elemArr[6] + '" />’ + '<a href="’ + elemArr[4] + '">’ + elemArr[3] + '</a>の’ + '<a href="’ + elemArr[5] + '">’ + formatTime + '</a>の投稿’ + '</p>’ +
' ]]>’
item.date = Time.parse(formatTime)
end
end
#
=begin
百景
http://movapic.com/pic/200910031352404ac6d8986af75
http://image.movapic.com/pic/s_200910031352404ac6d8986af75.jpeg
#
twitpic
http://twitpic.com/k5boe
http://twitpic.com/show/thumb/k5boe
#
http://f.hatena.ne.jp/twitter/20091011145112
http://img.f.hatena.ne.jp/images/fotolife/t/twitter/20091011/20091011145112_120.jpg
#
20091011145112
20091011145112_120.jpg
#
<a href="http://f.hatena.ne.jp/twitter/20091017214947" target="_blank"><img src="http://img.f.hatena.ne.jp/images/fotolife/t/twitter/20091017/20091017214947_120.jpg" class="tl-image"></a>
#
#
=end
#
# RSSを出力します
f_rss.puts rss
f_rss.close
# <![CDATA[の"<"を実態参照しないために変換
data = File.read(path + snowRssFile)
data2 = data.gsub('&lt;’,'<').gsub('&gt;’,’>’).gsub('&quot;’,’"’)
#
f_rss = File.open(path + snowRssFile, "wb")
#
# content:encodedを追加するため編集
# ※descriptionでは改行や<p>コードなどがWordPress側で反映されない。
# またrss/makerにはRSS2.0でcontent:encodedを追加する機能はない。
# atomにはcontent:encodedを追加できるがそもそもバグがありatomを生成できない。
data2.each_line do |line|
case line
=begin
★検証時はコメントオフ?
when %r[rss version="2.0"]
f_rss.puts line
f_rss.puts ' xmlns:content="http://purl.org/rss/1.0/modules/content/"’
=end
when %r[<description>] && line !~ %r[<description>snowJP</description>]
f_rss.puts line
f_rss.puts line.gsub('description’,’content:encoded’)
else
f_rss.puts line
end
end
#
# ★Todo:画像パスの展開
# RSSもうちょい見やすくする
# http://exco.sakura.ne.jp/twit_snow/
#
# 0:日時
# 1:コメント
# 2:soruce
# 3:author
# 4:author page
# 5:perm link
# 6:profile photo
[/ruby]
※ ファイルダウンロードはこちら
※ wordpressのSyntaxHighlightには空行を含めない制約があるので、空行には先頭に"#"を入れてます。
※ 検索ワード指定部分は配列になっていて、自分の好きな言葉を指定するとその分APIを叩いてRSSを生成します。サーバに置いてcronで実行することで自家製RSS製造機に!
### Q&A
Q.こんなしょぼいプログラムを公開して恥ずかしくないのですか?
A.僕の本業はシステム管理。プログラマーではないので恥ずかしくありません><
Q.それにしても、あまりに保守性が低くありませんか?
A.さすがに自分でも見難いので、もうちょいきれいにします。