おぴよの気まぐれ日記

おぴよの気まぐれ日記

岡山やプログラミング、ファッションのこと、子育てや人生、生き方についての備忘録。

【Rails】ファイルをデータベースに保存する方法

ファイルを保存する方法はRailsチュートリアルとかでは、CarrierWaveというgemを使う。 だがちょっとしたアプリとか作りたい時には、DBに保存した方が都合がいい時もあるはず。

gemを使う方法は別の機会でまとめるとして、今日はDBに保存する方法を。

モデルを作る

$ rails g model Image name:string picture:binary
  invoke  active_record
  create    db/migrate/20171215092903_create_images.rb
  create    app/models/image.rb
# migrate
class CreateImages < ActiveRecord::Migration[5.0]
  def change
    create_table :images do |t|
      t.string :name
      t.binary :picture

      t.timestamps
    end
  end
end
$ rails db:migrate
== 20171215092903 CreateImages: migrating =====================================
-- create_table(:images)
   -> 0.3369s
== 20171215092903 CreateImages: migrated (0.3370s) ============================

コントラーラーを作る

$ rails g controller Images
Running via Spring preloader in process 7555
      create  app/controllers/images_controller.rb
      invoke  erb
      create    app/views/images
      invoke  helper
      create    app/helpers/images_helper.rb
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/images.coffee
      invoke    scss
      create      app/assets/stylesheets/images.scss
# images_controller.rb
class ImagesController < ApplicationController
  def new
    @image = Image.new
    @image.name = "hogehoge"
  end

  def create
    @image = Image.new(image_paprams)
    @image.name = params[:image][:picture].original_filename
    @image.picture = params[:image][:picture].read

    if @image.save
      redirect_to root_path
    else
      render 'new'
    end
  end

  private

    def image_paprams
      params.require(:image).permit(:name, :picture)
    end

end

ビューを作る

# new.html.erb
<%= form_for(@image) do |f| %>
  <div class="form-group">
    <%= @image.name %>
    <%= f.file_field :picture %>
    <%= f.submit "画像を投稿する" %>
  </div>
<% end %>

↓htmlに変換されると...

<form class="new_image" id="new_image" enctype="multipart/form-data" action="/images" accept-charset="UTF-8" method="post"><input name="utf8" type="hidden" value="✓"><input type="hidden" name="authenticity_token" value="ZgO8WthU8KtI/rspO/oVgHavcwUUVQl1jDwm0DQAsZXwCHYLO02KUqcX+Kt54gKlUlQP4sqDe3NUltVWjtZQDw==">
  <div class="form-group">
    hogehoge
    <input type="file" name="image[picture]" id="image_picture">
    <input type="submit" name="commit" value="画像を投稿する" data-disable-with="画像を投稿する" disabled="">
  </div>
</form>

まとめ

こんな感じでしょうか。 とりあえず言いたいことは、

  • binary型のカラムをモデルに用意します。
  • form_forf.file_fieldを使います。
  • params[:image][:picture].readで画像データが保存できる

form_forがよしなにやってくれるのは分かるのだけど、いまいち理解が浅いので色々動かしてみて勉強しよう。

で、実際に登録された画像データはこんな感じです。

$ rails c
> Image.all
  Image Load (1.6ms)  SELECT "images".* FROM "images"
=> #<ActiveRecord::Relation [#<Image id: 1, name: "17704-koala-bear-pv.jpg", picture: "\xFF\xD8\xFF\xE0\x00\x10JFIF\x00\x01\x01\x01\x00H\x00H\x00\x00\xFF\xDB\x00C\x00\n\a\a\b\a\x06\n\b\b\b\v\n\n\v\x0E\x18\x10\x0E\r\r\x0E\x1D\x15\x16\x11...", created_at: "2017-12-15 09:52:29", updated_at: "2017-12-15 09:52:29">]>

【Ruby on Rails】月の最初の日、最終日はどうやって取得する?

画面から入力された日付の月が何日までなのか取得する方法が分からなかったので調べてみました。

月の最終日

irb> Date
NameError: uninitialized constant Date
Did you mean?  Data
    from (irb):9
    from /Users/taku/.rbenv/versions/2.3.3/bin/irb:11:in `<main>'

irb> require "date"
=> true

irb> date = Date.today
=> #<Date: 2017-12-13 ((2458101j,0s,0n),+0s,2299161j)>

irb> date.end_of_month
NoMethodError: undefined method `end_of_month' for #<Date: 2017-12-13 ((2458101j,0s,0n),+0s,2299161j)>
  from (irb):13
  from /Users/taku/.rbenv/versions/2.3.3/bin/irb:11:in `<main>'

むむむ何でだ。 end_of_monthというメソッドを使うと月の最終日が取れる。取れたんだけど...

調べてみると、end_of_monthはRailsで定義されたメソッドみたい。 なのでrails cで検証スタート。

> rails c

> date = Date.today
=> Wed, 13 Dec 2017

> date.end_of_month
=> Sun, 31 Dec 2017

> date.end_of_month.to_s
=> "2017-12-31"

月の最初の日

月の最初の日を求める時はbeginning_of_monthを使う

> date = Date.today
=> Wed, 13 Dec 2017

> date.beginning_of_month
=> Fri, 01 Dec 2017

> date.beginning_of_month.to_s
=> "2017-12-01"

その他のちょっとしたあれこれ

> Date.today.year
=> 2017

> Date.today.month
=> 12

> Date.today.day
=> 13

> Date.today.wday
=> 3 # 「0」が日曜日なので「3」は水曜日

【Rails】created_atとupdated_atへデータを登録する方法

過去データの取り込みで直接DBにデータを入れる際にcreated_atupdated_atにはどうやって値をセットすれば良いのか分からなかったから調べてみた。

結論としてはTimeWithZoneクラスが使われているからTime.zone.nowすれば良いっぽい。

> Shop.first.created_at.class
  Shop Load (0.9ms)  SELECT  "shops".* FROM "shops" WHERE "shops"."deleted_at" IS NULL  ORDER BY "shops"."id" ASC LIMIT 1
=> ActiveSupport::TimeWithZone

TimeWithZone?

使い方はこんな感じ。 http://api.rubyonrails.org/classes/ActiveSupport/TimeWithZone.html

> Time.zone = 'Eastern Time (US & Canada)'
=> "Eastern Time (US & Canada)"
> Time.zone.now
=> Mon, 18 Dec 2017 01:17:43 EST -05:00

> Time.zone = "Asia/Tokyo"
=> "Asia/Tokyo"
> Time.zone.now
=> Mon, 18 Dec 2017 15:17:52 JST +09:00
> Time.zone.now.to_s
=> "2017-12-18 15:18:31 +0900"

> Time.zone.local(2012,12,31,15,30,00)
=> Mon, 31 Dec 2012 15:30:00 JST +09:00
> Time.zone.local(2012,12,31,15,30,00).to_s
=> "2012-12-31 15:30:00 +0900"

> Time.zone.parse('2013-12-31 15:30:00')
=> Tue, 31 Dec 2013 15:30:00 JST +09:00
> Time.zone.parse('2013-12-31 15:30:00').to_s
=> "2013-12-31 15:30:00 +0900"

その他の使い方は

> t = Time.zone.now
=> Mon, 18 Dec 2017 17:53:08 JST +09:00
> t.year
=> 2017
> t.month
=> 12
> t.day
=> 18
> t.hour
=> 17
> t.min
=> 53
> t.sec
=> 8

> t.zone
=> "JST"
> t.to_s
=> "2017-12-18 17:53:08 +0900"
> t + 1.day
=> Tue, 19 Dec 2017 17:53:08 JST +09:00
> (t + 1.day).to_s
=> "2017-12-19 17:53:08 +0900"
> t.beginning_of_year
=> Sun, 01 Jan 2017 00:00:00 JST +09:00
> t.ago(1.day)
=> Sun, 17 Dec 2017 17:53:08 JST +09:00
> t.ago(1.day).to_s
=> "2017-12-17 17:53:08 +0900"
> t.inspect
=> "Mon, 18 Dec 2017 17:53:08 JST +09:00"
> t.to_a
=> [8, 53, 17, 18, 12, 2017, 1, 352, false, "JST"]

で、UTCってなんだってなったのでメモ。

「UTC(協定世界時)」とは、世界各地の標準時を決めるときの基準となる「世界標準時」のことです。たとえば日本の標準時(JST)は「UTC」よりも 9時間進んでいるため「UTC+09:00」と表示されます。 http://www.724685.com/word/wd140611.html

UTCは世界標準の時間で、日本の場合はJSTで9時間ずれてるから+9するのですね。 納得。

【Rails環境構築】bundle installでエラーになる時の対処法!

こんにちは。opiyoです。

  • 昨日まで普通に動いていたのに
  • 教科書、本と通りに設定しているはずなのに
  • 何もしてないのに動かないぞー

って経験無いでしょうか?

僕も昨日新しくMacが手に入ったのでRails環境をローカルに構築して今作っているRailsアプリを動かそうとしたら...


2時間近くはまってしまいました。

元々使ってたMacと全く同じ設定のはずなのに何故動かないんだーって状況でした。

どうにかこうにか解決する事が出来たので、その方法について共有できればと思います。

エラーが出た時に「どうすれば良いか」が分からなくて諦めてしまう。ってのが結構あると思うのですが答えは必ずエラーの中に書かれている ので頑張ってGoogle翻訳使って一つずつ解決していくのが大事だと思いますので僕がどうやって進めていったのかのログです。

動かしたいRailsアプリについて

僕の場合は、こんな感じ。

$ ruby -v
ruby 2.5.0p0 (2017-12-25 revision 61468) [x86_64-darwin15]

$ rails -v
Rails 5.1.4

bundle installができない

$ bundle install

Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

    current directory: /Users/tnakano/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/pg-0.21.0/ext
/Users/tnakano/.rbenv/versions/2.5.0/bin/ruby -r ./siteconf20180125-28967-e942ot.rb extconf.rb
checking for pg_config... no
No pg_config... trying anyway. If building fails, please try again with
 --with-pg-config=/path/to/pg_config
checking for libpq-fe.h... no
Can't find the 'libpq-fe.h header
*** extconf.rb failed ***
Could not create Makefile due to some reason, probably lack of necessary
libraries and/or headers.  Check the mkmf.log file for more details.  You may
need configuration options.

Provided configuration options:
    --with-opt-dir
    --without-opt-dir
    --with-opt-include
    --without-opt-include=${opt-dir}/include
    --with-opt-lib
    --without-opt-lib=${opt-dir}/lib
    --with-make-prog
    --without-make-prog
    --srcdir=.
    --curdir
    --ruby=/Users/tnakano/.rbenv/versions/2.5.0/bin/$(RUBY_BASE_NAME)
    --with-pg
    --without-pg
    --enable-windows-cross
    --disable-windows-cross
    --with-pg-config
    --without-pg-config
    --with-pg_config
    --without-pg_config
    --with-pg-dir
    --without-pg-dir
    --with-pg-include
    --without-pg-include=${pg-dir}/include
    --with-pg-lib
    --without-pg-lib=${pg-dir}/lib

To see why this extension failed to compile, please check the mkmf.log which can be found here:

  /Users/tnakano/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/extensions/x86_64-darwin-16/2.5.0-static/pg-0.21.0/mkmf.log

extconf failed, exit code 1

Gem files will remain installed in /Users/tnakano/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/pg-0.21.0 for inspection.
Results logged to /Users/tnakano/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/extensions/x86_64-darwin-16/2.5.0-static/pg-0.21.0/gem_make.out

An error occurred while installing pg (0.21.0), and Bundler cannot continue.
Make sure that `gem install pg -v '0.21.0'` succeeds before bundling.

In Gemfile:
  pg

これだけ見ても何が何だか分からないですよね。 ですが諦めずに良く良く表示されたメッセージを見てください。

ポイントは最後辺りのMake sure thatgem install pg -v '0.21.0'succeeds before bundling.ですね。

直訳するとバンドルの前にgem install pg -v '0.21.0'が成功していることを確認してください。`になります。

