The decline of Google and rise of alternative searches as the source of traffic
STFN
The decline of Google and rise of alternative searches as the source of traffic
June 9, 2026<br>Click to skip the introduction and go straight to the results.
The first part of the story is that, as I already wrote<br>here and<br>here, I am using self-hosted<br>Umami as the analytics engine for this blog. I am using it<br>because I am curious to know how many people visit my blog, and I like numbers<br>and graphs. Of course in this day and age, saying "humans" is a stretch, because<br>you can never be sure if a visit is a human, or a bot. Umami does filter out a<br>lot of the automated traffic because it requires JS on the client to count the<br>visit, but it is not foolproof.
The second part is that lately I've been into Data Science and ML, I am doing an<br>Associate Data Scientist course at datacamp.com, and<br>combining it with exercises at Kaggle. Right now I am<br>focusing on Pandas, sadly not the monochrome<br>bears, but a library for data analysis and manipulation.
And the third part is that for the last months I have been witnessing a steady<br>decline of visits coming to my blog from Google, and on the other appendage, a<br>rise with visitors finding my blog through other search engines. This seems to<br>be in line with the fact that Google is pushing more and more AI into its search<br>engine to the point that it plans to no longer show search results as<br>links,<br>and that causes a reaction of people leaving Google Search for other search places.
This blog post is an attempt to connect those dots, and see in concrete numbers<br>how the visits coming from various search engines have been changing for the<br>last two years. Two years, because I have been using Umami since July 2024.
Here's how I did it.
Data Extraction
I am running Umami as a Docker Compose service in one of my VPSes. The service<br>consists of the web worker container, and a Postgres container running the<br>database. I cannot, and don't want to connect straight to that database, because<br>it is not reachable outside the Docker internal network, and should not be.
The first step was to create a dump of the database by running the command below on the VPS:
docker compose exec -t db pg_dumpall -U umami > dump.sql<br>This extracts the database into a .sql file. I then scp it into my laptop.
I have a PostgreSQL database running in an LXC container in my Proxmox node in<br>the homelab, which is easily reachable from my laptop. So I decided that the<br>next step was to scp the dump file to the database LXC, restore the dump to<br>that database, and use it to query the data. But first I had to create the user<br>and the database in my local PostgreSQL.
su postgres<br>createuser --pwprompt umami<br>createdb -O umami umami<br># providing host enforces password authentication<br>psql --username umami -h localhost -W umami dump.sql<br>Now I have a mirror of the Umami database in my local homelab to which I can<br>easily connect and ingest the data.
Data Analysis
During the last P.I.W.O (Poznań Free Software Fest) I<br>attended a lecture about Marimo, which aims to be the next<br>gen replacement of Jupyter Notebooks. The next day I installed it on my laptop<br>and got instantly hooked, Marimo feels so nice, much more polished than Jupyter,<br>and fits perfectly into my current Data Science plot arc. And so it has been the<br>tool of choice for this investigation.
Here's how I did the analysis in Python and Pandas:
First, connecting to the database and fetching the data:
import pandas as pd<br>import psycopg2<br>from sqlalchemy import create_engine
host = "192.168.88.XXX"<br>port = 5432<br>dbname = "umami"<br>username = "umami"<br>pwd = "correcthorsebatterystaple"
engine = create_engine(f'postgresql+psycopg2://{username}:{pwd}@{host}/{umami}',connect_args={"options": "-c client_encoding=utf8"})
visits = pd.read_sql("SELECT * FROM website_event where website_id = 'b28dd954-acaa-48b1-a1db-dd161dd35d98';", con=engine)<br>This is basic, self-explanatory Python. Import the packages, define the<br>variables for the connection. Then use a popular SQL ORM, SQLAlchemy to do the<br>actual connection handling, and finally load the table into a Pandas Dataframe.<br>website_event is the table storing the visits to my blog, which each row being<br>a single visits to a page. One thing of note is the WHERE clause, I have more<br>than one website tracked in Umami, so I had to filter the visits to include this<br>blog only.
And the analysis itself:
summary = (<br>visits<br>.groupby(pd.Grouper(key='created_at', freq='MS'))<br>.agg(<br>all_visits=("event_id", "count"),<br>google_visits=('referrer_domain', lambda x: x.str.contains('google', case=False, na=False).sum()),<br>ddg_visits=('referrer_domain', lambda x: x.str.contains('duckduckgo', case=False, na=False).sum()),<br>bing_visits=('referrer_domain', lambda x: x.str.contains('bing', case=False, na=False).sum()),<br>ecosia_visits=('referrer_domain', lambda x: x.str.contains('ecosia', case=False, na=False).sum()),<br>qwant_visits=('referrer_domain', lambda x:...