URLs to Events
Turn a raw URL column into structured event names using a URL path tree.
Each URL is matched against the nodes tree. A node with
aggregate_children set becomes an aggregation point: the node's own URL
keeps its path as the event name, while every deeper page collapses to the
single label <path>/<slug>. The original URL column is replaced in-place.
Usage
stream.urls_to_events(
column="page",
nodes=[
{"path": "/catalog", "aggregate_children": True},
{"path": "/checkout", "aggregate_children": True, "name": "checkout"},
],
)
How it works
Web logs usually carry a URL per pageview, and raw URLs make terrible event
names: every product page is its own event, so no two paths look alike and
every representation of them comes out shredded. urls_to_events turns URLs into a handful of readable event names by
declaring which parts of the site matter.
Each URL is first normalized — host, query string and locale prefix are stripped
by default — and then matched against the nodes tree.
Before, a raw page column:
| page |
|---|
https://shop.com/en/catalog |
https://shop.com/en/catalog/phones?sort=price |
https://shop.com/catalog/phones/iphone-15 |
https://shop.com/checkout/payment |
https://shop.com/admin/logs |
stream.urls_to_events(
column="page",
nodes=[
{"path": "/catalog", "aggregate_children": True},
{"path": "/checkout"},
{"path": "/admin", "exclude": True},
],
)
After:
| page |
|---|
catalog |
catalog/sub-page |
catalog/sub-page |
checkout/payment |
| (row dropped) |
Three things happened:
/catalogitself, being the aggregation node's own URL, keeps its path.- Everything below
/catalogcollapsed into the single eventcatalog/sub-page. The slug is a fixed placeholder — it is not the URL's own segment, so/catalog/phonesand/catalog/laptops/macland in the same event. That is the whole point: one event for "some page inside the catalog". /adminwas excluded, so that row is gone from the eventstream./checkouthas noaggregate_children, so its pages keep their full paths.
Naming the collapsed pages
name sets the slug used for the pages collapsed into a node — it does not
rename the node's own URL:
nodes=[{"path": "/catalog", "aggregate_children": True, "name": "product"}]
# /catalog -> catalog
# /catalog/phones -> catalog/product
To pull one branch back out of the aggregation, declare it as its own node.
Giving it a name gives that branch its own slug; giving it
aggregate_children makes it a nested aggregation point:
nodes=[
{"path": "/catalog", "aggregate_children": True},
{"path": "/catalog/phones", "name": "phones"},
]
# /catalog/phones/iphone-15 -> catalog/phones
# /catalog/laptops/mac -> catalog/sub-page
Set keep_full_paths=True to ignore every aggregate_children and keep the
normalized path of each URL — useful for a first look at what is actually in the
column before deciding where to cut. The host_col, query_col, locale_col
and slug_col parameters save the stripped-off parts into columns of their own
instead of discarding them.
Parameters
| Parameter | Type | Description |
|---|---|---|
column | str | Name of the column that contains raw URL strings. |
nodes | list of dict | URL path tree. Each node dict must have a "path" key (str) and may include: |
strip_host | bool, default True | Remove the scheme and hostname, keeping only the pathname. |
strip_query | bool, default True | Remove the query string and URL fragment. |
strip_locale | bool, default True | Remove a leading 2-letter BCP-47 locale segment (e.g. "en", "fr-ca"). |
keep_full_paths | bool, default False | When True, aggregate_children nodes are ignored and every URL keeps its normalized path. |
host_col | str, optional | If provided, save the extracted hostname into this new column. |
query_col | str, optional | If provided, save the extracted query string into this new column. |
locale_col | str, optional | If provided, save the detected locale prefix into this new column. |
slug_col | str, optional | If provided, save the sub-page slug into this new column. |