↓ Skip to main content
AI & Data

Building about.samarthya.me, part 1: Google Analytics, from page tag to API

Building about.samarthya.me - This article is part of a series.
Part 1: This Article
A static blog is a shop with no till: it serves pages, but it can’t count who came in. For that I lean on Google Analytics. This post is about understanding that tool properly, and teaching a small script to read its numbers, before I trust them on a public page.
Pipeline: GA4 property, Data API with a read-only service account, JSON files, a join with catalog.json, the ranking and the charts on about.samarthya.me
From Google Analytics to a static stats page, built on my own PC

The series
#

After the move to Hugo the blog is fast and simple, but it has no front desk: nowhere that says here is what’s new, here is what people keep reading, here is where they come from. So I’m building a small hub, about.samarthya.me, and writing down each piece:

PartWhat it covers
1. Google Analytics (this post)What it is, why I use it, what’s free, and reading it from code
2. Keeping the numbers honestWhat the stats would have said without filtering, and the ranking that fixes it
3. Cloudflare in frontDNS, certificates, redirects and caching for the new subdomain
4. Who is knocking?How Cloudflare tells people from bots, and what that showed about my traffic
5. Building the siteA Quarto template: pages, side menu, charts, light and dark
6. Shipping itUploading a static site to a host I don’t trust, and keeping it fresh

What Google Analytics is
#

Google describes it simply: “a platform that collects data from your websites and apps” and turns it into reports. The mechanics:

  1. A small piece of JavaScript, the tag, sits in every page of the blog.
  2. When someone opens a page, the tag sends an event to Google: page viewed, with the address, the browser’s language, the device type and where the visit came from (a search, a link, nothing).
  3. Google adds the events up into reports: views per page, visitors per country, time spent reading.

Everything for one site lives in a property. GA4 is short for Google Analytics 4, the current version.

Why I use it
#

  • A static site can’t count for itself. There is no server program on my side to log a visit; GitHub Pages serves files and that’s all.
  • History. The same property has measured the blog since 2022, through the WordPress years and the move. Starting with a new tool would mean starting from zero.
  • It’s free for a site of this size, and it has an API, so a script can read it.

It isn’t the only option. Cloudflare Web Analytics is free and “does not collect or use your visitors’ personal data”; part 4 looks at what Cloudflare sees. For now, GA4 has the years of history, so it is the source for the stats page.

What it doesn’t do for me
#

GA4 removes known bots automatically, using Google’s research and the IAB’s list of spiders and bots, and you “cannot disable known bot traffic exclusion or see how much known bot traffic was excluded”. Known is the important word. Crawlers that run JavaScript and aren’t on the list are counted as people. Part 2 shows how many of those I had.

What’s free and what isn’t
#

ItemCostNotes
Google Analytics (standard)FreeGoogle: “free of charge”
Analytics 360Paid (sales contract)Higher limits: 2M API tokens a day, 50 months of exploration data, larger exports
Data APINo price, quota-limitedStandard property: 200,000 tokens a day, 40,000 an hour, 14,000 per project per hour; bigger requests cost more tokens
Google Cloud project and service accountFree“All use of Identity and Access Management API is free of charge.”
BigQuery export (raw events)Free in the sandbox, then paid“Each export will incur costs on the BigQuery side for storage and processing”; not used here
Data retentionFree, but limitedStandard: 2 or 14 months, and it “only affects explorations and funnel reports”. Standard reports, which the API reads, keep their totals, which is why I can still read 2022

The hidden price is the visitor’s: their visit is recorded by Google. That is part of why the hub publishes only totals (more on privacy below).

Words used in this post
#

TermMeaning here
APIA door for programs: send a structured request, get structured data (JSON) back
Dimension / metricWhat to group by (page, country, year) / what to count (views, seconds)
Google Cloud projectAn account container for using Google APIs: it owns the quota and credentials, and here runs nothing
Service accountA Google identity for a program, not a person
JSON keyThe service account’s password, as a file
RESTOrdinary HTTPS requests with JSON, the same as a browser fetching a page
gRPCGoogle’s faster, binary way for programs to call functions on another machine; the client library’s default
TLS certificateWhat lets your computer check it’s really talking to Google over HTTPS

Setting it up, step by step
#

Google’s quickstart uses your personal login. A script that runs on its own needs a service account instead:

  1. Create (or pick) a Google Cloud project at the Cloud console.
  2. Enable the Google Analytics Data API for that project (the quickstart has an Enable button).
  3. Create a service account: IAM & Admin → Service accounts → Create (Google’s steps). Give it no Cloud roles; its only permission will live inside Analytics.
  4. Create a JSON key for it and download it.
  5. Let it read your property: in Analytics, Admin → Property → Access management → + → Add users, paste the service account’s e-mail address and choose Viewer (Google’s steps). Viewer reads reports and changes nothing.
  6. Install the Python client, google-analytics-data (0.23.3 at the time of writing):
