任何关于基于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:

http://github.com/johnboxall/django-ab/tree/master

Django精益是一个很好的A / Btesting选项

http://bitbucket.org/akoha/django-lean/wiki/Home

如果你像使用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,这足够我正在尝试做的事情。 我试图很好地包装它,使初学者易于使用。 这是超级基础,试试吧:

https://github.com/crobertsbmw/RobertsAB