マイペースなRailsおじさん

Ruby、Ruby on Rails、オブジェクト指向設計を主なテーマとして扱います。だんだん大きくなっていくRuby on Rails製プロダクトのメンテナンス性を損なわない方法を考えたり考えなかったりしている人のブログです。

RubyでWebスクレイピングしたい(ダウンロード編) ~Google画像検索で検索した画像をダウンロードしてくるgemを作りました~

ダウンロード編として、スクレイピングの方法を解説する記事を書く予定だったのですが、gemの公開をもって解説ということにします。 特にメンテしていく気は無いのですが、スクレイピングで色々とコツが必要だったので忘れないようにgemとして形にしました。

github.com

使い方

検索ワードと画像の数を入れて起動します。

$ gem install google_image_scraper
$ google_image_scraper 猫 10

裏で地道にダウンロードしているのでそこそこ時間かかります。 コマンドが正常終了すると、こんなかんじで画像が保存されています。

f:id:ytnk531:20210102230739p:plain

仕組み

下記6段階のステップを踏んでダウンロードします。

  1. Google画像検索で画像を検索
  2. 検索された画像をクリックして右側に詳細を表示
  3. 高画質な画像がダウンロードされるまで待つ
    1. 3.を繰り返す
  4. 画面をスクロールして追加の画像を読み込む
  5. 2に戻る

Google画像検索のHTMLの構造が複雑だったので、けっこう無理のある実装をしました。HTMLの構造が変わったらたちまち壊れることが想定されます。

  1. のステップは工夫が必要なところでした。 2.で表示された詳細ウィジェットに表示している画像は、最初は画質が荒いサムネイルなのです。遅延読み込みをしているらしく、少し待つと掲載元サイトにアップロードされている画像への直リンクに差し替えられて、高画質に表示されます。 f:id:ytnk531:20210102231553p:plain

かなり壊れやすい実装なのと、機能も最低限しか無いです。メンテしていくかは未定です。

RubyでWebスクレイピングしたい(ChromeDriverインストール編)

Seleniumを使って、Google画像検索から画像をスクレイピングすることにしました。

環境

  • Ubuntu 18.04.2 LTS
  • WSL2 on Windouws 10
  • ruby 2.7.2

selenium-webdriverをインストール

まずはruby経由でWebDriverへのリクエストを送信するためのgem、selenium-webdriverをインストールします。

selenium-webdriver | RubyGems.org | your community gem host

gem install selenium-webdriver

selenium-webdriverの主な役割は、WebDriverと通信するためのAPIを提供することです。WebDriverが含まれているわけではないので、操作したいブラウザとそのブラウザ用のWebDriverを別途インストールする必要があります。

Chromeをインストール

操作したいブラウザの本体をインストールします。今回はChromeにします。すでにChromeをインストール済みの場合はスキップしてください。

WSL2では、GUIを操作するためにWindows用のX ServerであるをVcXsrv利用します。インストール時に少し工夫が必要なため、下記のサイトを参考にしました。Ubuntuのバージョンは違いましたが、特に手順は変えずにインストールできました。

  1. WSL2+Ubuntu 20.04でGUIアプリを動かす | AsTechLog
  2. WSL2+Ubuntu 20.04にChromeをインストール | AsTechLog

この操作でWSL2からGUIが使えるようになるので、ついでにgnome-openできるようにしたい方はこちらを参考にしてみてください

WSL2の導入とGUI環境の構築とsshfsしたものもgnome-openしたい!! [Ubuntu18.04/20.04] - Qiita

chromedriverをインストール

WebDriverは各ブラウザごとに実装されています。Chrome用のWebDriverであるchromedriverを使います。

Googleが配布しているchromedriverをダウンロードして、PATHの通っている場所にchromedriverのバイナリを配置すれば完了です。

1. Chromeのバージョンを確認

$ google-chrome --version
Google Chrome 86.0.4240.183

2. chromedriverをインストール

