sifue's blog

プログラマな二児の父の日常 ポートフォリオは www.soichiro.org

複数のニコ動のURLからコメントを取得してGmailで送ってくれるRubyスクリプト

最近の休日プログラミングの方はひたすらRuby。やっぱりJavaに比べると実装時間が短くて済みます。
昨日たまたまタイトルのようなRubyスクリプトが欲しくなって自作。現在Ubuntuのcronタスクとして実行されるようになっているものを紹介します。


ファイルは、以下のncomment.rbとlist.txtを同じフォルダに作って、あとは"ruby ncomment.rb"と実行するだけです。結果は以下の通り。動作は、Ubuntu8.04(Ruby 1.8.6)とMacOSX10.5.7(Ruby 1.8.7)確認済です。

  • ncomment.rb
#!/usr/bin/env ruby -Ku
# -*- coding: utf-8 -*-
# このスクリプトは、ニコニコ動画のURLのリストから
# 指定した時間内に投稿されたコメントを最大500件収集し、
# Gmailから結果のメールを送信してくれるスクリプトです。
# 設定項目1(ニコニコ動画アクセス)と設定項目2(Gmail)があります
#######################################################

require 'yaml'
require 'rubygems'
require 'mechanize'
require 'cgi'
require "rexml/document"
require 'net/smtp'
require 'tlsmail'
require 'base64'
$KCODE = 'u'

