Tutorial: Znajdowanie danych osobowych w ścieżkach stron GA4 i ich usuwanie

Spis treści
Dane osobowe nie trafiają do usługi analitycznej wskutek decyzji. Trafiają tam, bo formularz wysyła metodą GET zamiast POST, bo odnośnik do resetu hasła niesie adres albo bo pole wyszukiwania wpisuje wpisaną treść do adresu.
Nic w interfejsie tego nie zgłasza. Ścieżka strony to zwykły wymiar, a ścieżka z adresem pocztowym wygląda na liście czterech tysięcy jak każda inna. Znalezienie jej kosztuje jedno zapytanie i sześć wzorców; pozbycie się jej kosztuje dwa osobne działania, bo wyciek i zapisany zasób to różne zadania.

Co w ścieżce strony jest daną osobową
Przypadki oczywiste to adresy pocztowe i numery telefonów. Mniej oczywiste sprawiają więcej kłopotu, bo w ogóle nie zostają rozpoznane jako osobowe.
Numer zamówienia jest daną osobową, gdy tylko da się go rozwiązać do osoby w innym systemie – a zwykle się da. Token do resetu hasła nie jest daną osobową, lecz tajemnicą, a tajemnica w raporcie analitycznym stoi otworem dla każdego z prawem odczytu usługi. Wyrażenie wyszukiwane jest daną osobową, gdy tylko ludzie wpisują w pole własne nazwisko albo adres – w sklepie z wyszukiwaniem zamówień codziennie.
Wszystkie łączy droga: przychodzą adresem URL, a adres dociera do GA4 w całości. Wszystko za znakiem zapytania staje się częścią wymiaru strony, a fragment za krzyżykiem jest jedyną częścią, która nigdy nie opuszcza przeglądarki.
Pobranie ścieżek stron z usługi
Data API zwraca te same wymiary co raporty, bez ich ograniczeń liczby wierszy. Jedno żądanie za długi okres przynosi wszystko, co kiedykolwiek zostało zebrane.
curl -X POST \
"https://analyticsdata.googleapis.com/v1beta/properties/123456789:runReport" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"dateRanges": [{ "startDate": "395daysAgo", "endDate": "today" }],
"dimensions": [{ "name": "pagePathPlusQueryString" }],
"metrics": [{ "name": "screenPageViews" }],
"limit": 100000,
"orderBys": [{ "metric": { "metricName": "screenPageViews" }, "desc": true }]
}'
O kompletności wyniku rozstrzygają trzy szczegóły. Wymiar musi być tym z ciągiem zapytania – sam pagePath odcina dokładnie tę część, w której dane zwykle siedzą. Okres powinien sięgać tak daleko wstecz, jak usługa przechowuje dane, bo wyciek naprawiony pół roku temu nadal tkwi w zapisanych wierszach. A usługa z wieloma ścieżkami wymaga stronicowania: odpowiedź niesie liczbę wierszy, a wszystko poza limitem wymaga drugiego żądania z przesunięciem.
Słowo o przydziale. API rozlicza znaczniki na usługę i dzień, a zapytanie za czterysta dni ze stoma tysiącami wierszy nie jest darmowe. Przeprowadzać je raz na kwartał ma sens; przeprowadzać w pętli przy budowaniu zużywa dzienny przydział w jedno popołudnie.
Wzorce, których warto szukać
Sześć wzorców obejmuje niemal każde znalezisko i należy je stosować do postaci surowej oraz odkodowanej ścieżki – bo adres przychodzi równie często jako %40 jak jako @.
import re, urllib.parse
MUSTER = {
"mail": re.compile(r"[\w.+-]+@[\w-]+\.[\w.]{2,}"),
"mail_kod": re.compile(r"[\w.+-]+%40[\w-]+"),
"telefon": re.compile(r"(?:\+|00)\d{7,15}|\b0\d{8,13}\b"),
"schluessel": re.compile(r"[?&](?:token|hash|auth|key|sig|pwd|password)=", re.I),
"namensfeld": re.compile(r"[?&](?:name|vorname|nachname|first|last|user)=", re.I),
"suche": re.compile(r"[?&](?:q|s|search|query)=", re.I),
}
def pruefe(pfad):
formen = {pfad, urllib.parse.unquote(pfad)}
return {name for name, m in MUSTER.items() if any(m.search(f) for f in formen)}
Dwa ostatnie wymagają drugiego spojrzenia, zanim cokolwiek zostanie zgłoszone. Parametr o nazwie name niesie często nazwę produktu, a nie osobę, a parametr wyszukiwania jest problemem tylko wtedy, gdy ludzie wpisują tam dane osobowe – rozstrzyga to przeczytanie dwudziestu prawdziwych wartości, a nie nazwa parametru.
Wzorzec telefonu daje najwięcej fałszywych trafień ze wszystkich: numery zamówień, numery artykułów i daty postaci 20260920 pasują do długiego ciągu cyfr. Ograniczenie go do parametrów, w których występuje, zamiast do całej ścieżki, usuwa większość tego.
Redakcja na strumieniu zamiast w tagu
Gdy znaleziska są znane, wyciek zamyka się w najwcześniejszym opanowanym miejscu. GA4 oferuje to na samym strumieniu danych i obowiązuje to dla wszystkiego, co strumień odbiera, niezależnie od tego, który tag wysłał.
Ustawienie siedzi w administracji przy strumieniu internetowym, w ustawieniach tagu, pod nazwą Redagowanie danych. Składa się z dwóch części: automatycznego usuwania wszystkiego, co wygląda jak adres pocztowy, oraz listy nazw parametrów, których wartości zostają zastąpione przed zapisem.
token, hash, auth, key, sig, pwd, password,
email, mail, e-mail, user, username,
phone, tel, mobile,
name, firstname, lastname
Ta lista należy do tego samego miejsca co wyniki przebiegu i powinna być uzupełniana, gdy tylko przebieg znajdzie nowy parametr. Redakcja działa dokładnie: parametr zapisany na jednej stronie inaczej – e_mail zamiast email – przechodzi nietknięty.
Dwie uwagi o umiejscowieniu wobec pozostałych dróg. Nadpisanie page_location w menedżerze tagów też działa i chwyta wcześniej, ale wyłącznie dla trafień tego jednego tagu; drugi tag albo wywołanie gtag wpisane na stałe w motyw je omija. Czyszczenie w kontenerze serwerowym jest drogą najmocniejszą i jedyną chroniącą także przekazanie do platform reklamowych – to osobna praca z własnym artykułem. Ustawienie strumienia jest tym, które kosztuje dziesięć minut i obejmuje wszystko.
Już zapisany zasób
Redakcja obowiązuje dla trafień od tej chwili. Nie zmienia nic w wierszach leżących już w usłudze – a właśnie one liczą się dla pytania o ochronę danych.
Usuwa je wniosek o usunięcie danych, w administracji na poziomie usługi. Właściwy typ kasuje wybrane parametry we wszystkich zdarzeniach, a adres strony jest jednym z tych parametrów.
Trzy właściwości tego trybu warto poznać przed uruchomieniem. Ma okres oczekiwania liczony w dniach, w którym da się go wycofać – to zamierzone i pożyteczne. Raz uruchomiony, potrzebuje do około dwóch miesięcy na pełne zastosowanie. I jest nieodwracalny: wymiar strony z tego okresu przepada potem także dla ścieżek nieszkodliwych, jeżeli wniosek obejmował cały parametr.
Czego wniosek nie dotyka, to eksport do BigQuery. Dane wyeksportowane przed usunięciem leżą w zbiorze, nad którym usługa nie ma już władzy, i tam trzeba je usunąć osobno – instrukcją DELETE albo przez zbudowanie tabeli na nowo, za ten sam okres.
UPDATE `projekt.analytics_123456789.events_*`
SET event_params = ARRAY(
SELECT AS STRUCT
p.key,
IF(p.key = 'page_location',
STRUCT(REGEXP_REPLACE(p.value.string_value,
r'([?&](?:email|token|phone)=)[^&]*', r'\1[entfernt]')
AS string_value, p.value.int_value,
p.value.float_value, p.value.double_value),
p.value) AS value
FROM UNNEST(event_params) AS p)
WHERE _TABLE_SUFFIX BETWEEN '20260101' AND '20260920';
Czego przebieg nie widzi
Trzy miejsca niosą ten sam rodzaj danych i w tym raporcie się nie pojawiają.
Pierwszym są parametry zdarzeń poza adresem strony. Pomiar formularza wysyłający wpisaną wartość jako parametr odkłada ją do wymiaru niestandardowego, a powyższy przebieg nigdy tam nie zagląda. To samo zapytanie Data API z wymiarem niestandardowym zamiast ścieżki strony ją znajduje, i takie zapytanie warto wykonać raz dla każdego zgłoszonego wymiaru.
Drugim jest pole identyfikatora użytkownika. Usługa mająca odbierać identyfikator użytkownika, a dostająca zamiast tego adres pocztowy, to naruszenie podręcznikowe i w raportach niewidoczne, bo w większości z nich pole to w ogóle nie występuje. Sprawdzenie jest takie jak wszędzie: obejrzeć prawdziwe wartości, a nie nazwę pola.
Trzecim jest wszystko, co dom już opuściło. Jeżeli trafienia szły także do platformy reklamowej, redakcja w GA4 nie zmienia nic w kopii, która tam dotarła – a ta platforma ma własną drogę usuwania. Listę odbiorców warto spisać przed pierwszym wnioskiem o usunięcie, bo sam wniosek jej nie wytwarza.