Hatena::ブログ(Diary)

わさっき RSSフィード

2018年03月03日

[] 垂直タブを含むHTMLを,REXMLに通すとエラー

数日前から,少しずつ時間をとって,Rubyスクリプトを書いています.2つに分けています.1番目のスクリプトファイルを実行すると,Selenium (selenium-webdriver)とヘッドレスChromeを使って,Webページを収集します.その後,2番目のスクリプトファイルを実行しまして,収集して保存したファイルを順に参照し,必要な要素だけを取り出します.

2番目のスクリプトで,読み出しにはREXMLライブラリを使用しています.読み出し対象は,XMLではなくHTMLですが,とくにエラーは発生しません.REXML::XPath.firstを使うと,XPath指定を使ってピンポイントで必要な箇所を取得できるのがメリットとなります.なお,XPathを手書きするとバグのもとですが,保存したHTMLファイルをFirefoxで開き,F12キーで開発ツールを出して,インスペクターでほしい要素を見つければ,右クリックして「コピー > XPath」により,ロケーションパスがコピーできます.

気を良くして,処理させていると,2番目のスクリプトで,実行時エラーが発生しました.

エラーメッセージには「Illegal character "\v" in raw string」と出ています.

「\v」は垂直タブです.HTMLファイルを,Emacsで開くと,「^K」と表示される1バイトコードとなっていました.C-dで削除し,保存しました.

他のHTMLファイルにもあれば,またそこでエラーが出ます.grepコマンドで探すとして…「grep -l '\v' *.html*1ではうまくいきません.http://www.atmarkit.co.jp/flinux/rensai/linuxtips/a018bashhexcode.htmlによると「$'<文字列>'という指定をすると、それがバックスラッシュでエスケープされている文字に置き換えられる」とあり,zshでもうまくいきました.コマンドは,「grep -l $'\v' *.html」です.と,苦労したものの,実行結果は「なし」です.先ほどの1件だけだった,というわけです.

ともあれ今後も,垂直タブを含んだHTMLが出てくるかもしれません.1番目のスクリプトで,ファイルに保存する前に,取り除きましょう.driver.page_sourceにより,ページのソースを獲得している箇所を,「driver.page_source.gsub!(/\v/, "")」とします.2つのスクリプトを順に実行し,とりあえずエラーは出ませんでした.

*1オプション「-l」を指定することで,該当ファイル名だけを出力します.

2018年01月22日

[] 数か月ぶりのスクリプト実行,DB関連を修正

いくつかのRubyスクリプトを数か月ぶりに動かしてみました.実行するのは2台のLinuxサーバです.その間,Rubyのバージョンは2.3.0-devから2.5.0-devに上げています(rbenv使用).

さて,データベースアクセスを伴う,あるスクリプト(your_script.rb)を実行すると,エラーが出ました.

Traceback (most recent call last):

1: from your_script.rb:22:in `<main>'

/.../.rbenv/versions/2.5.0-dev/lib/ruby/gems/2.5.0/gems/activerecord-5.1.4/lib/active_record/migration.rb:525:in `inherited': Directly inheriting from ActiveRecord::Migration is not supported. Please specify the Rails release the migration was written for: (StandardError)

class MyMigration < ActiveRecord::Migration[4.2]

「your_script.rb:22」と表示されているので,該当行を見ると,「class MyMigration < ActiveRecord::Migration」となっていました.Webで少し情報収集し,結局,エラーメッセージの最終行のとおり,「class MyMigration < ActiveRecord::Migration[4.2]」と書き換えました.

実行し直すと,メッセージが変わりました.またもエラーです.

Traceback (most recent call last):

5: from your_script.rb:160:in `<main>'

4: from your_script.rb:11:in `connect_db'

3: from /.../.rbenv/versions/2.5.0-dev/lib/ruby/gems/2.5.0/gems/activerecord-5.1.4/lib/active_record/connection_handling.rb:58:in `establish_connection'

2: from /.../.rbenv/versions/2.5.0-dev/lib/ruby/gems/2.5.0/gems/activerecord-5.1.4/lib/active_record/connection_adapters/abstract/connection_pool.rb:880:in `establish_connection'

1: from /.../.rbenv/versions/2.5.0-dev/lib/ruby/gems/2.5.0/gems/activerecord-5.1.4/lib/active_record/connection_adapters/connection_specification.rb:185:in `spec'

/.../.rbenv/versions/2.5.0-dev/lib/ruby/gems/2.5.0/gems/activerecord-5.1.4/lib/active_record/connection_adapters/connection_specification.rb:188:in `rescue in spec': Specified 'postgresql' for database adapter, but the gem is not loaded. Add `gem 'pg'` to your Gemfile (and ensure its version is at the minimum required by ActiveRecord). (Gem::LoadError)

最後の文の「Add `gem 'pg'` to your Gemfile」が,問題解決の肝心なところと理解しつつも,実際のところGemfileを使用していません.

