それマグで!

知識はカップより、マグでゆっくり頂きます。 takuya_1stのブログ

習慣に早くから配慮した者は、 おそらく人生の実りも大きい。

SeleniumのWebDriver(chrome)からCookieを取りだしてHTTP::CookieにしたりCurlで再利用する

Selenium のWebDriverからCookieを取り出したら

Seleniumで画像をだけを取り出して保存したり、ファイルを取り出すのは不便だよね。

だけど、直接Curlで叩くのもCookieの問題でもっと不便だよね。

そこでSeleniumCookieCurlで使える形式やNetscape形式で保存したり、RubyPythonなどの http cookieクラスに変換して保存したら楽だと思ったので作ってみた。

私はスクレイピングメインなのでChromeを使っています。

Selenium からCookieを取り出してファイルに保存する。

保存する形式は次の通り。

imid=HeNFSXsHS82I7KZB9xiYkQ; expires=Thu, 06-Jun-2019 17:43:13 GMT; domain=.im-apps.net; path=/;
def save_cookie_to_cookie_jar(driver)
    s = driver.manage.all_cookies.map{|e| [
        "#{e[:name]}=#{e[:value]}" ,
        "#{e[:secure] ? 'secure; ':'' }",
        (e[:expires]||Time.now).strftime('%a, %d-%b-%Y %T GMT'),
        "domain=#{e[:domain]}" ,
        "path=#{e[:path]}" ,
    ].reject{|e| e == '' }.join("; ")

    }.join("\n")
    tmp = Dir::Tmpname.make_tmpname( 'a', '.cookie_jar.txt' )
    tmp = File.expand_path(tmp, Dir.pwd)
    open(tmp, 'w'){|f| f.puts s}
    return tmp
  end

curl で使える形式に保存してみる

## 取り出したファオルの形式は次の通り。( タブ区切り )
example.com    FALSE    /    TRUE    0    XSRF-TOKEN    ePG7sHRafqxj
  def save_cookie_for_curl(driver)
    s=  driver.manage.all_cookies.map{|e|
      [
        e[:domain],
        ((e[:domain]=~/^\./)==0).to_s.upcase,
        (e[:expires]||Time.now).to_time.to_i ,
        e[:path],
        e[:secure].to_s.upcase,
        e[:name], e[:value]
      ].join("\t")
    }.join("\n")
    tmp = Dir::Tmpname.make_tmpname( 'a', '.cookie_jar.txt' )
    tmp = File.expand_path(tmp, Dir.pwd)
    open(tmp, 'w'){|f| f.puts s}
    return tmp
  end

cookie さえなんとかすれば

スクレイピングでHTMLからデータを抜くのに、並行処理だとか、分散処理が出来て一気に楽になる。

サーバーの負荷はたしかに気になるけど、ドメインを dig って CDN 経由ならもう気にしないことにしてる。

CDNはそう簡単に落ちないし、強い。

wkhtmltopdf と組合せたり、 chrome --print-pdf と組合せたり、curl と組合せたり、Mechanizeと組合せたり、Cookieさえなんとかすれば結構楽しい世界が広がる。

関連資料

http://takuya-1st.hatenablog.jp/entry/2013/08/15/031247

SeleniumのchromeDriverのプロファイルのPATHを調べる方法

起動済みのChromeのプロファイルの場所を調べるには

SeleniumからChromedriverが起動したときに、プロファイルがちゃんと渡されて起動したかどうか

それを調べるにはどうすれば良いのか。少し考えてみたら chrome://version/ を見るしか無いと思われる

chromeのプロファイル

f:id:takuya_1st:20170627035700p:plain

version に行くとわかる。

Seleniumなら

executeScript("window.open('chrome://version')")

を実行して タブで開いて、タブへアクセスして version のページからプロファイルを取得することが出来る。

ただし headlessは

chrome --headless で起動している場合には、version のページが全く空っぽになるのでここから取ることはできなかった。

別の方法を考えたけどやっぱり無理で。 headlessに関わるアレコレ(widthやnew tab )に苦しめられるので linux ならxvfb ( X virtual frame buffer) を使うほうが、ずっと楽な気がします。

selenium で chrome ヘッドレスで新規タブの要素をクリックできない

selenium + chromedriver を headlessを扱うときに困る。

新規タブを開かれると、element not clickable で、currently visibleで、 displayed がfalse になる。

なんで、クリックできないエラーになるのか頭をひねったら、width だった。

原因と再現

