クローラー memo
ウェブサービスを作るにあたり、どっかからデータを引っ張ってきてそれを反映させたいときにはクローリングを作ることで開発が劇的に効率化する。ただし、むやみにデータを引っ張ってくるのはアウトなので、デリケートに使わなければいけない。
参考書は Rubyによるクローラー開発技法 巡回・解析機能の実装と21の運用例 Amazon CAPTCHA
取り上げられることが多いですがやはりめっちゃ楽しかったですこれは rubyの文法を一通り理解した人にとっては、クローラーだけではなくRuby文法の理解も一層深まりますのでオススメです。
クローラーの大まかな流れは
対象ページをダウンロード
そのページを読み込んで解析(-> ソースコードの癖を見抜くなど....)
必要なデータ部分を文字列処理等で抜き出す
データを加工する
出力する
以外とシンプル! しかし、3.4がなかなか初心者にとっては難しい所で、正規表現等はを上手く扱える必要があります。
奇跡的な競馬サービス作りたいので出馬表とか、過去のレース成績とかのデーター収集のために勉強していきますー。
2つのカスタムバリデーターを1つのメソッド内で処理する方法
カスタムバリデーターは独自のバリデーションを作れるものです。 今まで一つのバリデータを作るたびに1ファイル作成してたのですが、大変非効率だと思っていた所先輩が教えてくれたので共有します。
モデルは銀行口座の管理を行うものです。bank_codeは銀行コード, bank_branch_codeには銀行支店コードが入る想定です。入力フォームでbank_code, bank_branch_codeのいずれかに不正な番号が入力された際に(そのコードは存在しませんよーという)バリデーションを発動させます。
app/models/control_trading_account.rb
ここではカスタムバリデーターを使いますよという合図を出す class ControlTradingAccount < ApplicationRecord validates :bank_code, presence: true, exist_association: { key: :code, model: 'Bank' } # このBankクラスをバリデーターメソッドに渡したい validates :bank_branch_code, presence: true, exist_association: { key: :code, model: 'BankBranch' }# このBankBranchをバリデーターメソッドに渡したい end
app/validators/exist_association_validator.rb
さっきまではこれと同じようなメソッドを2つ書いていました....orz 一つのメソッドで2つの処理を実現させるためには、この中で上のBankとBankBranchを受け取る! class ExistAssociationValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) model = options[:model].safe_constantize # optionsでパラメータを受け取れるとのこと! safe_constantizeは文字列処理 unless model.find_by(options[:key] => value) # model変数にBankとBankBranchがそれぞれ入ります record.errors[attribute] << (options[:message] || '正しくありません') end end end
バリデーターに限らず、optionでパラメーターを取得するのは今後多用しそうなので備忘録としての投稿でしたー。
ごいすー
rake task の作り方, submoduleについて、バルクインサート(activerecord-import)の使用法
Rake とは?
自分でWebサイトを作っているとして、いつもページを更新するたびにやらないといけない、定型的な処理があるとする。 この類の手作業は、Rubyを使っている以上決まった処理はスクリプトにやらせたい!!! railsならDB定義をしたあとにrake db:migrateとか必ずやりますよね...
普段何気なく使っているあのrake db:migrateもどこかで作られているものだということです。
$ rake -T
上記のコマンドでrake taskの一覧が確認できます。 rake routesなどももちろん入っています。
そこで今回は自作のrake taskを作っていきます。
rake taskの作成
lib/tasks内にhoge.rakeの形でファイルを作成
今回は
$ rake master_data:load:banks_and_branchesとすると処理が実行されるように作っていきます。
サンプルコード lib/tasks/master_data.rake
namespace :master_data do namespace :load do desc '銀行データの作成と更新' task :banks_and_branches => :environment do # :environment はモデルにアクセスするのに必須 yml = YAML.load_file("#{ Rails.root }/submodules/zengin-code/data/banks.yml") #各自読み込みたいymlファイルのパスを設定してください MasterBank.transaction do MasterBank.delete_all # データは最初にリセットする master_banks = yml.each_with_object([]) do |(_key, val), ary| ary << MasterBank.new(code: val['code'], name: val['name'], name_kana: val['kana']) end MasterBank.import master_banks # バルクインサート 下部で説明しています end end end end
submodules/zengin-code/data/banks.yml
このymlファイルに1000件近い銀行データが入っている。。
--- '0001': code: '0001' name: "みずほ" kana: "ミズホ" hira: "みずほ" # 今回hiraとromaはDBで定義していないのでcreateする前に省く処理をプログラム側で実行します roma: mizuho # '0005': code: '0005' name: "三菱東京UFJ" kana: "ミツビシトウキヨウUFJ" hira: "みつびしとうきようUFJ" roma: mitsubishitoukiyouufj 0009: code: 0009 name: "三井住友" kana: "ミツイスミトモ" hira: "みついすみとも" roma: mitsuisumitomo
$ rake mater_data:load:banks_and_branches
一応確認してみる
$rails db # select * from master_banks; id | code | name | name_kana | created_at | updated_at ----+------+----------------+--------------------------+----------------------------+---------------------------- 16 | 0001 | みずほ | ミズホ | 2016-08-04 10:25:00.742385 | 2016-08-04 10:25:00.742385 17 | 0005 | 三菱東京UFJ | ミツビシトウキヨウUFJ | 2016-08-04 10:25:00.782744 | 2016-08-04 10:25:00.782744 18 | 0009 | 三井住友 | ミツイスミトモ | 2016-08-04 10:25:00.79938 | 2016-08-04 10:25:00.79938 (3 rows)
入ってますね!!! これはいろいろ応用できそう!。
サブモジュールとは?
http://qiita.com/go_astrayer/items/8667140aef8875742a36
あるプロジェクトで作業をしているときに、プロジェクト内で別のプロジェクトを使わなければならなくなることがよくあります。サードパーティが開発しているライブラリや、自身が別途開発していて複数の親プロジェクトから利用しているライブラリなどがそれにあたります。こういったときに出てくるのが「ふたつのプロジェクトはそれぞれ別のものとして管理したい。だけど、一方を他方の一部としても使いたい」という問題です。
例を考えてみましょう。ウェブサイトを制作しているあなたは、Atom フィードを作成することになりました。Atom 生成コードを自前で書くのではなく、ライブラリを使うことに決めました。この場合、CPAN や gem などの共有ライブラリからコードをインクルードするか、ソースコードそのものをプロジェクトのツリーに取り込むかのいずれかが必要となります。ライブラリをインクルードする方式の問題は、ライブラリのカスタマイズが困難であることと配布が面倒になるということです。すべてのクライアントにそのライブラリを導入させなければなりません。コードをツリーに取り込む方式の問題は、手元でコードに手を加えてしまうと本家の更新に追従しにくくなるということです。
Git では、サブモジュールを使ってこの問題に対応します。サブモジュールを使うと、ある Git リポジトリを別の Git リポジトリのサブディレクトリとして扱うことができるようになります。これで、別のリポジトリをプロジェクト内にクローンしても自分のコミットは別管理とすることができるようになります。
=> 今回はzengin-code(日本の銀行データが入ってる)というgemをsubmoduleとして登録しました。
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
[応用編] 多数レコードをDBに登録する際にはバルクインサートがめちゃ使える
上で、ymlファイルからDBにレコード登録する方法を説明しましたが、多数レコードになる場合はバルクインサートが効率的で良さげです。 端的に言うと、eachによって一回ずつinsert文を実行していたのを、一度のinsert文でクエリをまとめて、DBに流しちゃうというものです。 バルクインサートの方が実行スピードがだいぶ速くなるので、オススメです
activerecord-importというgemを用います。 Gemfile
gem 'activerecord-import'
$ bundle
参考) とてもわかりやすいので参照してみてください。 blog.livedoor.jp
railsでgithubログインを実装する
- githubにapplicationを追加する (githubにアクセス-> settings -> Oauth applications-> developer applicationのタブ内のresister new applicationをクリック)
application名とurlとAuthorization callback URLにそれぞれ記入し作成(この時にのちに使うclient-idとclient-secretが生成される)
urlはlocalhost://3000でおk
Authorization callback URLも http://localhost:3000/auth/github/callback とかで大丈夫です
gem 'omniauth-github'
$ bundle install
userテーブルに新しく3つのカラムを追加していく $ rails g migration add_columns_to_user provider:string uid:string image:string
$ rake db:migrate
app/model/user.rbに書いていく ちなみにuserモデルはdeviseを使っていますのでその前提で書いています>_<。
class User < ActiveRecord::Base devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable, :omniauthable #最後の:omniauthableのみを追記しました。 def self.find_for_github_oauth(access_token, signed_in_resource = nil) data = access_token['info'] user = User.where(:provider => access_token['provider'], :uid => access_token['uid']).first if user return user else registered_user = User.where(:email => data['email']).first if registered_user return registered_user else if data['name'].nil? name = data['nickname'] else name = data['name'] end user = User.create( name: name, provider: access_token['provider'], email: data['email'], uid: access_token['uid'], image: data['image'], password: Devise.friendly_token[0, 20], ) end end end end
続いてinitializers/devise.rbをいじる
#見やすくするために不要なコメント行は全て削除しています Devise.setup do |config| require 'devise/orm/active_record' config.case_insensitive_keys = [:email] config.strip_whitespace_keys = [:email] config.skip_session_storage = [:http_auth] config.stretches = Rails.env.test? ? 1 : 10 config.reconfirmable = true config.expire_all_remember_me_on_sign_out = true config.password_length = 8..128 config.reset_password_within = 6.hours config.sign_out_via = :delete require 'omniauth-github' config.omniauth :github, 'Client-id', 'Client-Secret', scope: 'user:email' #'Client-id', 'Client-Secret'はgithubでapplicationを登録した時に生成されたものをそれぞれクオーテーション内に貼り付けてください end
続いてcontrollerを新規で作成 app/controllers/omniauth_callbacks_controller.rbで作成しました
class OmniauthCallbacksController < Devise::OmniauthCallbacksController def github @user = User.find_for_github_oauth(request.env["omniauth.auth"], current_user) if @user.persisted? flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Github" sign_in_and_redirect @user, :event => :authentication else session["devise.user_data"] = request.env["omniauth.auth"] redirect_to new_user_registration_url end end end
ルーティング設定
routes.rb devise_for :users, :path => '', :path_names => { :sign_in => 'login', :sign_out => 'logout', :edit => 'profile' }, :controllers => { :omniauth_callbacks => 'omniauth_callbacks' }
あとはviewで新規登録(ログイン)リンクを生成するだけです
views/devise/registrations/new.html.erb <%= link_to "sign up with Github", user_omniauth_authorize_path(:github) %>
装飾は割愛してますのでお好きなようにどうぞ!
以上!! facebookもにこんな感じで実装できちゃうので試してください。
gemのfriendly_idを使ってみた
friendly_idを使うとurlが変わる
例えばrails特有の /projects/1 が/projects/homeworkとかに変わってくれる。
https://rubygems.org/gems/friendly_id/versions/5.1.0 gemfile
gem 'friendly_id', '~> 5.1'
$ rails g friendly_id
$ rails g migration add_slug_to_project slug:string:uniq
app/model/project.rbで extend FriendlyId friendly_id :name, use: [:slugged, :finders] #projectのname部分がidの代わりのurlとして代用される
を追加
こうするとprojectのname部分がidの代わりとして代用されるようになります before => /projects/1 after => /projects/homework
是非お試しください
カスタムMarkdown機能を実装する方法
gemを使って簡単にmarkdown機能を実装していきます。
まずはgemfileに2つのgemを追加
markdownのgem redcarpet | RubyGems.org | your community gem host
シンタックスハイライトのgem coderay | RubyGems.org | your community gem host
Gemfile gem 'redcarpet', '~> 3.3', '>= 3.3.4' gem 'coderay', '~> 1.1'
app/helpers/application_helper.rbにメソッドを書いていきます。(コピペで大丈夫)
module ApplicationHelper class CodeRayify < Redcarpet::Render::HTML def block_code(code, language) CodeRay.scan(code, language).div(:line_numbers => :table) end end def markdown(text) coderayified = CodeRayify.new(:filter_html => true, :hard_wrap => true) options = { :fenced_code_blocks => true, #ここのoptionは任意で好きなようにカスタマイズしてok :no_intra_emphasis => true, :autolink => true, :strikethrough => true, :lax_html_blocks => true, :superscript => true } markdown_to_html = Redcarpet::Markdown.new(coderayified, options) markdown_to_html.render(text).html_safe end end
viewのmarkdownにしたいところでメソッドを呼ぶだけ
app/views/tasks/show.html.erb
<%= markdown(@task.note) %>
ここまでで実装は終了です。 フォームのtext_field内で実際に試してみましょう。
viewの画面で確認
できたああああああ
なお ActionView::Template::Error (Symbol or String expected, but NilClass given.のエラーが途中出てきた場合は Block code starting with 4 spaces cannot be rendered when used with CodeRayify · Issue #308 · vmg/redcarpet · GitHub ここを参考にしてみてください。
備忘録
プレイスホルダー
プレイスホルダー → パラメーターの置き場所。条件式に対して、実行時に任意のパラメーターを引き渡すことができる。
例) scope :active, -> { where('event_date >= ?', Date.today) } 設定されたイベント日を過ぎてしまったら、そのイベントは表示させないようにしたい
?にDate.todayを引き渡している。 またカンマ区切りで複数つなげることもできるし、params[:id]などの引数も取れる。
devise便利メソッド
user_signed_in? => booleanが返り値
current_user => userが返り値(ログインユーザ情報を取得できる)
とりあえずここまで