Mezzanine で構築したブログのコンテンツのメタデータ を Google Analytics にインポートしてみる(失敗)


Google Analytics の データインポート機能について調べていました。
個人的な認識で、ユーザー情報のインポートができるだけだと思っていたのですが、コンテンツのメタデータもインポートできるようなので、インポートして具合を見てみようと思います。


この記事の注意点(残念なポイント)

下記の実装方法で Google Analytics 上でコンテンツのメタデータのインポートまでは成功しました。
しかしながら、インポートしたデータの閲覧が上手くできておりません。
おそらく、インポートデータのフォーマットの問題かとは思いますが、原因はわかっておりません。
ただ、インポート自体は成功していて、Google analytics での紐付けがうまくいっていない状態で、APIに対する手続き自体は間違った手続きではないので、公開します。
インポートデータのフォーマットについては話半分くらいで、このやり方だと上手くいかないと理解頂ければと思います。


前提

以下の環境で実行しています。

  • OS
    CentOS release 6.9 (Final)

  • Python Version
    Python 2.7.8

  • Package (必要そうなものだけ抜粋)
    Django (1.10.7)
    Mezzanine (4.2.3)


Goolge Analytics の 参考記事

Django/Mezzanine の 参考記事


手順

以下の手順で進めていきます。

  1. メタデータとしてインポートする項目の決定

  2. Google Analytics に カスタムディメンションを作成する

  3. インポート用 CSV作成スクリプトの実装

  4. Google Analytics にデータのインポート

  5. Google Analytics API 経由で、データをインポートする


1. メタデータとしてインポートする項目の決定

BlogPostの項目の確認

Mezzanine の ブログ記事のModel である BlogPost は以下の項目を保持しています。

  • Django shellの起動

    python2.7 manage.py shell
    

  • Modelのフィールド一覧を出力

    >>> from __future__ import print_function
    >>> from mezzanine.blog.models import BlogPost
    >>> for field in BlogPost._meta.get_fields():
    ...     print(field)
    ... 
    blog.BlogPost.id
    blog.BlogPost.comments_count
    blog.BlogPost.keywords_string
    blog.BlogPost.rating_count
    blog.BlogPost.rating_sum
    blog.BlogPost.rating_average
    blog.BlogPost.site
    blog.BlogPost.title
    blog.BlogPost.slug
    blog.BlogPost._meta_title
    blog.BlogPost.description
    blog.BlogPost.gen_description
    blog.BlogPost.created
    blog.BlogPost.updated
    blog.BlogPost.status
    blog.BlogPost.publish_date
    blog.BlogPost.expiry_date
    blog.BlogPost.short_url
    blog.BlogPost.in_sitemap
    blog.BlogPost.content
    blog.BlogPost.user
    blog.BlogPost.allow_comments
    blog.BlogPost.featured_image
    blog.BlogPost.categories
    blog.BlogPost.related_posts
    blog.BlogPost.rating
    blog.BlogPost.comments
    blog.BlogPost.keywords
    >>> 
    
    以前も同じことをやった覚えがあります。

メタデータに出力する項目の抜粋

以下の項目を使うことにします。

  • blog記事のURL
    これは、BlogPost#get_absolute_url() で取得します。
    Google Analytics 上でのキー値にします。

  • BlogPost.id
    一応キー値なので出力しておきます。

  • BlogPost.user
    Blogの投稿者です。
    私一人ですが、出力しておきます。

  • BlogPost.description
    記事のDescriptionです。
    表示数に対してクリック数が少ない時に意図しない説明文になっていないかを確認できるかと思います。
    出力します。

  • BlogPost.created
    作成日時、更新日時とセットで出力することで、その後のメンテナンス状況を確認できるかと

  • BlogPost.updated
    更新日時、作成日時とセットで出力します。

  • BlogPost.categories
    ブログ記事のカテゴリを取得します。
    ブログ記事とカテゴリの関係はN対Nになります。
    Google Analytics 上でどう扱えばいいのか現状わかってないのですので一旦、名称の昇順で;区切りで出力するようにします。


2. Google Analytics に カスタムディメンションを作成する

手順については、Google Analyticsでコンテンツの属性情報を使った分析を行う | GMOメディア エンジニアブログ に記載がありますので、そちらをご確認ください。
ここでは、登録したカスタムディメンションと、スキーマの定義について説明します。

カスタムディメンション

以下の通りカスタムディメンションを登録しました。
範囲はヒットで設定しています。
Dimensions

スキーマ定義

スキーマの定義は以下の通りです。
Schema
ディメンションの登録順序でディメンションキー値がきまるのですが、CSVの並びと異なりますので、以下対応づけを記載します。