unzip が必要なので、インストールされていなければインストールします。

sudo apt-get install unzip

公式サイトより、先程確認したChromeのバージョンとなるべく近いバージョンのものをダウンロードし、パスの通っているフォルダにはいちします。実行権限を付与するのも忘れずに。

wget https://chromedriver.storage.googleapis.com/86.0.4240.22/chromedriver_linux64.zip
unzip chromedriver_linux64.zip
sudo cp chromedriver /usr/bin
sudo chmod +x /usr/bin/chromedriver

3. 動作確認

irbを起動し、chromeを立ち上げます。

require 'selenium-webdriver'
driver = Selenium::WebDriver.for :chrome

f:id:ytnk531:20201116055649p:plain

無事起動したら、irbからブラウザを操作できることを確かめます。

driver.get 'https://google.com'

googleにアクセスできていれば成功です。 f:id:ytnk531:20201116055944p:plain

起動時にCapabilitiesというオブジェクトを渡すと、オプションを設定できます。ウィンドウを立ち上げないヘッドレス状態で起動するには下記のように指定します。

    caps = Selenium::WebDriver::Remote::Capabilities.chrome(
      'chromeOptions' => { args: %w[--headless --disable-gpu] }
    )
    Selenium::WebDriver.for :chrome, desired_capabilities: caps

ブラウザの状態を確認しつつプログラムを書いたりデバッグをする際はGUI付きで、自動実行する場合はヘッドレス状態で起動するのがよいでしょう。

続く

WSL2にChromeをインストールするところはVcXSrvの起動オプションなどに工夫が必要なので、ハマりポイントかもしれません。 次はようやく画像をダウンロードしていきます。

RubyでWebスクレイピングしたい(ツール選定編)

自作したスクレイピングツールで画像をあつめたい

現在開発中のアプリケーションで、エフェクターボードの画像が100枚くらい欲しかったので、Google画像検索から画像を集めることにしました。

画像収集は機械学習などでかなり需要があるらしく、自作せずとも利用可能なツールがいくらかあるようです。

せっかくですが、rubyではWebスクレイピングするようなプログラムは書いたことがなかったので、自作してみることにしました。

nokogiriかSelenium

rubyスクレイピングをする場合、nokogiriSelenium(あるいはcapybara経由でSelenium)を使うのが主流です。

nokogiriは、HTMLのパーサーです。nokogiriを使う場合は、対象のページのHTMLタグを解析して、所望のデータを取得することになります。テキストデータとして解析するだけなので、JavaScriptなどは実行されません。

Seleniumは、rubyプログラムからブラウザを操作するためのライブラリです。こちらを使う場合、対象のページをブラウザで開いて、rubyプログラムからブラウザ経由で画面内に表示されている要素に対して操作、データ取得を行うことになります。

nokogiriの得意・不得意

  • 得意
    • seleniumを使う場合に比べて余計なことをしないので、高速に動作させられる
    • 単純なページを簡単に解析できる
  • 不得意
    • 収集対象のデータがJavaScriptによって非同期でダウンロードされたり、動的にDOM要素が変化したりする場合、構造の解析が難しい。

Seleniumの得意・不得意

  • 得意
    • JavaScriptで動的に画面が動的に変化する場合でも、あまり大変な解析をせずに操作できる
    • ブラウザで見た感じで操作する手順を記述していけばいいので、nokogiriを使う場合に比べてDOM構造への理解が少なくて済む
  • 不得意
    • ブラウザを起動させて操作するためにかなりリソースを使うので、高速には動作しない

Seleniumにする

最近はJSでゴリゴリ動的にDOM変化させたり、REST APIやGraph QL使って非同期でデータ取得するページが増えている気がするので、Seleniumを使うほうが簡単だと思います。 nokogiriを使ったさい、難読化されたJSを読む羽目になる未来が恐ろしい。

