Madogiwa Blog

主に技術系の学習メモに使っていきます。

Ruby:nokogiriを使ったWebスクレイピングの基礎・入門

勉強がてらnokogiriを使ってwebスクレイピングをやってみたので、頭の整理を兼ねて色々とメモしてみたφ(..)

Webスクレイピングとは

ウェブスクレイピング - Wikipedia

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

ようするにWebページにアクセスして、HTMLを取得すること = Webスクレイピング webスクレイピングによって、Web上に掲載された特定の情報を素早く・扱いやすい形で取得することが出来ます。

例) 特定のジャンルのニュース記事のタイトル一覧を作成する等

nokogiriとは

Rubyを使ったWebスクレイピングでよく使われるgemがnokogiriです。
nokogiriを使うことによって簡単にhtmlを解析し、必要な情報を抽出することが出来ます。

github.com

morizyun.github.io

Webスクレイピングをやってみる

nokogiriを使ったwebスクレイピングの練習として、yahoo newsにアクセスしタイトルを取得してみたいと思います。

# ==================
# gemの読み込み
# ==================
require 'nokogiri'
require 'open-uri'

# ==================
# 定数宣言
# ==================
URL = "https://news.yahoo.co.jp/"

# ==================
# スクレイピング処理
# ==================

# 変数宣言
charset = nil
# webページにアクセスし、HTMLを取得
html = open(URL) do |f|
  charset = f.charset # 文字種別を取得
  f.read              # htmlを読み込んで変数htmlに渡す
end

# nokogiriで扱えるように取得したHTMLを変換
doc = Nokogiri::HTML.parse(html, nil, charset)

# 取得したHTMLのタイトルを表示
p doc.title

nokogiriを使ったWebスクレイピングの流れは下記のように行っていきます。

  1. open-uriを使ってWebページにアクセスし、HTMLを取得
  2. Nokogiri::HTML.parseを使って扱いやすいように変換
  3. cssxpathを使って必要な情報を抽出

今度は少し応用で、yahoo newsにアクセスしてトップニュースを取得してみましょう。 ちょっと試すだけだったら、paiza.ioが環境構築もいらないのでオススメです!

XpathCSSセレクタ

nokogiriを使ったWebスクレイピングでは、二種類の抽出方法が主に使われます。

Xpath

HTMLを'//div/p[@class="text"]'というように要素の階層構造を指定し、抽出することが出来ます。

qiita.com

CSS

HTMLをjQueryで扱うように'div p.text'という形で要素を抽出することができます。

www.qoosky.io

教えてgoo!をスクレイピングしてみた

私は今回、教えてgoo!のスクレイピングツールを作ってみました(/・ω・)/
特定カテゴリの質問のタイトルと質問文、ベストアンサー、その他回答の一覧をjsonファイルで吐き出します。

おわりに

Webスクレイピングを使えば、必要な情報を素早く・扱いやすい形で取得できます。
画像の収集やニュースアプリの作成等、色々なことに使えるのではないかと思いましたφ(..)
私自身の学びの整理のために書きましたが、この記事がみなさんの学習の一助にも、なれば幸いです:D

Ruby on Rails:生のSQLを作成(直書き)して実行させる方法

はじめに

Railsのアプリを作ってて、複数TBLを結合した結果をGROUP BYで集計するような、ちょっと複雑な検索処理が必要となり生のSQLを実行したかったので、そのやり方をメモしましたφ(..)

やりかた

SELECT文の場合は、ActiveRecord::Base.connection.select_allの引数にSQLの文字列を与えてあげればOK(/・ω・)/
返り値をto_hashしてあげれば、[{"column1" => "hoge1","column2" => "hoge2"},{"column1" => "hoge1","column2" => "hoge2"}...]のような形で扱える。

# SQLを作成
sql = <<-"EOS"
SELECT
  pages.name,
  pages.url,
  COUNT(*)
FROM pages
INNER JOIN favorites
  ON pages.id = favorites.page_id
GROUP BY pages.name, pages.url
ORDER BY COUNT(*) DESC
EOS

# sqlを実行し、取得結果をhashに変換
ActiveRecord::Base.connection.select_all(sql).to_hash      

ちなみにUPDATEの場合は、ActiveRecord::Base.connection.executeを使うらしい。

