Tutorial: Podwójnie zakodowane parametry UTM i miejsce ich naprawy

Spis treści
Kampania o nazwie Sommer Aktion pojawia się w raporcie jako Sommer%20Aktion. Nic nie jest zepsute – ruch dotarł, sesje policzone, konwersje przypisane. Nie zgadza się jedynie nazwa, a dziedziczy ją każdy filtr, każde porównanie i każdy eksport.
Przyczyna jest zawsze ta sama: wartość zakodowano dwukrotnie. Różni się miejsce drugiego przebiegu – a sam raport mówi, ile ich było.

Co robi kodowanie procentowe i co dzieje się dwa razy
Adres może zawierać tylko ograniczony zestaw znaków. Wszystko inne zapisuje się jako znak procentu z szesnastkową wartością bajtów – ze spacji powstaje %20, z ampersandu %26, a z ü powstaje %C3%BC, bo w UTF-8 ma dwa bajty.
Sam znak procentu należy do znaków wymagających zakodowania – i na tym opiera się cały mechanizm problemu. Ponowne zakodowanie już zakodowanego ciągu zamienia każde % w %25.
Sommer Aktion wartosc, tak jak zapisana
Sommer%20Aktion zakodowana raz - w adresie poprawnie
Sommer%2520Aktion zakodowana dwa razy - %20 zakodowano ponownie
Sommer%252520Aktion zakodowana trzy razy
Odczyt w drugą stronę jest równie mechaniczny. Dekoder zamienia %2520 z powrotem w %20, a to dosłowne procent-dwa-zero, a nie spacja – wartość dociera więc jako tekst ze znakiem procentu w środku, i dokładnie to pokazuje raport.
Trzy miejsca powstawania drugiego przebiegu
Adres rzadko powstaje w jednym kroku, a każdy kolejny krok jest kandydatem.
Pierwszym jest sam kreator odnośników. Narzędzie biorące gotowo złożony adres i kodujące całość wytwarza dokładnie to. Poznać to po tym, że dotknięte są także ampersandy między parametrami: adres z %26utm_source%3D zakodowano jako całość zamiast po składnikach – i jako odnośnik już nawet nie działa.
Drugim jest pomiar kliknięć. Narzędzie pocztowe albo platforma reklamowa pakująca cel jako parametr własnego adresu musi go raz zakodować – i słusznie. Jeżeli cel był już zakodowany, kodowanie opakowania jest drugim przebiegiem, a wynik dociera do witryny nienaruszony, lecz z podwojonymi wartościami.
https://klick.beispiel/r?u=https%3A%2F%2Fshop.example%2F%3Futm_campaign%3DSommer%2520Aktion
└─ poprawne kodowanie celu ─┘ └ juz zakodowane ┘
Trzecim jest system redakcyjny. Pole przechowujące adres i maskujące go przy zapisie, a potem maskujące ponownie przy wyświetlaniu, daje ten sam wynik – i to jedyny przypadek, w którym nic w narzędziu marketingowym nie zawiniło, dlatego najdłużej zwraca na siebie uwagę.
Rozróżnienie przypadków w raporcie
Liczbę przebiegów da się odczytać z wartości, a wskazuje ona za każdym razem innego sprawcę.
| W raporcie | Przebiegi | Gdzie szukać |
|---|---|---|
| Sommer Aktion | 1 | Poprawnie – nic do zrobienia |
| Sommer%20Aktion | 2 | Kreator odnośników albo pomiar kliknięć |
| Sommer%2520Aktion | 3 | Dwa opakowania jedno po drugim |
| Sommer+Aktion | 1 | Inna konwencja, patrz niżej |
| Sommer%C3%A4Aktion | 2 | Ten sam problem, znak spoza ASCII |
Najszybciej widać to wszystko przez posortowanie wymiaru kampanii i przeszukanie go pod kątem znaku procentu. Jeden filtr na % po kampanii, źródle, medium, słowie i treści znajduje każdą dotkniętą wartość w jednym przebiegu, a liczba obok mówi, ile ruchu ląduje w niewłaściwym koszyku.
Ostatni wiersz zasługuje na uwagę. Raz zakodowane ä to %C3%A4 i jest to poprawne – raport powinien pokazać literę. Zobaczenie sekwencji ucieczki w raporcie oznacza, że zakodowano dwa razy, dokładnie jak przy spacji.
Kodowanie składników, a nie adresów
Zasada zapobiegająca temu wszystkiemu to jedno zdanie: zakodować każdą wartość osobno, a potem złożyć. Nigdy nie kodować adresu, w którym stoją już parametry.
const ziel = "https://shop.example/sommer";
const felder = {
utm_source: "newsletter",
utm_medium: "email",
utm_campaign: "Sommer Aktion",
utm_content: "kopfbild & titel"
};
const url = ziel + "?" + Object.entries(felder)
.map(([k, v]) => encodeURIComponent(k) + "=" + encodeURIComponent(v))
.join("&");
// https://shop.example/sommer?utm_source=newsletter&utm_medium=email
// &utm_campaign=Sommer%20Aktion&utm_content=kopfbild%20%26%20titel
Liczy się tu różnica między dwiema dostępnymi funkcjami. encodeURIComponent koduje wszystko, co nie jest niezastrzeżone, łącznie z &, = oraz ?, i należy do pojedynczych wartości. encodeURI zostawia właśnie te znaki w spokoju, bo niosą znaczenie strukturalne, i należy do całego, jeszcze niezakodowanego adresu. Zastosowanie tej drugiej do wartości to droga, którą ampersand w nazwie kampanii rozbija ją na dwa parametry.
Prosty nawyk domyka pozostałą lukę: zbudować adres ostateczny raz i wstawiać wszędzie ten ciąg. Adres składany ponownie w drugim narzędziu to adres kodowany ponownie.
Spacja, która bywa plusem
Dla spacji w ciągu zapytania istnieją dwie konwencje i obie są w użyciu.
Kodowanie procentowe zapisuje %20. Kodowanie formularzowe, format przeglądarki przy wysyłce formularza metodą GET, zapisuje +. Jedno i drugie występuje w adresach, a różnica polega na tym, że dekoderowi trzeba powiedzieć, co ma przed sobą: dekoder ogólny zostawia plus plusem, a formularzowy robi z niego spację.
Skutek praktyczny: nazwy kampanii ze znakiem plus nie da się odróżnić od nazwy ze spacją, a to, które z dwojga zgłasza dane narzędzie, jest właściwością tego narzędzia. Dwa systemy mogą więc być odmiennego zdania o tej samej kampanii, a żaden z nich nie będzie w błędzie.
Wyjściem jest unikanie tej dwuznaczności zamiast jej rozwiązywania. Wartości kampanii bez spacji – sommer-aktion zamiast Sommer Aktion – całe to pytanie omija, a przy okazji przeżywają zamianę na małe litery, którą część platform wykonuje bez pytania. Konwencja nazewnicza z myślnikami i małymi literami usuwa całą klasę szumu w raportach kosztem nieco mniej ładnego widoku w arkuszu.
Naprawa zapisanego zasobu
Poprawienie odnośnika zatrzymuje nowe szkody. Zebrane sesje zachowują dotychczasowe wartości, a dróg jest trzy, uporządkowanych według trwałości.
W raporcie dwie wartości różniące się jedynie kodowaniem da się zestawić ręcznie – porównanie, segment albo grupowanie w arkuszu. To droga uczciwa dla kampanii zakończonej: nic nie zostaje zmienione, różnica po prostu zostaje rozliczona.
Dla kampanii trwającej lepsza jest reguła przy zbieraniu. GA4 potrafi przepisać przychodzący parametr przed zapisem, a to samo jest możliwe krok wcześniej w tagu – dekodować wartość, dopóki zostaje w niej sekwencja ucieczki, i zapisać z powrotem.
function () {
var wert = {{URL - utm_campaign}};
if (!wert) { return undefined; }
// dekodowac, dopoki zostaje sekwencja jak %20 lub %25
while (/%[0-9A-Fa-f]{2}/.test(wert)) {
try { wert = decodeURIComponent(wert); } catch (e) { break; }
}
return wert;
}
Konstrukcja try wokół dekodowania nie jest ozdobą. Wartość z pojedynczym znakiem procentu, nietworzącym poprawnej sekwencji ucieczki, każe decodeURIComponent rzucić wyjątkiem, a bez zabezpieczenia zmienna nie zwraca nic – z problemu kosmetycznego robi się wtedy brakująca kampania.
A w BigQuery wiersze historyczne da się poprawić w zapytaniu zamiast w danych, co pozostawia surowy eksport nietknięty i jest odwracalne.
SELECT
REPLACE(REPLACE(kampagne, '%2520', ' '), '%20', ' ') AS kampagne_sauber,
COUNT(*) AS sitzungen
FROM `projekt.dataset.sitzungen`
GROUP BY kampagne_sauber
ORDER BY sitzungen DESC;
Która z tych trzech dróg jest właściwa, zależy od jednego pytania: czy liczby idą do raportu, według którego ktoś będzie działał, czy do archiwum. Dla pierwszego poprawiać przy zbieraniu. Dla drugiego wystarczy poprawka w zapytaniu – i zostawia ona dowód tego, co faktycznie dotarło.
Pytania i odpowiedzi
Czy filtr na % znajduje też nazwy kampanii z prawdziwym znakiem procentu?
Tak, filtr ich nie odróżnia. Pozostałość podwójnego kodowania ma zawsze postać znaku procentu i dwóch cyfr szesnastkowych, na przykład %20 albo %C3, natomiast po prawdziwym znaku procentu, jak w 20%-taniej, stoi zwykle coś innego. Jednoznaczność pojawia się dopiero wtedy, gdy wartości kampanii unikają znaku procentu tak samo jak spacji, na przykład 20-procent-taniej.
Czy przekierowanie na własnym serwerze także może zakodować adres podwójnie?
Tak, i jest to miejsce, którego nie pokazuje żadne narzędzie marketingowe. Każde przekierowanie między kliknięciem a stroną docelową, z http na https, z domeny bez www na domenę z www albo ze starego adresu na nowy, przekazuje ciąg zapytania dalej i jest kolejnym krokiem, który może go dotknąć.
Apache pokazuje obie strony. Jeśli RewriteRule przejmuje ciąg zapytania bez zmian, pozostaje on taki, jaki był. Jeśli składa go na nowo, na przykład żeby dołączyć parametr, mod_rewrite przy przekierowaniu zewnętrznym maskuje cel jeszcze raz, a z każdego % robi się %25: Sommer%20Aktion zmienia się w Sommer%2520Aktion. Flaga [NE] wyłącza to maskowanie dla danej reguły.
Rozpoznać ten przypadek da się po tym, że odnośnik dociera podwójnie zakodowany, choć w kreatorze odnośników i w pomiarze kliknięć wygląda poprawnie. Dowód daje sam łańcuch przekierowań: curl -sIL wywołany dla adresu kampanii pokazuje w nagłówku Location każdego kroku, w którym miejscu %20 zamienia się w %2520.