it-swarm.com.de

Irgendwelche Gedanken zu A/B-Tests in einem Django-Projekt?

Wir haben gerade mit den A/B-Tests für unser auf Django basierendes Projekt begonnen. Kann ich Informationen zu bewährten Methoden oder nützliche Erkenntnisse zu diesem A/B-Test erhalten?.

Im Idealfall wird jede neue Testseite mit einem einzelnen Parameter unterschieden (genau wie bei Google Mail). mysite.com/?ui=2 sollte eine andere Seite anzeigen. Daher muss ich für jede Ansicht einen Decorator schreiben, um verschiedene Vorlagen basierend auf dem Parameterwert 'ui' zu laden. Und ich möchte keine Vorlagennamen in Dekorateuren hart codieren. Wie würde das urls.py-URL-Muster aussehen?

57
None-da

Es ist nützlich, einen Schritt zurückzutreten und zu abstrahieren, was A/B-Tests versuchen, bevor Sie in den Code eintauchen. Was genau brauchen wir, um einen Test durchzuführen?

  • Ein Ziel, das eine Bedingung hat
  • Mindestens zwei verschiedene Pfade, um die Bedingung des Ziels zu erfüllen
  • Ein System zum Senden von Zuschauern über einen der Pfade
  • Ein System zur Aufzeichnung der Testergebnisse

Vor diesem Hintergrund sollten wir über die Implementierung nachdenken.

Das Ziel

Wenn wir über ein Ziel im Internet nachdenken, meinen wir normalerweise, dass ein Benutzer eine bestimmte Seite erreicht oder eine bestimmte Aktion ausführt, beispielsweise die erfolgreiche Registrierung als Benutzer oder das Aufrufen der Checkout-Seite.

In Django können wir das auf verschiedene Arten modellieren - vielleicht naiv in einer Ansicht, indem wir eine Funktion aufrufen, wenn ein Ziel erreicht wurde:

    def checkout(request):
        a_b_goal_complete(request)
        ...

Das hilft jedoch nicht weiter, da wir den Code überall dort hinzufügen müssen, wo wir ihn benötigen - und wenn wir steckbare Apps verwenden, möchten wir den Code lieber nicht bearbeiten, um unseren A/B-Test hinzuzufügen.

Wie können wir A/B-Ziele einführen, ohne den Anzeigecode direkt zu bearbeiten? Was ist mit einer Middleware?

    class ABMiddleware:
      def process_request(self, request):
          if a_b_goal_conditions_met(request):
            a_b_goal_complete(request)

Auf diese Weise können wir die A/B-Ziele überall auf der Website nachverfolgen.

Woher wissen wir, dass die Bedingungen eines Ziels erfüllt wurden? Zur Vereinfachung der Implementierung schlage ich vor, dass bekannt ist, dass die Bedingungen für ein Ziel erfüllt sind, wenn ein Benutzer einen bestimmten URL-Pfad erreicht. Als Bonus können wir dies messen, ohne uns die Hände in einer Ansicht schmutzig zu machen. Um zu unserem Beispiel der Registrierung eines Benutzers zurückzukehren, können wir sagen, dass dieses Ziel erreicht wurde, wenn der Benutzer den URL-Pfad erreicht:

/Registrierung abgeschlossen

Also definieren wir a_b_goal_conditions_met:

     a_b_goal_conditions_met(request):
       return request.path == "/registration/complete":

Pfade

Wenn man über Pfade in Django nachdenkt, ist es naheliegend, verschiedene Vorlagen zu verwenden. Ob es einen anderen Weg gibt, bleibt abzuwarten. Beim A/B-Testen machen Sie kleine Unterschiede zwischen zwei Seiten und messen die Ergebnisse. Daher sollte es eine bewährte Methode sein, eine einzelne Basispfadvorlage zu definieren, von der aus sich alle Pfade zum Ziel erstrecken sollten.

Wie sollen diese Vorlagen gerendert werden? Ein Dekorateur ist wahrscheinlich ein guter Anfang - es ist in Django eine bewährte Methode, einen Parameter template_name in Ihre Ansichten aufzunehmen, damit ein Dekorateur diesen Parameter zur Laufzeit ändern kann.

    @a_b
    def registration(request, extra_context=None, template_name="reg/reg.html"):
       ...

