読者です 読者をやめる 読者になる 読者になる

hashtagsjpをスクレイピングしてはてダ用に変換するスクリプト

Ruby XML XPath Scraping

概要

Google Chrome 5.0 betaを騙って、hashtagsjpからつぶやきを取ってきて1行1tweetに変換します。
XPathを使ってるのでとっても見通しが良い実装だと自画自賛w

はてダに直接張れるHTMLを標準出力に吐き出しますが、cssは出力結果に合わせていぢって下さい。

ソースを晒しておく

#!/usr/bin/ruby

require 'net/http'
require 'rexml/document'
include REXML

class HashtagsJP
  attr_reader :timelines

  HTTP_HEADERS = {
    'User-Agent' => 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.9 Safari/533.2',
    'Accept' => 'application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5',
    'Accept-Charset' => 'Shift_JIS,utf-8;q=0.7,*;q=0.3',
    'Accept-Language' => 'ja,en-US;q=0.8,en;q=0.6',
  }

  HOST = "hashtagsjp.appspot.com"
  URI_ROOT = "http://" + HOST
  DIR = "/tweets/"
  XPATH_EXPR = '//html/' +
                 'body/' +
                 'div[@id="wrap"]/' +
                 'div[@id="main"]/' +
                 'div[@id="main_l"]/' +
                 'div[@class="box_body"]/' +
                 'div[@class="tl clearfix"]'

  def initialize(tag)
    @server = Net::HTTP.new(HOST, 80)
    @tag_path = DIR + tag
    @timelines = []
  end
 
  def parse(html)
    html.delete!("\r\n\t")
    html.gsub!(%r|<script.+?</script>|m, "")
    html.gsub!(%r|<OBJECT.+?</OBJECT>|m, "")
    html.gsub!('<a href="/', '<a href="' + URI_ROOT + "/")
    html.gsub!("&nbsp;", " ")
    html.gsub!("&", "&amp;")
    doc = Document.new html

    count = @timelines.count
    XPath.each(doc, XPATH_EXPR) do |element|
      tl = element.to_s
      tl.gsub!('</a></strong>', '</a></strong>&nbsp;')
      tl.gsub!(%r|> +<|, "><")
      @timelines.push tl
    end
    tweets = @timelines.count - count
    STDERR.puts "parsed " + tweets.to_s + " tweet(s)."
  end

  def fetch(page)
    path = @tag_path.dup
    path << "/" + page.to_s if page != 1
    html = self.get(path)
    self.parse(html)
  end

  def fetch_all(pages)
    1.upto(pages) do |page|
      self.fetch(page)
    end
  end

  def get(path)
    req = Net::HTTP::Get.new(path)
    HTTP_HEADERS.each do |key, value|
      req[key] = value
    end
    STDERR.print "fetching: " + URI_ROOT + path + " ..."
    res = @server.request(req)
    STDERR.puts " done."
    res.body
  end
end

paegs = 1
begin
  raise "arguments error" if ARGV.count != 1 && ARGV.count != 2
  tagname = ARGV[0]
  pages = ARGV[1].to_i if ARGV.count == 2
rescue
  STDERR.puts "usage: " + $0 + " hashtag [pages]"
  exit 1
end
  
hashtag = HashtagsJP.new(tagname)
hashtag .fetch_all(pages)
timelines = hashtag.timelines

while timelines.count > 0 do
  tl = timelines.pop.to_s
  puts tl
end

使い方

例えば、#flosssハッシュタグを2ページ分抜き出すには、下記のように実行します。

shingo@shingo-server:~$ ./tweet-format.rb flosss 2 > flosss.txt
fetching: http://hashtagsjp.appspot.com/tweets/flosss ... done.
parsed 20 tweet(s).
fetching: http://hashtagsjp.appspot.com/tweets/flosss/2 ... done.
parsed 9 tweet(s).
shingo@shingo-server:~$ 

これからの勉強会は

事前のhashtagsjpへの公式ハッシュタグの登録と、帰宅してからのまとめエントリをお忘れなくw*1

以前C#スクレイピングやりましたが

前のサイトと比べて綺麗なXHTMLだったので、Tidyのお世話にならなくて良かったです。というか脊髄反射的にTidyを通したらハマったというのはここだけのヒミツw

*1:先週金曜日の分を纏めようとしたら、もう無かった。orz