driver.execute_script("var tab = window.open('about:blank','_blank');");
driver.switch_to.window(driver.window_handles.last)
driver.execute_script('return {"w":window.innerWidth, "h":window.innerHeight};')
# => {"w"=>0, "h"=>0}

お疲れ様でした。Width=0,Height=0です。これではクリックできない。そうなります

chrome headless はdriver.manage.window.resize も通らないのでこれは詰んだと思う。phantomJSだとたしか出来たような。。

新規タブを開かせないように window.open を 乗っ取るしか無いのかもしれない

追記

Selenium側の問題かもしれないと思って Chrome の Dev tool で ポートにアクセスして、実際に window.open して、リモートデバッグで見てきたが、駄目だった.remote debug 経由でタブを開くと問題ないなけど。window.openが呼ばれるとどうも駄目みたい。chrome 側のバグっぽい。

f:id:takuya_1st:20170614154132p:plain:w600

ちょっとした思いなど。

chrome の headlessは 新規タブが開いてフォーカスが動かないのもちょっと面倒だし。 xvfb の方が楽ですね。それかseleniumすてて chrome の debugging protocol を使う方が良いかもしれない。

Chrome DevTools Protocol Viewer

戻る禁止の銀行WEBは戻る禁止 の制限があるので、新規タブを開きまくる。戻る禁止はCRSFやセッションハイジャック対策だとおもうけど、そんなことはPOSTを使うページだけに限定したらいい話だし。戻る対策としては戻る禁止はtoo bad だし、全ページをPOSTにするから戻るができないので新規タブを開きまくる。なのでwebappとして間違ってると思う。iframeに埋め込まれることを防ぐならHTTPヘッダでやるべきだと思うし。クリックジャッキングやはCSPでなんとかなるだろうし、もう戻る禁止と新規タブはやめてほしいわ

macのスマート引用符・スマートダッシュ機能が罠だった。

なんかオカシイなと思ったら

スマートダッシュ・スマート引用符で自動変換されていた件。

Evernote に適当にコードメモしたのが動かなくて

Evernoteに適当にメモしたコード断片をIPython や Pryに貼り付けたら動かなくて、なんだこれ? とおもってよく調べてみたら、引用符が引用符じゃなくなっていた。

引用符が引用符じゃなくなっていた。

お前は何を言ってるんだ?

f:id:takuya_1st:20170626154649j:plain:w150

私も一瞬パニックになったのですが。

よく調べてみたら、ダブルクォートじゃなくて、別の引用符に変換されていました。

いつ起きたのかと調べてみたら、どうやらスマート引用符という機能のようです。

f:id:takuya_1st:20170626153935p:plain

f:id:takuya_1st:20170626153947p:plain

スマート引用符の例

スマート引用符が有効になった状態で、ダブルクォートを入れると、自動的に「引用符」をキレイにしてくれる例です。

www.youtube.com

スマートダッシュの例

同じように、スマートダッシュについても見てみました。ハイフンが変換されていました。

www.youtube.com

防ぐ方法

自動変換機能をオフにするのが一番です。

小さめのフォント(10px前後)を使っていると見逃すので注意が必要です。

クラス・メソッドをインスタンス内から呼び出す

ruby でクラスに所属するメソッドを呼び出す。

クラスに所属するStaticなメソッドを呼び出したときに、クラス名を直接指定するのは不便だよね。

class A
   def  self.do 
       puts :do
   end
   def say
       A.do # クラス名が・・・
   end
end
a = A.new
a.say

self で参照する

self.class で参照すると楽ですね。

self.class.do

ついでに send も取り入れるともっと楽かも?

Object.send でメソッド名を sym か str で指定できるので、そっちを使っても便利かもしれない。

class A
   def  self.do 
       puts :do
   end
   def say
       self.class.send(:do)
   end
end
a = A.new
a.say

これで、むやみにインスタンスメソッドを生やさなくて済む。インスタンスメソッドは「インスタンス」を扱うようにしたいよねやっぱり。インスタンスが無くても動くような処理はクラス・メソッドに登録しておくと使い方を変えられるので意図が明確になる気がする。

クラスの特異メソッドにモジュールのメソッドを取り込む

モジュールのメソッドを、クラスの特異メソッドに取り込む

module で定義した、メソッドやローカル変数を、クラスの特異メソッドに取り込む

次のようにすると実現できる。

module S1
  def sample
    puts :sample
  end
end

class A
  class << self
    include S1
  end
end

共通処理をクラスに書いてしまうとクラスのStaticな部分とインスタンスな部分がごちゃ混ぜになってしまうので、分けて書けるのはMix-inが強いなと思いました まる。