class Nicovideo
  LOGIN_URL = 'https://secure.nicovideo.jp/secure/login?site=niconico'
  WATCH_URL = 'http://www.nicovideo.jp/watch/'
  FLV_URL   = 'http://www.nicovideo.jp/api/getflv?v='
  INFO_URL = 'http://www.nicovideo.jp/api/getthumbinfo/'

  def initialize(url)
    @video_id = url.scan(/sm\d+$/).first
    @agent = WWW::Mechanize.new
  end

  def login(mail, passwd)
    @agent.post(LOGIN_URL, {'mail' => mail, 'password' => passwd})
  end

  def setURL(url)
    @video_id = url.scan(/sm\d+$/).first
  end

  def save_comment(filename)
    @agent.get_file(WATCH_URL + @video_id)
    content = @agent.get_file(FLV_URL + @video_id)
    params = content.scan(/([^&]+)=([^&]*)/).inject({}){|h, v| h[v[0]] = v[1]; h}

    puts "#{@video_id} saving comments as #{filename}\n"

    comment_host, path = %r{http://([\w\.]+)(.*)}.match(CGI.unescape(params['ms'])).to_a.values_at(1,2)

    thread_id = params['thread_id']
    body = %!<thread res_from="-500" version="20061206" thread="#{thread_id}" />!

    comments = Net::HTTP.start(comment_host, 80) {|http|
      response = http.post(path, body)
      response.body
    }

    File.open("#{filename}", "wb") {|f| f.write comments }
  end

  def save_thumbinfo(filename)
    content = @agent.get_file(INFO_URL + @video_id)

    file = File.open("#{filename}", "wb")
    file.print content
    file.close
  end
end

#class Nicovideo
########################

class Gmail
        Port = 587
        HeloDomain="gmail.com"
        SmtpHost="smtp.gmail.com"

        def self.send(from, passwd, to, subject, message)
                smtpserver = Net::SMTP.new(SmtpHost, Port)
                smtpserver.enable_tls(OpenSSL::SSL::VERIFY_NONE)

                header_body = <<EOD
From: #{from}
To: #{to}
Date: #{Time::now.strftime("%a, %d %b %Y %X %z")}
X-Mailer: imput.rb
Subject: #{subject}

#{message}
EOD

                smtpserver.start('gmail.com', from, passwd, :login) do |smtp|
                        smtp.send_message header_body, from, to
                end
        end
end

#class Gmail
########################

if $0 == __FILE__
  Dir::chdir(File::dirname(__FILE__))

  ####### 設定項目1 #######
  listFilename = "list.txt" # 動画のURLのリスト
  id = "hoge@hage.org" # ニコニコ動画のidのメールアドレス
  pass = "hoge" # ニコニコ動画のpassward
  secWait = 8 # 連続取得をするとエラーが出るので、そのための待ち時間 (秒)
  hour = 6  # 時間前までのコメントのダイジェストを作成

  # コメントファイルを作るフォルダを作成
  if !(File.exists?("./comment")) then
     Dir::mkdir("./comment")
  end

  # ファイルを開いてログインした後、ひたすらコメントと動画情報取得
  file = open(listFilename)
  isFirst = true
  while text = file.gets do

    next if /^\s*$/ =~ text
    next if /^#/ =~ text

    if isFirst then
      nico = Nicovideo.new(text)
      nico.login(id , pass)
      isFirst = false
    else
      nico.setURL(text)
    end

    begin
      sleep(secWait)
      nico.save_comment('./comment/' + text.scan(/sm\d+$/).first + '_c.xml')
      nico.save_thumbinfo('./comment/' + text.scan(/sm\d+$/).first + '_i.xml')
    rescue => ex
      errorCount += 1
      if errorCount < 10 then # 9回まで再チャレンジ
        secRetry = 180 * errorCount
        print "wait #{secRetry} sec and retry #{errorCount} : #{text} \n"
        sleep(secRetry)
        retry
      else
        print ex.message, "\n"
      end
    end

  end

  # commentフォルダのXMLを調べてコメントのダイジェストを取得
  body = ""
  intFrom = (Time.now - hour*60*60).to_i # 取得する時間のUNIX秒
  Dir::foreach("./comment"){ |f|
    if /_c.xml/ =~ f then
      print "processing : " + "./comment/" +  f + "\n"

      xmlComment = ""
      file = File.open("./comment/" + f)
      while text = file.gets do
        s = text.gsub(/(<\/[a-zA-Z]+>|\/>)/){|matched|
          matched + "\n"
        }
        xmlComment.concat s
      end
      file.close

      doc = REXML::Document.new xmlComment
      nodes = REXML::XPath.match(doc, "/packet/chat")

      docInfo = REXML::Document.new File.open("./comment/" + f.gsub(/_c/, "_i") )
      title = REXML::XPath.match(docInfo, "/nicovideo_thumb_response/thumb/title")[0].text
      viewCount = REXML::XPath.match(docInfo, "/nicovideo_thumb_response/thumb/view_counter")[0].text
      commentCount = REXML::XPath.match(docInfo, "/nicovideo_thumb_response/thumb/comment_num")[0].text
      mylystCount = REXML::XPath.match(docInfo, "/nicovideo_thumb_response/thumb/mylist_counter")[0].text

      isExist = false
      nodes.each{ |node|
        intDate = node.attributes["date"].to_i
        if intDate > intFrom then
          if !isExist then
            body.concat "\n" + title + "\n"
            body.concat "再生:" + viewCount + " コメント:" + commentCount + " マイリスト:" + mylystCount + "\n"
            isExist = true
          end
          body.concat "\t" + (node.text == nil ? "" : node.text )
          body.concat " (" + Time.at(intDate).strftime("%Y/%m/%d %H:%M:%S") + ")\n"
        end
      }
    end
  }

  ####### 設定項目2 #######
  print body
  Gmail.send("yourgmail@gmail.com", "password", "send@hoge.org", "#{hour}時間以内のニコ動コメント" ,body)

  # 不要になったcommentフォルダを消す
  dirlist = Dir::glob("./comment/" + "**/").sort {
    |a,b| b.split('/').size <=> a.split('/').size
  }
  dirlist.each {|d|
    Dir::foreach(d) {|f|
      File::delete(d+f) if ! (/\.+$/ =~ f)
    }
    Dir::rmdir(d)
  }

  print "finished!\n"
end
  • list.txt
http://www.nicovideo.jp/watch/sm7736286
http://www.nicovideo.jp/watch/sm7689160
  • 送られてくるメールの本文(件名: 6時間以内のニコ動コメント)
かゆいところに足が届かぬ猫
再生:119382 コメント:1337 マイリスト:2352
       ぴょこぴょこ (2009/07/26 08:53:14)
       リアルにゃんこ先生wwwww (2009/07/26 10:25:55)
       しぇーーー!! (2009/07/26 11:40:19)
       ぜんぜん届いてない件 (2009/07/26 11:51:41)
       ふといw (2009/07/26 12:55:46)
       wwwwwwwww (2009/07/26 12:55:55)

声がエロい亀
再生:51672 コメント:2065 マイリスト:1103
       声帯丸見えwww (2009/07/26 08:28:59)
       wwwwwwwwwwww (2009/07/26 08:31:39)
       えええええええええええええええ (2009/07/26 08:31:42)
       www (2009/07/26 08:32:08)
       ええええええええええええええええええええええええええええ (2009/07/26 08:32:09)
       エロ過ぎるwwwwwwwwwwwww (2009/07/26 08:32:13

設定は1と2があるので、好きなように変えて動かしてください。無論、rubyrubygemsが必要です。インストールは各プラットホームごとのやりかたをググってもらえればと思います。


あと、"mechanize"と"tlsmail"が別途必要なので、"sudo gem install mechanize" と "sudo gem install tlsmail"のインストールをお忘れなく。何気にUbuntuの方は、mechanizeを入れるのに、apt-getで"ruby1.8-dev"、"hpricot"、"libxml2-dev"、"libxslt1-dev"を入れないと"sudo gem1.8 install mechanize"でインストールできませんでした。


このスクリプトを作るのに以下のサイトから二つのクラスを拝借、改造させてもらいました。

ありがとうございました。


議論が活発な動画や、コメントを書いた後の動向が気になる動画のコメントを自動チェックさせたりするのに役立つかもしれません。なお、ニコニコ動画のコメント取得APIは連続でアクセスしまくると繋がらなくなります。一応待ち時間を設定できるようにしてありますが、30件ぐらいならば待ち時間8秒ぐらいでなんとかなりました。数百件となになると、10秒以上待った方がよいかもしれません。


追伸
リトライ機構を設けておきました。連続取得失敗した際も時間はかかりますが復旧してくれるはずです。待ち時間15秒で、400件の動画でも2時間ほどかかりますがやり終えてくれるようです。