下記の記事では、instagramからnokogiriを使って画像収集ため、ページ閲覧中に発生するHTTPリクエストを解析して、効率的な画像収集の方法を発見しています。こんなかんじで解析できるのが理想的ではありますが、私レベルではこういう作業は答えが見つかるまでどれくらい時間がかかるか読めないので、なかなか手が出しにくい方法です。 Instagramから大量の画像を集める - Qiita

SeleniumとWebdriver

Seleniumを使う場合、操作したいブラウザのdriverが必要になります。 この辺ややこしいのですが、こちらの記事の図が非常にわかりやすいです。

入門、Selenium - Seleniumの仕組み | CodeGrid

f:id:ytnk531:20201110072203p:plain
引用元: 「入門、Selenium - 第1回 Seleniumの仕組み」

Seleniumライブラリは、WebDriverの立ち上げと、WebDriverへのJSONの発行を行ってくれます。WebDriverは各ブラウザごとに用意されていて、基本的にOSごとのバイナリ実行ファイルを取得するかビルドしておく必要があります。

次はchromedriverをインストールして起動するところまで書きます。だいぶ進んでませんが、今日はこのへんで。

やる気が出ないエンジニア、目標がわからなくなってしまったエンジニア、伸び悩んでいるエンジニア…みんな今すぐ「情熱プログラマー」を読もう

とにかくおすすめな「情熱プログラマー

エンジニアのChad Fowlerさんが自身の経験を元に、エンジニアがどう生きていくべきか、について書いた本です。

技術革新によって自分の仕事がなくなるかもしれない、あるいは稼げなくなるかもしれない、という危機にさらされ続けるエンジニアという職業においては、戦略を持ってキャリアを築いて行くべきだ、というのが著者の主張です。

そのため、下記に示すようなエンジニアにとって必要な戦略を教えてくれます。

  • どんなスキルを磨くべきか
  • スキルの身につけ方、磨き方
  • スキルを仕事にどう活かすか
  • コネクションの作り方
  • などなど

とにかくおすすめな理由

どんなレベルのエンジニアでも学びがある(はず)

エンジニアとしての仕事を初めたばかりの人が読むのが一番学びがあると思うが、対象の読者は特に絞られていない。

おそらく、実務をある程度長くこなしてきた人から見ても、そんなの当たり前じゃんっというようなことはあまり書いていないと思う。それは、ジャズミュージシャンからシリコンバレーの第一線で働くことになったChad Fowlerさんならではの気付きと洞察が散りばめられているからだ。

ハートに火が点くことが書いてある

この本に書いてあることを、言われるがままに実践できる人は少ないと思う。それくらい、高い目標を目指すように訴えかけてくる。ただそれが嫌味っぽくなく、なるほどたしかにと思うような文面で書かれている。

本当にいろいろな面からエンジニアに必要な能力についてかいてあるので、人によって感銘を受けるポイントは違うと思う。 私が特に印象に残っている文章を引用する。

変容するIT業界で生き残るスペシャリストはこんな人だ。例えば.NETのスペシャリストは、.NET以外に何も知らないことのいいわけではない。.NETについては権威であるような人だ。IISサーバがハングして再起動する必要があるんだけど・・・・・・「お安い御用だ」Visual Studio .NETとソースコントロールの統合をしたいんだけど・・・・・・「方法を教えてあげよう」よくわからないパフォーマンスの問題で顧客がカンカンだ・・・・・・「30分くれ」
これが君の意味するスペシャリストと違うなら、どうかスペシャリストを名乗らないでほしい。

これは、スペシャリストについてのセクションに書かれた一文だ。スペシャリストとはどんな人を指すのかについて書いてある。この文章を読んで、どんな印象をもっただろうか?耳が痛い、そんなやついるわけない、そんなレベル求められてない…人によって感じることは様々だと思う。

わたしの場合は、「自分はまだまだ甘かった。」だった。 また、「あ、そうか、そこを目指せばいいんだ」とも思った。

目標が明確になる

