Top header xs

【Rails】Google Analytics APIを使って人気記事ランキングを作る

このブログの人気記事を取得する際にGoogle Analytics APIを使って頑張ったのでメモ。

Railsでアクセス数を元に人気ランキングを作る方法はいくつかあるけどこのブログはGoogle Analyticsを使っているのでどうせならAPIで取得しようと思い、実装しました。


前提

Google AnalyticsをRailsアプリに組み込んでいること。
Google Analyticsも導入はgemを使って簡単にできます。

bgarret/google-analytics-rails

google-analytics-rails - Rails 3 helpers to manage google analytics tracking. Mostly intended for small to medium websites.

ちなみにこのブログでは自分のアクセスをトラッキングしないようにadmin user(自分)のときはmetaタグを表示させてません。

application.html.erb
<%= analytics_init if Rails.env.production? && !admin_user? %>


Google Analytics APIの導入

こちらの記事が大変参考になります。

Google Analytics API を叩いてデータを取得するまでの流れ(Ruby) - QiitaQiita

業務でサイトに人気記事ランキングを実装することになって、そのデータを Google Analytics から取得することにしました。自サーバーにRedisなどを入れてPVデータをカウントしておく方法もありますが、Google Analyticsのデータを使えば任意のディメンションや指標を使った集計が簡単に行えるのが魅力です。

キーのタイプをJSONで取得していますが今回はP12を使用しました。

APIを使う

Google APIsの設定が済んだらコードを書いていきます。

まずはRakeタスクファイルを生成します。
lib/tasks/analytics.rakeとします。

こちらを参考にAPIを使います。

https://www.rubydoc.info/github/google/google-api-ruby-client/Google/Apis/AnalyticsreportingV4/AnalyticsReportingService

Google APIの認証

最初に、Google APIの認証をして利用できるようにします。

まずは以下のgemを入れます。 
https://github.com/googleapis/google-api-ruby-client

gem 'google-api-client'

もろもろ設定書いていきます。
ここでは同ディレクトリにgoogle_analytics.p12を置いてます。

issuerと認証キーのP12ファイルのパスワードは環境変数に入れてます。

以下コードで認証は終わりです。

# lib/tasks/analytics.rake
require 'google/apis/analyticsreporting_v4'
require 'google/api_client/auth/key_utils'

namespace :analytics do
  desc 'get popular posts from google analytics'

  task popular_posts: :environment do
    keyfile = Rails.root.join('google_analytics.p12')
    analytics = Google::Apis::AnalyticsreportingV4
    client = analytics::AnalyticsReportingService.new
    client.authorization = Signet::OAuth2::Client.new(
      token_credential_uri: 'https://accounts.google.com/o/oauth2/token',
      audience: 'https://accounts.google.com/o/oauth2/token',
      scope: 'https://www.googleapis.com/auth/analytics.readonly',
      issuer: ENV['GA_ISSUER'],
      signing_key: Google::APIClient::KeyUtils.load_from_pkcs12(keyfile, ENV['GA_KEY_SECRET'])
    )
    client.authorization.fetch_access_token!
  end
end


APIからデータ取得

APIの認証ができたので次に、欲しいデータを指定してリクエストを送ります。
人気記事ランキングを作りたいので、欲しいデータは「過去7日間のpageviewが多い順のpageのpath」です。

view_id = ENV['GA_VIEW_ID']

metric = analytics::Metric.new(expression: 'ga:pageviews', alias: 'pageviews')
dimension = analytics::Dimension.new(name: 'ga:pagePath')
date_range = analytics::DateRange.new(start_date: '7DaysAgo', end_date: 'today')
order_by = analytics::OrderBy.new(field_name: 'ga:pageviews', sort_order: 'DESCENDING')

request = analytics::GetReportsRequest.new(
  report_requests: [analytics::ReportRequest.new(
    view_id: view_id,
    metrics: [metric],
    dimensions: [dimension],
    date_ranges: [date_range],
    order_bys: [order_by]
  )]
)
response = client.batch_get_reports(request)

解説していきます。

Google AnalyticsのView IDを定義します。

view_id = ENV['GA_VIEW_ID']

欲しいデータを指定します。
簡単に言うと、メトリックをpageviews、取得するものをpagePath、範囲を過去7日、ソートをpageviewsの多い順にします。

metric = analytics::Metric.new(expression: 'ga:pageviews', alias: 'pageviews')
dimension = analytics::Dimension.new(name: 'ga:pagePath')
date_range = analytics::DateRange.new(start_date: '7DaysAgo', end_date: 'today')
order_by = analytics::OrderBy.new(field_name: 'ga:pageviews', sort_order: 'DESCENDING')

以上で指定ができたので、リクエストを送り、レポートを取得します。

request = analytics::GetReportsRequest.new(
  report_requests: [analytics::ReportRequest.new(
    view_id: view_id,
    metrics: [metric],
    dimensions: [dimension],
    date_ranges: [date_range],
    order_bys: [order_by]
  )]
)
response = client.batch_get_reports(request)
data = response.reports.first.data