とりあえず,gem listを実行して,activerecordと,データベース接続に使用するライブラリpg)を見てみると,「activerecord (5.1.4)」と,「pg (1.0.0, 0.21.0)」と出ました.https://rubygems.org/gems/pgにアクセスしたところ,バージョン1.0.0がリリースされたのはつい最近(January 10, 2018)となっています.

最新のpgを,これまた最新のactiverecordでは受け付けてくれないらしいと判断し,Rubyスクリプト(your_script.rb)内で,pgの古いバージョンを指定することにしました.

gem "pg", "0.21.0"
require "pg"
require "active_record"

再度実行すると,エラーが消え,期待する動作となりました.

2017年09月13日

[] 「不満ファースト」「辞任ファースト」の次は?

はてなブックマーク - 都民ファ、SNSなど発言制限…議員から不満も : 政治 : 読売新聞(YOMIURI ONLINE)*1では「不満ファースト」,はてなブックマーク - 都民ファ、野田代表が辞任 「特別秘書に専念」 - 共同通信 47NEWSでは「辞任ファースト」とコメントして,スターをいただきました.

さて次にどんな「○○んファースト」を言えばいいんだろう,と思案すること数秒,あらかじめ「○○んファースト」を用意しておけばいいやと思い立ちました.

さっそくRubyでプログラミングです.ソースコードと結果は,Gistに公開しました.

「○○ん」の候補については,SKK辞書から抽出しました.SKK辞書を見たところ,SKK-JISYO.Lが最も良さそうです.作成したRubyスクリプトでは,カレントディレクトリにSKK-JISYO.L.gzがあればそれを使用し,なければダウンロードして保存することで,実行し直した際の通信を抑制しました.

ダウンロードや,gz形式の伸張は,Rubyの標準添付ライブラリを使用しました.伸張した中身は(Emacsで開いたところ)EUC-JPなので,UTF-8に変換してから処理しました.

「○○ん」のパターンマッチを行うのは,「/^(\p{Hiragana}[^ぁぃぅぇぉっゃゅょ]ん) /」の正規表現です.かみ砕くと,

  • 先頭文字はひらがな*2
  • 2文字目はひらがな小文字以外
  • 3文字目は「ん」
  • その次は空白(4文字以上の読みは対象外)

であるかどうかを判定します.「あうん /阿吽/」の行はこの正規表現にマッチし,ここから「阿吽」を取得して「阿吽ファースト」を作りました.「kaki = $'.split(/[\/;]/)[1]」と書いたのは,「あへん /阿片/鴉片;=阿片/」の行から「阿片」だけを獲得したかったからです.

「SKK-JISYO.L,v 1.1367 2017/09/09 00:10:02」のバージョンから獲得できたのは,974個でした.「都民ファースト」「不満ファースト」「辞任ファースト」のいずれも,含まれています.「課金ファースト (かきんファースト)」「鞄ファースト (かばんファースト)」「欺瞞ファースト (ぎまんファースト)」「出禁ファースト (できんファースト)」などが,将来どこかで使えそうです.

2017年08月13日

[] 倍数の数直線

いきなりですが問題です.

2の倍数であり,3の倍数でもあり,4の倍数でもあり,5の倍数でもあり,6の倍数でもあるけれども,7の倍数ではなく,8の倍数でもなく,9の倍数でもないという,最小の正の整数は何ですか?

さっそくですが解答です…60ですね.というのも,2,3,4,5,6の最小公倍数は60であり,この数は,7でも,8でも,9でも割り切れないからです.

続いてですが問題です.

0から60までの整数について,2の倍数,3の倍数,…,9の倍数を見つけて,分かりやすく表示しましょう.

これについては画像です.

f:id:takehikom:20170813095649p:image

もしブラウザ表示の都合で縮小表示されたら,オリジナルサイズをどうぞ.

数直線の右端が60で,2から5までの倍数には○がついており,7から9までの倍数には○がついていません.またそれより左には,5つに連続して○がついている数がなく,最初の問題について,60が答えであることを,視覚化したものとなっています.

ところでこの画像は,約数や倍数を考える際には0を対象としないという,小学校の算数に基づいています.0は任意の整数の倍数という立場の画像も作成しましたので,こちらよりご覧ください.

きっかけは,大学図書館にあった,啓林館の算数教科書『わくわく算数』(平成16年1月31日検定済)です.https://twitter.com/takehikom/status/895752622426079232から始まるツイートをするより前に,画像を生成していましたが,生成のためのRubyスクリプトを手直しし,Gistで公開しました.

