65
63

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【アプリ評価★5を増やす機能】ヘビーユーザにだけレビュー依頼

Last updated at Posted at 2015-11-25

3日間に10回スマホアプリを起動した人をヘビーユーザと認識して、ヘビーユーザにだけアプリのレビュー依頼した結果90%が星5評価をつけたという事例がAMLから発表されました。目から鱗だったので該当機能を実装してみました。

元ネタはなんでそんな可愛い絵ばっかり書けると話題のAMLの記事アプリ評価が★2→★4に改善されると、ダウンロード率が5.4倍に。アプリのレビューが与える影響と、レビュー改善2つの成功事例です。最初に「楽しんでる?」とユーザに尋ねるダイアログを表示するのは、簡単ですが効果的な良い方法だと思います。

■ 元記事から画像転載
s5.png

仕様

たぶんこんな仕様になるんじゃないかなー

  1. アプリ起動回数はサーバで記録(レビューしたら報酬付与するため)
  2. 3日間に10回以上起動した人にだけレビュー依頼ダイアログを表示
  3. 1度レビュー依頼を出したら24時間レビュー依頼権利をユーザが得る(レビューしたら報酬付与するため)
  4. 起動回数の記録にredis使ってみた

ファイル構成とインストール

redisパッケージのインストール
pip install redis

■ ファイル構成
スクリーンショット 2015-11-25 19.04.37.png

使い方

main.py
# -*- coding: utf-8 -*-
from __future__ import absolute_import, unicode_literals
from review.review import ReviewManager

USER_ID = "B00001"

manager = ReviewManager(USER_ID)

# 権利を持っているか確認
manager.can_review()

# アプリ起動を記録
manager.app_startup()

主ロジック

review.py
# -*- coding: utf-8 -*-
from __future__ import absolute_import, unicode_literals
import datetime
from pip.utils import cached_property
import redis

PREFIX = 'REVIEW'

# 3日間のうちに10回起動すればレビュー機能が有効
STARTUP_SPAN = 3600 * 24 * 3  # 3days
STARTUP_COUNT = 10
STARTUP_MAX_COUNT = 100

# レビュー機能が有効になったら24時間有効
EXPIRE_REVIEW_RIGHTS = 3600 * 24 * 1  # 1days

# レビューする権利 string型
KEY_REVIEW = '%s:REVIEW:{user_id}' % PREFIX

# アプリを起動した履歴 list型
KEY_STARTUP_HISTORY = '%s:STARTUP-HISTORY:{user_id}' % PREFIX


def get_unix_time(now=None):
    """
    UnixTimeを返却
    :rtype : int
    """
    if now:
        return now.strftime('%s')
    return datetime.datetime.now().strftime('%s')


def unix_time_to_datetime(unix_time):
    """
    UnixTimeをdatetimeに変換
    :rtype : datetime
    """
    return datetime.datetime.fromtimestamp(float(unix_time))


class ReviewManager(object):
    def __init__(self, user_id):
        self.user_id = user_id

    @cached_property
    def storage(self):
        """
        :rtype : Storage
        """
        return Storage(self.user_id)

    def can_review(self):
        """
        3日間のうちに10回以上起動していてレビューできる権利を持つ
        ヘビーユーザならTrueを返却
        :rtype : bool
        """
        startup_history = self.storage.get_range_startup_history(20)
        now = datetime.datetime.now()
        startup_count = sum([self._within(ut, now=now) for ut in startup_history])
        return startup_count >= STARTUP_COUNT

    def _within(self, unix_time, now=None):
        """
        対象のunix_timeが3日以内の日付であれば次の値を返却
        期間内なら1
        期間外なら0
        """
        if now is None:
            now = datetime.datetime.now()
        point_time = get_unix_time(now=now - datetime.timedelta(seconds=STARTUP_SPAN))
        if point_time <= unix_time:
            return 1
        return 0

    def app_startup(self):
        """
        アプリの起動を記録
        :rtype : None
        """
        # アプリ起動を記録
        self.storage.push_startup_history()

        # もし直近3日間に10回起動していたらレビュー権利期間を延長
        if self.can_review():
            self.storage.set_review()
            self.storage.touch_review()
        return

    def _debug_reset(self):
        self.storage.client.delete(self.storage.key_review)
        self.storage.client.delete(self.storage.key_startup_history)


