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 – raz zdekodować wartość i zapisać z powrotem.
function () {
var wert = {{URL - utm_campaign}};
if (!wert) { return undefined; }
// odkodowac raz dodatkowo, jesli zostalo %25
while (/%25/.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.