じゃー次にgem pgって何だってなると思いますので、これを調べてみるとPostgreSQLにアクセスするために必要なことだと分かります。

「あっそもそもPostgreSQLインストールしてねーじゃん」ってことに僕は気づきましたので、PostgreSQLをインストールします。

postgresqlをインストールする

$ brew install postgresql
==> Installing postgresql

PostgreSQLがインストール出来たので、もっかいbundle install実行!

$ bundle install
Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

    current directory: /Users/tnakano/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/rmagick-2.16.0/ext/RMagick
/Users/tnakano/.rbenv/versions/2.5.0/bin/ruby -r ./siteconf20180125-42802-14ohnoi.rb extconf.rb
checking for clang... yes
checking for Magick-config... no
checking for pkg-config... yes
Package MagickCore was not found in the pkg-config search path.
Perhaps you should add the directory containing `MagickCore.pc'
to the PKG_CONFIG_PATH environment variable
No package 'MagickCore' found
checking for outdated ImageMagick version (<= 6.4.9)... *** extconf.rb failed ***
Could not create Makefile due to some reason, probably lack of necessary
libraries and/or headers.  Check the mkmf.log file for more details.  You may
need configuration options.

Provided configuration options:
    --with-opt-dir
    --without-opt-dir
    --with-opt-include
    --without-opt-include=${opt-dir}/include
    --with-opt-lib
    --without-opt-lib=${opt-dir}/lib
    --with-make-prog
    --without-make-prog
    --srcdir=.
    --curdir
    --ruby=/Users/tnakano/.rbenv/versions/2.5.0/bin/$(RUBY_BASE_NAME)

To see why this extension failed to compile, please check the mkmf.log which can be found here:

  /Users/tnakano/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/extensions/x86_64-darwin-16/2.5.0-static/rmagick-2.16.0/mkmf.log

extconf failed, exit code 1

Gem files will remain installed in /Users/tnakano/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/rmagick-2.16.0 for inspection.
Results logged to /Users/tnakano/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/extensions/x86_64-darwin-16/2.5.0-static/rmagick-2.16.0/gem_make.out

An error occurred while installing rmagick (2.16.0), and Bundler cannot continue.
Make sure that `gem install rmagick -v '2.16.0'` succeeds before bundling.

In Gemfile:
  rmagick

おーエラーの内容が変わりましたね。一歩前進です。

次にエラーになっている原因を探すと、さっきと同じようなメッセージが出ていますね。

Make sure thatgem install rmagick -v '2.16.0'succeeds before bundling.これを直訳するとバンドルする前にgem install rmagick -v '2.16.0'が成功していることを確認してください。ってなります。

同じようにgem rmagickを調べて解決方法を探します。するとImagemagickってのをインストールしないといけないっぽいですね。

という感じで表示されたメッセージを良く読んで、エラー文をそのままGoogleさんに聞くと同じように悩んで解決したって記事がいっぱい見つかると思うので一つずつ試していきます。

imagemagickがインストールできない

$ brew install imagemagick
==> Installing imagemagick

Imagemagickがインストール出来たので、もっかいbundle install実行します。

$ bundle install
.
.
.
checking for outdated ImageMagick version (<= 6.4.9)... no
checking for presence of MagickWand API (ImageMagick version >= 6.9.0)... no
checking for Ruby version >= 1.8.5... yes

またエラーになりますね。

なんだかこの辺りが怪しそうなので、色々調べてみるとImageMagickのバージョンが7系だとダメみたい。

$ convert --version
Version: ImageMagick 7.0.7-22 Q16 x86_64 2018-01-22 http://www.imagemagick.org

うん。ダメぽ。

$ brew uninstall imagemagick
Uninstalling /usr/local/Cellar/imagemagick/7.0.7-22... (1,527 files, 23.3MB)
$ convert --version
-bash: /usr/local/bin/convert: No such file or directory

うん。削除できてるっぽいですね。

$ brew install imagemagick@6
==> Downloading https://homebrew.bintray.com/bottles/imagemagick@6-6.9.9-34.sierra.bottle.tar.gz
######################################################################## 100.0%
==> Pouring imagemagick@6-6.9.9-34.sierra.bottle.tar.gz
==> Caveats
This formula is keg-only, which means it was not symlinked into /usr/local,
because this is an alternate version of another formula.

If you need to have this software first in your PATH run:
  echo 'export PATH="/usr/local/opt/imagemagick@6/bin:$PATH"' >> ~/.bash_profile

For compilers to find this software you may need to set:
    LDFLAGS:  -L/usr/local/opt/imagemagick@6/lib
    CPPFLAGS: -I/usr/local/opt/imagemagick@6/include
For pkg-config to find this software you may need to set:
    PKG_CONFIG_PATH: /usr/local/opt/imagemagick@6/lib/pkgconfig

==> Summary
🍺  /usr/local/Cellar/imagemagick@6/6.9.9-34: 1,472 files, 22.8MB
$ convert --version
-bash: /usr/local/bin/convert: No such file or directory

あれっなんだ。おかしいぞ。 もっかい見直してみる。

This formula is keg-only, which means it was not symlinked into /usr/local,
because this is an alternate version of another formula.

If you need to have this software first in your PATH run:
  echo 'export PATH="/usr/local/opt/imagemagick@6/bin:$PATH"' >> ~/.bash_profile

For compilers to find this software you may need to set:
    LDFLAGS:  -L/usr/local/opt/imagemagick@6/lib
    CPPFLAGS: -I/usr/local/opt/imagemagick@6/include
For pkg-config to find this software you may need to set:
    PKG_CONFIG_PATH: /usr/local/opt/imagemagick@6/lib/pkgconfig

この辺りがエラーだろう。とりあえず翻訳だー

この式はkeg-onlyであり、/ usr / localにシンボリックリンクされていないことを意味し、
これは別の式の代替バージョンであるためです。

PATHでこのソフトウェアを最初に実行する必要がある場合:
  echo 'export PATH = "/ usr / local / opt / imagemagick @ 6 / bin:$ PATH"' >>〜/ .bash_profile

ふむふむ。パスが通ってないってことだと思うので...

$ echo 'export PATH="/usr/local/opt/imagemagick@6/bin:$PATH"' >> ~/.bash_profile
$ source .bash_profile

もっかいimagemagickをインストール

$ brew uninstall imagemagick@6
$ brew install imagemagick@6
$ convert --version
Version: ImageMagick 6.9.9-34 Q16 x86_64 2018-01-22 http://www.imagemagick.org

よしよし。これで6.9になったのでもっかい

$ bundle install
Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

    current directory: /Users/tnakano/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/rmagick-2.16.0/ext/RMagick
/Users/tnakano/.rbenv/versions/2.5.0/bin/ruby -r ./siteconf20180125-47095-pm8xl8.rb extconf.rb
checking for clang... yes
checking for Magick-config... yes
checking for outdated ImageMagick version (<= 6.4.9)... no
checking for presence of MagickWand API (ImageMagick version >= 6.9.0)... no
Package MagickWand was not found in the pkg-config search path.
Perhaps you should add the directory containing `MagickWand.pc'
to the PKG_CONFIG_PATH environment variable
No package 'MagickWand' found
Package MagickWand was not found in the pkg-config search path.
Perhaps you should add the directory containing `MagickWand.pc'
to the PKG_CONFIG_PATH environment variable
No package 'MagickWand' found
Package MagickWand was not found in the pkg-config search path.
Perhaps you should add the directory containing `MagickWand.pc'
to the PKG_CONFIG_PATH environment variable
No package 'MagickWand' found
Package MagickWand was not found in the pkg-config search path.
Perhaps you should add the directory containing `MagickWand.pc'
to the PKG_CONFIG_PATH environment variable
No package 'MagickWand' found
.
.
An error occurred while installing rmagick (2.16.0), and Bundler cannot continue.
Make sure that `gem install rmagick -v '2.16.0'` succeeds before bundling.

In Gemfile:
  rmagick

くそーダメぽ。

もっかいエラーと睨めっこしてみるとPKG_CONFIG_PATHってのがNo言われてる。 これを設定すれば良いのか?

$ echo 'export PKG_CONFIG_PATH="/usr/local/opt/imagemagick@6/lib/pkgconfig"' >> ~/.bash_profile
$ source .bash_profile

今度こそ〜神様〜

$ bundle install
Installing rmagick 2.16.0 with native extensions
.
.
Bundle complete! 26 Gemfile dependencies, 95 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.

できたー!!!!!

まとめ

ハマるポイントとしては

  • imagemagicは6系をインストールする必要がある!
  • インストールされた6系に対してパスを通してあげる必要がある
  • エラーをよく読む!

答えはいつも「エラー」にある!

英語大事...

初心者でも30分で出来た!MacにRails開発環境を作ってみた

こんばんは。opiyoです。

プログラマの勉強をしていて最初につまずくポイントは環境構築じゃないでしょうか?

今はCloud9とかWeb上で勉強できるサイトなどが当たり前になってきているので、作るきっかけは無いかもしれないですがエンジニアとして働いていく上で環境構築はきっと出来た方がいいはずです!

新しく会社に入った時を想像すると最初にやる仕事はきっと、渡されたMacに携わるプロジェクトの環境を構築する事だと思います。

という事で、今日は1から作業してみたいと思います。

このMacについて

  • OS X El Capitan(10.11.6)

Homebrewインストール

Homebrewは、Macでソフトウェアの導入を単純化するパッケージ管理システムだと。 これをインストールすることbrew hogeみたいな感じで簡単に色々なソフトウェアをダウンロードできる。

インストール方法は、ターミナルに以下のコマンドを貼り付けて実行する

$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
.
.
.
==> Installation successful!

どんなバージョンが入っているかはbrewコマンドに-vオプション付けます。

$ brew -v
Homebrew 1.5.2

brewコマンドのパスを設定します

$ echo 'export PATH=/usr/local/bin:$PATH' >> .bash_profile
$ source .bash_profile

brewの最新バージョンを取得する場合はupdateします

$ brew update

これ以外にも沢山の事が出来るので操作方法をチェックするにはhelpで確認してみましょう!

$ brew help
Example usage:
  brew search [TEXT|/REGEX/]
  brew (info|home|options) [FORMULA...]
  brew install FORMULA...
  brew update
  brew upgrade [FORMULA...]
  brew uninstall FORMULA...
  brew list [FORMULA...]

Troubleshooting:
  brew config
  brew doctor
  brew install -vd FORMULA

Developers:
  brew create [URL [--no-fetch]]
  brew edit [FORMULA...]
  https://docs.brew.sh/Formula-Cookbook.html

Further help:
  man brew
  brew help [COMMAND]
  brew home

gitインストール

gitは、ソースコードのバージョン管理の仕組みです。 gitは普段仕事でも使っているのですが、インストールとか設定とか全然覚えていないので復習

インストールは先程のbrewコマンドを使って行います。 ついでにメールアドレスの設定も行います。  ※メールアドレスだけ設定しておけばgithubpushした際にgithubがよしなに解釈してくれます。

$ brew install git 
$ git --version
git version 2.16.1

$ git config --global user.email email@example.com

githelpを使うと使い方が出てきます。

$ git --help
usage: git [--version] [--help] [-C <path>] [-c name=value]
           [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
           [-p | --paginate | --no-pager] [--no-replace-objects] [--bare]
           [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
           <command> [<args>]

These are common Git commands used in various situations:

start a working area (see also: git help tutorial)
   clone      Clone a repository into a new directory
   init       Create an empty Git repository or reinitialize an existing one

work on the current change (see also: git help everyday)
   add        Add file contents to the index
   mv         Move or rename a file, a directory, or a symlink
   reset      Reset current HEAD to the specified state
   rm         Remove files from the working tree and from the index

examine the history and state (see also: git help revisions)
   bisect     Use binary search to find the commit that introduced a bug
   grep       Print lines matching a pattern
   log        Show commit logs
   show       Show various types of objects
   status     Show the working tree status

grow, mark and tweak your common history
   branch     List, create, or delete branches
   checkout   Switch branches or restore working tree files
   commit     Record changes to the repository
   diff       Show changes between commits, commit and working tree, etc
   merge      Join two or more development histories together
   rebase     Reapply commits on top of another base tip
   tag        Create, list, delete or verify a tag object signed with GPG

collaborate (see also: git help workflows)
   fetch      Download objects and refs from another repository
   pull       Fetch from and integrate with another repository or a local branch
   push       Update remote refs along with associated objects

rbenvインストール

rbenvは複数のRubyのバージョンを切り替えて使う為の環境を提供してくれる。 プロジェクトによってバージョンが違う事があるからこれで管理するそうだ。

インストール方法は、先程のbrewを使う。 この時に、ruby-buildというRubyをインストールするプラグインも合わせてインストールする

$ brew install rbenv ruby-build

rbenvコマンドにパスを設定する

$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> .bash_profile
$ echo 'eval "$(rbenv init -)"' >> .bash_profile
$ source .bash_profile

pathを設定する際に出てくる「$HOME」という奴は環境変数って奴です。 環境変数はプログラムの検索先を変数に入れて、毎回長いpathを打つのを省略してくれます。 今回であればrbenvの実行ファイルが格納されているディレクトリをPATHに追加しています。

Rubyインストール

次にインストール可能なRubyのバージョン一覧を表示する

¥ rbenv install --list

するとずらーっと表示されるので、最新のバージョンをインストールします。

今回であれば、2.5.0ですかね。

  2.3.6
  2.4.0-dev
  2.4.0-preview1
  2.4.0-preview2
  2.4.0-preview3
  2.4.0-rc1
  2.4.0
  2.4.1
  2.4.2
  2.4.3
  2.5.0-dev
  2.5.0-preview1
  2.5.0-rc1
  2.5.0
  2.6.0-dev
$ rbenv install 2.5.0

これが結構時間かかる。 で、終わったらインストールしたバージョンを使いますよーって宣言する

$ rbenv global 2.5.0
$ ruby -v
ruby 2.5.0p0 (2017-12-25 revision 61468) [x86_64-darwin16]

ちなみにインストールしているバージョンを調べたい時は

$ rbenv versions
  system
* 2.5.0 (set by /Users/tnakano/.rbenv/version)

*が付いているのが今使われているrubyのバージョンです。

Railsインストール

では、最後にRailsをインストールします。

Railsプロジェクトを作りたい場所に移動してからインストール

$ cd ~/rails
$ gem install rails

bundler(Rubyのライブラリを管理するツール)のインストール

$ gem install bundler

インストールした内容を反映

$ rbenv rehash
$ source ~/.bash_profile

インストールできたか確認

$ rails -v
Rails 5.1.4

これで準備が整いましたー

せっかくなので、動く所までやってみよう。

Railsプロジェクト作成

¥ rails new sample
¥ cd sample
¥ rails s

rails s コマンドでwebサーバが起動するので、ブラウザを開いて「localhost:3000」にアクセスすると……

あっという間にRailsアプリが完成です。

Atom

最後に、開発エディタについてですが会社では皆RubyMineを使っているのですが無料のAtomを使いたいと思います。

インストール方法は、こちらからダウンロードするだけ。

https://atom.io/

まとめ

こんな感じで何もエラーにさえならなければ30分くらいで簡単に出来ると思いますので、皆様も是非Macに開発環境を作ってみると良いと思います。

お前何言ってんだよってのがありましたらご教授いただけますと幸いです。

【Ruby on Rails】スクレイピングで簡単にmeta情報を取得するgem metainspector

こんばんわ。エンジニアに夢を描くopiyoです。

最近はずっとスクレイピングについて触れることが多いのですが、こんな経験ないですか?

ブログ書く時とかurl貼り付けるだけで引用文が作成されるけど、あれどうやってんだ?

まさにこういう奴↓↓

opiyotan.hatenablog.com

多分ですが、これもスクレイピングで情報を引っ張ってきているはずなのです。

この情報を簡単に取得できちゃうgemを見っけたので今日は、それについて。

使う準備

こちらは、おなじみGemfileに`metainspector'を記述します。

