バグですか?仕様ですか?筋肉です💪

筋肉とプログラミング、その他アウトプットをしていきます。

Google Analytics APIを使用しページごとのセッション数を取得

この記事に書かれていること

背景

コーポレートサイトのブログ記事の人気ランキングを実装のために、ブログの記事ごとのセッション数を取得する必要がありました。

そこで、Google Analytics APIを使用しデータを取得し、実装しました。

Google Analyticsでできること。

Google Analyticsは、シェアNo.1のアクセス解析ツールで、Googleアナリティクスを使えば、Webサイト運営に必要なデータはほぼ見ることができます。

  • リアルタイムの利用状況
  • ユーザーの基本属性
  • ユーザーがどこから来たか
  • サイト内でのユーザーの動き
  • Webサイトの成果

などです。

実装

準備

まず必要な登録や、設定、情報を取得する必要がありますが、 以下の記事をご参照ください。

また今回の実装においては、大変参考になった記事です🙇‍♂️

https://qiita.com/Sa2Knight/items/0b61efc2be0bdf33ec48

https://qiita.com/ryota-sasabe/items/a5efd2aac244cfcce5c7

全体像

lib/tasks/google_analytics.rakeに具体的な処理、

lib/google_analytics_reporting.rbにGoogleAnalyticsReportingクラスを定義、

必要な情報、秘匿情報は各種ファイルに記述しました。

config/environments/

config/credentials

# lib/tasks/google_analytics.rake
namespace :google_analytics do
  desc 'update_articles_sessions_count_with_Google_Analytics'
  task update_articles_sessions_count: :environment do

    reporting = GoogleAnalyticsReporting.new(
      '2019-01-01',
      'today',
      'ga:uniquePageviews',
      'ga:pagePath',
    )
    reporting = reporting.get_reporting
    articles_pages = reporting.rows.select { |row| row.dimensions.first.match(%r{articles/.+}) }

    articles = []
    Article.published.find_each do |article|
      article = article.attributes
      article['category'] = Article.categories[article['category']]
      article['public_status'] = Article.public_statuses[article['public_status']]

      articles_page = articles_pages.find { |articles_page| articles_page.dimensions.first.gsub("/articles/",  "") == article['uid'] }
      next if articles_page.nil?
      article['sessions_count'] = articles_page.metrics.first.values.first.to_i
      articles << article
    end

    if articles.present?
      Article.upsert_all(articles)
      Article.reindex
    end
  end
end
# lib/google_analytics_reporting.rb
class GoogleAnalyticsReporting
  require 'google/apis/analyticsreporting_v4'
  SCOPE = ['https://www.googleapis.com/auth/analytics.readonly'].freeze
  def initialize(start_date, end_date, metric_type, dimension_type)
    @analytics = Google::Apis::AnalyticsreportingV4
    @client = @analytics::AnalyticsReportingService.new
    ENV['GOOGLE_PRIVATE_KEY'] = Rails.application.credentials.google_analytics[:private_key]
    ENV['GOOGLE_CLIENT_EMAIL'] = Rails.application.credentials.google_analytics[:client_email]
    ENV['GOOGLE_PROJECT_ID'] = Rails.application.credentials.google_analytics[:project_id]
    @start_date = start_date
    @end_date = end_date
    @metric_type = metric_type
    @dimension_type = dimension_type

    @client.authorization = Google::Auth::ServiceAccountCredentials.make_creds(
      scope: SCOPE
    )
  end

  def get_reporting
    request = build_request
    response = @client.batch_get_reports(request)
    response.reports.first.data
  end

  def build_request
    date_range = @analytics::DateRange.new(start_date: @start_date, end_date: @end_date)
    metric = @analytics::Metric.new(expression: @metric_type)
    dimension = @analytics::Dimension.new(name: @dimension_type)
    @analytics::GetReportsRequest.new(
      report_requests: [@analytics::ReportRequest.new(
        view_id: Rails.configuration.google_analytics_view_id,
        metrics: [metric],
        dimensions: [dimension],
        date_ranges: [date_range]
      )]
    )
  end
end

コード解説

# lib/google_analytics_reporting.rb
class GoogleAnalyticsReporting
  require 'google/apis/analyticsreporting_v4'
  SCOPE = ['https://www.googleapis.com/auth/analytics.readonly'].freeze
  def initialize(start_date, end_date, metric_type, dimension_type)
    @analytics = Google::Apis::AnalyticsreportingV4
    @client = @analytics::AnalyticsReportingService.new

まずクラスので必要な情報を定義していきます。

