40歳からのキャリアチェンジ

20代はエンジニア・PM、30代はWeb系エンジニア向けのキャリアアドバイザー。40代の今はフリーランスで開発含めて色々やってます。技術ネタとしてはRuby/RailsとJavaScript関連あたり

044-Crawler実装しなおす

昨日書いたエントリでふれたようにサーバ側のJSON生成部分から見直すことにするため028-Crawlerは一応完成というエントリで触れたCrawlerから手をつけました

これまでのCrawlerと今後実装するべき処理検討

あすなろブログの各エントリ情報を取得する際に

  • ブログタイトル
  • ブログ本文
  • permalink

は取得していたけれど、投稿日を抽出してなかったのでその処理を実装することにしました

あすなろブログは、当然CMS使って管理しているのですが、投稿日の情報取得が結構やりづらい構造になっています。

具体的に016:Lifestyle アディダスのランニングシューズの期間限定レンタルサービス登場というエントリの該当箇所を引用するとこんな感じになってます。

<p class="posted"> 
 
投稿者&nbsp;:&nbsp;小山田 浩 | 
投稿日時&nbsp;:&nbsp;2011.04.05&nbsp;07:10
</p> 

上記が仮にこうなっていたらとっても処理がラクだったのですがこればっかりは嘆いていても仕方が無いので、実装することにしました。

<div class="blogger_name">小山田 浩</div>
<div class="post_date">2011/04/05 7:10</div>

投稿日の処理試行錯誤

投稿日のフォーマット見る限り、正規表現を使わないとダメだと思ったのですが、正直正規表現は苦手なのでうまく出来るかどうか自信がありませんでした

そのためirbで、試行錯誤しながら作業を進めていきました。

最初考えたのは、エントリ中から、投稿日の部分を抜き出す所。
Ruby+Nokogiriで

require 'nokogiri'
require 'open-uri'
starturl = "http://blog.pasonatech.co.jp/counselor/career_blog/102/15947.html"
http = open(
                starturl,
                "User-Agent" => "MySpider",
                "From" => "xxxxx@gmail.com",
                "Referer" => "http://d.hatena.ne.jp/h5y1m141"
                )
doc = Nokogiri::HTML(http)
doc.search('//p[@class="posted"]').to_html.match(/\d{4}\.\d{2}\.\d{2}.*/i)

とすることで最終的には取得出来ましたが、1つはまったのが正規表現マッチさせる所。

Nokogiri使って得られた結果をString型にしないとmatch()が利用できないみたいで、最初は

doc.search('//p[@class="posted"]').match(/\d{4}\.\d{2}\.\d{2}.*/i)

としてしばらく悩んでいました。

次に考えたのは投稿日のフォーマット変換

投稿日が
yyyy.mm.dd<空白スペース>hh:mm
となっているため、これだと汎用的に利用しづらいと思ったので、扱いやすい形に変換する必要があると思ってirbで確認しながらひとまずこんな処理を思いつきました

# 〜中略〜
temp_post_date = doc.search('//p[@class="posted"]').to_html.match(/\d{4}\.\d{2}\.\d{2}.*/i)
yyyymm = temp_post_date.to_s.split(".")
dd = yyyymm[2].to_s.split("\302\240")
hhmm = dd[1].to_s.split(":")

post_date = Time.local(
                       yyyymm[0].to_s,
                       yyyymm[1].to_s,
                       dd[0].to_s,
                       hhmm[0].to_s,
                       hhmm[1].to_s,
                       "00"
                       )

これでひとまずイメージ通りにできそうなので、実際にCrawlerを修正しました

最終的なソースコード

require 'rubygems'
require 'nokogiri'
require 'open-uri'
require 'entry.rb'

class Crawler
  def initialize(blogger)
    @blogger = blogger  
  end
  attr_accessor :html_body, :next_link, :title, :current_page
  
  def run(starturl)
    entry = Entry.new()
    http = open(
                starturl,
                "User-Agent" => "MySpider",
                "From" => "xxxxx@gmail.com",
                "Referer" => "http://d.hatena.ne.jp/h5y1m141"
                )    doc = Nokogiri::HTML(http)
    
    entry.permalink = starturl
    entry.blogger = @blogger

    entry.title = doc.search('//div[@class="entry"]').search("h1").text
    entry.html_body = doc.search('//div[@class="entryText"]').inner_html
    entry.post_date = self.conv_date_formt(doc.search('//p[@class="posted"]').to_html)
    entry.save
    
    result = doc.search('//p[@class="entrylink"]')
    if result.text.include?("次のエントリー&#187;") then
      @next_link = result.search("a")[1]['href']
    else
      @next_link = nil
    end
    
    if @next_link === nil then
      exit
    else
      sleep(2)
      self.run(@next_link)
    end
  end
  def conv_date_formt(contents)
    temp_post_date = contents.match(/\d{4}\.\d{2}\.\d{2}.*/i)
    yyyymm = temp_post_date.to_s.split(".")
    dd = yyyymm[2].to_s.split("\302\240")
    hhmm = dd[1].to_s.split(":")
    post_date = Time.local(
                       yyyymm[0].to_s,
                       yyyymm[1].to_s,
                       dd[0].to_s,
                       hhmm[0].to_s,
                       hhmm[1].to_s,
                       "00"
                       )
    return post_date
  end
end