sql = "UPDATE favorites SET read = true"
ActiveRecord::Base.connection.execute(sql)

参考リンク

morizyun.github.io

書いたあとに調べたけど、テーブル同士の結合とかは、ActiveRecordのメソッドを使っても実現出来たかもしれない。。。

qiita.com

おわりに

生のSQLを書くのは、DBを変更(posgresql -> mysql等)の際に不具合の元になったりしそうなので、あまり推奨されないと思いますが、どうしても必要なときもあると思いますので、参考になれば幸いです。


Ruby:初心者向け今すぐ使えるいい感じの書き方まとめ

はじめに

Rubyですぐに使えそうな見やすい書き方を自分のためにも整理してみました(/・ω・)/

書き方

複数行の代入

before

hoge.title = "hoge_name"
hoge.read = "hoge_name"
hoge.content = "hoge_content"

after

hoge.title   = "hoge_name"
hoge.read    = "hoge_name"
hoge.content = "hoge_content"

=の位置を合わせた方が見やすいです!

bool型の判定

before

if flg == true
  puts hoge
end

after

if flg
  puts hoge
end

bool型の場合は、そのままでOKです!

1行のdo…end

before

hoge.each do |h|
  puts h
end

after

hoge.each{ |h| puts h }

1行であれば{...}を使用した方が見やすいです!

1行のif分

before

if hoge 
  puts hoge
end

after

puts hoge if hoge 

実行したい処理が1行であれば、後ろにifをつけた方が見やすいです!

複数値の判定

before

if hoge == "a" || hoge == "b" || hoge == "c"
  puts hoge
end

after

if ["a", "b", "c"].include(hoge)
  puts hoge
end

対象を配列にいれてしまってinclude?で判定したほうが見やすいと思います!

範囲の判定

before

if 0 < hoge && hoge < 100
  puts hoge
end

after

if hoge.between?(1,99)
  puts hoge
end

between?(min, max)で判定したほうが見やすいと思います!

配列中で特定の条件に合致する要素のみを抽出

before

values = ["a", "a", "b", "c" ]
result = []
values.each |v| do
  result.push(v) if v == "a"
end

after

values = ["a", "a", "b", "c" ]
result = values.select{ |v| v == "a"}

select{条件}を使用すると見やすく書けると思います。

クラスの配列から特定のプロパティ値の配列を取得する

before

result = []
hoges.each { |h| result.push(h.name) }

after

reslut = hoges.map{ |h| h.name }

mapを使用すると見やすく書けると思います。

おわりに

とりあえず、思いついたものを書いてみました! ソースコードが見やすくなると時間がたったあとでも処理が思い出しやすくなったり、 モチベーションがあがりますよね!

また思いついたら追記しようと思いますφ(..)

Webデザイン:使ってみようWebフォント

デフォルトのフォントだと満足出来なくなってきたので、Webフォントを使ってみようと思ったらGoogleが良いものを提供してくれていました・・・!(..)アリガタヤ
使い方をメモしておきます。

[目次] [:contents]

そもそもWebフォントってなによ

Webフォントを導入すると、サーバー上にあるフォントファイルを参照するため、ユーザーのPCにインストールされていないフォントを表示できるようになります。 CSS3が一般化してきて、そろそろ本格的に利用したくなってきましたね。

liginc.co.jp

Webフォント = サーバー上のフォントファイル!
「ユーザーのローカルにフォントが無くて、ページがださくなる!」といったことが無くなる!(/・ω・)/

Webフォントの使い方

Webフォントの使い方は思ってた以上に簡単でした(..)

Webフォントを見つける

まずはWebフォントを見つけましょう!
私は下記のサイトを利用しました(/・ω・)/

fonts.google.com

↓詳しい使い方はこちら saruwakakun.com

Webフォントを導入する

見つけたWebフォントをHTMLファイルに導入しましょう・ω・

sample.html

<html>
    <head>
        <title>sample</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <!-- ↓WebフォントへのURL  -->
        <link href="https://fonts.googleapis.com/css?family=Poiret+One" rel="stylesheet">
        <style>
            body{
                /* Webフォントを反映 */
                font-family: 'Poiret One', cursive;
            }
        </style>
    </head>
    <body>
        <h1>Sample Title</h1>
        <h2>Sample Section</h2>
        <p>Sample Text1</p>
        <p>Sample Text2</p>
        <p>Sample Text3</p>
    </body>
