‘Twitter’ タグのついている投稿

コラムなう

2010年2月13日 土曜日

また新しいbot作ってます。@columnNOW

とりあえず、いろんなニュースサイトからコラム(というか記名記事)を拾ってきて表示するbotにする予定。

作ってみようと思ったきっかけは、2chのビジネスnews板などで煽り記事が出るたびに、元サイトに見に行って著者名を見てがっくり&納得するということが時々起きてたからです。見てがっくりするより、あらかじめ著者が分かっておけばいいんじゃね?と。

技術的にはHTML Scrapingの練習にもなってます。今回はNokogiriというRubyのライブラリを使ってます。これがあるとないとでは効率が大違い。html.css(”p.author”)とかjQueryっぽく出来るのはいいわー。

今対応しているサイト。

  • ASCII.jp
  • ダイヤモンド・オンライン
  • ITmedia
  • 日経NET IT-PLUS
  • ITpro
  • マイコミジャーナル
  • ニューズウィーク日本版
  • 日経ビジネス
  • 日経 Ecolomy
  • 日経トレンディネット
  • PC Watch

ついったーでのぽりしー

2010年2月5日 金曜日

とりあえずざっとまとめ。

フォロー&リムーブ

  • 日本語でつぶやいている人は基本的にフォロー返します。後でリムーブすればいいやというタイプなので。
  • こちらからフォローすることは滅多にないです。
  • 政治的な発言は嫌いなので、たくさん流したり、公式RTを使った人はリムーブの対象になりやすいです。
  • それ以外でリムーブすることは滅多にないです。
  • リムーブされた場合は気がつき次第リムーブ返すことが多いです。

自分のついーとについて

  • 流量は30〜90/日くらい。
  • 内容は適当です。挨拶とか飯とかの話が多め。

くぎゅうについて

  • くぎゅう=釘宮理恵
  • むくぎゅう:起きた
  • おはくぎゅう:おはよう
  • いってくぎゅう:行ってくる
  • とうちゃくぎゅう:到着
  • いただくぎゅう:いただきます
  • きたくぎゅう中なう:帰宅中
  • きたくぎゅう:帰宅
  • ただくぎゅう:ただいま
  • おかくぎゅう:おかえり
  • ばたんくぎゅう:寝る
  • おやくぎゅう:おやすみ

よるほーbotのソースコード

2010年1月31日 日曜日

とりあえずPOSTする箇所とかは除いて、コアの部分だけ。
質問はコメントにください。

?Download yoruho.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
#!/home/ikemo/local/bin/ruby -Ku
 
# -*- encoding: utf-8 -*-
 
require 'net/http'
require 'rexml/document'
require 'timeout'
require 'time'
require 'dbm'
require 'uri'
 
include REXML
 
$yoruho = URI.encode("よるほ")
$yoruho_pittari = URI.encode("さんよるほーピッタリ賞です。おめでとうございます。")
 
user = 'yoruho'
pass = 'password'
 
def get_yoruho_buzztter(max_id)
  rss = ""
  begin
    res = nil
    now = Time::now
    yoru = Time::local(now.year, now.month, now.day, 0, 0, 0)
 
    timeout(60, IOError) {
      http = Net::HTTP.new("buzztter.com")
      if max_id then
        res = http.get("/ja/rss/#{$yoruho}?since=#{yoru.to_i}&max_id=#{max_id}",
                      {'User-Agent' => "Ruby/#{RUBY_VERSION}"})
      else
        res = http.get("/ja/rss/#{$yoruho}?since=#{yoru.to_i}",
                      {'User-Agent' => "Ruby/#{RUBY_VERSION}"})
      end
      rss = res.body
    }
 
    raise IOError if res.code != "200"
  end
 
  id = nil
  success = []
  doc = Document.new(rss)
  doc.elements.each("/rss/channel/item") {|item|
    title = item.elements["title"].text
    title_regexp = %r|([^:]+): .*|
    title_regexp.match(title)
    name = $1
    time = Time::parse(item.elements["pubDate"].text).localtime
    link = item.elements["link"].text
    link_regexp = %r|http://twitter.com/[^/]+/statuses/(.*)|
    link_regexp.match(link)
    id = $1
    if time.day == Time::now.day && time.hour == 0 && time.min == 0 && time.sec == 0 then
      success.push(name)
    elsif time.day != Time::now.day then
      return success, true, id
    end
  }
 
  return success, false, id
end
 