Sie konnten sehen, dass dieser Dekorator entweder die umschlossene Funktion in Augenschein nahm und das Argument template_name änderte oder irgendwo (wie bei einem Modell) nach den richtigen Vorlagen suchte. Wenn wir nicht zu jeder Funktion den Decorator hinzufügen möchten, können wir dies als Teil unserer ABMiddleware implementieren:

    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

Wir müssten auch eine Möglichkeit hinzufügen, um zu verfolgen, in welchen Ansichten A/B-Tests ausgeführt werden usw.

Ein System zum Senden von Zuschauern auf einem Pfad

Theoretisch ist dies einfach, aber es gibt viele verschiedene Implementierungen, sodass nicht klar ist, welche die beste ist. Wir wissen, dass ein gutes System die Benutzer gleichmäßig auf den Pfad verteilen sollte. Es muss eine Hash-Methode verwendet werden. Vielleicht können Sie den Modul des Memcache-Zählers dividiert durch die Anzahl der Pfade verwenden. Vielleicht gibt es einen besseren Weg.

Ein System zur Aufzeichnung der Testergebnisse

Wir müssen aufzeichnen, wie viele Benutzer welchen Pfad zurückgelegt haben - wir müssen auch auf diese Informationen zugreifen können, wenn der Benutzer das Ziel erreicht hat (wir müssen sagen können, welchen Pfad sie zurückgelegt haben, um die Bedingung des Ziels zu erfüllen) - wir Verwenden Sie ein oder mehrere Modelle, um die Daten aufzuzeichnen, und verwenden Sie entweder Django-Sitzungen oder Cookies, um die Pfadinformationen beizubehalten, bis der Benutzer die Zielbedingung erfüllt.

Schlussgedanken

Ich habe eine Menge Pseudocode für die Implementierung von A/B-Tests in Django angegeben - das oben Gesagte ist keineswegs eine vollständige Lösung, sondern ein guter Anfang, um ein wiederverwendbares Framework für A/B-Tests in Django zu erstellen.

Als Referenz können Sie sich Paul Mar's Seven Minute A/Bs auf GitHub ansehen - es ist die ROR-Version der obigen! http://github.com/paulmars/seven_minute_abs/tree/master


Update

Weitere Überlegungen und Untersuchungen zum Google Website-Optimierungstool haben ergeben, dass die obige Logik Lücken aufweist. Durch die Verwendung verschiedener Vorlagen zur Darstellung von Pfaden wird die gesamte Zwischenspeicherung in der Ansicht aufgehoben (oder, wenn die Ansicht zwischengespeichert ist, wird immer derselbe Pfad bereitgestellt!). Anstatt Pfade zu verwenden, würde ich stattdessen die GWO-Terminologie stehlen und die Idee von Combinations verwenden - das ist ein spezifischer Teil einer Template-Änderung - zum Beispiel das Ändern des <h1>-Tags einer Site.

Die Lösung würde Template-Tags beinhalten, die bis auf JavaScript rendern würden. Wenn die Seite in den Browser geladen wird, sendet das JavaScript eine Anfrage an Ihren Server, die eine der möglichen Kombinationen abruft.

Auf diese Weise können Sie mehrere Kombinationen pro Seite testen, während das Caching erhalten bleibt!


Update

Es gibt immer noch Raum für das Umschalten von Vorlagen - wenn Sie beispielsweise eine völlig neue Homepage einführen und die Leistung mit der alten Homepage testen möchten, möchten Sie immer noch die Technik des Umschaltens von Vorlagen verwenden. Denken Sie daran, dass Sie einen Weg finden müssen, um zwischen der X-Anzahl der zwischengespeicherten Versionen der Seite zu wechseln. Dazu müssen Sie die zwischengespeicherte Standard-Middleware überschreiben, um festzustellen, ob es sich um einen A/B-Test handelt, der unter der angeforderten URL ausgeführt wird. Dann könnte es die richtige zwischengespeicherte Version auswählen, die angezeigt werden soll !!!


Update

Mit den oben beschriebenen Ideen habe ich eine steckbare App für grundlegende A/B-Tests von Django implementiert. Sie können es von Github bekommen:

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

95
jb.

Django Lean ist eine gute Option für A/B-Tests

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

12

Wenn Sie die GET-Parameter so verwenden, wie Sie es vorgeschlagen haben (?ui=2), sollten Sie urls.py überhaupt nicht berühren müssen. Ihr Dekorateur kann den request.GET['ui'] einsehen und herausfinden, was er benötigt.