キャリアって、一体どこを目指せばいいのかわからず、迷子になることがよくあると思う。この本は、あなたが次になにをすべきなのかのヒントをくれる。できるだけ迷わないように、自分が何を学ぶべきなのか的が絞れるように手助けしてくれる。

やる気ブースターとして活用しよう

情熱プログラマーに書いてあることは本当に意識が高いし素晴らしい。それだけ聞くと、そんな情熱ないよとか、自分には無理だとか、思うかもしれない。そういう人こそこの本を読んでほしい。

確かに、実行に移すのが難しいこともたくさん書いてある。でも、決してやれないことではない。勇気を持って人脈を作り、師匠を見つけ、弟子を取り、達人になっていくことはあなたにもできる。きっとハートに火がついてプログラミングを頑張れる一言が書いてあるはずだ。

Tennis-Refactoring-KataでRubyのリファクタリングの練習をやってみた

Refactoring Kataでリファクタリングを練習しています。今日も頑張りました。

Tennis-Refactoring-Kata

github.com

概要

  • テニスのスコアリングをするプログラムのリファクタリングを行う
  • テストは書いてある
  • 同じ仕様のプログラムが3パターン用意されていて、それぞれ違ったややこしさを持っている。

やってみた

やってみました。 github.com

差分はこちら。
Comparing emilybache:master...ytnk531:master · emilybache/Tennis-Refactoring-Kata · GitHub

以下、やったことや感想を書いていきます。

TennisGame1

Comparing emilybache:master...ytnk531:master · emilybache/Tennis-Refactoring-Kata · GitHub

全体の流れは複雑では無いものの、条件分岐がif文が多くて流れがわかりにくくなっている印象。メソッドに切り出すだけでだいぶ改善した。

  • scoreメソッドが長いので、とりあえずメソッド抽出した
  • メッセージを出し分ける部分をメソッドに抽出して、文字列の加工を簡単にした
  • 条件をメソッドにして、何を判定するのかわかるようにした

TennisGame2

Comparing emilybache:master...ytnk531:master · emilybache/Tennis-Refactoring-Kata · GitHub

文字列を生成するためにif文が並びまくっていて、とにかく流れが追いかけにくい。複数の条件に引っかかりながら進むパターンもあるので、条件分の前後関係も実は変えては行けなかったりする場所がある。わかりにくくいじりにくい凶悪なコード。

条件分岐をシンプルにしてやり、文字列の加工も単純にすることでだいぶ改善した。

  • scoreメソッドの分岐を単純にした。
  • 重複した条件の場所を探してまとめた。@p1points > @p2pointsは実は3つの条件に当たっているのを見つけた。
  • スコアの名前を取り出すのにif文を使っていて無駄が多いので、ハッシュから出すようにした。

TennisGame3

Comparing emilybache:master...ytnk531:master · emilybache/Tennis-Refactoring-Kata · GitHub

よくある、短くかくためにいろいろ省略しすぎて謎のコードになっているやつ。謎の数式があるので首をかしげる

リファクタリングは、ロジックの改善よりはメソッド名や変数名を使ってコメントを付けるような感覚だった。もっとわかりやすくできた気がするが、あまり変えなくてもそこまでわかりにくくなかったので、ほどほどで手を止めた。

  • 条件をメソッドに切り出した
  • 数式での条件判定を変数に入れて名前をつけた
  • 謎の変数にちょうどいい名前をつけた

感想

どれも頭が痛くなるコードだったが、TennisGame2がとにかく凶悪だった。こういうの、20~30分でかたづけられるといいんですけどねぇ。今回はテストがついていたのでとてもやりやすかったです。GildedRoseはテストも自分で書かないと行けなかったので。

他のアイディアややりかたについてもっと知りたいと思いました。きっともっといいやり方があるんだろうなぁという。こういうの一緒にできる知り合いがいるといいんですが、難しいですね。