def get_yoruho_official(i)
  rss = ""
  begin
    res = nil
 
    timeout(60, IOError) {
      http = Net::HTTP.new("search.twitter.com")
      res = http.get("/search.atom?q=#{$yoruho}&page=#{i}&rpp=50",
                    {'User-Agent' => "Ruby/#{RUBY_VERSION}"})
      rss = res.body
    }
 
    raise IOError if res.code != "200"
  end
 
  success = []
  doc = Document.new(rss)
  doc.elements.each("//feed/entry") {|entry|
    url = entry.elements["author/uri"].text
    name_regexp = %r|^http://twitter.com/(.*)$|
    name_regexp.match(url)
    name = $1
    time = Time::parse(entry.elements["updated"].text).localtime
    if time.day == Time::now.day && time.hour == 0 && time.min == 0 && time.sec == 0 then
      success.push(name)
    elsif time.day != Time::now.day then
      return success, true
    end
  }
 
  return success, false
end
 
def get_yoruho(i)
  rss = ""
  begin
    res = nil
 
    timeout(60, IOError) {
      http = Net::HTTP.new("pcod.no-ip.org")
      res = http.get("/yats/search?query=#{$yoruho}&page=#{i}&rss",
                    {'User-Agent' => "Ruby/#{RUBY_VERSION}"})
      rss = res.body
    }
 
    raise IOError if res.code != "200"
  end
 
  success = []
  doc = Document.new(rss)
  doc.elements.each("//feed/entry") {|entry|
    name = entry.elements["author/name"].text
    time = Time::parse(entry.elements["updated"].text).localtime
    if time.day == Time::now.day && time.hour == 0 && time.min == 0 && time.sec == 0 then
      success.push(name)
    elsif time.day != Time::now.day then
      return success, true
    end
  }
 
  return success, false
end
 
def get_yoruho_timeline(max_id)
  xml = ""
  begin
    res = nil
 
    timeout(60, IOError) {
      path = if max_id != nil then
               "/statuses/friends_timeline.xml?count=200&max_id=#{max_id}"
             else
               "/statuses/friends_timeline.xml?count=200"
             end
      req = Net::HTTP::Get.new(path)
      req.basic_auth "yoruho", "password"
 
      Net::HTTP.start("twitter.com", 80) {|http|
        res = http.request(req)
 
        xml = res.body
      }
    }
 
    raise IOError if res.code != "200"
  end
 
  success = []
  id = nil
  doc = Document.new(xml)
  doc.elements.each("/statuses/status") {|status|
    screen_name = status.elements["user/screen_name"].text
    time = Time::parse(status.elements["created_at"].text).localtime
    text = status.elements["text"].text
    id = status.elements["id"].text
 
    if text.include?("よるほ") && time.day == Time::now.day && time.hour == 0 && time.min == 0 && time.sec == 0 then
      success.push(screen_name)
    elsif time.day != Time::now.day then
      return success, true, id
    end
  }
 
  return success, false, id
end
 
def post(name, status)
  # ここでTwitterにPOSTする
end
 
#
# kokokara
#
success_list_timeline = []
max_id = nil
(1..50).each {|i|
  count = 0
 
  begin
    (success, end_flag, max_id) = get_yoruho_timeline(max_id)
    success_list_timeline += success
    if end_flag == true then
      break
    end
  rescue
    count += 1
    retry if count <= 0
  end
}
 
success_list_buzztter = []
max_id = nil
[1].each {|i|
  count = 0
 
  begin
    (success, end_flag, max_id) = get_yoruho_buzztter(max_id)
    success_list_buzztter += success
    if end_flag == true then
      break
    end
  rescue
    count += 1
    retry if count <= 0
  end
}
 
success_list_pcod = []
(1..0).each {|i|
  count = 0
 
  begin
    (success, end_flag) = get_yoruho(i)
    success_list_pcod += success
    if end_flag == true then
      break
    end
  rescue
    count += 1
    retry if count <= 0
  end
}
 
success_list_official = []
(1..50).each {|i|
  count = 0
 
  begin
    (success, end_flag) = get_yoruho_official(i)
    success_list_official += success
    if end_flag == true then
      break
    end
  rescue
    count += 1
    retry if count <= 0
  end
}
 
success_list = success_list_timeline | success_list_pcod | success_list_official | success_list_buzztter
 
db = DBM::open("yoruho")
success_list.each {|name|
  if db[name] == nil then
    db[name] = "1"
  else
    db[name] = (db[name].to_i + 1).to_s
  end
}
 
