Rails / SNSの人気順で記事を並び替えたい(カウント数を取得して保存) Facebook / Twitter / Pocket / はてな / Google+

Shunsuke Sawada

人気の記事を表示させたいなぁと思ったけど、いざやるとなると指標を何にしていいのかわからない。
Facebookでシェアが多いものもあれば、はてなブックマークが多いものもある。

だから全部にしてみた。

  
social

取得するのは次の5種類。

コード

rakeタスクのつもりだったけど、
コントローラーでも使うかなーと思ったのでモジュールにしました。
Nokogiriつかうので bundle install しておいてください。
  
なんだかDRYじゃない感じですが取得の仕方がそれぞれ違ってくるので、
ばらばらのメソッドにしておいた方が楽だと思います。

ruby
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
module SnsCount

    require 'open-uri'
    require 'nokogiri'

    def pocket_count(url)
        api = "http://widgets.getpocket.com/v1/button?v=1&count=horizontal&url="
        content = get_content(api + url)
        return 0 if content.blank?

        content.xpath("//*[@id='cnt']").try(:text)
    end


    def twitter_count(url)
        api = "http://urls.api.twitter.com/1/urls/count.json?url="
        html = open(api + url).read
        return 0 if html.blank?

        json = JSON.parse(html)
        json["count"]
    end


    def hatena_count(url)
        api = "http://api.b.st-hatena.com/entry.count?url="
        html = open(api + url).read
        return 0 if html.blank?

        html
    end


    def facebook_count(url)
        api = "http://graph.facebook.com/"
        html = open(api + url).read
        return 0 if html.blank?

        json = JSON.parse(html)
        json["shares"]
    end


    def google_count(url)
        api = "https://apis.google.com/_/+1/fastbutton?url="
        content = get_content(api + url)
        return 0 if content.blank?

        content.xpath("//*[@id='aggregateCount']").try(:text)
    end


    def get_content(url)
        html = open(url).read
        Nokogiri::HTML(html, nil, 'utf-8')
    end

end

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#Pocket
http://widgets.getpocket.com/v1/button?v=1&count=horizontal&url=

#Twitter
http://urls.api.twitter.com/1/urls/count.json?url=

#はてな
http://api.b.st-hatena.com/entry.count?url=

#Facebook
http://graph.facebook.com/

#Google+
https://apis.google.com/_/+1/fastbutton?url=

それぞれカウント取得したいページのURLをつけて叩けば何かしら返ってくるので、
あとはそこから数字を抜き取っているだけ。
  

コントローラーで使ったり

app/controllers/posts_controller.rb

ruby
1
2
3
4
5
6
7
8
9
10
11
class PostsController < ApplicationController

    require 'sns_count'
    include SnsCount

    def show
      @post = Post.find(params[:id])
      @pocket = pocket_count("http://www.workabroad.jp/posts/#{post.id}")
    end

end

rakeタスクで使ったり

今回やったのはこっち。
毎日1回このタスクを走らせればいいかなと思ってる。

  
カウント数をDBに保存するので、それ用のカラムが必要です。
migrate/20150203140953_add_sns_count_to_post.rb

rake db:migrate してね。

1
2
3
4
5
6
7
8
9
10
class AddSnsCountToPost < ActiveRecord::Migration
  def change
    add_column :posts, :pocket_count, :integer, default: 0
    add_column :posts, :twitter_count, :integer, default: 0
    add_column :posts, :hatena_count, :integer, default: 0
    add_column :posts, :facebook_count, :integer, default: 0
    add_column :posts, :google_count, :integer, default: 0
  end
end

  
lib/task/get_sns_count.rake

ruby
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
namespace :sns do

    desc "Get SNS count and store them in DB."

    task get_sns_count: :environment do
        get_sns_count
    end

    def get_sns_count
        sns = SnsController.new

        Post.all.where(published: true).each do |post|
            # www(sub domain) is required.
            # Otherwise twitter/google/hatena won't return the count properly.
            base_url = "http://www.workabroad.jp/posts/"
            url = base_url + post.id.to_s
            puts "Getting SNS count: #{url}"
            post.pocket_count   = sns.pocket_count(url).to_i
            post.twitter_count  = sns.twitter_count(url).to_i
            post.hatena_count   = sns.hatena_count(url).to_i
            post.facebook_count = sns.facebook_count(url).to_i
            post.google_count   = sns.google_count(url).to_i

            puts    "p:#{post.pocket_count} | t:#{post.twitter_count} | h:#{post.hatena_count} | " +
                        "f:#{post.facebook_count} | g:#{post.google_count}"
            if post.changed?
                post.save
                puts "Saved!"
            else
                puts "Not Changed."
            end
            sleep(1)
        end
    end

end

  
このブログはルートドメインへのアクセスをwww.に転送しています。
→詳しくはこちら。
heroku で 独自ドメインを使う際の最善策を考えた | Workabroad.jp

そんな場合は、
http://workabroad.jp/:id ではなく
http://www.workabroad.jp/:id とURLを指定しないとうまくカウントが返ってきません。
違うページだと判断されているんだと思う。

ここハマった。
× Twitter / Google / はてな
○ FacebookとPocketはルートでもwww.でも同一とみなしてくれる。

表示

あとはSNSカウントの合計が多い順に記事を並べれば完成。
リアルタイムではないけど、ランキング用途には十分だと思います。

app/models/post.rb

ruby
1
2
3
4
5
class Post < ActiveRecord::Base

    scope :by_sns, -> { unscoped.order('pocket_count + twitter_count + hatena_count + facebook_count + google_count DESC') }

end

  
app/controllers/posts_controller.rb

ruby
1
2
3
4
5
6
7
class PostsController < ApplicationController

    def index
      @popular_posts = Post.by_sns.limit(10)
    end

end

  
以上です。
しかし一番人気のある記事がドラゴンボールの曲だとは…。
なんだか拍子抜け。。

参考

はてなブックマーク件数取得API - Hatena Developer Center
Google+のカウント数を取得するために色々試した結果がこちら
  

92
Shunsuke Sawada

おすすめの記事

acts-as-taggable-on タグを表示させる順番を決めたい
Railsを4.2にバージョンアップしたら、Vagrantのローカル開発環境にアクセスできなくなった問題
Railsのバリデーションエラー後にレイアウトが崩れるとき