Firefoxでエクスポートした「bookmarks.html」をrubyのnokogiriで後々使いやすくする方法

簡単な流れ

  1. bookmarks.htmlをエクスポートする
  2. (一部分を削除…←これもRubyでやればいいのでは?(後日、追記しました#「Rubyで不要部分削除」))
  3. 下記6番のコード(スクリプト)を実行

実際にプログラムを書くときは出力した「bookmarks.html」のコードを表示しながら、少しずつ検証しながら進めていくことになります。

環境

Windows10Pro64bit、ruby2.4、Nokogiri1.10.5

応用

HTMLの解析なのでNokogiriを使います。なぜ見出しが「応用」か?

それは私が素人だからです。「nokogiriはWebから取得したhtmlを解析するものだ」という頭しかないわけです。そうなんです。本質を忘れていました。私は「nokogiriイコールWeb情報!Web情報!!」って頭になっています。

しかし、「あれ?Firefoxから出力したbookmarks.htmlってHTMLだからnokogiriが使えるのでは?」と思って、実験したらできたので「HTML解析イコールNokogiriを使う」ということを思い出しました。

準備

全てのブックマーク(リンク)は、フォルダを作成し、格納しておくこと。そうすると後で見やすくなる。

要するに「他のブックマーク」の中にはフォルダしかない状態にする。

次の2~5を飛ばしてください

bookmarks.htmlを出力して、コードを走らせたら終了です。

コードの「#「Rubyで不要部分削除」」を追記したことで、面倒なことをしなくて済みました。要は1と6だけやれば処理できます。

「あぁ、これが効率化(プログラミング)なんだな」と思いました。

1.「bookmarks.html」をエクスポートする。

今回のターゲットは「他のブックマーク」の中の任意作成フォルダです。

(2.ブックマークをテキストエディタで開く。)

私はVisualStudioCodeを利用しています。

DL、p、DTとか散乱していて、ノイズが激しい。もちろんFirefoxとして必要だから出力しているんだけど、今回必要なのは「URL」と「タイトル」と「カテゴリ」がわかればいいので不要部分を削除する。

※あとでわかったが、「ADD_DATE」があればブックマーク順にソートできるかも。

(3.ctrl+fで「他のブックマーク」を検索。)

とりあえず、まず、とにかく「他のブックマーク」から上を削除する。ちょっとだけスッキリする。タイトルやメタ情報もすべて削除する。

最初のカテゴリから上を削除する。そして、ソースの最下部の終了タグも削除。これでフォルダ名とそれに格納されているリンクだけが残る。

インデントを左端に揃えておく。

自分に必要なタグだけのhtmlにする。

このbookmarks.htmlの中に、個人的に必要ないと思われるランダムな文字列がたくさんある。実際はランダムではなくて「Firefoxがそれを必要だから付加している」のが正解だと思う。特にアイコン画像を文字列化したbase64などがかなりの文字数を占領している。個人的には、ページタイトルとURLだけあればいい。あとフォルダ名か。

(4.ノイズ文字を削除し、Nokogiriで利用しやすくするためにタグ変換する。)

元データを書き換えずに、ごっそり削除したhtmlを「bookmarks_modified.html」として保存する。

(5.「bookmarks_modified.html」をNokogiriで解析し、次のようなハッシュで残)す。

これでかなり見やすくなる、と思う。

Nokogiriの使い方を学べば自分の好きなように加工できます。

{
  "フォルダ名" => [
                  ["ページタイトル", "URL"],
                  ["ページタイトル", "URL"]
                 ],
  "フォルダ名" => [
                  ["ページタイトル", "URL"],
                  ["ページタイトル", "URL"]
                 ]
}

6.コード記載「4と5のロジックを合わせたもの」

require "nokogiri"
require "pp"

#■■■フェーズ1「不要部分削除のHTML作成」■■■
bookmarks_str = File.read("bookmarks.html")

#「Rubyで不要部分削除」
bookmarks_str.gsub!(/[\s\S]*?他のブックマーク[\s\S]*?<DL><p>/, "")

#puts bookmarks_str[(bookmarks_str.length - 15)..bookmarks_str.length]数字部分はこれで検証しました

bookmarks_str[(bookmarks_str.length - 15)..bookmarks_str.length] = ""
#gsubと同じ役割を果たします


#ノイズ除去方法、gsubを使います
#bookmarks_str.gsub!(//, "").gsub!(//, "").gsub!(//, "").gsub!(//, "")
#正規表現…学習しましょう!「.+」←マジ助かる
bookmarks_str.gsub!(/LAST_MODIFIED=".+"/, "").gsub!(/ICON_URI=".+" ICON=".+"/, "")


#タグ変換
#個人的に使いやすいdivを利用する
bookmarks_str.gsub!(/<DT><H3/, "<div class=\"kategori\"><H3").gsub!(/<\/DL><p>/, "</DL><p></div>")


#bookmarks.htmlを書き換えたものを別途保存する
File.open("bookmarks_modified.html", "w") do |f|
  f.puts bookmarks_str
end


#■■■フェーズ2「ハッシュ作成」■■■
html_code = File.read("bookmarks_modified.html")
nokogiri = Nokogiri::HTML(html_code)

#1つのarrayには、リンクを複数持つ。
array = nokogiri.css("div.kategori")

#ハッシュでデータを持つことにする
hash = {}

#ハッシュに任意の形でデータを詰めていく
array.each do |kategori|
  key = kategori.css("h3").text
  links = []
  kategori.css("a").each do |link|
    title = link.text
    url = link[:href]
    add_date = link[:add_date]
    #links = [title, url] 「=」じゃダメ。最後のリンクだけしか残らなくなる
    links << [title, url, add_date] #←後日追加しました
    #linksはこんな形[[a, b, c], [a, b, c]]
    #puts link.keys
  end
  hash[key] = links
end

#pp hash
#puts nokogiri.css("a").countで総リンク数が合うはずです


#■■■フェーズ3「ハッシュを結果的にどう生かす?」■■■
puts "「↑このハッシュを結果的にどう生かす?」→考え中"
puts "ブックマークしたWebサイトをどうしたいか?"
puts "とりあえずhtml作成しました。見た目はほぼbookmarks.htmlと同じ?"


#■■■フェーズ4「link_collection.html」作成■■■
#add_dateでソートする→ダメだ。Firefoxのブックマークに入れる作業の段階でADD_DATEは付与されるので、ブックマーク追加順をこっちが注意しないといけない

File.open("link_collection.html", "w") do |f|
  hash.each do |key, value|
    f.puts "<h3>#{key}</h3>"
    value.each do |title, url|
      f.puts "<p><a href=\"#{url}\">#{title}</a></p>"
    end
  end
end

補足

「正規表現で「複数行にまたがる」任意の文字を削除したい。」

Google検索「正規表現 文字列 改行含む
【参考】https://qiita.com/officemove/items/a6caa20c978197eaff50

ユニークな文字列あってもなくてもいい[\s\S]*?ユニークな文字列あってもなくてもいい

[\s\S]*?→これ単体で使うとファイルの中身が真っ白になります。

コード内のnokogiriで取得した「kategori」のイメージ

フォルダ名があって、aリンクが格納分ぶら下がっているイメージ

  • kategori
    • h3
    • a
    • a

変数の命名が結構大事だと痛感した。

keysが意外と使える

要素の属性値を取得したい時に「どのキー(文字列)でアクセスしていいのか?」がわからないので、取得した要素に「.keys」メソッドを使うと教えてくれる。

この記事で個人的にベンチ入りにしたメソッドだが、今後もよく使いそうだ。

引き続きnokogiriのメソッドは覚えていこう

  • .value
  • .attribute
  • .text

など。

重要なことはコードを覚えるのではなく、いちからコーディングできること

覚えることは少ない。これは巷でプログラミングの学習でよく言われていること。今回「確かに…」と改めて思った。というのは逆に「全部覚えることはほぼ100%無理」それに私は「プログラマーを目指している」わけではない。そしたら、逆転の発想で「じゃあ本質は何が大事なのか?」を見抜くことだと思う。

ちなみに今回のコードで大事なところは以下です。

  • gsubを使うところ
  • 正規表現でかなり楽ができるところ
  • nokogiriのcssメソッドでどこをとるか?というところ
  • 変数命名のわかりやすさ