Um das Hardcodieren von Vorlagennamen zu vermeiden, könnten Sie möglicherweise den Rückgabewert aus der Ansichtsfunktion umbrechen. Anstatt die Ausgabe von render_to_response zurückzugeben, können Sie auch einen Tupel von (template_name, context) zurückgeben und den Namen der Vorlage vom Dekorateur ändern lassen. Wie wäre es mit so etwas? WARNUNG: Ich habe diesen Code nicht getestet

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

Dies ist ein wirklich grundlegendes Beispiel, aber ich hoffe, es vermittelt die Idee. Sie können verschiedene andere Dinge an der Antwort ändern, z. B. Informationen zum Vorlagenkontext hinzufügen. Sie können diese Kontextvariablen verwenden, um sie in Ihre Site Analytics zu integrieren, beispielsweise in Google Analytics.

Als Bonus könnten Sie diesen Dekorateur in Zukunft überarbeiten, wenn Sie sich dafür entscheiden, die Verwendung von GET-Parametern zu beenden und zu etwas überzugehen, das auf Cookies usw. basiert.

Update Wenn Sie bereits viele Ansichten geschrieben haben und nicht alle ändern möchten, können Sie Ihre eigene Version von render_to_response schreiben.

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})
7
Justin Voss

Ein Code basierend auf dem von 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

beispielfunktion mit dem Code:

@ab_test()
def index1(request):
    return (request,'website/index.html', locals())

@ab_test('?ui=33')
def index2(request):
    return (request,'website/index.html', locals())

Was passiert hier: 1. Der übergebene UI-Parameter wird in der Sitzungsvariablen gespeichert. 2. Die gleiche Vorlage wird jedes Mal geladen, aber eine Kontextvariable {{ui}} speichert die UI-ID (Sie können sie zum Ändern der Vorlage verwenden). 3. Wenn der Benutzer die Seite ohne? Ui = xx eingibt, wird er im Fall von index2 zu '? Ui = 33' umgeleitet, im Fall von index1 wird die UI-Variable auf 0 gesetzt.

Ich verwende 3, um von der Hauptseite zum Google Website-Optimierungstool umzuleiten, das wiederum mit einem richtigen? Ui-Parameter zur Hauptseite zurückleitet.

1
kolinko

Diese Antworten wirken abgestanden. Heutzutage ist Google Analytics wahrscheinlich die beliebteste und beste kostenlose Option für die meisten Websites. Hier einige Ressourcen zur Integration von Django in Google Analytics:

Plugins :

Wie Tos :

1
Brian

Django-Lean sieht fantastisch aus. Ich werde noch versuchen, es herauszufinden. Am Ende drehte ich meine eigene Lösung, die für das, was ich versuchte, ausreicht. Ich habe versucht, es schön zu verpacken und es für den Anfänger benutzerfreundlich zu machen. Es ist super einfach, probieren Sie es aus:

https://github.com/crobertsbmw/RobertsAB

1
Chase Roberts

Justins Antwort ist richtig ... Ich empfehle Ihnen, für diesen einen zu stimmen, da er der erste war. Sein Ansatz ist besonders nützlich, wenn Sie mehrere Ansichten haben, für die diese A/B-Anpassung erforderlich ist.

Beachten Sie jedoch, dass Sie keinen Dekorateur oder Änderungen an urls.py benötigen, wenn Sie nur eine Handvoll Ansichten haben. Wenn Sie Ihre urls.py-Datei unverändert gelassen haben ...

(r'^foo/', my.view.here),

... können Sie mit request.GET die gewünschte View-Variante ermitteln:

def here(request):
    variant = request.GET.get('ui', some_default)

Wenn Sie vermeiden möchten, Vorlagennamen für die einzelnen A/B/C/etc-Ansichten fest zu codieren, machen Sie sie einfach zu einer Konvention in Ihrem Vorlagenbenennungsschema (wie dies auch Justins Ansatz empfiehlt):

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')
1
Jarret Hardie

Ich habe ein Split-Test-Beispiel für Django geschrieben, das für jeden, der es sich ansieht, hilfreich sein könnte.

https://github.com/DanAncona/Django-mini-lean

Freue mich zu hören, was du davon hältst und wie ich es hilfreicher machen kann!

0
Dan Ancona