> ## Content Index
> Fetch the complete content index at: https://serpapi.com/blog/llms.txt
> Use this file to discover other available public pages before exploring further.

# How to Build a Brand Monitoring Dashboard with SerpApi and Python
- URL: https://serpapi.com/blog/how-to-build-a-brand-monitoring-dashboard-with-serpapi-and-python/
- Published: 2026-04-09T13:00:53.000Z
- Updated: 2026-07-09T18:29:02.000Z
- Description: Track your brand across Google News, YouTube, and online discussions in a single Python dashboard. This tutorial walks through fetching mentions from three SerpApi engines, parsing the results, and visualizing everything in Streamlit.
- Author: Tomás Murúa
- Tags: Python, tutorials, streamlit, YouTube, google news, google perspectives

Knowing what people say about your product usually means checking Google News, scrolling through YouTube, and digging into different social media threads. That's three tabs, three interfaces, and no way to compare what you find. This tutorial builds a single dashboard that pulls brand mentions from all three sources using Python and [SerpApi](https://serpapi.com/). 

By the end, you'll have a [Streamlit](https://streamlit.io/) app with three tabs, one for news articles, one for YouTube videos, and one for social media and forum discussions. We'll use "serpapi" as the search query, but you can swap the brand or product name. 

![](https://storage.ghost.io/c/a5/00/a5004977-0dd2-4bcd-9292-dd0e05d4c59e/content/images/2026/04/Screenshot-2026-04-06-at-15.05.41-1-1.png)

Brand monitoring dashboard showing metrics row with total mentions, news articles, YouTube videos, and perspectives counts

## Set Up Your Environment 

Requirements:

- Python 3.8+
- SerpApi API Key (the free plan includes 250+ searches/month)
- Dependencies (`serpapi`, `pandas`, `streamlit`, `altair`)

The `serpapi` package is the [official Python SDK](https://github.com/serpapi/serpapi-python). It handles request signing, retries, and response parsing. 

The complete code, including a Jupyter notebook version, is available in the [SerpApi tutorials repository](https://github.com/serpapi/tutorials/tree/master/python%5Fprojects/brand-monitoring-dashboard).

[tutorials/python\_projects/brand-monitoring-dashboard at master · serpapi/tutorialsPublic repo to store our blog and video demo code snippets - serpapi/tutorials![](https://static.ghost.org/v5.0.0/images/link-icon.svg)GitHubserpapi![](https://opengraph.githubassets.com/8ed6b95202f11427fadd2a16fbf4107c8278e465b1e4c1930067f6d99dae0ee8/serpapi/tutorials)](https://github.com/serpapi/tutorials/tree/master/python%5Fprojects/brand-monitoring-dashboard)

## The Pipeline

The app follows the same three-step pattern from the [GitHub Issues dashboard](https://serpapi.com/blog/how-to-analyze-issues-with-the-github-api-and-python/), fetch raw data, transform it, and display the analysis.

![](https://storage.ghost.io/c/a5/00/a5004977-0dd2-4bcd-9292-dd0e05d4c59e/content/images/2026/04/brand-monitoring-diagram-2.png)

Pipeline diagram showing three stages: fetch, transform, and display

The difference this time is three separate engines running in parallel. Each returns a different response structure, so the transform step normalizes everything into DataFrames before the dashboard consumes it. 

## Fetch the Data

A single SerpApi client instance works for all three engines:

```Python
import serpapi
import os

SERPAPI_KEY = os.environ.get("SERPAPI_KEY", "")
client = serpapi.Client(api_key=SERPAPI_KEY)
```

### Google News

The [Google News API](https://serpapi.com/google-news-api) returns articles through the `news_results` key. Each result includes `title`, `link`, `source` (a dict with `name` and `icon`), `date`, and `snippet`.

```Python
def fetch_news(client, brand):
    """Fetch news articles mentioning the brand via Google News."""
    results = client.search({
        "engine": "google_news",
        "q": brand,
        "gl": "us",
        "hl": "en",
    })
    return results.get("news_results", [])
```

For more use cases with this engine, refer to the [news monitoring](https://serpapi.com/use-cases/news-monitoring). 

### YouTube

The [YouTube Search API](https://serpapi.com/youtube-search-api) uses `search_query` instead of `q`, and the `sp` parameter controls time filters. The values `EgIIAw%3D%3D` (this week) and `EgIIBA%3D%3D` (this month) are YouTube's internal encoding for upload date filters. You can grab these from YouTube's URL bar after applying a filter manually.

We run both filters and deduplicate by link, since the month results include everything from the week:

```Python
YT_FILTER_WEEK = "EgIIAw%3D%3D"
YT_FILTER_MONTH = "EgIIBA%3D%3D"

def fetch_youtube(client, brand):
    """Fetch YouTube videos, combining week and month filters."""
    seen = set()
    videos = []

    for sp_filter in (YT_FILTER_WEEK, YT_FILTER_MONTH):
        results = client.search({
            "engine": "youtube",
            "search_query": brand,
            "sp": sp_filter,
        })
        for video in results.get("video_results", []):
            link = video.get("link", "")
            if link and link not in seen:
                seen.add(link)
                videos.append(video)

    return videos
```

For more examples using the YouTube API, refer to this [link](https://serpapi.com/blog/tag/youtube/).

### Google Perspectives

[Google Perspectives API](https://serpapi.com/perspectives) surfaces user-generated content from LinkedIn, Reddit, Quora, and blogs. It uses the standard Google engine, and the results appear under the `perspectives` key:

![](https://storage.ghost.io/c/a5/00/a5004977-0dd2-4bcd-9292-dd0e05d4c59e/content/images/2026/04/Screenshot-2026-04-07-at-16.33.04.png)

SerpApi search with the Google perspectives result

```Python
def fetch_perspectives(client, brand):
    """Fetch user-generated content (Reddit, LinkedIn, Quora)."""
    results = client.search({
        "engine": "google",
        "q": brand,
        "google_domain": "google.com",
    })
    return results.get("perspectives", [])
```

### Fetch in Parallel

Three sequential API calls take roughly three seconds. Running them in parallel with Python `ThreadPoolExecutor` brings that down to about one second. Each call runs in its own thread while the others wait for their response:

```Python
from concurrent.futures import ThreadPoolExecutor

@st.cache_data(ttl=300)
def fetch_all_mentions(brand):
    """Fetch all brand mentions from three engines in parallel."""
    client = serpapi.Client(api_key=SERPAPI_KEY)

    with ThreadPoolExecutor(max_workers=3) as pool:
        news_future = pool.submit(fetch_news, client, brand)
        yt_future = pool.submit(fetch_youtube, client, brand)
        persp_future = pool.submit(fetch_perspectives, client, brand)
        return news_future.result(), yt_future.result(), persp_future.result()
```

SerpApi also offers a server-side [async](https://serpapi.com/search-api#api-parameters-serpapi-parameters-async:~:text=be%20used%20together.-,async,-Optional) parameter for large-scale batch processing, where you submit searches and retrieve results later. For our three concurrent calls, client-side threading is simpler and equally effective.

The `@st.cache_data(ttl=300)` decorator caches results for 5 minutes. Without it, every Streamlit interaction would re-trigger the API calls. This works alongside SerpApi's own [1-hour result cache](https://serpapi.com/search-api#:~:text=no%5Fcache), which serves identical queries from the cache at no extra search cost unless you explicitly pass `no_cache=true`. Together, these two layers minimize redundant API calls during development and testing. 

For more optimization techniques when working with SerpApi at scale, refer to this [blog](https://serpapi.com/blog/optimize-serp-scraping-performance-serpapi/). 

## Transform the Data

All three engines return dates as relative strings (`"3 hours ago"`, `"2 days ago"`). We need a shared parser to convert them into `datetime` objects for sorting.

### Parse Relative Dates

Two details worth noting. The regex is compiled once and reused since this function runs for every result in all three engines. And the fallback returns `datetime.now(timezone.utc)` instead of `None`, so results without a parseable date sort to the top rather than breaking pandas operations. 

```Python
import re
from datetime import datetime, timedelta, timezone

RELATIVE_DATE_RE = re.compile(
    r"(\d+)\s+(second|minute|hour|day|week|month|year)s?\s+ago", re.IGNORECASE
)

UNIT_TO_TIMEDELTA = {
    "second": lambda n: timedelta(seconds=n),
    "minute": lambda n: timedelta(minutes=n),
    "hour":   lambda n: timedelta(hours=n),
    "day":    lambda n: timedelta(days=n),
    "week":   lambda n: timedelta(weeks=n),
    "month":  lambda n: timedelta(days=n * 30),
    "year":   lambda n: timedelta(days=n * 365),
}

def parse_relative_date(text):
    """Convert '3 hours ago' into a datetime object."""
    if not text:
        return datetime.now(timezone.utc)

    match = RELATIVE_DATE_RE.search(str(text))
    if not match:
        return datetime.now(timezone.utc)

    amount = int(match.group(1))
    unit = match.group(2).lower()
    delta = UNIT_TO_TIMEDELTA.get(unit, lambda n: timedelta())(amount)
    return datetime.now(timezone.utc) - delta
```

### Build DataFrames

Each engine gets into its own transformer. Here's the news version:

```Python
def transform_news(results):
    """Convert raw Google News results into structured records."""
    records = []
    for item in results:
        source = item.get("source") or {}
        source_name = source.get("name", "Unknown") if isinstance(source, dict) else str(source)

        records.append({
            "title": item.get("title", ""),
            "link": item.get("link", ""),
            "source": source_name,
            "date": parse_relative_date(item.get("date", "")),
            "snippet": item.get("snippet", ""),
        })
    return records
```

The `source` field can be a dict or a plain string depending on the result, so the `isinstace` check handles both. YouTube and Perspectives follow the same pattern, with two differences worth highlighting.

YouTube views come back as strings like `"1,234 views"`, so we strip non-numeric characters before converting:

```Python
views = item.get("views") or 0
if isinstance(views, str):
    views = int(re.sub(r"[^\d]", "", views) or 0)
```

### Build the Dashboard 

The Streamlit interface starts with a form for the brand query and a row of summary metrics across all three sources:

```Python
st.set_page_config(page_title="Brand Monitoring Dashboard", layout="wide")
st.title("Brand Monitoring Dashboard")

with st.form("brand_form"):
    brand = st.text_input("Brand or keyword to monitor", value="serpapi")
    submitted = st.form_submit_button("Search")
```

![](https://storage.ghost.io/c/a5/00/a5004977-0dd2-4bcd-9292-dd0e05d4c59e/content/images/2026/04/Screenshot-2026-04-06-at-13.37.00.png)

Brand or keyword selector to monitor

After fetching, the dashboard shows four metrics at the top for a quick overview, then splits into three tabs:

```Python
col1, col2, col3, col4 = st.columns(4)
col1.metric("Total Mentions", total_mentions)
col2.metric("News Articles", len(news_records))
col3.metric("YouTube Videos", len(yt_records))
col4.metric("Perspectives", len(persp_records))
```

![](https://storage.ghost.io/c/a5/00/a5004977-0dd2-4bcd-9292-dd0e05d4c59e/content/images/2026/04/Screenshot-2026-04-06-at-13.37.05.png)

Dashboard metrics row displaying total mentions across three sources

### News Tab

The News tab pairs an [Altair](https://altair-viz.github.io/) bar chart of top sources with a sortable table. Altair ships with Streamlit, so there's nothing extra to install. We use it instead of `st.bar_chart` because it gives control over orientation, tooltips, and styling. 

```Python
source_df = news_df["source"].value_counts().head(10).reset_index()
source_df.columns = ["source", "count"]

source_chart = alt.Chart(source_df).mark_bar(
    cornerRadiusTopRight=4, cornerRadiusBottomRight=4
).encode(
    x=alt.X("count:Q", title="Articles"),
    y=alt.Y("source:N", sort="-x", title=""),
    color=alt.value("#4A90D9"),
    tooltip=["source:N", "count:Q"],
).properties(height=350)
st.altair_chart(source_chart, use_container_width=True)
```

![](https://storage.ghost.io/c/a5/00/a5004977-0dd2-4bcd-9292-dd0e05d4c59e/content/images/2026/04/Screenshot-2026-04-06-at-13.40.09.png)

News tab with horizontal bar chart of top sources and sortable article table

The table uses `st.column_config.LinkColumn` so each article title links directly to its source.

### YouTube Tab

The YouTube tab shows views by channel and a sorted video table. The chart groups views by channel to surface which creators talk about the brand the most. 

```Python
channel_df = yt_df.groupby("channel")["views"].sum().reset_index()
channel_df = channel_df.sort_values("views", ascending=False).head(10)

channel_chart = alt.Chart(channel_df).mark_bar(
    cornerRadiusTopRight=4, cornerRadiusBottomRight=4
).encode(
    x=alt.X("views:Q", title="Views", axis=alt.Axis(format="~s")),
    y=alt.Y("channel:N", sort="-x", title=""),
    color=alt.value("#4A90D9"),
    tooltip=["channel:N", alt.Tooltip("views:Q", format=",")],
).properties(height=350)
```

![](https://storage.ghost.io/c/a5/00/a5004977-0dd2-4bcd-9292-dd0e05d4c59e/content/images/2026/04/Screenshot-2026-04-06-at-13.43.40.png)

YouTube tab showing views by channel chart and video table

### Perspectives Tab

The Perspectives tab splits the layout between a discussion table on the left, and a donut chart of mentions by platform on the right. The donut chart makes it easy to see where conversations happen, whether it's LinkedIn, Reddit, X, etc.

```Python
platform_chart = alt.Chart(platform_df).mark_arc(
    innerRadius=60, outerRadius=120
).encode(
    theta=alt.Theta("count:Q"),
    color=alt.Color("source:N", legend=alt.Legend(title="Platform")),
    tooltip=["source:N", "count:Q"],
).properties(height=350)
```

![](https://storage.ghost.io/c/a5/00/a5004977-0dd2-4bcd-9292-dd0e05d4c59e/content/images/2026/04/Screenshot-2026-04-06-at-13.43.49.png)

Perspectives tab with discussions table on the left and donut chart of mentions by platform on the right

## When to use this approach

### Ideal for:

- Tracking brand mentions across news, video, and social in one view
- Monitoring product launches, PR campaigns, or competitor names
- Building internal dashboard for marketing or DevRel teams

### Not recommended for:

- Real-time alerting. The API returns a snapshot, not a stream. For notifications, schedule the script on an interval and compare results.
- Historical analysis. Each engine returns recent results, not a complete archive.

If you want to explore the API response before writing code, the [SerpApi Playground](https://serpapi.com/playground) lets you test any engine interactively. And if you only need news coverage, the [Google News API](https://serpapi.com/google-news-api) alone handles most brand monitoring use cases.

## Where to Go from Here

This dashboard gives you a live snapshot. The natural next step is turning it into a historical record. Store each fetch in a database (SQLite, PostgreSQL, or even a CSV) and you can compare mention volume week over week, track which sources cover your brand consistently, and spot trends that a single snapshot can't show.

With historical data in place, you can layer on more analysis. Identify content gaps by looking at what topics competitors get covered on but you don't. Track which YouTube channels mention your product and how their view counts trend over time. Flag new platforms or authors that start discussing your brand.

The data is yours to work with however fits your needs. The three engines give you the raw material; what you build on top depends on the questions you're trying to answer.

## Conclusion

The full application is about 350 lines in a single Python file. Three API calls, three DataFrames, three tabs. The query input at the top lets you switch brands without changing the code.

What started as a way to check where "serpapi" shows up on the web became a tool that surfaces patterns you miss manually. The Perspectives tab pulls in LinkedIn posts, Reddit threads, and Quora answers that don't appear in regular news or video searches, and combining them in one view gives you the full picture.