python -m venv .venv
.venv/bin/python -m pip install google-analytics-data==0.23.3 truststore

Look after the key. Google warns that service account keys “can become a security risk if not managed carefully”. Mine sits outside every repository, the script finds it through an environment variable, and .gitignore blocks key files anyway. If a key is ever committed, deleting the file isn’t enough: delete the key itself in Cloud IAM, then make a new one.

The first surprise: my antivirus said no
#

The first run failed with a certificate error on every call.

Here’s why. My antivirus checks HTTPS traffic by standing in the middle: it opens the connection to Google itself and shows my programs its own certificate. Windows trusts that certificate, so browsers are happy. The client library, though, talks gRPC by default, and gRPC brings its own list of trusted certificates instead of asking Windows, so it refused the antivirus’ certificate.

The fix is two lines: talk REST (plain HTTPS) instead, and let Python trust what Windows trusts with truststore:

import truststore
truststore.inject_into_ssl()          # Python's HTTPS now uses the Windows certificate store

from google.analytics.data_v1beta import BetaAnalyticsDataClient

client = BetaAnalyticsDataClient.from_service_account_file(KEY_PATH, transport="rest")

Same API, same answers, slightly slower, which doesn’t matter for seven requests.

The questions I ask
#

Each report is one call to runReport. A helper keeps them short:

from google.analytics.data_v1beta.types import (
    DateRange, Dimension, Metric, OrderBy, RunReportRequest)

def report(client, dims, mets, start, order=None):
    req = RunReportRequest(
        property=f"properties/{PROPERTY_ID}",
        dimensions=[Dimension(name=d) for d in dims],
        metrics=[Metric(name=m) for m in mets],
        date_ranges=[DateRange(start_date=start, end_date="today")],
        order_bys=[OrderBy(metric=OrderBy.MetricOrderBy(metric_name=order), desc=True)] if order else [],
        limit=10000,
    )
    return [([d.value for d in r.dimension_values], [float(m.value) for m in r.metric_values])
            for r in client.run_report(req).rows]

Dates can be exact (2022-01-01) or relative (28daysAgo, today). The field names come from the API schema:

QuestionDimensionsMetricsWindowSaved as
How much is each page read?pagePathscreenPageViews, userEngagementDuration28 days, 90 days, since 2022pages.json
Where do readers come from?country, countryIdengagedSessionssince 2022countries.json
How did each year go?yearactiveUsers, engagedSessionssince 2022years.json
Headline totalsnoneactiveUsers, screenPageViews, engagedSessionssince the new site, since 2022totals.json

That’s seven calls. userEngagementDuration is the total seconds a page was on screen, so dividing by views gives the average reading time per view. Part 2 shows why that number ended up mattering most.

Before writing any code, try each query in Google’s GA4 Query Explorer, which runs a report in the browser, and look up field names in the Dimensions & Metrics Explorer.

Privacy, on purpose
#

  • Only totals leave Google: per page, per country, per year. No visitor IDs, cities or single visits.
  • Countries with very few visits are folded into “Other”, so nobody can be picked out.
  • The service account can view one property and do nothing else.
  • The downloaded JSON stays on my PC; only the finished page is published.

Takeaways
#

  • Google Analytics counts what a static site can’t: a tag in each page sends events, Google adds them up.
  • Standard GA4, the Data API, the Cloud project and the service account cost nothing; the limits are quota tokens, and the money starts with Analytics 360 or BigQuery beyond the sandbox.
  • A service account with Viewer on one property is all a build script needs; keep its key out of every repo.
  • Behind TLS-inspecting antivirus, use transport="rest" and truststore rather than fighting gRPC.
  • GA4 only removes known bots, so don’t publish its numbers yet. That’s part 2.

References
#

Earlier: Rebuilding samarthya.me, the series about the move that made this hub possible.

Author
Samarthya
Engineer, learner, tinkerer. This is my trophy shelf of everyday work and leisure.
Building about.samarthya.me - This article is part of a series.
Part 1: This Article

Related

Moving to Hugo, part 1: Why Hugo

After the blog was broken into, I had to decide where it would live next. This is how I made that choice, written so you can reuse the reasoning even if you end up somewhere else. The story of the break-in is in Rebuilding samarthya.me. This series is the practical companion: why Hugo, how to migrate step by step, and what living with it is like. My own site is the worked example throughout.