ただ、include や モジュールでほしいのは関数定義 def だけだったりするので、名前空間が独特でruby 分かりづらいよねともおもったり。クラスの継承をしなくて良いのは楽よね。

自動的にmysql のバックアップを取ってくれる automysqlbackup

ぼーっとしててやらかした。

履歴から入力してて、イライラしてるときに、truncate コマンドを本番環境に実行してしまった。もう詰んだ。死にたい。

二度とこんなことがないように

automysqlbackup

というパッケージを導入することにしました、

apt で

apt install automysqlbackup

mysql と一緒に入れておきましょう。

以前入れていたと思ったら、入って無くて本当に詰んだ。

Twitter の OAuth でメールアドレス求めるのはやめてほしい

なぜTwitterで新規登録すると「メールアドレス」が求められてしまうのか。

Twitterの認証連携をして、AuthorizeApp したあとにメールアドレスの確認が必要になる。ほとんどの「JPサービス」はこの形態を取る。

f:id:takuya_1st:20170723160732p:plain

OAuth わかってんのか

認証を丸投げしてるんだからメールアドレスを必須にされても手間が増えて困るだけなんですよ。

かといってFacebookで認証連携なんてフレンドリストやメールアドレスを抜かれて困る。

メールアドレスをわたしたくないのでTwitterを選ぶ

選ぶんだけどメアドが必須にされるのはナゼ?

わたしには理解できないです。そのそも設計がオカシイんじゃないのか。

それとも法律で「メールアドレス」を回収するように求められるのかな?公衆無線LANのように??

