はてなダイアリーからのトラックバックの移行(その3)

はてなダイアリーのインポート、まだ終わりません。インポートの終わったエントリは順次公開されているようですが、昨夜から投稿数1751件で止まっています。インポート前からあるエントリを差し引くと1700エントリくらいで、400件くらい残っているようです。徐々に進んでいるのならわかるんですが途中でピタッと止まった状態なのが気になりますが…

トラックバックデータの抽出のやり直し

まずこのシリーズの最初のエントリスクリプトにバグがありました。入力が HTML、出力が YAML のはずだったのですが、出力を YAML パーサで正しく読み込めない場合がありました。

YAML の出力を文字列で出していたせいで YAMLマークアップエスケープ処理が抜けていたせいです。具体的にはトラックバックのデータ(a 要素)の中に ": " が混じっているとハッシュとしてパースされてしまって、場合によってはエラーになるというもの。

出力をメモリに溜め込むのは趣味じゃないんですが、オブジェクトにデータを溜めてから YAML.dump() で出力するように修正しました。

まず各エントリ(日記のセクション)の HTML ファイルからトラックバックを抽出するスクリプトがこちら。

get-tb.rb:

#!/usr/bin/env ruby
# coding: utf-8

require 'yaml'
require 'nokogiri'

URL_BASE = "http://d.hatena.ne.jp"

def parse_entry_html_file (file)
  entry = {}
  f = File.open(file, "r:euc-jp")
  html = f.read().encode("utf-8", { :invalid => :replace, :undef => :replace })
  f.close
  doc = Nokogiri::HTML::Document.parse(html)
  path = doc.css("div.body > div.section > h3 > a").attribute("href").value
  entry['url'] = URL_BASE + path
  doc.css("div.refererlist").each { |div|
    type = div.css("div.caption > a").attribute("name").value
    tbs = []
    div.css("ul > li").each { |li|
      tbs.push(li.css("a").to_s)
    }
    entry[type] = tbs if tbs.length > 0
  }
  return entry
end

entries = []
ARGV.each { |file|
  begin
    entries.push(parse_entry_html_file(file))
  rescue => e
    STDERR.puts "#{file}: #{e}"
  end
}
YAML.dump(entries, STDOUT)

その2で書いた、匿名セクション(日記ページの最初のセクションの前ある文章)がある場合にその日記に付いたidトラックバックを抽出するスクリプトも同様に修正しました。

get-tb-for-anon-section.rb:

#!/usr/bin/env ruby
# coding: utf-8

require 'yaml'
require 'nokogiri'

def parse_diary_html_file (file)
  f = File.open(file, "r:euc-jp")
  html = f.read().encode("utf-8", { :invalid => :replace, :undef => :replace })
  f.close
  doc = Nokogiri::HTML::Document.parse(html)
  first_section = doc.css("div.body > div.section")[0]
  if first_section && first_section.css("h3").length == 0 then
    entry = {}
    entry['url'] = doc.css("div.day > h2 > a").attribute("href").value
    doc.css("div.refererlist").each { |div|
      type = div.css("div.caption > a").attribute("name")
      tbs = []
      div.css("ul > li").each { |li|
        tbs.push(li.css("a").to_s)
      }
      entry['idtb'] = tbs if tbs.length > 0
    }
    return entry
  else
    return nil
  end
end

entries = []
ARGV.each { |file|
  begin
    entry = parse_diary_html_file(file)
    entries.push(entry) if entry
  rescue => e
    STDERR.puts "#{file}: #{e}"
  end
}
YAML.dump(entries, STDOUT)

トラックバックの付いているエントリのデータだけを抽出する

get-tb.rb と get-tb-for-anon-section.rb で抽出したデータにはトラックバックの付いていないエントリのURLも含まれます。処理したエントリの件数などを把握するためにそうしておいたのですが、今後の処理では邪魔になるので以下のスクリプトで不要な分を削除します。

reject-empty-trackback.rb:

#!/usr/bin/env ruby
# coding: utf-8

require 'yaml'

YAML.load_stream(ARGF) { |entries|
  entries.reject! { |entry|
    tbs = entry['tb']
    idtbs = entry['idtb']
    (tbs == nil || tbs.empty?) && (idtbs == nil || idtbs.empty?)
  }
  YAML.dump(entries, STDOUT)
}

エントリのURL一覧を生成する

reject-empty-trackback.rb で処理したデータから URL のリストを生成します。こういうのは普通のテキストファイルでもいいんですが、YAML で出力しています。

2019-01-25: 入力に複数のYAMLドキュメントが含まれると出力が重複するバグがあったので修正しました。

get-urls.rb:

#!/usr/bin/env ruby
# coding: utf-8

require 'yaml'

urls = []
YAML.load_stream(ARGF) { |entries|
  entries.each { |entry|
    urls.push(entry['url'])
  }
}
YAML.dump(urls, STDOUT)

ダイアリーのURLとブログのURLの対応表を作る

ダイアリーのインポート後にリダイレクト設定をすると、ダイアリーのURLでアクセスした際に対応するブログのエントリにリダイレクトするようになります。これを利用してダイアリーのエントリのURLと移行後のブログのエントリの対応表を作ります。

でも匿名セクションに対応したエントリって対応する元のURLがないんですけどどうしますかね? おそらく日記のURLに対応するURLに何か付け足したURLになると思うのですが。これはリダイレクト設定後要確認ですね。

make-redirection-table.rb:

#!/usr/bin/env ruby
# coding: utf-8

require 'yaml'
require 'net/http'

def get_redirect_location(http, url)
  res = http.head(url)
  raise "#{res.code}: #{url}" if (/^[45]../.match(res.code))
  return (res.code == "301") ? res["location"] : nil
end

rnd = Random.new
Net::HTTP.start("d.hatena.ne.jp", 80) { |http|
  table = {}
  YAML.load_stream(ARGF) { |urls|
    urls.each { |url|
      location = get_redirect_location(http, url)
      table[url] = location if location
      sleep(0.5 + rnd.rand(0.5))
    }
  }
  YAML.dump(table, STDOUT)
}

はてなのサーバに連続してアクセスするのでちょっと wait を入れてあります。

今後の作業予定

  • インポート完了を待つ
  • リダイレクト設定
    • 匿名セクションに対応するエントリのURLの確認
  • URL対応表の作成
  • はてなブログライター形式でブログエントリを全件ダウンロード
  • ダウンロードしたエントリファイルの末尾にトラックバックを追加
  • はてなブログライターでエントリを更新