class Storage(object):
    _cli = None

    def __init__(self, user_id):
        self.user_id = user_id

    @property
    def client(self):
        """
        Redisのコネクションを返却
        :rtype : Redis
        """
        if Storage._cli is None:
            Storage._cli = redis.Redis(host='localhost', port=6379, db=3)
        return Storage._cli

    @property
    def key_review(self):
        """
        :rtype : str
        """
        return KEY_REVIEW.format(user_id=self.user_id)

    @property
    def key_startup_history(self):
        """
        :rtype : str
        """
        return KEY_STARTUP_HISTORY.format(user_id=self.user_id)

    def touch(self, key, expire):
        self.client.expire(key, expire)

    def touch_review(self):
        """
        レビュー権利を延長
        """
        self.touch(self.key_review, EXPIRE_REVIEW_RIGHTS)

    def touch_startup_history(self):
        """
        アプリ起動履歴の保存期間を延長
        """
        self.touch(self.key_startup_history, STARTUP_SPAN)

    def set_review(self):
        """
        レビュー権利を付与
        """
        self.client.set(self.key_review, 1)
        self.touch_review()

    def push_startup_history(self):
        """
        アプリ起動時間をUnixTimeで記録する。
        起動履歴が{STARTUP_MAX_COUNT}のときは
        アプリ起動履歴を最新の10件を残して消す。
        """
        # redisに起動時間のUnixTimeを記録
        self.client.lpush(self.key_startup_history, get_unix_time())
        self.touch_review()

        # 起動履歴が{STARTUP_MAX_COUNT}のときはアプリ起動履歴を最新の10件を残して消す。
        count = self.client.llen(self.key_startup_history)
        if STARTUP_MAX_COUNT >= count:
            self.client.ltrim(self.key_startup_history, (STARTUP_MAX_COUNT - 11) * -1, -1)

    def get_range_startup_history(self, count):
        """
        アプリ起動時間をN件取得
        :rtype : list[int]
        """
        return self.client.lrange(self.key_startup_history, 0, count - 1)

テストコード

tests.py
# -*- coding: utf-8 -*-
from __future__ import absolute_import, unicode_literals
import datetime
from .review.review import Storage, get_unix_time, unix_time_to_datetime, ReviewManager

USER_ID = "A00001"
USER_ID2 = "A00002"


def test_storage():
    s = Storage(USER_ID)
    s.client
    assert USER_ID in s.key_review
    assert USER_ID in s.key_startup_history
    s.touch_review()
    s.touch_startup_history()


def test_unix_time():
    # unix_timeの変換の誤差が5秒以下であることの試験
    now = datetime.datetime.now()
    unix_time = get_unix_time()
    print unix_time
    assert type(unix_time) == str
    float(unix_time)
    convert_unix_time = unix_time_to_datetime(unix_time)
    print now - convert_unix_time, convert_unix_time
    _delta = now - convert_unix_time
    assert _delta.seconds < 5


def test_manager():
    manager = ReviewManager(USER_ID)
    manager_user2 = ReviewManager(USER_ID2)

    # 全リセット
    manager._debug_reset()
    manager_user2._debug_reset()

    # 1度も起動していない
    assert manager.can_review() is False

    # 1回起動
    manager.app_startup()
    assert manager.can_review() is False

    # 追加で8回起動(計9回)
    for x in xrange(8):
        manager.app_startup()
    assert manager.can_review() is False

    # 10回目の起動
    manager.app_startup()
    assert manager.can_review() is True

    # 11回目の起動
    manager.app_startup()
    assert manager.can_review() is True

    # 追加で200回起動(計211回)
    for x in xrange(200):
        manager.app_startup()
    assert manager.can_review() is True

    # 起動履歴がtrimされて100件以下になっていること
    ct = manager.storage.client.llen(manager.storage.key_startup_history)
    assert ct <= 100

    # ユーザ1と2が混ざっていないこと
    assert manager_user2.can_review() is False
    for x in xrange(10):
        manager_user2.app_startup()
    assert manager_user2.can_review() is True

テスト実行結果
>>>py.test ./tests.py 
=============================================================================== test session starts ===============================================================================
platform darwin -- Python 2.7.5, pytest-2.8.3, py-1.4.30, pluggy-0.3.1
rootdir: /Users/ikeda/punk/qiita/heavy_user, inifile: 
collected 3 items 

tests.py ...

============================================================================ 3 passed in 0.50 seconds =============================================================================
65
63
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
65
63

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?