作成済みのm5sumとぱぱっと比較する方法(改竄検出の楽な方法

md5sum の -c オプション

-c 生成済みの md5sum の一覧とファイルを比較して改ざんを検出する。

事前にファイルのmd5sum の値の一覧を作成しておき、いまのファイルがそのファイルのハッシュ値と一致するかを調べておく。すると簡単に改竄検出や不正検出を見つけることが出来る。

使い方

最初に、md5sum の一覧を計算してファイルに書き出しておきます。

md5sum sample.txt > out.md5

次に、この出力結果を使ってファイルが一致するかチェックします

md5sum -c out.md5

out.md5の中身は次のようになっています。 md5値 ファイルPATHが列挙されています。

c3cec535915d86f2ce51c668f6919696  sample.ext

チェック-cの場合は、右側のファイルパスのファイルを探して、md5値を導出し事前に計算していた左側に一致するかを調べてくれます。

指定したフォルダの中をマトメてチェック

1ファイルだと何の役に立つかわかりにくいのですが、フォルダの中のまとめてチェックすると強いです。

find コマンドや xargs と組合せる。 フォルダのすべてのファイルについて、md5を計算する

sudo find /etc/  -mindepth 1 -type f   -exec md5sum {} \; > etc.md5

結果を使って、フォルダの中のすべてのファイルについて、ファイルの改竄検出を行う。

sudo md5sum -c etc.md5

if 文で使う

bash の if 文でつかときは --status オプションが便利です。

このオプションを付けると、表示をしない代わりに、チェック結果をコマンドの実行結果で返してくれます。

if ( md5sum -c --status etc.md5 ) ; do 
  echo "改竄チェックOK"
else
  echo "改竄チェックNG"
fi 

これで有るフォルダの中が書き換わってないかを手軽に調べることが出来ます。

もしチェックに失敗したファイルを見つけたいときは

sudo md5sum -c   out.md5 | grep -i failed

のようにしたら見つけることが出来ました。

もっと簡単にするには、

sudo md5sum --quiet -c   out.md5 

--quiet don't print OK for each successfully verified file このオプションをつけるのもいいでしょうね。

ハッシュ値をまとめて保存とは

もし、ハッシュ値保存したファイルを改ざんされると怖いので、ハッシュ値を保存したファイルのハッシュ値を取ってそれをブロックごととに保存していくといいです。このブロックが・・・

ブロックチェーン技術などで盛んにファイルの記録をハッシュ値で云々と叫ばれてます。 基本的にはハッシュ値を計算してファイルに保存してそのファイルのハッシュ値を計算してーってことです。似たようなものです。

フォルダの中のファイルつまり、あるエントリの内部のハッシュ値をすべて見るには、md5sum である程度十分です。便利だし。

衝突の可能性も有るでしょうがローカルファイルで衝突は可能性が低いしね。

参考資料

How to access all your cloud files in one place in Windows 10 | TechRadar

みんな大好き! man

nkfの使い方:base64のエンコードとデコード(ただし文字列だけ

nkf だけで base64エンコードとデコードが出来る

nkf には -mB-MB のペアがあるのでBase64エンコードとデコードが出来る。

nkfbase64 エンコード

echo  -n こんにちは | nkf -MB

nkfbase64 デコード

echo 44GT44KT44Gr44Gh44Gv| nkf -mBW
こんにちは

入力の文字コードを -W : utf8 で指定してる。

主に文字列

文字列だとうまくいく。nkf は「文字コード」を変えるコマンドなので、バイナリも文字として扱ってしまうのでデータは壊れる。アレコレすればいいだろうけど、そこまでやる必要は特になくbase64 コマンドを使ったほうが良いので。

関連記事

nkfでURLエンコードされた文字を扱える(エンコード・デコード・文字コード)

nkf だけで、URIエンコードされた文字を扱える

とある文字列を処理していて、CP932(Shift_JIS ) がそのまま、URLエンコードされた文字に出会って、ちょっと面倒な自体になった 。 node の decodeURI/decodeURIComponent だとUTF-8を期待されて動かない。ruby/pythonだと内部文字コード*1から変換が面倒くさい。nkf文字コードを変換を調べてたら、 nkf だけで行けることがわかった。

NKF で URL をデコードする

$ echo %E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%81%AF%EF%BC%81 | nkf --url-input
こんにちは!

--url-input オプションを使えば、デコード出来る

デコード結果をさらに utf8にするには -W オプションをつけておけばいい。nkf --url-input -W

NKF でURLをエンコードする。

takuya@:~$ echo こんにちは! | nkf -MQ | sed s/=/%/g
%E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%81%AF%EF%BC%81

-MQ オプションはQuoted stream に変換するので、 =を使ってる。それを%に変換すればいい

CP932(MSP92/Shift-JIS) の%エンコード文字コードを扱うには

組合せて使います。

echo %82%B1%82%F1%82%C9%82%BF%82%CD | nkf -w  --url-input
こんにちは

nkf 便利。

NKFは枯れた技術だし安心して使えるわ。Windows文字コードがutf8にならねーかなぁ

関連資料

文字コード変換コマンドの nkfの使い方と実例をまとめました。 - それマグで!

参考資料

*1:ruby の場合内部文字コードはないんだけど、utf-8がデフォルト仕様なので、やっぱり面倒くさい

Macのopen(開く・保存)のダイアログで、不可視ドットファイル(フォルダ)を選択するーフルパスで開くモード

Mac の保存ダイアログで不可視フォルダを選びたい

開くダイアログのフォルダ選択のときに、隠しファイルやフォルダを開きたいときがよくある。

ドットファイルやドットのフォルダ( .rbenv / . pyenv など)を開きたい。

たとえば、atom/visual studio code/intelli J のIDEドットファイルを見たいときが結構ある。

私の場合 RubyMineやPycharm で rubygemspythonのpip ファイルを見たいときが頻発した。毎回コマンドで指定するのも面倒くさいので、なんか方法がないか調べたらあった。

⌘⇧G / Shift+Cmd+G でフルパス入力

保存・開くダイアログで上記のショートカットを入力すると、ダイアログが開いて、フルパスで指定できるようになる。

f:id:takuya_1st:20170612012219p:plain

残念ながら、補完は効かないようで・・・

フルパスを取得するには、いったんターミナルなどでフルパスを作ってクリップボードに入れておく必要がありますね。

Windows(田)のNTFSみたいに、フォルダ・ファイルごとに可視・不可視を切り替えらればいいのにね。APFSで導入されないかなー

rubymine で .rbenv を選ぶときに活躍した。

jetbranins の rubymine の nvm / rbenv と webstorm で .npm / nvm など、ドットファイル (dotfile) になったフォルダから インタプリタで実行バイナリを選ぶときにこの方法が活躍する

rubymine などで実行環境SDKで .rbenv が出てこない時があってそのとき、この方法によって Shift+Cmd+G を押してフルパスを入力しフォルダを移動して解決する。 f:id:takuya_1st:20200511085837p:plain

f:id:takuya_1st:20200511085840p:plain

参考資料

http://inforati.jp/apple/mac-tips-techniques/system-hints/how-to-use-move-to-folder-command-in-a-open-or-save-dialog-in-macos.html

Windowsはフォルダ毎に不可視を設定できる。

Windowsのホームディレクトリに出てくる、dotfiles を非表示にする。 - それマグで!

2020-05-11 追記

intellij で困ったので追記した。

pry のEchoをオフにする

コピペなどでエコーが邪魔なとき

_pry_.config.print = proc {}

irb の場合は

conf.echo = false

こうすれば、長いStringのスクロールで死ななくて済む

一時的にオフにして、元に戻すなら

_pry_.config._print = _pry_.config.print
_pry_.config.print = proc{}

元に戻す

_pry_.config.print = _pry_.config._print

これをトグルできる関数にしてくれると便利なんだけどな。

参考資料

https://github.com/pry/pry/wiki/Customization-and-configuration#Config_print

chrome でHTML をPDFに変換する

HTMLをPDFに変換する --print-pdf オプション

HTMLをPDFに変換するコマンドは、いくつか有るけれど、 chrome のオプションだけで実現できる。

しかも chrome59 から headless オプションがあって更に便利になった。

コマンドの使い方

chrome-bin --headless --print-pdf=path/to/output.pdf  input_url

input_url のところには、ブラウザで開けるものなら、大抵のものが入る。

ローカルファイルなども印刷するできる。

chrome-bin --headless --print-pdf=path/to/output.pdf  /path/to/index.html

mac os の場合、直接バイナリを指定する

macOS X の場合は .app の中身をたどって指定する必要がある。

/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
 --headless\
 --print-to-pdf='/Users/takuya/Desktop/a.pdf' \
file:/Users/takuya/Desktop/sample.html

開けないURLはどうするのか

ログインが必要なURLなどはどうするのか。

Cookieを拾ってくるしか無い。Cookieはプロファイルに入ってるのでプロファイルから拾うしかない。

--user-data-dir=/Users/takuya/Library/ApplicationSupport/Google/Chrome/Default/"

プロファイルを指定することでCookieを拾えるのでそこから何とかする。ただし、プロファイルは起動時にロックが掛かるので、同時に複数のインスタンスで使用できない。その為に、セッションのCookieは扱うのがとても面倒なので、Chromeをいったん終了したら消えてしまうセッションCookieを扱うには、Chromeを強制終了したりSelenium側でAddCookieするとか面倒くさい処理がひつようになる。

参考資料

List of Chromium Command Line Switches « Peter Beverloo

Seleniumで印刷(print preview)を経てPDFを保存する方法

Selenium + chromedriverで 印刷プレビューにアクセスするのが面倒くさい

Chromeを webdriverでアクセスすると iframe で作られた、印刷プレビューにアクセスするのがとてもめんどくさい。(セキュリティ関連のアレコレの制限をdisableオプションで取っ払えば出来るんだろうけど)

HTMLを印刷したい。

たとえば、某銀行の印刷プレビューページなどをそのまま取りたいとか。印刷用のCSSが正しく動くかどうかとか、テストしたい箇所も山ほどある。

キオスクオプションが最強

1週間ほど毎日のように、あれこれ調べまくった結果、Chromeの起動オプションに –print-to-pdf がありました。しかしコレでは、「いま表示しているページ」を印刷するのが不便です。

Cookieを引き継ぐためにProfileを設定してChromeの再起動が必要です。

さらに探していたら、 --kiosk-printing とう「最高」なオプションを見つけたので歓喜しました。

--kiosk-printing とは?

印刷ダイアログが開くと、印刷ボタンを無条件に押してくれます。これはキオスクモードで、ユーザーにプリンタの選択をさせない為に作られているようです。

この印刷ボタンを無条件に押してくれるオプションを使うとHTMLをPDFで保存してくれます。

selenium で使うとき

Capabilitiesを活用します、そして該当ページへ移動したら、window.print() を実行します。

caps = Selenium::WebDriver::Remote::Capabilities.chrome(
      "chromeOptions" => {
          "args" => [
              '--kiosk-printing',
          ],
          "prefs" => {
              download: {
                  default_directory: '/path/to/download'
                  directory_upgrade: true,
                  prompt_for_download: false,
              }
          }
      })
driver = Selenium::WebDriver.for :chrome , :desired_capabilities => caps

driver.get 'chrome://version'
driver.execute_script(' return window.print();')

window.print で表示された、印刷ダイアログで、ボタンを押すことは kioskオプションがやってるくれるのでSelenium側では処理をする必要がありません。

保存先のディレクト

基本的には、保存先のディレクトリで指定したフォルダになりますが、設定が曖昧だと ~/Downloads に保存されます。

もし、設定がうまくいかないときは 、Seleniumで次のページへアクセスして、設定を変更したら間違いなくうまくいく。

driver.get 'chrome://settings'

参考資料

http://peter.sh/experiments/chromium-command-line-switches/