Anemone&Nokogiriでクローラ作成してスクレイピング
エンジニアインターンの浦川です。8ヶ月が過ぎましたが、初めて書きます。
院試明けのタスクとしてスクレイピングを割り当てられ、先程実装終了したので、
スクレイピングについて書こうと思います。
とりあえずgemをインストール。
# Gemfile gem 'anemone' gem 'nokogiri'
Anemone
クローリング(サイトの巡回)が簡単に出来てしまう素晴らしいgem
Nokogiri
ノコギリのようにスクレイピング(HTMLを解析しデータ抽出)してくれるこちらも素晴らしいgem
次に文字コードをUTF-8に変換してくれるライブラリkconvをロードする。
# crawler.rb require 'kconv'
これで準備は整いました。
実際のコードを書くと長くなってしまうので、一般的な使い方を掲載します。
# crawler.rb require 'kconv' url = "http://example.com/" Anemone.crawl(url) do |anemone| anemone.on_every_page do |page| doc = Nokogiri::HTML.parse(page.body.toutf8) items = doc.xpath("//div[class='item-list']/ul/li") items.each do |item| title = item.xpath("div[class='item-title']").text end end end
ここでやっているのは、
1. 指定したURLのサイトをAnemoneオブジェクト(*)にする
url = "http://example.com/" Anemone.crawl(url) do |anemone| end
2. そのサイトからリンクで辿れる全てのページをAnemoneオブジェクト(*)にする
anemone.on_every_page do |page| end
3. それをtoutf8でUTF-8に変換したものをNokogiriオブジェクトにする
doc = Nokogiri::HTML.parse(page.body.toutf8)
4. xpathを用いて欲しい要素を取得する
items = doc.xpath("//div[class='item-list']/ul/li") items.each do |item| title = item.xpath("div[class='item-title']").text end
です。
crawlの引数にはurlの配列やオプションも渡せます。
オプションの例としてdepth_limitがあり、これを0にすると(2.)においてリンクで辿れるページは無視します。
これでスクレイピング完了です。簡単ですね!!
(*)実際にはAnemone::Core, Anemone::Pageとなります。
詳しくはこちらを参照してください。
http://www.rubydoc.info/github/chriskite/anemone/Anemone
rspecを初めて使ってみた
「スキルなし・実績なし」 32歳窓際エンジニアがシリコンバレーで働くようになるまで
これ読んでブログがんばろうかなーという気持ちになっている榊間です。(前回から結構空いちゃいましたが)
今さらながらしっかりとtestできるようにならないとということで、社員さんからrspecの本を薦められて読んでいます。今日はそのことについて書いていきたいと思います。leanpub.com
本を読んで実際に書いたコード
主に使うのは
gem 'rspec-rails' gem 'factory-girl-rails' gem 'faker'
です。個人的には、rspecが実際にテストをするために必要なgemで、factorygirlはtestするためのデータ生成に使用、fakerは生成するデータをより現実のデータに近づけるために使用するといったイメージを持ってます。
僕が最初に書いたのは
require 'spec_helper' describe "Inquiry" do it "is valid with contents, email and name" do expect(build(:inquiry)).to be_valid end it "is invalid without contents" do inquiry = build(:inquiry, contents: nil) inquiry.valid? expect(inquiry.errors[:contents]).to include("を入力してください。") end ... describe "length of contents" do it "is invalid with more than 500 words" do inquiry = build(:invalid_contents) inquiry.valid? expect(inquiry.errors[:contents]).to include("は500文字以内で入力してください。") end end describe "format email" do it "match regex" do inquiry = build(:inquiry) expect(inquiry.email).to match(/.+@.+\..+/i) end it "does not match regex" do inquiry = build(:inquiry, email: "testexample.com" ) inquiry.valid? expect(inquiry.errors[:email]).to include("は不正な値です。") end end ... end
長くなるので少し省略していますが、この中でやりたかったことは
1、name,email,contentsのpresent: trueを確かめる。
2、contentsのvalidationを確かめる。
3、emailのvalidationを確かめる。
この三つです。
でも
・ちょっと長くて読みにくい
・テスト内容が細かすぎる(エラー文もテストしている)
・emailのvalidのテストは一番最初のテストで確かめられている(重複している)
などなど問題がありました。
そしてアドバイスを受けて書いたコードがこちら
require 'spec_helper' describe "Inquiry" do context "when valid" do subject(:inquiry) { build(:inquiry) } it { expect(inquiry).to be_valid } end context "when no contents" do subject(:inquiry) { build(:inquiry, contents: nil) } it { expect(inquiry).not_to be_valid } end ... context "with more than 500 words in contents" do subject(:invalid_contents) { build(:invalid_contents) } it { expect(invalid_contents).not_to be_valid } end context "with non-marching format of email" do subject(:invalid_email) { build(:inquiry, email: "testexample.com") } it { expect(invalid_email).not_to be_valid } end ... end
もう全然違います。contextで状態を表し、全てvalidかinvalidかテストするにとどめました。subujectでまとめることで、すっきりして読みやすくなっています。
テストの書き方は、結構好み・状況に左右されるなーという印象ですが、漏れと重複だけは気をつけてやらないといけないというのが今回の学びです。。
actionmailerを使ってみる
榊間です
お盆とかで少し間が空いてしまったので久しぶりの更新です。
今日はアクションメイラーについて書こうと思います。
今回やりたいことは、inquireページからお問い合わせを送信した時、運営者側と利用者に通知のメールを送ることです。
準備
まずメイラーを生成します(今回はinquirymailerにします)
rails generate mailer InquiryMailer
formで以下のように書いときます。(simpleform使ってますが、要はemailとかもろもろの情報をハッシュ形式で渡してます)
<%= simple_form_for(@inquiry) do |f| %> ... <%= f.input :email %> ... <%= f.button :submit, "この内容で送信する" %> <% end %>
次にapp/mailerに生成されたinquiry_mailer.rbにdefaultメソッドと、complete_inquiry、notice_inquiry(この二つの名前は自由)メソッドを定義します。
# encoding: utf-8 class InquiryMailer < ActionMailer::Base default from: "defaultの送信元メールアドレスを書いてください" def complete_inquiry(inquiry) @inquiry = inquiry mail(to: "利用者のメールアドレス", subject: "題名です") end def notice_inquiry(inquiry) @inquiry = inquiry mail(to: "運営側のメールアドレス", subject: "問い合わせ通知") end end
そして、controllerにメールを送りたいタイミングで、inquire_mailer.rbに定義したメソッドを呼び出します。
引数には@inquiryを渡しており、inquire_mailer.rbや、最終的にはview(後述)で使われています。
# encoding: utf-8 class InquiriesController < ApplicationController before_filter :set_inquiry, only: [:new, :create] def new end def create if @inquiry.save InquiryMailer.complete_inquiry(@inquiry).deliver InquiryMailer.notice_inquiry(@inquiry).deliver redirect_to inquiries_path else render action: "new" end end private def set_inquiry @inquiry = Inquiry.new(params[:inquiry]) end end
あとはメールの内容をviewで生成します。
一番最初に生成されたview/inquiry_mailerに、inquiry_mailerに定義したメソッドに対応するファイルである、complete_inquiry.text.erbとnotice_inquiry.text.erbを生成し編集します。ここでは@inquiryを利用して、formで入力された内容(例えば名前やメールアドレスなど)を利用できます。文面はお好みで。
お名前:<%= @inquiry.name %> 様 メールアドレス:<%= @inquiry.email %>
これでメールが送られるようになったはずです。