変数dataとしていい感じに整形します。

dataの中身はこんな感じ。

=> #<Google::Apis::AnalyticsreportingV4::ReportData:0x00007ffab8642ef8
 @maximums=[#<Google::Apis::AnalyticsreportingV4::DateRangeValues:0x00007ffab8641fd0 @values=["67"]>],
 @minimums=[#<Google::Apis::AnalyticsreportingV4::DateRangeValues:0x00007ffab8640ef0 @values=["1"]>],
 @row_count=31,
 @rows=
  [#<Google::Apis::AnalyticsreportingV4::ReportRow:0x00007ffabab77930
    @dimensions=["/posts/rails-blog"],
    @metrics=
     [#<Google::Apis::AnalyticsreportingV4::DateRangeValues:0x00007ffabab75ea0 @values=["67"]>]>,
   #<Google::Apis::AnalyticsreportingV4::ReportRow:0x00007ffab864bf30
    @dimensions=["/"],
    @metrics=
     [#<Google::Apis::AnalyticsreportingV4::DateRangeValues:0x00007ffab864b5d0 @values=["30"]>]>,
   #<Google::Apis::AnalyticsreportingV4::ReportRow:0x00007ffab864acc0
    @dimensions=["/posts/23-years-old-blog"],
    @metrics=
     [#<Google::Apis::AnalyticsreportingV4::DateRangeValues:0x00007ffab864a360 @values=["12"]>]>,
   #<Google::Apis::AnalyticsreportingV4::ReportRow:0x00007ffab8649a50
    @dimensions=["/posts/free-domain-and-https-heroku"],
    @metrics=
     [#<Google::Apis::AnalyticsreportingV4::DateRangeValues:0x00007ffab86490f0 @values=["11"]>]>,
   #<Google::Apis::AnalyticsreportingV4::ReportRow:0x00007ffab86487e0
    @dimensions=["/posts/make-markdown-editor"],
    @metrics=[#<Google::Apis::AnalyticsreportingV4::DateRangeValues:0x00007ffabab6fb40 @values=["8"]>]>,
   #<Google::Apis::AnalyticsreportingV4::ReportRow:0x00007ffabab6c5d0
    @dimensions=["/posts/axios-token-configure"],
    @metrics=[#<Google::Apis::AnalyticsreportingV4::DateRangeValues:0x00007ffab8653708 @values=["7"]>]>,
   #<Google::Apis::AnalyticsreportingV4::ReportRow:0x00007ffab8652df8
    @dimensions=["/about"],
    @metrics=[#<Google::Apis::AnalyticsreportingV4::DateRangeValues:0x00007ffab8652498 @values=["6"]>]>,
   #<Google::Apis::AnalyticsreportingV4::ReportRow:0x00007ffab8651b88
    @dimensions=["/categories/Life"],
    @metrics=[#<Google::Apis::AnalyticsreportingV4::DateRangeValues:0x00007ffab8651228 @values=["4"]>]>,
   #<Google::Apis::AnalyticsreportingV4::ReportRow:0x00007ffab8650918
    @dimensions=["/tags"],
    @metrics=[#<Google::Apis::AnalyticsreportingV4::DateRangeValues:0x00007ffabab67f08 @values=["4"]>]>,
   #<Google::Apis::AnalyticsreportingV4::ReportRow:0x00007ffabab66568
    @dimensions=["/categories/tech"],
    @metrics=[#<Google::Apis::AnalyticsreportingV4::DateRangeValues:0x00007ffab865ba48 @values=["3"]>]>,
   #<Google::Apis::AnalyticsreportingV4::ReportRow:0x00007ffab865b138
    @dimensions=["/posts/boiler-room-home-theater"],
    @metrics=[#<Google::Apis::AnalyticsreportingV4::DateRangeValues:0x00007ffab865a7d8 @values=["3"]>]>,
   #<Google::Apis::AnalyticsreportingV4::ReportRow:0x00007ffab8659ec8
    @dimensions=["/categories/Gadgets"],
    @metrics=[#<Google::Apis::AnalyticsreportingV4::DateRangeValues:0x00007ffab8659568 @values=["2"]>]>,
   #<Google::Apis::AnalyticsreportingV4::ReportRow:0x00007ffab8658c58
    @dimensions=["/categories/Music"],



これでデータの取得はできました。
あとは人気記事を割り出すだけです。

記事を抽出

以下のように書きました。

num = 0
data.rows.each do |row|
  next unless row.dimensions.to_s.include?('/posts/')

  slug = row.dimensions.first.to_s[7..-1]
  post = Post.find_by(slug: slug)
  next if post.nil?

  num += 1
  popular_post = PopularPost.where(rank: num).first_or_create! do |new_popular_post|
    new_popular_post.rank = num
    new_popular_post.post = post
  end
  popular_post.update!(post_id: post.id)
  puts "#{num}: #{row.metrics.first.values.first}views: #{post.title}"
  break if num == 8
end

PopularPostモデルが出てきていますが、中身はこんな感じです。

#popular_post.rb
#t.integer "rank", null: false
#t.bigint "post_id" 
#t.datetime "created_at", null: false
#t.datetime "updated_at", null: false
#t.index ["post_id"], name: "index_popular_posts_on_post_id"

class PopularPost < ApplicationRecord
  belongs_to :post
end

簡単に解説すると、
dataのrowsを展開

data.rows.each do |row|

/posts/をパスに含まないものはスキップ

next unless row.dimensions.to_s.include?('/posts/')

slugを抽出して記事をPostモデルから引っ張ってくる。

slug = row.dimensions.first.to_s[7..-1]
post = Post.find_by(slug: slug)
next if post.nil?

記事が入ったらnumを足してランクづけする。1〜8位まであるんですが、
例えば5位までしかDBになく、新しく6位があるときは作るようにfirst_or_create!します。
これで更新された新しい順位が出力されます。

num += 1
popular_post = PopularPost.where(rank: num).first_or_create! do |new_popular_post|
  new_popular_post.rank = num
  new_popular_post.post = post
end
popular_post.update!(post_id: post.id)
puts "#{num}: #{row.metrics.first.values.first}views: #{post.title}"
break if num == 8

出力された値はこちら

1: 67views: Railsでブログを作ってみた
2: 12views: 23歳はブログを少しずつでも書こうと思う
3: 11views: 無料でドメイン割り当て、HTTPSを実現。Heroku + Cloudflare
4: 8views: 3分でマークダウンエディターを作る【UIkit】
5: 7views: axiosのヘッダーにtokenを埋め込んで常時使えるようにする
6: 3views: Boiler Roomをお酒片手にプロジェクターで観ると幸せになれる
7: 2views: 奇跡の自然、モハーの断崖。まさに世界の果て 【旅ログ】

まだ現時点では7記事しかないので7位までです。
こんなブログでも1週間で以外にもPV数ありました(ない)。

以上で人気記事ランキングの更新ができました。

完成コード

require 'google/apis/analyticsreporting_v4'
require 'google/api_client/auth/key_utils'

namespace :analytics do
  desc 'get popular posts from google analytics'

  task popular_posts: :environment do
    keyfile = Rails.root.join('google_analytics.p12')
    analytics = Google::Apis::AnalyticsreportingV4
    client = analytics::AnalyticsReportingService.new
    client.authorization = Signet::OAuth2::Client.new(
      token_credential_uri: 'https://accounts.google.com/o/oauth2/token',
      audience: 'https://accounts.google.com/o/oauth2/token',
      scope: 'https://www.googleapis.com/auth/analytics.readonly',
      issuer: ENV['GA_ISSUER'],
      signing_key: Google::APIClient::KeyUtils.load_from_pkcs12(keyfile, ENV['GA_KEY_SECRET'])
    )
    client.authorization.fetch_access_token!

    view_id = ENV['GA_VIEW_ID']
    metric = analytics::Metric.new(expression: 'ga:pageviews', alias: 'pageviews')
    dimension = analytics::Dimension.new(name: 'ga:pagePath')
    date_range = analytics::DateRange.new(start_date: '7DaysAgo', end_date: 'today')
    order_by = analytics::OrderBy.new(field_name: 'ga:pageviews', sort_order: 'DESCENDING')
    request = analytics::GetReportsRequest.new(
      report_requests: [analytics::ReportRequest.new(
        view_id: view_id,
        metrics: [metric],
        dimensions: [dimension],
        date_ranges: [date_range],
        order_bys: [order_by]
      )]
    )
    response = client.batch_get_reports(request)

    data = response.reports.first.data
    num = 0
    data.rows.each do |row|
      next unless row.dimensions.to_s.include?('/posts/')

      slug = row.dimensions.first.to_s[7..-1]
      post = Post.find_by(slug: slug)
      next if post.nil?

      num += 1
      popular_post = PopularPost.where(rank: num).first_or_create! do |new_popular_post|
        new_popular_post.rank = num
        new_popular_post.post = post
      end
      popular_post.update!(post_id: post.id)
      puts "#{num}: #{row.metrics.first.values.first}views: #{post.title}"
      break if num == 8
    end
  end
end


実際に人気記事を取得する

Rakeタスクファイルに書いてあるので、ターミナルで

bundle exec rake analytics:popular_posts

と実行するだけです。

このブログはHerokuで動かしているので Heroku Schedulerを使って毎日0時に↑のコマンドを実行してます。
つまり、毎日、1週間の人気記事が更新されます。

以上、こんな感じで人気記事を取得しました。
人気記事取得のコードはもっとうまく書けるのかな。あったら教えてください。

About Me

Profile image

hirozak

エンジニア

音楽, 映画, 海外ドラマ, ガジェット, インテリア, お酒が好きです。

詳しく見る

This Blog

このブログはhirozakがRuby on Railsで開発しました。

GitHubにて、ソースコードを公開しています。