gem 'metainspector'

こちらも、おなじみbundle installします。

$ bundle install

使い方

はてなブログのトップページにある記事から情報を取得したいと思います!

広島県 VS EM研究機構 - warbler’s diary

※なんだかお堅いサンプルになってしまいしたが...利用させて頂きます。ありがとうございます。

概要=descriptionを取得する

やり方は非常に簡単で取得したデータに対してdescriptionメソッドを呼ぶだけ。

meta = MetaInspector.new("http://warbler.hatenablog.com/entry/2018/01/24/011739")
puts meta.description
.
.
.
広島県からEM菌の水質浄化効果を否定する内容の報告書が出された事に対して、EM研究機構が抗議をしていました。 ・EM菌の培養液は有機物と栄養塩類が高濃度に含まれることから「河川等の汚染源になり得る」という実験結果を報告した福島県に対しても、EM研究機構を含むEM推進側が抗議をしていた事については、既に本ブログで報告して…

画像を取得する

こちらも非常に簡単で取得したデータからimages.bestするだけ。

meta = MetaInspector.new(url)
puts meta.images.best
.
.
.
=> "https://cdn-ak.f.st-hatena.com/images/fotolife/w/warbler/20180124/20180124001356.png"

その他

その他にも色々な情報を簡単に取得できるので、ざらっと書いてみます。

