任何关于基于Django项目的A / Btesting的想法?
我们刚刚开始对基于Django的项目进行A / Btesting。 我可以获得一些有关此A / Btesting的最佳实践或有用的见解的信息。
理想情况下,每个新的testing页面将与单个参数(就像Gmail一样)进行区分。 mysite.com/?ui=2应该给一个不同的页面。 所以对于每个视图,我需要编写一个装饰器来加载基于'ui'参数值的不同模板。 我不想在装饰器中硬编码任何模板名称。 那么urls.py url模式将会如何呢?
在深入研究代码之前,退一步抽象出A / Btesting正在尝试做什么是有益的。 我们需要什么来进行testing?
- 有条件的目标
- 至less有两个不同的path来达到目标的条件
- 一个系统发送观众的path之一
- loggingtesting结果的系统
考虑到这一点,让我们思考一下实现。
目标
当我们在networking上思考一个目标时,我们通常意味着用户到达某个页面,或者他们完成了一个特定的操作,例如成功注册为用户或者进入结账页面。
在Django中,我们可以通过几种方式对其进行build模 – 也许天真地在一个视图中,只要达到目标就调用一个函数:
def checkout(request): a_b_goal_complete(request) ...
但是这并没有什么帮助,因为我们必须在需要的地方添加代码 – 如果我们使用任何可插入的应用程序,我们不希望编辑他们的代码来添加我们的A / Btesting。
我们如何直接编辑视图代码来引入A / B目标? 那么中间件呢?
class ABMiddleware: def process_request(self, request): if a_b_goal_conditions_met(request): a_b_goal_complete(request)
这将使我们能够跟踪网站上任何地方的A / B目标。
我们如何知道目标的条件已经得到满足? 为了便于实现,我们build议我们知道一个目标在达到特定URLpath时满足条件。 作为奖励,我们可以测量这一点,而不会让我们的手在视图内变脏。 回到我们注册用户的例子,我们可以说当用户到达URLpath时,这个目标已经被满足了:
/注册完成
所以我们定义a_b_goal_conditions_met
:
a_b_goal_conditions_met(request): return request.path == "/registration/complete":
path
在思考Django中的path时,跳到使用不同模板的想法是很自然的。 是否还有另一种方式还有待探索。 在A / Btesting中,您可以在两页之间进行细微的差异,然后测量结果。 因此,定义一个基本path模板应该是一个最佳实践,从中可以扩展目标的所有path。
如何呈现这些模板? 一个装饰器可能是一个好的开始 – 在Django中,最好的做法是将参数template_name
包含到视图中,装饰器可以在运行时更改此参数。
@a_b def registration(request, extra_context=None, template_name="reg/reg.html"): ...
你可以看到这个装饰器反省了包装函数,修改了template_name
参数,或者从某个地方(比如模型)查找了正确的模板。 如果我们不想将装饰器添加到每个函数中,我们可以将其作为ABMiddleware的一部分来实现:
class ABMiddleware: ... def process_view(self, request, view_func, view_args, view_kwargs): if should_do_a_b_test(...) and "template_name" in view_kwargs: # Modify the template name to one of our Path templates view_kwargs["template_name"] = get_a_b_path_for_view(view_func) response = view_func(view_args, view_kwargs) return response
我们还需要添加一些方法来跟踪哪些视图有A / Btesting运行等。
用于将观众沿着path发送的系统
理论上这很容易,但有很多不同的实现,所以不清楚哪一个是最好的。 我们知道一个好的系统应该将用户平均分配到path上 – 必须使用一些散列方法 – 也许你可以使用memcache计数器的模数除以path数 – 也许有更好的办法。
loggingtesting结果的系统
我们需要logging有多less用户下了什么path – 当用户达到目标(我们需要能够说出他们为了达到目标的状况而下了什么path)时,我们也需要访问这些信息 – 我们我们将使用某种模型来logging数据,并使用Django会话或Cookie来保存path信息,直到用户满足目标条件。
闭幕的想法
我在Django中给出了许多用于实现A / Btesting的伪代码 – 上面这个绝不是一个完整的解决scheme,而是在Django中为A / Btesting创build一个可重用的框架的良好开端。
作为参考,你可能想看看Paul Mar在GitHub上的七分钟A / B – 这是上面的ROR版本! http://github.com/paulmars/seven_minute_abs/tree/master
更新
关于Google网站优化器的进一步思考和调查,很明显上述逻辑中存在一些漏洞。 通过使用不同的模板来表示path,可以中断视图上的所有caching(或者如果视图被caching,它将始终使用相同的path!)。 相反,在使用path的时候,我会偷窃GWO术语,并使用Combinations
的想法 – 这是模板更改的一个特定部分 – 例如,更改站点的<h1>
标记。
解决scheme将涉及模板标签,这将渲染到JavaScript。 当页面被加载到浏览器中时,JavaScript向服务器发出请求,获取可能的组合之一。
这样,您可以在保留caching的同时testing每个页面的多个组合。
更新
仍然有模板切换的空间 – 比如说你引入一个全新的主页,并且想要testing它在老主页上的性能 – 你仍然想要使用模板切换技术。 要记住的是你将不得不找出一些方法来切换X页面的caching版本。 为此,您需要重写标准caching中间件,以查看它们是否是在请求的URL上运行的A / Btesting。 然后它可以select正确的caching版本来显示!
更新
使用上述的想法,我已经实现了一个基本的A / BtestingDjango的可插入的应用程序。 你可以把它closuresGithub:
Django精益是一个很好的A / Btesting选项
如果你像使用suggsted( ?ui=2
)那样使用GET参数,那么根本就不需要碰到urls.py。 你的装饰器可以检查request.GET['ui']
并find它需要的东西。
为了避免硬编码模板名称,也许你可以包装视图函数的返回值? 您可以不返回render_to_response的输出,而是返回(template_name, context)
一个元组,并让修饰器将模板名称打乱。 这样的事情呢? 警告:我没有testing这个代码
def ab_test(view): def wrapped_view(request, *args, **kwargs): template_name, context = view(request, *args, **kwargs) if 'ui' in request.GET: template_name = '%s_%s' % (template_name, request.GET['ui']) # ie, 'folder/template.html' becomes 'folder/template.html_2' return render_to_response(template_name, context) return wrapped_view
这是一个非常基本的例子,但是我希望它能够得到这个想法。 您可以修改其他几个有关响应的内容,例如将信息添加到模板上下文中。 例如,您可以使用这些上下文variables与您的网站分析集成,例如Google Analytics(分析)。
作为奖励,如果您决定停止使用GET参数并转移到基于cookie的内容,您可以在将来重构此装饰器。
更新如果你已经写了很多视图,并且你不想修改它们,你可以编写自己的render_to_response
版本。
def render_to_response(template_list, dictionary, context_instance, mimetype): return (template_list, dictionary, context_instance, mimetype) def ab_test(view): from django.shortcuts import render_to_response as old_render_to_response def wrapped_view(request, *args, **kwargs): template_name, context, context_instance, mimetype = view(request, *args, **kwargs) if 'ui' in request.GET: template_name = '%s_%s' % (template_name, request.GET['ui']) # ie, 'folder/template.html' becomes 'folder/template.html_2' return old_render_to_response(template_name, context, context_instance=context_instance, mimetype=mimetype) return wrapped_view @ab_test def my_legacy_view(request, param): return render_to_response('mytemplate.html', {'param': param})
贾斯汀的回答是对的…我build议你为他投票,因为他是第一个。 如果您有多个需要此A / B调整的视图,他的方法特别有用。
但是,请注意,如果您只有less数几个视图,则不需要装饰器或更改urls.py。 如果你离开你的urls.py文件是…
(r'^foo/', my.view.here),
…您可以使用request.GET来确定请求的视图变体:
def here(request): variant = request.GET.get('ui', some_default)
如果你想避免为单独的A / B / C / etc视图硬编码模板名称,只要在模板命名scheme中使它们成为一个约定(正如Justin的方法也build议的那样):
def here(request): variant = request.GET.get('ui', some_default) template_name = 'heretemplates/page%s.html' % variant try: return render_to_response(template_name) except TemplateDoesNotExist: return render_to_response('oops.html')
基于Justin Voss的代码:
def ab_test(force = None): def _ab_test(view): def wrapped_view(request, *args, **kwargs): request, template_name, cont = view(request, *args, **kwargs) if 'ui' in request.GET: request.session['ui'] = request.GET['ui'] if 'ui' in request.session: cont['ui'] = request.session['ui'] else: if force is None: cont['ui'] = '0' else: return redirect_to(request, force) return direct_to_template(request, template_name, extra_context = cont) return wrapped_view return _ab_test
使用代码的示例函数:
@ab_test() def index1(request): return (request,'website/index.html', locals()) @ab_test('?ui=33') def index2(request): return (request,'website/index.html', locals())
这里发生了什么:1.传递的UI参数存储在会话variables2中。每次加载相同的模板,但上下文variables{{ui}}存储UI id(您可以使用它来修改模板)。如果用户没有?ui = xx进入页面,那么在index2的情况下,他被redirect到'?ui = 33',如果是index1,UIvariables被设置为0。
我使用3将主页面redirect到Google网站优化工具,然后使用正确的用户参数redirect到主页面。
这些答案似乎陈旧。 如今,Google Analytics(分析)可能是大多数网站最stream行和最好的免费select。 以下是将django与Google Analytics进行整合的一些资源:
插件 :
- 可用插件比较: https : //www.djangopackages.com/grids/g/analytics/
- Django的分析似乎是stream行的(最近更新!): https : //github.com/jcassee/django-analytical
如何 :
- StackOverflow文章: 使用Django部署Google Analytics
- 关于django和分析的一般博客文章: http : //www.jeffknupp.com/blog/2012/02/07/analytics-for-django-sites/
我为django写了一个分裂的testing例子,任何看这个的人都会觉得有用 –
https://github.com/DanAncona/django-mini-lean
喜欢听到你的想法,以及如何让它更有帮助!
Django-lean看起来很棒。 我将尽力弄清楚。 我结束了自己的解决scheme,这足够我正在尝试做的事情。 我试图很好地包装它,使初学者易于使用。 这是超级基础,试试吧: