現在の"はてなブログ"になる前の"はてなダイアリー"時代から、日記の投稿は半自動(?)ツールを利用していました。
具体的にはPC本体に記事ごとのテキストファイルを用意しておき、それを投稿する形。
こうしておくとネットと繋がっていない状況で記事を書いて一気に投稿したり、はたまた検索もローカルにあるテキストファイルをgrepしたり置換も出来ちゃうのです。
"はてなダイアリー"時代は『はてなダイアリーライター』なるPerlで書かれたスクリプトを利用していましたが"はてなダイアリー"が廃止され"はてなブログ"に移行したら便利なツールも使えなくなってしまった・・・・
すると世の中の誰かがRubyスクリプトで作ってくれたのが『はてなブログライター』。
自動投稿のお話のうちの一件
https://…/2018/09/19/ はてなダイアリーライター → はてなブログライターへの移行を考える
記事の投稿はHtml/formの送信で誤魔化すのではなく、"はてなブログAPI"を利用するという正攻法で実装されています。
API仕様が記事一つずつにランダム付与される記事IDをキーにしてアクセスする必要があり、必然的にローカル側に過去の全記事と記事IDの紐づけデータベースが必要になってしまうのが難点。(初回投稿は付与される記事IDを記録し、2回目以降の修正時は記事IDとテキストを送る必要がある。)
正攻法のためローカル側からのアップロードの他、サイト側から記事をダウンロードすることもできる(はてなブログダウンローダー)のが"はてブラ"の利点。
今までは記事データはPC→サイトへの一方通行だったため記事の修正は必ずPCで行う必要がり、またはサイト上で記事を修正した場合は自分でそれを覚えておいてPC側のファイルも修正しておく必要がありました。(←これをよく忘れる。)
ここまでが経緯な訳で、ここからが本題。
Rubyスクリプトで書かれた"はてなブログダウンローダー"ですがその仕組み上・・・・
- 起動すると過去に投稿した全テキストファイルを読み込んでメモリ上にデータベースを構築
- その次にWebサイトにアクセスして日記日付順の最新n件の記事のリストを取得(記事ID・URL・更新日時)して記事IDからメモリ上のDBを検索してローカルファイル名(日記の日付)を特定
- サイト側記事更新日時とメモリDB上のローカルファイルの更新日時を比較しての更新の有無を判別
- 上記で更新ありと判断した場合は、更新された記事を記事ID指定で一つずつダウンロードしてローカルファイルを更新
・・・・のような動きをします。
おそらく普通に使うぶんにはこの仕様で問題ないと思われますが、私の場合は過去記事が7,000件近く存在するのです。
すると過去ファイルをすべて読み込んでメモリ上にDB(ハッシュテーブル)を構築するのにそこそこ時間がかかる・・・・
待てなくはないけれど便利なツールなので結構手動で実行しますし、同期忘れがないようにログインスクリプトにも組み込んでいてPCを起動したりネットワークにつなげると自動的に同期させるようにもしていたりするので、結構ストレス!
でもなぁ~、Rubyってじっくりと触ったことがないのよね。(←以前若干カスタマイズはしていますがね。)
簡単にカスタマイズした日記
https://…/2018/11/13/ はてなブログへの移行作業 その後の状況です
改造したいけれど『出来るかな?』のほうが先立ってしまい長年放置していたのですが、頑張ってみることに。
アルゴリズムは次のような感じに。
- 廃止!
過去の記事ファイルをすべて読み込みメモリ上にDB構築 - Webサイトにアクセスして日記日付順の最新n件の記事のリスト(記事ID・URL・更新日時)を取得
- はてなブログでは日記記事のURLの付け方を設定で選択可能になっていて、私の場合は日付を付けるようにしているのでこれを最大限利用して日付を特定する
- ローカルのテキストファイルを読み込んでメモリ上にDBを構築する。この時すべてのファイルを読み込むのではなく先で判別している日付ファイルだけに対象を絞り込んで読み込むため高速!(←昔は一日に複数記事を投稿していたので、日付だけでは記事を一つに絞り込めないため複数ファイルを読み込む。)
- 以下は現行の処理と同じで、サイト側記事更新日時とメモリDB上のローカルファイルの更新日時を比較しての更新の有無を判別
- 上記で更新ありと判断した場合は、更新された記事を記事ID指定で一つずつダウンロードしてローカルファイルを更新
意外とあっさりとコーディングできてしまった・・・・
Rubyそのものの言語目的が『プログラミングを楽しく』というくらいだからな。他の言語でのソフトウェアを知っていれば何とかなるのかも。
今までは実行前に7,000ファイルの読み込みを待っていたのですが、この変更により待ち無しで直ちにWebアクセスし、(標準では)7個の記事リストを取得して7ファイルだけメモリに読み込むのでとっても高速!
もっと早く修正しておくべきだった・・・・
ソースコードはこちら。(無保証)
HatenaBlogDownloader.rb #!/usr/bin/env ruby # coding: utf-8 require_relative './HatenaBlogWriter.rb' require_relative './HatenaBlogDownloader.rb' VERSION = "0.1.1" ESC_BGRED = "\e[41m" #@@TSM ESC_BGYELLOW = "\e[43m" #@@TSM ESC_BGBLUE = "\e[44m" #@@TSM ESC_RESET = "\e[0m" #@@TSM #@@TSM BEGIN 2023.03.11 #def load_db # db = {} # HBW::EntryMetaData.listData().each() { |data| # db[data.location] = data # } # return db #end #@@TSM END 2023.03.11 #@@TSM BEGIN 2023.03.11 def load_db1rec(edit,url) datestr = url.gsub("https://tarsama.hatenadiary.com/entry/","") datestr = datestr.slice(0,4) +"-"+ datestr.slice(4,2) +"-"+ datestr.slice(6,2) db1rec = {} HBW::EntryMetaData.listData1Rec(datestr).each() { |data| db1rec[data.location] = data } return db1rec end #@@TSM END 2023.03.11 def update_entry(data_file, entry_file, time_edited) entry_filename = data_file.entry_filename i = 0 loop do i += 1 filename = sprintf("#{entry_filename}.%d", i) next if File.exists?(filename) File.rename(entry_filename, filename) break end entry_file.save_as(entry_filename) puts "OK: " +ESC_BGYELLOW+ "#{entry_filename}: エントリファイルを更新しました。" +ESC_RESET data_file.set_updated(time_edited, entry_file.sha1) data_file.save() end def save_new_entry(entry_file, time_edited, location, url) base_filename = entry_file.date.strftime("%04Y-%02m-%02d") i = 0 loop do i += 1 filename = sprintf("#{base_filename}_%02d.txt", i) next if File.exists?(filename) entry_file.save_as(filename) puts "OK: " +ESC_BGBLUE+ "#{filename}: エントリファイルを作成しました。" +ESC_RESET data_file = HBW::EntryMetaData.new(filename) data_file.set_posted(location, time_edited, entry_file.sha1) data_file.set_url(url) data_file.save() break end end def update_data(data_file, entry, entry_file) data_file.set_updated(Time.parse(entry.edited.text), entry_file.sha1) data_file.save end entry_count_limit = 7 if ARGV.length > 0 then if ARGV[0] == 'version' then puts "HatenaBlogWriterDownloader v#{VERSION}" exit #@@TSM BEGIN 2023.03.18 elsif ARGV[0] == 'until' then entry_count_until = ARGV[1].to_i entry_count_limit = 0 #@@TSM END 2023.03.18 else entry_count_until = 0 entry_count_limit = ARGV[0].to_i end ARGV.shift end puts "HatenaBlogWriterDownloader v#{VERSION}: Start." loader = HBW::FeedLoader.new() #@@TSM BEGIN 2023.03.11 #db = load_db() #@@TSM END 2023.03.11 entry_count = 0 update_count = 0 #@@TSM rnd = Random.new loader.each_feed { |feed| # sleep(0.5 + rnd.rand(0.5)) if entry_count > 0 break if entry_count_limit > 0 && entry_count_limit <= entry_count feed.entries.each { |entry| break if entry_count_limit >0 && entry_count_limit <= entry_count entry_count += 1 puts "\e[K---" puts "title: " + entry.title url = nil edit = nil entry.links.each { |link| if link.rel == "edit" edit = link.href elsif link.rel == "alternate" url = link.href end } entry_file = nil begin entry_file = HBW::EntryFile.new(entry) rescue puts "エントリの解析に失敗しました。: #{$!}" next end time_edited = Time.parse(entry.edited.text) sha1 = entry_file.sha1() puts "URL: " + url puts "sha1: " + sha1 #@@TSM BEGIN 2023.03.11 datestr = url.gsub("https://tarsama.hatenadiary.com/entry/","") datestr = datestr.slice(0,4) + datestr.slice(4,2) + datestr.slice(6,2) if datestr.to_i == entry_count_until entry_count_limit = 1 end db1rec = load_db1rec(edit,url) # data = db[edit] #@@TSM data = db1rec[edit] #@@TSM END 2023.03.11 if data != nil then puts "既存のエントリです: " + data.entry_filename if data.sha1 == sha1 then puts "変更はありません。" else local_entry_file = HBW::EntryFile.new(data.entry_filename) if local_entry_file.date == nil then dateless_entry_file = HBW::EntryFile.new dateless_entry_file.parse_entry(entry, true) sha1 = dateless_entry_file.sha1() if data.sha1 == sha1 then puts "変更はありません。(date ヘッダなしのエントリ)" next end end puts "変更があります。" if data.mtime < File.mtime(data.entry_filename) then puts "警告: " +ESC_BGRED+ "エントリファイルは投稿後に更新されています。" +ESC_RESET end # puts "エントリファイルをリモートの内容で更新しますか? (yes/No)" # if gets.chomp == 'yes' data.set_url(url) update_entry(data, entry_file, time_edited) update_count += 1 #@@TSM # end end else puts "新規エントリです。" save_new_entry(entry_file, time_edited, edit, url) end } break if entry_count_limit > 0 && entry_count_limit <= entry_count print "Waiting: #{entry_count} articles. #{update_count} updated.\r" #@@TSM sleep(0.5 + rnd.rand(0.5)) if entry_count > 0 } puts "" #@@TSM puts "Results: #{entry_count} articles. #{update_count} updated." #@@TSM