$ rails c
> meta.url
=> "http://warbler.hatenablog.com/entry/2018/01/24/011739"

> meta.host
=> "warbler.hatenablog.com"

> meta.head_links
=> [{:rel=>"canonical", :href=>"http://warbler.hatenablog.com/entry/2018/01/24/011739"}, {:rel=>"shortcut icon", :href=>"https://cdn.image.st-hatena.com/image/favicon/0c16e66ee260e69eb13510af1e9878996b545656/version=1/https:%2F%2Fcdn.user.blog.st-hatena.com%2Fcustom_blog_icon%2F36385751%2F1514183975459583"}, {:rel=>"icon", :sizes=>"192x192", :href=>"https://cdn.image.st-hatena.com/image/square/7fdc8aba078df5faff51cf50422005a5085822ff/backend=imagemagick;height=192;version=1;width=192/https:%2F%2Fcdn.user.blog.st-hatena.com%2Fcustom_blog_icon%2F36385751%2F1514183975459583"}, {:rel=>"alternate", :type=>"application/atom+xml", :title=>"Atom", :href=>"http://warbler.hatenablog.com/feed"}, {:rel=>"alternate", :type=>"application/rss+xml", :title=>"RSS2.0", :href=>"http://warbler.hatenablog.com/rss"}, {:rel=>"alternate", :type=>"application/json+oembed", :href=>"http://hatenablog.com/oembed?url=http://warbler.hatenablog.com/entry/2018/01/24/011739&format=json", :title=>"oEmbed Profile of 広島県 VS EM研究機構"}, {:rel=>"alternate", :type=>"text/xml+oembed", :href=>"http://hatenablog.com/oembed?url=http://warbler.hatenablog.com/entry/2018/01/24/011739&format=xml", :title=>"oEmbed Profile of 広島県 VS EM研究機構"}, {:rel=>"author", :href=>"http://www.hatena.ne.jp/warbler/"}, {:rel=>"stylesheet", :type=>"text/css", :href=>"https://cdn.blog.st-hatena.com/css/blog.css?version=16c4f762d17230c5653ead590e78f5c1c08b8f84&env=production"}, {:rel=>"stylesheet", :type=>"text/css", :href=>"http://blog.hatena.ne.jp/-/blog_style/6653586347156021977/567bb7d3aa205029bfc477b21f9e4dc351ef5eee"}]

> meta.feed
=> "http://warbler.hatenablog.com/rss"

> meta.title
=> "広島県 VS EM研究機構 - warbler’s diary"

> meta.links.raw
=> ["#", "http://warbler.hatenablog.com/", "http://warbler.hatenablog.com/archive/2018/01/24", "http://warbler.hatenablog.com/entry/2018/01/24/011739", "https://www.emro.co.jp/information/04_HH/", "http://www.pref.aomori.lg.jp/kenminno-koe/24K23.html", "http://b.hatena.ne.jp/entry/http://warbler.hatenablog.com/entry/2018/01/24/011739", "https://twitter.com/share", "http://warbler.hatenablog.com/entry/2018/01/23/235522", "http://warbler.hatenablog.com/archive/2018/01/23", "http://warbler.hatenablog.com/entry/20150227/1425057866", "http://warbler.hatenablog.com/archive/2015/02/27", "http://warbler.hatenablog.com/archive/2013/09/03", "http://warbler.hatenablog.com/entry/20130903/1378217975", "http://warbler.hatenablog.com/entry/20130712/1373632961", "http://warbler.hatenablog.com/archive/2013/07/12", "http://warbler.hatenablog.com/entry/20130428/1367131822", "http://warbler.hatenablog.com/archive/2013/04/28", "http://warbler.hatenablog.com/about", "http://blog.hatena.ne.jp/guide/pro", "http://warbler.hatenablog.com/archive", "http://warbler.hatenablog.com/entry/2017/12/02/235126", "http://warbler.hatenablog.com/entry/2017/11/06/113739", "http://warbler.hatenablog.com/entry/2017/09/06/223001", "http://warbler.hatenablog.com/archive/category/EM%E9%96%A2%E4%BF%82", "http://warbler.hatenablog.com/archive/category/%E7%92%B0%E5%A2%83%E5%95%8F%E9%A1%8C", "http://warbler.hatenablog.com/archive/category/%E7%A7%91%E5%AD%A6", "http://warbler.hatenablog.com/archive/category/%E7%92%B0%E5%A2%83", "http://warbler.hatenablog.com/archive/category/%E7%A0%94%E7%A9%B6%E4%B8%8D%E6%AD%A3", "http://warbler.hatenablog.com/archive/category/%E8%AA%A4%E3%81%A3%E3%81%9F%E7%B5%B1%E8%A8%88", "http://warbler.hatenablog.com/archive/category/%E3%83%8B%E3%82%BB%E7%A7%91%E5%AD%A6", "http://warbler.hatenablog.com/archive/category/%E6%94%BE%E5%B0%84%E7%B7%9A", "http://warbler.hatenablog.com/archive/category/%E3%83%9E%E3%82%B9%E3%82%B3%E3%83%9F", "http://warbler.hatenablog.com/archive/category/%E5%B2%A1%E5%B1%B1%E5%A4%A7%E4%BA%8B%E4%BB%B6", "http://warbler.hatenablog.com/archive/category/%E6%9B%B8%E8%A9%95", "http://warbler.hatenablog.com/archive/category/%E9%A3%9F%E5%93%81", "http://warbler.hatenablog.com/archive/category/%E5%81%A5%E5%BA%B7", "http://warbler.hatenablog.com/archive/category/%E3%83%9B%E3%83%A1%E3%82%AA%E3%83%91%E3%82%B7%E3%83%BC", "http://warbler.hatenablog.com/archive/category/%E6%8D%8F%E9%80%A0%E8%AB%96%E6%96%87", "http://warbler.hatenablog.com/archive/category/%E7%99%BA%E9%81%94%E9%9A%9C%E5%AE%B3", "http://warbler.hatenablog.com/archive/category/%E5%8C%BB%E7%99%82", "http://warbler.hatenablog.com/archive/category/%E9%9B%91%E5%AD%A6", "http://warbler.hatenablog.com/archive/category/%E7%B5%B1%E8%A8%88", "http://warbler.hatenablog.com/archive/category/%E8%A6%AA%E5%AD%A6", "http://warbler.hatenablog.com/archive/category/%E6%95%99%E8%82%B2", "http://blog.hatena.ne.jp/register?via=200227", "http://hatenablog.com/guide", "http://hatenablog.com/", "http://www.hatena.ne.jp/faq/report/blog?target_label=warbler&target_url=http%3A%2F%2Fblog.hatena.ne.jp%2Fgo%3Fblog%3Dhttp%253A%252F%252Fwarbler.hatenablog.com%252Fentry%252F2018%252F01%252F24%252F011739&location=http%3A%2F%2Fblog.hatena.ne.jp%2Fgo%3Fblog%3Dhttp%253A%252F%252Fwarbler.hatenablog.com%252Fentry%252F2018%252F01%252F24%252F011739"]