success_list.each {|name|
  status = 'status=@' + name + "%20" + $yoruho_pittari + "%20" + db[name] + "%E5%9B%9E%E7%9B%AE%E3%80%82"
  post(name, status)
 
  # ここで段級の判定
 
  post(name, words)
}
 
# ここで人数を出力

よるほーbot作りました

2009年12月11日 金曜日

またTwitter Botヽ(´ー`)ノ

こんどはよるほーbotっての作りました。 00:00:00に「よるほ」とつぶやくと、祝福してくれます。10回ごとに段級と副賞がもらえます。

(2009/12/27追記) 2009/12/16から@yoruhoのTLを拾うように変更してます。確実に捕捉されたい人は@yoruhoをフォローしてください。

(2010/03/02追記) 2010/02/14から@yoruho2のTLも拾うように変更してます。@yoruhoはフォロー返し停止してますので、@yoruho2をフォローしてください。

仕様は次のような感じです。

  • 毎日0時5分にbotが起動(cron)
  • @yoruhoのTL、buzztter、<del>Twitter検索(yats)</del>(今は止めてます)、Twitter検索(公式)の順で検索して、00:00:00ぴったりに「よるほ」をつぶやいた人をリストにする。
  • ピッタリの人に対して@を送る
  • 最後に、今日ピッタリ賞だった人の人数をつぶやいて終わり。

ソースは今度整理して掲載する予定です。→整理してないけど公開しました

余談その1:Twitter検索の使える度

  • Twitter検索(yats):反映が遅いので速報にはあまり向いてませんが、数時間〜数ヶ月前あたりの検索はにはこれが一番です。あと、検索されない人も作者さんに言えば追加してくれるので安心。
  • Twitter検索(公式):速報性は高いですが、日本語の解釈がうまくないので、検索語によっては何もヒットしないことがあります。「たろっとさん」とかダメ。あと、ユーザー名の大文字小文字が違ってしまう問題があります。
  • buzztter:速報性は高いですが、RSSだと20件までしか取ってこれないので、よるほbotみたいにヒット数が多くなると使えません。Webからの検索で速報性を求めるのならおすすめ。
  • TLから検索:一番確実だけどフォローしてないとダメです。

よるほーbotでは公式検索から引っ張ってきてるのが一番多いです。次にTLから。

余談その2:よるほの起源

いくつか調べてみたところによると、

これだけしか分かりませんでした\(^o^)/。これより古い情報を持っている人いたらお願いします。

たろっとさん(@tarot3)の規制回避用アカウントたろっとさんさんさんさん(@tarot3333)を作りました

2009年11月14日 土曜日

たろっとさんの人気が増すにつれて引けないという話が大きくなってきたので、副アカウントを作ってみました。名前はたろっとさんさんさんさん。引く箇所は変えてないんですが、規制対策のためいろいろやってます。

まず、たろっとさんをフォローしている人だけに応答を返すようにしました。最初は誰でも構わず応答を返すようにしてたんですが、すぐにspam扱いされてsuspendにorz。これでアカウントが2つ死にました(´・ω・`)。なのでフォローしている人だけ応答するようにしてます。フォローしてなくても送ってしまうbotは珍しくないと思うのですが、知らない人に送る頻度が多かったのと、URLをくっつけてしまっているのがまずいのかなと。なので知っている人に変えよーということで。

ソースコード1がfollowersを取得してファイルに保存している箇所、ソースコード2がtarot3333のロジックを組み込んだソースコード。

ソースコードその1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#!/usr/local/bin/ruby
 
require 'net/http'
require 'rss'
require 'rexml/document'
 
include REXML
 
req = Net::HTTP::Get.new("/statuses/followers.xml?screen_name=tarot3333")
req.basic_auth "tarot3333", "password"
 
followers_from_xml = []
Net::HTTP.start("twitter.com", 80) {|http|
  res = http.request(req)
  doc = Document.new(res.body)
  doc.elements.each("*/user") {|user|
    followers_from_xml.push(user.elements["screen_name"].text)
  }
}
 
followers_from_db = open("tarot3333.db").readlines.each {|line| line.chop! }
 
followers_towrite = followers_from_xml | followers_from_db
followers_towrite.uniq!
open("tarot3333.db", "w") {|f|
  f.puts followers_towrite.join("\n")
}

ソースコードその2