ディメンションキー値ディメンション名
ga:pagePathキー値
ga:dimension1blogPostId
ga:dimension4blogPostUser
ga:dimension2blogPostDescription
ga:dimension3blogPostCreated
ga:dimension5blogPostUpdated
ga:dimension6blogPostCategories

3. インポート用 CSV作成スクリプトの実装

インポート用の CSVの作成するスクリプトを実装します。
Django コマンドとして以下作成しました。

  • export_blog_metadata.py
    # coding: utf-8
    from __future__ import print_function
    from django.core.management.base import BaseCommand
    from mezzanine.blog.models import BlogPost
    import datetime
    import csv
    
    header = ("ga:pagePath","ga:dimension1","ga:dimension4","ga:dimension2","ga:dimension3","ga:dimension5","ga:dimension6")
    
    class Command(BaseCommand):
        help = "Export Blog MetaData as CSV"
    
        def handle(self, *args, **options):
    
            # ファイル名を生成する
            now = datetime.datetime.now()
            file_name = (now.strftime("blog_meta_%Y%m%d%H%M%S.csv"))
            with open(file_name, "w") as file:
                writer = csv.writer(file,quoting=csv.QUOTE_ALL)
                writer.writerow(header)
    
                # カテゴリとBlogPostの関係が多対多なので、prefetch_relatedで取得
                blog_posts = BlogPost.objects.select_related('user').prefetch_related('categories')
                for blog_post in blog_posts:
                    row = []
                    row.append(blog_post.get_absolute_url())
                    row.append(blog_post.id)
                    row.append(blog_post.user.username.encode('utf8'))
                    row.append(blog_post.description.replace("\n","|||").replace("\r","").encode('utf8'))
                    row.append(blog_post.created)
                    row.append(blog_post.updated)
                    categories = blog_post.categories.all()
                    sorted(categories)
                    elems = []
                    for category in categories:
                        elems.append(category.title.encode('utf8'))
                    row.append(";".join(elems))
                    writer.writerow(row)
    

4. Google Analytics にデータのインポート

Google Analyticsでコンテンツの属性情報を使った分析を行う | GMOメディア エンジニアブログ を参考にデータをIMPORTします。
CSVのカラム値に改行コードが含まれると、項目をダブルクォートで囲っていても、取り込みに失敗します。
descriptionに改行コードが含まれていますので、以下の通り、改行コードを|||に置換しています。

row.append(blog_post.description.replace("\n","|||").replace("\r","").encode('utf8'))


5. Google Analytics API 経由で、データをインポートする

3. インポート用 CSV作成スクリプトの実装 のスクリプトを Management API 経由でアップロードできるようにしてみました。
前提として、google-api-python-client をインストールする必要があります。

pip インストール

% sudo pip install --upgrade google-api-python-client
Installing collected packages: httplib2, uritemplate, pyasn1, rsa, pyasn1-modules, oauth2client, google-api-python-client
Successfully installed google-api-python-client-1.6.3 httplib2-0.10.3 oauth2client-4.1.2 pyasn1-0.3.4 pyasn1-modules-0.1.4 rsa-3.4.2 uritemplate-3.0.0

変更後のスクリプト

