Every Monday morning used to start the same way. Open Google Analytics, export a few reports, paste them into a spreadsheet, clean up the columns, rebuild the same charts I'd rebuilt the week before, write two paragraphs of commentary, send it round. Two hours, gone, before I'd done a single thing that required actual judgement. And the worst part wasn't the time — it was that the tedious, mechanical bit was also the bit most likely to go wrong. Paste a column one row off and the whole story shifts.
So I did what you should do with any task you dread and repeat: I automated it. Here's roughly how, and more importantly, where the effort actually pays off.
The shape of the problem
A weekly customer-journey report is mostly the same skeleton every week. Same metrics, same segments, same funnel, same date logic ("last week vs. the week before"). The only thing that changes is the numbers. That is exactly the kind of work a computer should do and a person shouldn't.
The manual version has three failure modes worth naming, because automating fixes all three:
- It's slow — obviously.
- It's inconsistent — a human rebuilding a report by hand will, over months, subtly redefine things without noticing. Last month's "new user" and this month's quietly stop meaning the same thing.
- It's fragile — the errors hide in the boring steps (the copy-paste, the date range you forgot to shift), which are the steps you stop paying attention to precisely because they're boring.
Pulling the data instead of exporting it
The first shift is to stop exporting by hand and let Python ask Google Analytics for the numbers directly, through its reporting API. You describe what you want — the metrics, the dimensions, the date range — as a query, and get structured data straight back.
The date logic is the part that quietly saves you the most grief, because "last week" should compute itself:
from datetime import date, timedelta
today = date.today()
last_monday = today - timedelta(days=today.weekday() + 7)
last_sunday = last_monday + timedelta(days=6)
prior_monday = last_monday - timedelta(days=7)
prior_sunday = last_monday - timedelta(days=1)
That's a small thing, but "the report always covers the correct week without anyone remembering to change the dates" removes an entire category of Monday-morning mistake.
Once the data lands in a pandas DataFrame, all the reshaping you used to do by hand in a spreadsheet — grouping by channel, computing week-over-week change, sorting to find the movers — becomes a few lines that run identically every single time:
summary = (
df.groupby("channel")["sessions"]
.sum()
.sort_values(ascending=False)
)
change = (this_week - last_week) / last_week
No cell drift. No forgotten column. The same logic, applied the same way, forever.
Keeping the judgement, automating the drudgery
Here's the line I care about, because it's where people either get automation right or wrong: automate the assembly, not the interpretation.
The script builds the tables and the charts and drops them into a template. What it does not do is write the two paragraphs that say what actually happened and what we should do about it. That's the part that needs a human who understands the business, and it's the part worth my Monday morning. Automating the report didn't remove me from the loop — it moved me to the only part of the loop where I add something a script can't.
One practical note from doing this in a start-up: I wrapped the whole thing in Docker early. Not because it's fashionable, but because "it works on my laptop" is a promise you can't keep when the script needs to run somewhere else, or when a colleague inherits it. A container means the thing runs the same everywhere, which is the difference between a personal hack and something the team can actually rely on.
Make it run without you
A report that only runs when you remember to run it isn't really automated — it's just faster manual work. The last step is to let it run on a schedule with nobody watching: a scheduled task, or cron inside that Docker container. But "unattended" raises the stakes, because now the failures happen when you're not there to notice.
So the script has to handle its own bad days. The Analytics API will, occasionally, time out, return nothing, or tell you you've hit a quota. The wrong response is to let a blank or half-built report go out looking authoritative — a confidently empty report is worse than no report, because someone will act on it. The right response is to fail loudly to yourself: if the data comes back empty or errors, don't publish — alert and stop.
if df.empty:
notify("Weekly report ABORTED: GA returned no data.")
raise SystemExit(1)
It's the same principle I keep landing on everywhere: fail visibly, never silently. An automated report you can trust is one that would rather shout at you than quietly send a lie. Build that in from the first version, not after the first embarrassing Monday.
Automate the part that's the same every week. Keep the part that requires you to have an opinion.
The payoff wasn't just the two hours. It was that the numbers stopped being suspect. When the assembly is mechanical and identical every week, you stop quietly wondering whether this week's dip is real or whether you pasted something wrong. You get to spend your attention on the question that matters — why did it move — instead of on whether you built the report correctly. That reliability is the whole point. Speed is just the bonus.