作成したスクリプトファイル(number-line-drawer.rb)は,オプション指定により以下の描画をします.

  • 目盛りつきの数直線を描きます.目盛りの数は任意に指定できます.またフォント指定があれば,各目盛りの上に,0から最大値までの整数値を書くこともできます.
  • 目盛りつきの数直線を上下方向に並べて,それぞれの左に「xの倍数」を書き,それぞれの倍数に関する数直線を構成します.xは,1から9までであれば任意に指定できます.該当する倍数に○をつけることもできます.0を倍数とする(0にも○をつける)こともできます.「〜の倍数」の代わりに,「Multiples of 〜」として英語で表記することもできます.

描画にあたっては,ImageMagickのconvertコマンドを使用しています.画像の上に画像を貼り付ける際,貼り付け対象の画像サイズを知るのに,ImageMagickのidentifyコマンドも使用しています.

フォント名などはスクリプト内に書かず,環境変数FNから取得するようにしました.8月11日のツイート時には,MSゴシック(msgothic.ttc)を使用していましたが,Takaoゴシック(TakaoGothic.ttf)のほうが見やすいと感じ,上の画像でも採用しています.Linuxでも動作確認をしています.内部で,高さ:幅=2:1の等幅フォントを前提としている箇所があり,プロポーショナルフォントでの動作確認をしていません.


「0は偶数だが2の倍数ではない」について,先月から今月にかけて,サブブログでいくつか記事を書きました.

本記事ではその是非に立ち入りません.むしろ「0を2(および他の整数)の倍数に入れる」にせよ「入れない」にせよ,オプション指定でどちらの図も描けるよう配慮しました.同様の問題意識で作成した記事(そしてRubyスクリプト)に,Rubyで6÷2(1+2)があります.

2017年04月07日

[] iPhoneの写真とMicrosoft Pixとで,ExifのDateTime情報が異なる?

日常風景の写真撮影に,コンパクトデジタルカメラから,Android端末やiPhoneに切り替わって,何年になるでしょうか.

いずれにせよ,撮った写真は,1台のPCのハードディスクに集約しています.ファイル名に日本語があればASCII文字のみとなるよう変換し,撮影年月日でフォルダに振り分けます.撮影年月日は,ファイル名ではなく,Exif情報の中のDateTimeタグを参照します.

なのですが,先月から,その振り分けでエラーが発生していました.振り分けは自作のRubyスクリプトで,都合によりそのソースコードは非公開とします.ともあれエラーの発生状況を再現してみます.

2つのファイルがあります.もともとは長いファイル名ですが,簡単のため1.jpg,2.jpgとします.Rubyでは,gem install exifrのコマンドで,exifrライブラリをインストール済みです.

irbコマンドを使って,日時情報を見てみます.まずは1.jpgのほう.

$ irb
irb(main):001:0> require "exifr"
=> true
irb(main):002:0> ex = EXIFR::JPEG.new("1.jpg"); nil
=> nil
irb(main):003:0> ex.date_time
=> 2017-04-03 06:29:45 +0900
irb(main):004:0> ex.date_time.class
=> Time
irb(main):005:0> exit

途中の「; nil」について,書かなかったら,exに代入されるオブジェクトの内容が表示されます.バイナリデータもあり,膨大な出力となります.

次に,2.jpgのほうですが,こちらは自作の振り分けスクリプトでエラーが発生するものです.

$ irb
irb(main):001:0> require "exifr"
=> true
irb(main):002:0> ex = EXIFR::JPEG.new("2.jpg"); nil
=> nil
irb(main):003:0> ex.date_time
=> "2017:04:03 06:30:00.284"
irb(main):004:0> ex.date_time.class
=> String
irb(main):005:0> exit

エラーになったという2.jpgでは,日時情報は「.284」(秒未満の値?)がついた文字列です.それに対し1.jpgでは,日時情報がTimeのインスタンスとなっています.

Exifの仕様に,RubyのTimeクラスが入っているというのは,おかしな話なので,ここはおそらくexifrライブラリがお節介をしているのでしょう.DateTimeタグの値はあくまでバイト列であり,その値を,容易な操作でTimeに変換できるのであれば,そのインスタンスに,そうでなければStringのインスタンスにしている,と解釈すればすっきりします.

年月日を獲得する処理は,これまで,次のように記述していました.

dt = ex.date_time
ymd = dt.strftime("%Y%m%d")

これを以下のように書き換えました.

dt = ex.date_time
case dt
when Time
  ymd = dt.strftime("%Y%m%d")
when String
  ymd = dt.gsub(/\D/,"")[0,8]
else
  raise
end

ちなみに1.jpgはiPhoneのカメラ(標準アプリ)で,2.jpgはMicrosoft Pixで撮影したものです.標準アプリだと,GPS関連の情報も,Exifとして入っています.上で「; nil」を書いて中身を見ませんでしたが,中身を見てみると,終わりのほうに「@exif=」から始まって詳細を知ることができ,1.jpgと2.jpgとで,gps関連の有無も確認できました.

Microsoft Pixは,無音で撮影したいときや,詳細なメタ情報が不要なときに,活用することとし,家族写真など,日常の風景は,標準アプリのカメラのほうが良さそうです.