> meta.links.http
=> ["http://warbler.hatenablog.com/entry/2018/01/24/011739", "http://warbler.hatenablog.com/", "http://warbler.hatenablog.com/archive/2018/01/24", "https://www.emro.co.jp/information/04_HH/", "http://www.pref.aomori.lg.jp/kenminno-koe/24K23.html", "http://b.hatena.ne.jp/entry/http://warbler.hatenablog.com/entry/2018/01/24/011739", "https://twitter.com/share", "http://warbler.hatenablog.com/entry/2018/01/23/235522", "http://warbler.hatenablog.com/archive/2018/01/23", "http://warbler.hatenablog.com/entry/20150227/1425057866", "http://warbler.hatenablog.com/archive/2015/02/27", "http://warbler.hatenablog.com/archive/2013/09/03", "http://warbler.hatenablog.com/entry/20130903/1378217975", "http://warbler.hatenablog.com/entry/20130712/1373632961", "http://warbler.hatenablog.com/archive/2013/07/12", "http://warbler.hatenablog.com/entry/20130428/1367131822", "http://warbler.hatenablog.com/archive/2013/04/28", "http://warbler.hatenablog.com/about", "http://blog.hatena.ne.jp/guide/pro", "http://warbler.hatenablog.com/archive", "http://warbler.hatenablog.com/entry/2017/12/02/235126", "http://warbler.hatenablog.com/entry/2017/11/06/113739", "http://warbler.hatenablog.com/entry/2017/09/06/223001", "http://warbler.hatenablog.com/archive/category/EM%E9%96%A2%E4%BF%82", "http://warbler.hatenablog.com/archive/category/%E7%92%B0%E5%A2%83%E5%95%8F%E9%A1%8C", "http://warbler.hatenablog.com/archive/category/%E7%A7%91%E5%AD%A6", "http://warbler.hatenablog.com/archive/category/%E7%92%B0%E5%A2%83", "http://warbler.hatenablog.com/archive/category/%E7%A0%94%E7%A9%B6%E4%B8%8D%E6%AD%A3", "http://warbler.hatenablog.com/archive/category/%E8%AA%A4%E3%81%A3%E3%81%9F%E7%B5%B1%E8%A8%88", "http://warbler.hatenablog.com/archive/category/%E3%83%8B%E3%82%BB%E7%A7%91%E5%AD%A6", "http://warbler.hatenablog.com/archive/category/%E6%94%BE%E5%B0%84%E7%B7%9A", "http://warbler.hatenablog.com/archive/category/%E3%83%9E%E3%82%B9%E3%82%B3%E3%83%9F", "http://warbler.hatenablog.com/archive/category/%E5%B2%A1%E5%B1%B1%E5%A4%A7%E4%BA%8B%E4%BB%B6", "http://warbler.hatenablog.com/archive/category/%E6%9B%B8%E8%A9%95", "http://warbler.hatenablog.com/archive/category/%E9%A3%9F%E5%93%81", "http://warbler.hatenablog.com/archive/category/%E5%81%A5%E5%BA%B7", "http://warbler.hatenablog.com/archive/category/%E3%83%9B%E3%83%A1%E3%82%AA%E3%83%91%E3%82%B7%E3%83%BC", "http://warbler.hatenablog.com/archive/category/%E6%8D%8F%E9%80%A0%E8%AB%96%E6%96%87", "http://warbler.hatenablog.com/archive/category/%E7%99%BA%E9%81%94%E9%9A%9C%E5%AE%B3", "http://warbler.hatenablog.com/archive/category/%E5%8C%BB%E7%99%82", "http://warbler.hatenablog.com/archive/category/%E9%9B%91%E5%AD%A6", "http://warbler.hatenablog.com/archive/category/%E7%B5%B1%E8%A8%88", "http://warbler.hatenablog.com/archive/category/%E8%A6%AA%E5%AD%A6", "http://warbler.hatenablog.com/archive/category/%E6%95%99%E8%82%B2", "http://blog.hatena.ne.jp/register?via=200227", "http://hatenablog.com/guide", "http://hatenablog.com/", "http://www.hatena.ne.jp/faq/report/blog?target_label=warbler&target_url=http://blog.hatena.ne.jp/go?blog=http%253A%252F%252Fwarbler.hatenablog.com%252Fentry%252F2018%252F01%252F24%252F011739&location=http://blog.hatena.ne.jp/go?blog=http%253A%252F%252Fwarbler.hatenablog.com%252Fentry%252F2018%252F01%252F24%252F011739"]

> meta.links.internal
=> ["http://warbler.hatenablog.com/entry/2018/01/24/011739", "http://warbler.hatenablog.com/", "http://warbler.hatenablog.com/archive/2018/01/24", "http://warbler.hatenablog.com/entry/2018/01/23/235522", "http://warbler.hatenablog.com/archive/2018/01/23", "http://warbler.hatenablog.com/entry/20150227/1425057866", "http://warbler.hatenablog.com/archive/2015/02/27", "http://warbler.hatenablog.com/archive/2013/09/03", "http://warbler.hatenablog.com/entry/20130903/1378217975", "http://warbler.hatenablog.com/entry/20130712/1373632961", "http://warbler.hatenablog.com/archive/2013/07/12", "http://warbler.hatenablog.com/entry/20130428/1367131822", "http://warbler.hatenablog.com/archive/2013/04/28", "http://warbler.hatenablog.com/about", "http://warbler.hatenablog.com/archive", "http://warbler.hatenablog.com/entry/2017/12/02/235126", "http://warbler.hatenablog.com/entry/2017/11/06/113739", "http://warbler.hatenablog.com/entry/2017/09/06/223001", "http://warbler.hatenablog.com/archive/category/EM%E9%96%A2%E4%BF%82", "http://warbler.hatenablog.com/archive/category/%E7%92%B0%E5%A2%83%E5%95%8F%E9%A1%8C", "http://warbler.hatenablog.com/archive/category/%E7%A7%91%E5%AD%A6", "http://warbler.hatenablog.com/archive/category/%E7%92%B0%E5%A2%83", "http://warbler.hatenablog.com/archive/category/%E7%A0%94%E7%A9%B6%E4%B8%8D%E6%AD%A3", "http://warbler.hatenablog.com/archive/category/%E8%AA%A4%E3%81%A3%E3%81%9F%E7%B5%B1%E8%A8%88", "http://warbler.hatenablog.com/archive/category/%E3%83%8B%E3%82%BB%E7%A7%91%E5%AD%A6", "http://warbler.hatenablog.com/archive/category/%E6%94%BE%E5%B0%84%E7%B7%9A", "http://warbler.hatenablog.com/archive/category/%E3%83%9E%E3%82%B9%E3%82%B3%E3%83%9F", "http://warbler.hatenablog.com/archive/category/%E5%B2%A1%E5%B1%B1%E5%A4%A7%E4%BA%8B%E4%BB%B6", "http://warbler.hatenablog.com/archive/category/%E6%9B%B8%E8%A9%95", "http://warbler.hatenablog.com/archive/category/%E9%A3%9F%E5%93%81", "http://warbler.hatenablog.com/archive/category/%E5%81%A5%E5%BA%B7", "http://warbler.hatenablog.com/archive/category/%E3%83%9B%E3%83%A1%E3%82%AA%E3%83%91%E3%82%B7%E3%83%BC", "http://warbler.hatenablog.com/archive/category/%E6%8D%8F%E9%80%A0%E8%AB%96%E6%96%87", "http://warbler.hatenablog.com/archive/category/%E7%99%BA%E9%81%94%E9%9A%9C%E5%AE%B3", "http://warbler.hatenablog.com/archive/category/%E5%8C%BB%E7%99%82", "http://warbler.hatenablog.com/archive/category/%E9%9B%91%E5%AD%A6", "http://warbler.hatenablog.com/archive/category/%E7%B5%B1%E8%A8%88", "http://warbler.hatenablog.com/archive/category/%E8%A6%AA%E5%AD%A6", "http://warbler.hatenablog.com/archive/category/%E6%95%99%E8%82%B2"] 

