在Google App Engine上获取唯一用户
在Google App Engine(Python)上怎么做:
SELECT COUNT(DISTINCT user) FROM event WHERE event_type = "PAGEVIEW"
AND t >= start_time AND t <= end_time
长话短说:
我有一个用Python写的Google App Engine应用,用户会生成一些事件,比如页面浏览。我想知道在特定的时间段内,有多少个独立用户产生了页面浏览事件。我最关心的时间段是一周,而在这一周里大约会有一百万个这样的事件。我想把这个放在定时任务里运行。
我的事件数据结构大概是这样的:
class Event(db.Model):
t = db.DateTimeProperty(auto_now_add=True)
user = db.StringProperty(required=True)
event_type = db.StringProperty(required=True)
如果用SQL数据库,我会这样做:
SELECT COUNT(DISTINCT user) FROM event WHERE event_type = "PAGEVIEW"
AND t >= start_time AND t <= end_time
我首先想到的是获取所有的页面浏览事件,然后过滤掉重复的用户。大概是这样的:
query = Event.all()
query.filter("t >=", start_time)
query.filter("t <=", end_time)
usernames = []
for event in query:
usernames.append(event.user)
answer = len(set(usernames))
但这样不行,因为最多只能支持1000个事件。接下来我想到的是先获取1000个事件,然后当这些用完后再获取下一千个,依此类推。但这样也不行,因为要处理一千个查询并获取一百万个数据会超过30秒的请求时间限制。
然后我想应该按用户排序,这样可以更快地跳过重复的用户。但这样不行,因为我已经在使用不等式“t >= start_time AND t <= end_time”。
看来在30秒内是无法完成这个任务的,所以需要把它分成小块来处理。但找出独特的项目似乎不容易分成小任务。我能想到的最好办法是在每次定时任务调用时找到1000个页面浏览事件,然后从中提取独特的用户名,并把它们放在一个像Chard这样的实体里。它可能看起来像这样:
class Chard(db.Model):
usernames = db.StringListProperty(required=True)
所以每个chard里最多会有1000个用户名,如果有重复的会被去掉。大约经过16个小时(这没问题),我就会有所有的chard,然后可以做类似这样的事情:
chards = Chard.all()
all_usernames = set()
for chard in chards:
all_usernames = all_usernames.union(chard.usernames)
answer = len(all_usernames)
看起来可能可行,但并不是一个完美的解决方案。而且如果独特用户足够多,这个循环可能会耗时太长。我还没测试过,希望有人能提出更好的建议,所以不知道这个循环是否足够快。
有没有更好的解决办法呢?
当然,所有这些独特用户的计数可以通过Google Analytics轻松完成,但我正在构建一个应用特定指标的仪表盘,这将是许多统计数据中的第一个。
4 个回答
NDB 目前还是不支持 DISTINCT(去重)。我写了一个小工具方法,可以在 GAE(谷歌应用引擎)上使用去重功能。
可以查看这里。 http://verysimplescripts.blogspot.jp/2013/01/getting-distinct-properties-with-ndb.html
从SDK版本1.7.4开始,现在有了对DISTINCT函数的实验性支持。
详情请查看: https://developers.google.com/appengine/docs/python/datastore/gqlreference
这里有一个可能可行的解决方案。它在一定程度上依赖于使用内存缓存(memcache),所以你的数据可能会在不确定的情况下被删除。买家自负盈亏。
你可以创建一个叫做 unique_visits_today 的内存缓存变量,或者类似的名字。每当用户当天第一次访问页面时,你就用 .incr() 函数来增加这个计数。
判断这是用户的第一次访问是通过查看一个叫 last_activity_day 的字段来实现的。用户访问时,你查看这个字段,如果它显示的是昨天的日期,你就把它更新为今天,并增加内存缓存的计数。
每天午夜时分,一个定时任务(cron job)会把内存缓存计数的当前值写入数据存储,并将计数器重置为零。你会有一个这样的模型:
class UniqueVisitsRecord(db.Model):
# be careful setting date correctly if processing at midnight
activity_date = db.DateProperty()
event_count = IntegerProperty()
然后你可以简单、快速地获取所有符合日期范围的 UniqueVisitsRecords,并把它们的 event_count 字段中的数字加起来。