</html>

これだけ!ヮ(゚д゚)ォ!

実際の画面

f:id:madogiwa0124:20170815235825p:plain

おわりに

フォントでWebページのイメージはガラッと変わりますよね!
Webフォントを使ってお洒落なページを作っていきたいですね(/・ω・)/

Ruby on Rails:form_forでundefined method `to_key'が出続けてハマった話

f:id:madogiwa0124:20170729104257p:plain

あるモデルに紐づくモデルの登録を'form_for'を使って行おうとしたところ、下記エラーが出続けてハマったので対応策をメモφ(..)

ActionView::Template::Error at /
undefined method `to_key' for #<Favorite::ActiveRecord_AssociationRelation:0x007fcefaa056b0>
Did you mean?  to_set to_ary

原因

紐づくモデルの取得をmodel.childmodel.whereで行っていたことが原因。
whereメソッドは、モデルのインスタンスではなく、ActiveRecord::Relationを返却するため適切にフォームを作成できなった。

修正前のソースコード

  <% favorite = page.favorite.where(user_id: current_user.id) %>
  <%= form_for favorite, method: "delete", class: "form-inline" do |f| %>
    <%= f.submit "" %>
  <% end %>

解決策

モデルのインスタンスを返却するfind_byメソッドを使って取得するように変更。

修正後のソースコード

  <% favorite = page.favorite.find_by(user_id: current_user.id) %>
  <%= form_for favorite, method: "delete", class: "form-inline" do |f| %>
    <%= f.submit "" %>
  <% end %>

参考ページ

Active Record クエリインターフェイス railsguides.jp

Ruby on Rails:複数の取得結果を合わせて、更新日時の降順で並び変える方法

f:id:madogiwa0124:20170729104257p:plain

ruby on railsで別のメソッドで取得した結果を合わせて、更新日時の降順に並べ替える際にハマったのでメモφ(..)

やりたかったこと

別メソッドで取得した結果を結合後に更新日時の降順に並べ替える。

例:キーワードに合致するタグ及び名称を持つレコード

最初にやろうとしたこと

下記ページを参考に実装してみたが、上手く行かず。。。

loudspeaker.sakura.ne.jp

def self.search(key)
  # 検索値に合致したページの一覧を降順で返却
  result1 = Page.where("name like '%#{ key }%'")
  result2 = Page.tagged_with(key)
  (result1 + result2).uniq.sort_by{ |v| -v['updated_at'] }
end

エラー画面 f:id:madogiwa0124:20170810220448p:plain

解決策

他にもっと良いやり方がありそうですが、一旦昇順で取得し、reverseで降順にすることで解決・・・!

def self.search(key)
  # 検索値に合致したページの一覧を降順で返却
  result1 = Page.where("name like '%#{ key }%'")
  result2 = Page.tagged_with(key)
  (result1 + result2).uniq.sort_by{ |v|  v['updated_at'] }.reverse
end

RubyonRails:deviseを導入したらテストが通らなくなってハマったのでメモ

deviseを導入し、userモデルを作成後テストが通らなくなったので、 その事象と対応策をメモしますφ(..)

事象

deviseを導入しuserモデルを作成後、rails test実行時に下記エラーが発生し、テストが通らなくなった。

$ rails test

ActiveRecord::RecordNotUnique: PG::UniqueViolation: ERROR:  duplicate key value violates unique constraint "index_users_on_email"
DETAIL:  Key (email)=() already exists.
: INSERT INTO "users" ("created_at", "updated_at", "id") VALUES ('2017-08-06 23:19:03.809282', '2017-08-06 23:19:03.809282', 298486374)

解決策

userモデル作成時に自動生成されたuser.ymlファイルが原因。 デフォルトで設定値が入っていないため、emailnilが設定されKey重複エラーとなっていた。 そのため、root/app/test/fixtures/user.ymlを削除し、test時にユーザーが作成されないようにしたらエラーが解消した。

user.yml

# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
 
# This model initially had no columns defined. If you add columns to the
# model remove the '{}' from the fixture names and add the columns immediately
# below each fixture, per the syntax in the comments below
#
one: {}
# column: value
#
two: {}
# column: value