# lib/google_analytics_reporting.rb
    ENV['GOOGLE_PRIVATE_KEY'] = Rails.application.credentials.google_analytics[:private_key]
    ENV['GOOGLE_CLIENT_EMAIL'] = Rails.application.credentials.google_analytics[:client_email]
    ENV['GOOGLE_PROJECT_ID'] = Rails.application.credentials.google_analytics[:project_id]

は、認証のために必要な情報で、参考にしたqiitaにはjsonファイルで記述されていますが、環境変数に定義しても認証が行えるので 今回はこのように定義しました。 中身は、秘匿情報、環境ごとに取得したいのでcredentialsに記述しました。

# lib/google_analytics_reporting.rb
    @start_date = start_date
    @end_date = end_date
    @metric_type = metric_type
    @dimension_type = dimension_type

データの取得の期間、メトリックとディメンション(条件のようなもの)を引数で渡せるように定義しました。 将来、異なる情報を取得したいときにもこのクラスを使用できます。

# lib/google_analytics_reporting.rb
    @client.authorization = Google::Auth::ServiceAccountCredentials.make_creds(
      scope: SCOPE
    )
  end

initializeが呼び出されたときに認証を行なっています。

# lib/tasks/google_analytics.rake
    reporting = GoogleAnalyticsReporting.new(
      '2019-01-01',
      'today',
      'ga:uniquePageviews',
      'ga:pagePath',
    )

rake taskにてクラスをインスタンス化させます。 その際に、今回取得したいデータの条件を引数として渡してあげます。

# lib/tasks/google_analytics.rake
    reporting = reporting.get_reporting
    articles_pages = reporting.rows.select { |row| row.dimensions.first.match(%r{articles/.+}) }

get_reportingメソッドでGAからのデータを取得します。中身は、

# lib/google_analytics_reporting.rb
def get_reporting
    request = build_request
    response = @client.batch_get_reports(request)
    response.reports.first.data
  end
# lib/google_analytics_reporting.rb
  def build_request
    date_range = @analytics::DateRange.new(start_date: @start_date, end_date: @end_date)
    metric = @analytics::Metric.new(expression: @metric_type)
    dimension = @analytics::Dimension.new(name: @dimension_type)
    @analytics::GetReportsRequest.new(
      report_requests: [@analytics::ReportRequest.new(
        view_id: Rails.configuration.google_analytics_view_id,
        metrics: [metric],
        dimensions: [dimension],
        date_ranges: [date_range]
      )]
    )
  end

となっており、先ほど引数で渡した条件からデータを取得しています。 その際にview_idを渡していますが、これはconfig/environments/以下の環境ごとのファイルにそれぞれ定義しています。

# lib/tasks/google_analytics.rake
    articles = []
    Article.published.find_each do |article|
      article = article.attributes
      article['category'] = Article.categories[article['category']]
      article['public_status'] = Article.public_statuses[article['public_status']]

      articles_page = articles_pages.find { |articles_page| articles_page.dimensions.first.gsub("/articles/",  "") == article['uid'] }
      next if articles_page.nil?
      article['sessions_count'] = articles_page.metrics.first.values.first.to_i
      articles << article
    end

    if articles.present?
      Article.upsert_all(articles)
      Article.reindex
    end

ここで、dbに存在する。ブログ記事のデータを取得し、GAと同じuid(url)のもののsessions_countを更新させる処理を行なっています。 レコード数は少ないとは思いますが、大量にデータを扱っても良いようにfind_eachを使用しました。 https://blog.toshimaru.net/rails-find_each/

また、1件ずつsqlを発行するのは、処理的によくないのでbulk_insertのupsert_allを使いました。 最後に、reindexをしているのは、elastic searchを使用しているため、upsert_allして値を更新したらreindexをしないといけないからです。

最後に

APIを使用するのは初めてだったので、最初はqiitaの丸写しに近いものでしたが、コードレビューを行なってもらい、より自分の実装したいものになっていきました。 また、内容の理解やrubyの理解も深まりました。 同じ処理でも、よりパフォーマンスの良い処理、実は省くことのできる記述などがありました。例えばjsonファイルではなく必要な情報だけ環境変数に記述すれば良いところなど。

最初にqiitaからヒントを得るのは悪くないと思いますが、そこから

  • コピペしたものの中身はどうなっているのか
  • 何が必要なもので、何が不必要なのか
  • 削れる処理はないか
  • よりパフォーマンスをあげるには

をもう少し自分で考え、実装できるようにしないとなと思いました。