Management API を使うように変更したスクリプトは以下になります。
{your_...} の部分を取得した、JSONキー、Google Analtytics の ID 等に変更してください。

  • export_blog_metadata.py

    # coding: utf-8
    from __future__ import print_function
    from django.core.management.base import BaseCommand
    from mezzanine.blog.models import BlogPost
    import datetime
    import csv
    
    header = ("ga:pagePath","ga:dimension1","ga:dimension4","ga:dimension2","ga:dimension3","ga:dimension5","ga:dimension6")
    
    class Command(BaseCommand):
        help = "Export Blog MetaData as CSV"
    
        def handle(self, *args, **options):
    
            # ファイル名を生成する
            now = datetime.datetime.now()
            file_name = (now.strftime("blog_meta_%Y%m%d%H%M%S.csv"))
            with open(file_name, "w") as file:
                writer = csv.writer(file,quoting=csv.QUOTE_ALL)
                writer.writerow(header)
    
                # カテゴリとBlogPostの関係が多対多なので、prefetch_relatedで取得
                blog_posts = BlogPost.objects.select_related('user').prefetch_related('categories')
                for blog_post in blog_posts:
                    row = []
                    url = blog_post.get_absolute_url()
                    # エンコードされたデータを取り込んだが、反映されないので、デコードをしてみたがやはり反映されない。    
                    import urllib2
                    url = urllib2.unquote(url).encode('raw_unicode_escape')
                    row.append(url)
                    row.append(blog_post.id)                                                                                              
                    row.append(blog_post.user.username.encode('utf8'))
                    row.append(blog_post.description.replace("\n","|||").replace("\r","").encode('utf8'))
                    row.append(blog_post.created)
                    row.append(blog_post.updated)
                    categories = blog_post.categories.all()
                    sorted(categories)
                    elems = []
                    for category in categories:
                        elems.append(category.title.encode('utf8'))
                    row.append(";".join(elems))
                    writer.writerow(row)
    
            try:
                from apiclient.discovery import build
                from oauth2client.service_account import ServiceAccountCredentials
                from httplib2 import Http
                key_file_location = "{your_json_key}"
                scopes = ['https://www.googleapis.com/auth/analytics.edit']
                credentials = ServiceAccountCredentials.from_json_keyfile_name(key_file_location, scopes=scopes)
                http_auth = credentials.authorize(Http())
                service = build('analytics', 'v3', http=http_auth)
    
                # Note: This code assumes you have an authorized Analytics service object.
                # See the Data Import Developer Guide for details.
    
                # This request uploads a file custom_data.csv to a particular customDataSource.
                # Note that this example makes use of the MediaFileUpload Object from the
                # apiclient.http module.
                from apiclient.http import MediaFileUpload
                import urllib2
                try:
                    media = MediaFileUpload(file_name,
                                  mimetype='application/octet-stream',
                                  resumable=False)
    
                    daily_upload = service.management().uploads().uploadData(
                             accountId="{your_account_id}",
                             webPropertyId='{your_property_id}',
                             customDataSourceId='{your_datasource_id}',
                             media_body=media).execute()
    
                except TypeError, error:
                    # Handle errors in constructing a query.
                    print('There was an error in constructing your query : %s' , error)
    
                except urllib2.HTTPError, error:
                    # Handle API errors.
                    print('There was an API error : %s : %s', error.resp.status, error.resp.reason)
            finally:
                import os
                os.remove(file_name)
    

  • Management API の version について
    Analytics API の version は v4 リリースされていますが、Management API は v3 に含まれています。
    このため、

        service = build('analytics', 'v3', http=http_auth) 
    
    という記述になります。

  • アップロードファイル名について
    以下の実装でManagement API 経由でアップロードすると、ファイル名が 不明なファイル名 になります。

        daily_upload = service.management().uploads().uploadData(
                                 accountId="{your_account_id}",
                                 webPropertyId='{your_property_id}',
                                 customDataSourceId='{your_datasource_id}',
                                 media_body=media).execute()
    
    ドキュメントを見る限り設定するパラメータはなさそうで少し調べてみたのですが、以下 StackOverFlow の記事を見つけました。
    php - File name in uploadData Google Analytics - Stack Overflow
    上記はphp ですが、設定するAPIがなくIssue として登録されているようで、WEB API を直接叩く例が記載されています。
    私はそこまでファイル名を設定したい思いはなかったので、不明なファイル名 のままでいくことにしました。

  • 取り込みがうまくいかないので、URLをデコードしてみた

        # エンコードされたデータを取り込んだが、反映されないので、デコードをしてみたがやはり反映されない。    
        import urllib2
        url = urllib2.unquote(url).encode('raw_unicode_escape')
        row.append(url)
    
    インポート後、紐付けがうまくいかなかったので、試しにエンコードされたURLをデコードしてみましたが、やはりうまくいきませんでした。。

発生したエラー

  • 403エラー
    googleapiclient.errors.HttpError: <HttpError 403 when requesting https://www.googleapis.com/upload/analytics/v3/management/accounts/uploads?uploadType=media&alt=json returned "Access Not Configured. Google Analytics API has not been used in project xxxxxxxxxxxxx before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/analytics.googleapis.com/overview?project=xxxxxxxxxxxxxx then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.">
    
    JSONキーを発行したアカウントに、Google Analytics の権限を付与しておらず、上記エラーが発生しました。
    アカウントを追加し、権限として 編集, 共有設定, 表示と分析 を付与しました。

まとめ

Google Analytics のデータインポート機能を使って、ブログのコンテンツデータの取り込みを行いました。
CSVの出力、Management API を使ってUploadの実行はうまくできましたが、肝心のGoogle Analytics 上での表示がうまくいかない。
という結果になりました。
どなたか原因を知っている方がいましたら、ご教示くださいませ。。

カスタムディメンション自体の送信は、Googleタグマネージャのデータレイヤーで、Googleアナリティクスのカスタムディメンションをいれてみた - Qiita のようにGoogle Tag Manager のデータレイヤー変数を書き出すことで実装できるようなので、そちらで試してみようかと思います。

以上です。

コメント