?Download tarot.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#!/usr/local/bin/ruby
 
require 'net/http'
require 'rss'
require 'rexml/document'
require 'time'
 
include REXML
 
def post(name, status)
  req = Net::HTTP::Post.new("/statuses/update.json")
  req.body = status
 
  Net::HTTP.start("twitter.com", 80) {|http|
    req.basic_auth "tarot3", "password"
    res = http.request(req)
 
    $log.puts(name + "," + res.inspect + "," + Time::now.to_i.to_s)
 
    if res.code == "200" then
      return
    end
 
    tarot3333 = open("tarot3333.db").readlines.each {|line| line.chop! }
    if tarot3333.include?(name) then
      req.basic_auth "tarot3333", "password"
      res = http.request(req)
 
      $log.puts(name + "," + res.inspect + "," + Time::now.to_i.to_s + ",tarot3333")
    end
  }
end
 
req = Net::HTTP::Get.new("/statuses/mentions.xml")
req.basic_auth "tarot3", "password"
 
names = []
Net::HTTP.start("twitter.com", 80) {|http|
  res = http.request(req)
  doc = Document.new(res.body)
  doc.elements.each("*/status") {|item|
    name = item.elements["user/screen_name"].text
    text = item.elements["text"].text
    time = Time::parse(item.elements["created_at"].text)
 
    now = Time::now
    start_time = Time::local(now.year, now.month, now.day, now.hour, now.min, 0, 0) - 120
    end_time = start_time + 119
 
    if time >= start_time && time <= end_time && !text.include?("RT") then
      names.push(name)
    end
  }
}
 
tarot = open("tarot.csv")
$log = open("log.txt", "a")
lines = tarot.readlines
 
names.each {|name|
index = rand(lines.size)
 
  status = 'status=@' + name + "%20%E3%81%AE%E3%82%AB%E3%83%BC%E3%83%89%E3%81%AF" + URI.encode(lines[index])
  post(name, status)
}

たろっとさんのコード公開します(@で反応するTwitter Botの例)

2009年10月13日 火曜日

まぁTwitter Bot Makerでも作れるんですが、たろっとさんはいろいろと実験したいので別管理にしてます。

ポイントはRSSの解析の箇所ですね。例えば23:18:03にcronでこのスクリプトが呼ばれたとすると、23:16:00〜23:17:59までの@がリプライの対象になります。以前はnow – 120とかやってたんですが、誤差が生じるのでこっちの方法にしました。

追記。たろっとさんさんさんさん作りました

?Download tarot.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#!/usr/local/bin/ruby
 
require 'net/http'
require 'rss'
require 'rexml/document'
require 'time'
 
include REXML
 
user = 'tarot3'
pass = 'password'
 
req = Net::HTTP::Get.new("/statuses/mentions.xml")
req.basic_auth user, pass
 
names = []
Net::HTTP.start("twitter.com", 80) {|http|
  res = http.request(req)
  doc = Document.new(res.body)
  doc.elements.each("*/status") {|item|
    name = item.elements["user/screen_name"].text
    text = item.elements["text"].text
    time = Time::parse(item.elements["created_at"].text)
 
    now = Time::now
    start_time = Time::local(now.year, now.month, now.day, now.hour, now.min, 0, 0) - 120
    end_time = start_time + 119
 
    if time >= start_time && time <= end_time && !text.include?("RT") then
      names.push(name)
    end
  }
}
 
tarot = open("tarot.csv")
lines = tarot.readlines
 
names.each {|name|
index = rand(lines.size)
 
  req = Net::HTTP::Post.new("/statuses/update.json")
  req.basic_auth user, pass
  req.body = 'status=@' + name + "%20" + URI.encode("のカードは") + URI.encode(lines[index])
 
  Net::HTTP.start("twitter.com", 80) {|http|
    res = http.request(req)
  }
}

たろっとさんのよくある質問(気が向いたら更新)

カードを引いてくれないんですが
上のアルゴリズムを見れば分かるんですが、応答を保存してるわけではないです。なので、運悪く(最近は頻繁に…)投稿規制に引っかかったときはいつまで経っても返事が来ないということが起きます。その時はリトライしてください。
たろっとさんさんさんさんが出来ました!@tarot3333をフォローして15分待って、@tarot3に話しかけるとよいです。
引いてくるカードがツンなんですが
正位置と逆位置両方ともあまり良くないカードがあるので(The Towerとか)そう見えるのかもしれません。