使い方はgithubにサンプルが乗ってますので、是非ご覧ください。

github.com

まとめ

何か困った時は先ずは調べてみることで、簡単に実装できるgemがいっぱいあるのでRubyは楽しいですね。

今日は、そんな感じです。

【Rails】NokogiriじゃなくてMechanize gemで簡単スクレイピング!

先日Nokogiriを使ったWebスクレイピング方法を紹介しましたが、もっと簡単に出来るgemを見つけたのでご紹介。

opiyotan.hatenablog.com

その名はMechanize

利用する準備

gemのインストール

gem 'mechanize'
$ bundle install

スクレイピング先のサイト情報を取得

今回も「はてなブログ」を使ってやってみたいと思います。

先ずは「はてなブログ」の情報を取得します。

class Mechanize
  url = "http://hatenablog.com/"

  agent = Mechanize.new
  page = agent.get(url)
end

使い方

titleを取得する

  puts page.search('title’)
$ rails runner lib/mechanize.rb

<title>Hatena Blog</title>

タグの中身になるテキストを取得する

  title = page.search('title')

  title.each do |ti|
    puts ti.inner_text
  end
$ rails runner lib/mechanize.rb

Hatena Blog

aタグのリンクurlを取得する

classを指定したい時は、.hogehogeのように指定すればok!

  entry_a = page.search('a.serviceTop-entry-img-a')
  entry_a.each do |a|
    puts a.get_attribute(:href)
  end
.
.
.
  # こうしてもやってることは、同じ
  entry_a = page.search('div.serviceTop-entry > a')
$ rails runner lib/mechanize.rb
Running via Spring preloader in process 18370
http://moognyk.hateblo.jp/entry/2018/01/23/080000
http://blog.jnito.com/entry/2018/01/23/075856
http://barzam154.hatenablog.com/entry/2018/01/22/205222
http://ibaya.hatenablog.com/entry/2018/01/23/111105
http://bandaicandy.hateblo.jp/entry/build22
http://www.netlorechase.net/entry/2018/01/22/080000
http://www.hinata-family.com/entry/2018/01/23/064657
http://www.bitco-salaryman.com/entry/2018/01/22/194205
http://zoweb.hatenablog.com/entry/2018/01/22/093904
http://www.beikokukabu.xyz/entry/02
http://hobby-diary.hatenablog.com/entry/2018/01/19/204839
http://www.daij1n.info/entry/2018/01/18/021528
http://www.lean-style.com/entry/fashionfortune
http://www.kandosaori.com/entry/2018/01/16/171030
http://www.black-gamer.com/entry/KITTE_pancake
https://www.hotpepper.jp/mesitsu/entry/kinniku/18-00007
http://osyobu-osyobu-3889.hatenadiary.jp/entry/the_shutter_is_released14
http://www.hoshinokiiro.com/entry/20180122/recipe/tukurioki-and-hotcook
http://www.megamouth.info/entry/2017/01/19/053801
https://srdk.rakuten.jp/entry/2017/01/19/110000
http://mistclast.hatenablog.com/entry/2017/01/22/110637

該当する情報を1件だけ取得したい場合は、atを使います。

  puts page.at('div.serviceTop-entry > a').get_attribute(:href)
$ rails runner lib/mechanize.rb
Running via Spring preloader in process 19231
http://blog.jnito.com/entry/2018/01/23/075856

aタグのテキストとリンクを全部取得する

  entry_a = page.links
  entry_a.each do |a|
    puts a.text
    puts a.href
  end
$ rails runner lib/mechanize.rb

http://hatenablog.com/
雪の日のユキヒョウ&オオカミ:東京が大雪だったので多摩動物公園に行ってきた
http://moognyk.hateblo.jp/entry/2018/01/23/080000
I AM A DOG
http://moognyk.hateblo.jp/entry/2018/01/23/080000
かねてより念願だった、雪の日の動物園に行ってきましたよ! 雪の動物園がずっと見たかった 雪の降る動物園はいつもと違う景色が観られて楽しいだろうな… と思…
http://moognyk.hateblo.jp/entry/2018/01/23/080000
・
・
・

現在のURLを取得する

agent.page.uri.to_s

descriptionを取得する

こちらが苦戦中。metainspectorというgemを使えば取得できることは分かったのですが、上と同じような感じで取得することが出来ない。

  puts page.at(':og:description’)
  puts page.at('meta[:og:description]’)
  puts page.at('meta[property=:og:description]')
  puts page.at('meta[property=":og:description"]')
  puts page.at('meta[name=":og:description"]')

うーん。全部ダメ。どうすればいいのか解決できず..

metainspectorというgemについては、また紹介します。

まとめ

簡単にスクレイピング出来ることが分かりました。

データさえ取得できてしまえば、後はどのように使うかだけなので色々面白いことが出来そうですね。

今日はこんな感じです。

【Ruby】Nokogiriを使って「はてなブログ」をスクレイピングする!

こんばんは。エンジニアになれるか不安なopiyoです。

今日はWebサイトの情報を取得することが出来る、Webスクレイピングという技術をRubyでやってみます!

Webスクレイピングとは

Webサイトの情報を取得する技術ってイメージですが、wikipediaでちゃんと調べてみました。

ウェブスクレイピング(英: Web scraping)とは、ウェブサイトから情報を抽出するコンピュータソフトウェア技術のこと。ウェブ・クローラー[1]あるいはウェブ・スパイダー[2]とも呼ばれる。 通常このようなソフトウェアプログラムは低レベルのHTTPを実装することで、もしくはウェブブラウザを埋め込むことによって、WWWのコンテンツを取得する。

nokogiriのインストール

Gemfilenokogiriを記述します。

# Gemfile
gem 'nokogiri'

ターミナルで、いつも通りbundle installします

$ bundle install

スクレイピング実行するプログラム

class Scrape
  url = 'http://b.hatena.ne.jp/ctop/it'

  charset = nil

  opt = {}
  opt['User-Agent'] = "I Love Ruby"

  html = open(url, opt) do |f|
    charset = f.charset
    f.read
  end

  doc = Nokogiri::HTML.parse(html, nil, charset)
  doc.css('h1').each do |node|
    p node.text
    p node["href"]
  end
end

はまりポイント

503エラーが発生する事があるのだが、原因はUser-Agentが定義されていない。

$ rails runner lib/scrape.rb
Running via Spring preloader in process 14182
/Users/taku/.rbenv/versions/2.4.1/lib/ruby/2.4.0/open-uri.rb:363:in `open_http': 503 Service Temporarily Unavailable (OpenURI::HTTPError)

User-Agentってのは、そのアクセスした端末が何を使っているのかが定義されていてアプリ側では、この設定を見て「こいつはIEだ」とか「こいつはスマホだ」とか判断したりします。 例えばこんな感じ

  • Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36
    • Mac OS X
    • Chrome

で、サイトによってはこのUser-Agentが定義されていないとエラーになってしまうらしいので、一緒に渡してやる。

スクレイピングしてみる

ここからが本題ですね。

データを取得する際に、どの情報を取得するのか定義してやります。

上のサンプルを例に幾つかやってみます。取得先は、はてなブックマークのテクノロジー一覧ページです。

はてなブログから「h1」に定義されているテキストを取得

  doc.css('h1').each do |node|
    p node
    p node.text
  end
$ rails runner lib/scrape.rb
#<Nokogiri::XML::Element:0x3fc3eed74b04 name="h1" children=[#<Nokogiri::XML::Element:0x3fc3eed74820 name="a" attributes=[#<Nokogiri::XML::Attr:0x3fc3eed747bc name="href" value="/">] children=[#<Nokogiri::XML::Element:0x3fc3eed74244 name="span" children=[#<Nokogiri::XML::Text:0x3fc3eed74064 "はてなブックマーク">]>]>]>
"はてなブックマーク"

はてなブログの各記事「h3」に定義されているテキストを取得(記事の一覧)

  doc.css('h3.hb-entry-link-container').each do |node|
    p node.text
  end
$ rails runner lib/scrape.rb
"コミュ力もリーダーシップもいらない。Googleが考える、本当に“優秀な人材“とは"
"Googleの画像認識APIを基に、好きな画像を学習させて認識機能を簡単にカスタ..."
"質問箱が暴露「4万人以上が自作自演」 ⇒ 反発を受けて実装した新機能とは?"
"最新版!Webページを作成する時のベースになる、最小限の構成で記述されたHT..."
"必見? Mozillaが提供しているユーザー環境週報がなにげに興味深い件につい..."
"Google ウェブマスター向け公式ブログ: ページの読み込み速度をモバイル検索..."
"はてなのアイコン設定したい"
"2月末に株式会社groovesを退職します - アジャイルSEの憂鬱"
"ネットでの誹謗中傷!書き込んだ人物を特定するための手順"
"\n      \n        Androidアーキテクチャことはじめ ― 選定する意味と、MVP、Clean Architecture、MVVM、Fluxの特徴を理解する - エンジニアHub|若手Webエンジニアのキャリアを考える!\n      \n    "
"\n      \n        フロントエンド開発に Babel も Webpack も必要ない ※ - KAYAC engineers' blog\n      \n    "
"\n      \n        Google ウェブマスター向け公式ブログ: ページの読み込み速度をモバイル検索のランキング要素に使用します\n      \n    "
"\n      \n        確率密度比推定まわりの書籍・解説記事・論文・ソフトウェアの各種情報まとめ - 備忘録\n      \n    "
"\n      \n        コード整形をStandardJSからPrettierに乗り換えたら捗った - 丁寧に手を抜く\n      \n    "
"\n      \n        大学を辞めたけど何も起こらなかった - 素人がプログラミングを勉強していたブログ\n      \n    "
"\n      \n        「ITをITで支援する」MSP事業者が、変革する開発・運用で果たす役割──スキルの価値が10年で消える世界においてエンジニアはどうあるべきか - GeekOut\n      \n    "
"\n      \n        プログラミングとUIデザインの境界、およびデザインの環境設定について – timakin – Medium\n      \n    "
"\n      \n        Golangでtestingことはじめ(1) - DeNA Testing Blog\n      \n    "
"\n      \n        ECS コンテナインスタンスをモニタリングするときは mackerel-agent v0.49.0 以上にすると良さそう - kakakakakku blog\n      \n    "
"\n      \n        IntelliJ IDEAで特定行のGitHub PRを開く\"Find Pull Request\" pluginがまじイノベーティブ - Islands in the byte stream\n      \n    "
"\n      \n        Perl で Compiler::CodeGenerator::LLVM を用いて LLVM IR を出力する - アルパカ三銃士\n      \n    "
"\n      \n        CodeStar で AWS Lambda + Golang の雛形をサクッと作成してみた - ソモサン\n      \n    "
"\n      \n        Kubernetes を利用したコンテナベース機械学習基盤の構築 - LIVESENSE Data Analytics Blog\n      \n    "
"\n      \n        Serverless Frameworkで静的サイトをBASIC認証付きでホスティングするためのボイラープレートを作った - Copy/Cut/Paste/Hatena\n      \n    "
"はてな、500 Startups Japanを通じたスタートアップ支援。サーバー監視サー..."
"Google、プログラミングができなくてもAIツールを作れる「AutoML」のα版提供..."
"「ITパスポート試験」83歳が合格 最年長記録更新 - ITmedia NEWS"
"ドミノ・ピザ、「20分保証」の宅配サービス (ITmedia ビジネスオンライン)..."
"AWS最先進ユーザーNetflix - 「サル軍団」にシステム障害を起こさせる、Netflixの驚異的なトラブル撲滅..."
"CPUの脆弱性対策パッチでSSDのランダムアクセスが大幅減速?影響をチェックしてみた - AKIBA PC Hotline!"
"word2vec(Skip-Gram Model)の仕組みを恐らく日本一簡潔にまとめてみたつもり - これで無理なら諦めて..."
"Microsoft 、VRを用いて実在する建物内で起こる災害を実際のスケールで走り..."
"Nintendo Switchを使ってロボットや楽器コントローラーなど様々なものを作れ..."
"任天堂、Switchと合体する“段ボールコントローラー”「Nintendo Labo」発売..."
"首元にのせるだけで周囲360度のパノラマムービー撮影が可能なウェアラブルカ..."
"15.5型ノート「VAIO S15」に追加モデル 店頭向けエントリー構成、新色のピ..."
"第8世代Coreと13.3型IGZO液晶を備えたスリムノート「Razer Blade Stealth」..."
"日本参入か? 中国OPPOが日本語Twitterアカウントを開設 - ITmedia Mobile"
"iPhoneでも使えるOfficeの手書き プレゼンにも便利 - 日経トレンディネット"

はてなブログの各記事「h3」に定義されているリンクURL

  doc.css('h3.hb-entry-link-container > a').each do |node|
    p node["href"]
  end
$ rails runner lib/scrape.rb
"http://www.huffingtonpost.jp/2018/01/16/piotr_a_23334437/"
"http://www.publickey1.jp/blog/18/googleapicloud_automl_vision.html"
"http://www.huffingtonpost.jp/2018/01/17/peing_a_23335512/"
"http://coliss.com/articles/build-websites/operation/work/html5-template-for-2018.html"
"https://forest.watch.impress.co.jp/docs/serial/yajiuma/1101696.html"
"https://webmaster-ja.googleblog.com/2018/01/using-page-speed-in-mobile-search.html"
"https://anond.hatelabo.jp/20180118015611"
"http://sinsoku.hatenablog.com/entry/2018/01/18/144408"
"https://sakuya-shougainenkin.com/slander"
"https://employment.en-japan.com/engineerhub/entry/2018/01/17/110000"
"http://techblog.kayac.com/pure-js-app"
"https://webmaster-ja.googleblog.com/2018/01/using-page-speed-in-mobile-search.html"
"http://tam5917.hatenablog.com/entry/2018/01/16/233057"
"http://craftzdog.hateblo.jp/entry/prettier-is-good"
"http://javascripter.hatenablog.com/entry/2018/01/18/033738"
"https://geek-out.jp/column/entry/2018/01/18/110000"
"https://medium.com/@timakin/3ee839874d19"
"http://swet.dena.com/entry/2018/01/16/211035"
"http://kakakakakku.hatenablog.com/entry/2018/01/17/124717"
"http://gfx.hatenablog.com/entry/2018/01/17/133457"
"http://codehex.hateblo.jp/entry/2018/01/16/221634"
"http://rohki.hatenablog.com/entry/2018/01/16/211941"
"http://analytics.livesense.co.jp/entry/2018/01/18/090000"
"http://k1low.hatenablog.com/entry/2018/01/18/085843"
"http://hatenacorp.jp/press/release/entry/2018/01/18/153000"
"http://www.itmedia.co.jp/news/articles/1801/18/news096.html"
"http://www.itmedia.co.jp/news/articles/1801/18/news090.html"
"https://headlines.yahoo.co.jp/hl?a=20180118-00000066-zdn_mkt-bus_all"
"http://itpro.nikkeibp.co.jp/atcl/column/17/122800596/010500003/"
"https://akiba-pc.watch.impress.co.jp/docs/sp/1101498.html"
"http://www.randpy.tokyo/entry/word2vec_skip_gram_model"
"https://kadenkaigi.com/entry/354850025"
"https://kadenkaigi.com/entry/354832865"
"https://kadenkaigi.com/entry/354831021"
"https://kadenkaigi.com/entry/354829845"
"https://kadenkaigi.com/entry/354862221"
"https://kadenkaigi.com/entry/354859010"
"https://kadenkaigi.com/entry/354858999"
"https://kadenkaigi.com/entry/354822870"

まとめ

やばい。楽しくなってきた。

cssを定義するように書けるので非常に非常に分かりやすいし簡単ですね。

スクレイピングを使うと色々な情報を取得することが出来るので、非常に便利ですね。

が、便利な反面迷惑かけてしまうことも当然ありますので、しっかりルールを守って使っていきたいですね。

と記事を書き終えた所で、なんとスクレイピング用のgemがあるらしい。これは、また別の機会で…くそう。

【Ruby on Rails】決まった時間に自動で処理してくれる`whenever`の使い方!

こんばんは。早くエンジニアになりたいopiyoです。

以前rssを取得する方法について、まとめて見たのですが今日はそれの発展させたいと思います。 opiyotan.hatenablog.com

時代は変わる。そして情報も常に変わる。

だから、rss情報も常に最新の情報を自動で取得したいですよね。

そんな時に便利なのが自動で決まった時間に処理をしてくれるwheneverというgemです。

  • 毎日2時間毎にrss情報を取得する!
  • 毎日朝3時にDBのバックアップを取得する!

みたいな事が出来ちゃう訳ですね。

wheneverの導入

先ずは、wheneverという定期実行するためのgemをインストールします。

# Gemfile
gem 'whenever', :require => false

そして、魔法のbundle install

$ bundle install

次にやるのが、wheneverの設定を記述するファイルを作成します。 これはコマンドが用意されています。

$ bundle exec wheneverize .
[add] writing `./config/schedule.rb'
[done] wheneverized!

すると、schedule.rbというファイルが出来上がります!

# config/schedule.rb
# Use this file to easily define all of your cron jobs.
# 
# It's helpful, but not entirely necessary to understand cron before proceeding.
# http://en.wikipedia.org/wiki/Cron
# 
# Example:
# 
# set :output, "/path/to/my/cron_log.log"
# 
# every 2.hours do
#   command "/usr/bin/some_great_command"
#   runner "MyModel.some_method"
#   rake "some:great:rake:task"
# end
# 
# every 4.days do
#   runner "AnotherModel.prune_old_records"
# end
# 
# Learn more: http://github.com/javan/whenever

ここに定期実行する為の処理を書いていきますよー

schedule.rbの書き方

今回は、動作確認までしたいので1分毎にHelloWorldを出力するrakeタスクを呼び出す処理を設定します。

# config/schedule.rb
# cronログを吐き出す場所
set :output, File.join(Whenever.path, "log", "cron.log")
# ジョブの実行環境の指定
set :environment, :development

# 1分毎に`HelloWorld`を出力する
every 1.minutes do
  begin
    rake "rss:hello"
  rescue => e
    raise e
  end
end
# lib/tasks/rss.rake
namespace :rss do
  task :hello => :environment do
    rss_hello = RssImport.new
    rss_hello.hello
  end
end
# lib/rss_import.rb
class RssImport
  def hello
    puts "HelloWorld"
  end
end

これで準備は完了です!

wheneverを実行する

先ずは、設定ファイルの記述にミスが無いかどうかチェック!

エラーになる

$ bundle exec whenever
bundler: failed to load command: whenever (/Users/taku/.rbenv/versions/2.4.1/bin/whenever)
NameError: undefined local variable or method `import' for #<Whenever::JobList:0x007fb7fba35330>

こんなエラーが出た時は、設定ファイルをもう一度見直してください。 私はrakeタスクを定義する場所を""で囲っていなかったので最初ずっとエラーになってました。

他の問題としては、rakeタスクを設定しているファイルが読めていない可能性があるのでschedule.rbの最初にrequire "/path/to/config/environment.rb"を定義してみてください。

成功する

成功すると、こんなメッセージが表示されます。

$ bundle exec whenever
* * * * * /bin/bash -l -c 'cd /Users/taku/rails/mapachannel && RAILS_ENV=development bundle exec rake rss:hello --silent >> /Users/taku/rails/mapachannel/log/cron.log 2>&1’

## [message] Above is your schedule file converted to cron syntax; your crontab file was not updated.
## [message] Run `whenever --help' for more options.

ここまで来たら実際に、これをcrontabというXXに登録してあげます!

$ bundle exec whenever --update-crontab
[write] crontab file updated

正しく設定されているかどうかは、crontab -lコマンドでチェック出来ます!

$ crontab -l

# Begin Whenever generated tasks for: /Users/taku/rails/mapachannel/config/schedule.rb at: 2018-01-18 14:21:42 +0900
* * * * * /bin/bash -l -c 'cd /Users/taku/rails/mapachannel && RAILS_ENV=development bundle exec rake rss:hello --silent >> /Users/taku/rails/mapachannel/log/cron.log 2>&1'

# End Whenever generated tasks for: /Users/taku/rails/mapachannel/config/schedule.rb at: 2018-01-18 14:21:42 +0900

なんかどのサイトを確認方法はcrontab -eって書かれてるのだけど、-eにするとviが開くから良くないと思うのだけど。 間違えて-eしてしまった場合は慌てずに、esc + :q!で閉じれます!

間違えて設定してしまった場合は、これで

$ bundle exec whenever --clear-crontab

定期実行されているかチェック

最後に1分間毎にHelloWorldが呼ばれているかチェックします。

logを見て見ましょう!

$ tail -f log/cron.log
HelloWorld
・
・
・

tail -fとするとリアルタイムにログを見る事が出来ます。 1分間毎にHelloWorldが増えて表示されれば成功してますね。

【Bootstrap】ナビゲーションメニューをセンタリングして均等化する方法!

こんばんは。opiyoです。

よくあるナビゲーションメニューですが、センタリングされてないとダサいですよね。

bootstrapnavのサンプルを、そのままコピペして試してみたのですが

  • センタリングされない
  • 文字数に応じた領域になってしまいかっちょ悪い

って状況だったので、色々試してみました。

サンプルそのまま

    <nav class="navbar navbar-default">
        <div class="container-fluid">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbarEexample1">
                    <span class="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a class="navbar-brand" href="#">タイトル</a>
            </div>

            <div class="collapse navbar-collapse" id="navbarEexample1">
                <ul class="nav navbar-nav">
                    <li><a href="#">メニューAAAAAAAAAAAaaaaa</a></li>
                    <li class="active"><a href="#">メニューB</a></li>
                    <li><a href="#">メニューCCc</a></li>
                </ul>
            </div>
        </div>
    </nav>

出来上がったサンプルがこちら↓↓

f:id:opiyotan:20180117171913p:plain

解決方法

センタリングして均等化させたかったので調べてみるとnav-justifiedを書けばいいってあったのでやってみたのだが上手くいかぬ。

色々と悩んでいたのだが、navにこだわる必要あるか?ってなったのでbtn-groupで囲ってるやる作戦に変更したらすんなり上手くいった。

    <div class="btn-group btn-group-justified btn-group-lg">
      <%= link_to root_path, class: "btn btn-default" do %>
        <%= fa_icon("commenting comment-info", text: "人気") %>
      <% end %>
      <%= link_to root_path, class: "btn btn-default" do %>
        <%= fa_icon("commenting comment-info", text: "新着") %>
      <% end %>
      <%= link_to root_path, class: "btn btn-default" do %>
        <%= fa_icon("commenting comment-info", text: "見つける") %>
      <% end %>
      <%= link_to root_path, class: "btn btn-default" do %>
        <%= fa_icon("commenting comment-info", text: "サイトについて") %>
      <% end %>
    </div>

↓html展開すると

<div class="btn-group btn-group-justified btn-group-lg">
      <a class="btn btn-default" href="/">
        <i class="fa fa-commenting fa-comment-info"></i> 人気
      </a>
      <a class="btn btn-default" href="/">
        <i class="fa fa-commenting fa-comment-info"></i> 新着
      </a>
      <a class="btn btn-default" href="/">
        <i class="fa fa-commenting fa-comment-info"></i> 見つける
      </a>
      <a class="btn btn-default" href="/">
        <i class="fa fa-commenting fa-comment-info"></i> サイトについて
      </a>    
</div>

出来上がったサンプルがこちら↓↓ f:id:opiyotan:20180117171900p:plain

こんな感じ。 画面の領域狭めてもきちんとレスポンシブになってるぞい。

navについては、こちらのサイトにいっぱいサンプルある。やっほいー

http://bootstrap3.cyberlab.info/components/navbar.html

※「bootstrap nav」とかで調べて最初に出てくるページはbootstrap4だから注意。全然違うのよ。

まとめ

僕の場合はデザインが、ある程度見栄え良くないと作る気がどんどん失せていくので、こういうのがサラッと出来るって大事だなーと思う。