gemの概要を雑に把握するシリーズ③ rack/rack

rackgithub.com

概要

WebサーバーとWebフレームワークの組み合わせを自由に選択できるように、統一的なインターフェースを提供するライブラリ。 Middlewareを選択することで、双方向の通信の間に特定の処理を割り込ませることができる。 各ソフトウェアで必要になるであろう、MimeタイプやRequestなど、HTTPサーバーに基本的な構成要素も提供している。

気になる実装

ポートとか、基本的なWebサーバーの設定は全部rackで受け持っていた。pumaとかにrackからバイパスしている。 OptParserの呼び出し方がかっこよく見える。参考にしよう。 'X-Sendfile', 'X-Lighttpd-Send-File'とか、知らないヘッダがありそうなので、Applicationから使いたくなりそうなHTTPの勉強にはかなり良いかもしれない

bin/rackup xxx.ru とすると、Rack::Builderの中で.ruに書かれたスクリプトを実行する。 Appのインターフェースを実装したものをrunにわたすと@appとして設定される。

Middlewareという名称でロガーやサーバーの立ち上げのためのフックを提供していて、これをカスタマイズしてつかうこともできる。

Refactoring Kataを使ってRubyのリファクタリングを練習する

Refactoring Kata

CodeKataというサイトがありまして、これはプログラミングの練習をしたい人のための問題集です。この様な練習問題を総称して「Code Kata」あるいは「Coding Kata」と呼んでいます。Emily Bacheさんは、リファクタリングに焦点を絞ったCode Kataを公開していて、これらをRefactoring Kataと読んでいます。

Refactoring Kataは、非常に可読性の悪いコードに対して、新しい機能を追加するという課題になっていて、とても練習になります。もともと、こういう課題が無いかなぁと探していたのですが、とてもいいものが見つかりました。手始めに、Gilded RoseというKataに挑戦してみました。

Gilded Rose

こちらのKataのリファクタリングに挑戦します。

github.com

ファンタジーの世界の宿屋Gilded Roseで、商品の管理を行うプログラムに機能追加せよ、という課題です。仕様書の日本語版がついています。ありがとうございます!

GildedRose-Refactoring-Kata/GildedRoseRequirements_jp.md at master · emilybache/GildedRose-Refactoring-Kata · GitHub

コードを眺める

Rubyでやりたいので、'ruby/gilded_rose.rb'を眺めます。

GildedRose-Refactoring-Kata/gilded_rose.rb at master · emilybache/GildedRose-Refactoring-Kata · GitHub

ワーオ、これ、よく作れたなっていうレベルで難しいプログラムになってます。「リファクタリング」に照らし合わせて、どこにどのリファクタリングを適用すべきかを考えていくのも面白いかもしれません。

テストを書く

プログラムが非常に可読性が低いことがわかりましたが、以外にもちゃんと動きます。ということでテストを黙々と書いていきます。

リファクタリングする

テストが書けたらリファクタリングに取り組みます。

いろいろやりましたが、とにかく分岐を無くすこと、ネストを浅くすることを心がけました。 Qualityの更新に関するルールがわかるようになったのではないでしょうか。

gist.github.com

OOPを駆使する

Itemクラスの変更が禁止されていたので、OOPはやらなくて良いかなーと思ったんですが、Adapterクラスを入れればできないこともないしなーとおもい、思いっきりOOPしてみました。

結果としては、個々のメソッドを単純にできたのですが、全体の構造は複雑になり、見通しが悪くなってしまった気がします。 Qualityをクラスにしたのはやりすぎだったように思います。

gist.github.com

練習あるのみ

リファクタリングって、かなり時間のかかる行為だと思っています。実際、今回はトータル3時間くらいコードを触っていました。ただ、それも慣れの問題が大きいと感じていて、こういった教材を使って経験を積んでいくことにより、短時間でリファクタリングすることは十分可能なはずです。

Refactoring Kataを使って日々鍛錬をしていきましょう。