How to Track People Also Ask Results with a SERP API A developer built a Python script that uses a SERP API to track Google's People Also Ask (PAA) results over time. The script fetches PAA questions for a set of keywords, saves snapshots as CSV files, and enables comparison to detect new or disappearing questions. This approach helps SEO professionals and content researchers identify content gaps and shifts in search intent without relying on large SEO platforms. People Also Ask results are easy to ignore. They sit in the middle of Google results, looking like a small accordion of questions. But for SEO, content research, and AI search workflows, they are useful. They show what people are asking around a topic. Not what a keyword tool thinks they might ask. Not what your content calendar guessed three months ago. Actual questions appearing inside search results. If you track them over time, you can see: new questions appearing old questions disappearing competitor topics expanding content gaps question patterns by location changes in search intent In this article, we will build a small Python script that tracks People Also Ask results with a SERP API. The goal is simple: keyword → SERP API → People Also Ask questions → save snapshot → compare changes No giant SEO platform. Just a practical script you can understand, run, and modify. People Also Ask, often shortened to PAA, is the block of related questions that appears on many Google results pages. For example, a query like: best project management software might show questions such as: What is the best project management tool? Which project management software is easiest to use? Is Trello better than Asana? What do small teams use for project management? These questions are useful because they reveal the searcher's next question. That is the good stuff. A keyword tells you what someone typed. People Also Ask tells you what they may want to understand next. A single PAA snapshot is useful. Tracking PAA over time is better. If you collect PAA questions every day or every week, you can answer questions like: Which questions keep appearing? Which new questions appeared this week? Which topics are growing? Which questions should we answer in content? Which questions should an AI assistant handle? Which competitors appear around these questions? This is useful for: PAA data is not a magic oracle. But it is a useful window into search intent. A slightly noisy window, yes, but still better than guessing in a dark room with a spreadsheet candle. We will write a Python script that: The output will look like this: keyword,question,answer snippet,source title,source url,date best project management software,What is the best project management software?,...,example.com,2026-01-01 The exact API response shape depends on your SERP API provider. Common field names include: people also ask related questions paa results questions So we will write the parser defensively. Create a new folder and install the packages: pip install requests python-dotenv pandas We will use: requests → call the API python-dotenv → load API keys pandas → save and compare CSV files Create a .env file: SERP API KEY=your api key SERP API URL=https://your-serp-api-endpoint.example.com/search This article uses a generic SERP API request format. Your provider may use different parameter names. For example, some providers use: q query engine location gl hl device Always check your provider's docs and adjust the request function. Create keywords.txt : best project management software crm software for small business google search api serp api ai search agent Use real keywords from your workflow. Do not only test clean examples. PAA gets interesting when queries are commercial, local, or messy. Create a file called track paa.py . python import os import requests from dotenv import load dotenv load dotenv SERP API KEY = os.getenv "SERP API KEY" SERP API URL = os.getenv "SERP API URL" def fetch serp query, location="United States", language="en" : if not SERP API KEY: raise ValueError "Missing SERP API KEY" if not SERP API URL: raise ValueError "Missing SERP API URL" params = { "api key": SERP API KEY, "engine": "google", "q": query, "location": location, "language": language, "output": "json", } response = requests.get SERP API URL, params=params, timeout=30, response.raise for status return response.json This function returns raw SERP JSON. Keep it separate from the parser. When something breaks, you want to know whether the API call failed or your parser failed. Debugging soup is bad. Debugging soup with no labels is worse. Add a helper to load keywords from a text file. python def load keywords filename="keywords.txt" : with open filename, "r", encoding="utf-8" as file: return line.strip for line in file if line.strip This lets you update keyword lists without touching code. Different APIs may return PAA data under different keys. Let's support several possible shapes. python def get paa items data : possible keys = "people also ask", "related questions", "paa results", "questions", for key in possible keys: value = data.get key if isinstance value, list : return value return This is intentionally simple. You can expand it later if your provider nests PAA inside another object. For example: python def get paa items data : possible keys = "people also ask", "related questions", "paa results", "questions", for key in possible keys: value = data.get key if isinstance value, list : return value serp = data.get "serp", {} for key in possible keys: value = serp.get key if isinstance value, list : return value return Use the version that matches your API response. A PAA item may include a question, answer snippet, title, link, and sometimes more fields. We will normalize each item into one stable format. python def normalize paa item keyword, item, date string : question = item.get "question" or item.get "title" or item.get "query" or "" answer snippet = item.get "snippet" or item.get "answer" or item.get "description" or "" source title = item.get "source title" or item.get "title" or "" source url = item.get "link" or item.get "url" or item.get "source url" or "" return { "date": date string, "keyword": keyword, "question": clean text question , "answer snippet": clean text answer snippet , "source title": clean text source title , "source url": source url, } Add a small text cleaner: python import re def clean text value : if not value: return "" if not isinstance value, str : value = str value value = re.sub r"\s+", " ", value return value.strip This removes weird spacing. Small cleanup now saves annoying CSV goblins later. Sometimes the API may return an item without a real question. Skip those. python def is valid paa row row : if not row "question" : return False if len row "question" < 5: return False return True For content research, the question is the core field. If there is no question, the row is not useful. The same question may appear more than once. Deduplicate by keyword plus normalized question. python def normalize question key question : question = question.lower .strip question = re.sub r" ^\w\s ", "", question question = re.sub r"\s+", " ", question return question def dedupe paa rows rows : seen = set unique rows = for row in rows: key = row "keyword" .lower .strip , normalize question key row "question" , if key in seen: continue seen.add key unique rows.append row return unique rows This makes comparison cleaner. Without dedupe, your snapshot can get noisy fast. Now combine the API call and parser. python from datetime import date def fetch paa for keyword keyword, location="United States", language="en" : date string = date.today .isoformat data = fetch serp query=keyword, location=location, language=language, paa items = get paa items data rows = normalize paa item keyword, item, date string for item in paa items rows = row for row in rows if is valid paa row row return dedupe paa rows rows Now one keyword gives us clean rows. Add a loop for the full keyword list. python import time def fetch all paa keywords, location="United States", language="en", delay=1 : all rows = for keyword in keywords: print f"Fetching PAA for: {keyword}" try: rows = fetch paa for keyword keyword=keyword, location=location, language=language, all rows.extend rows except Exception as exc: print f"Failed keyword: {keyword}" print f"Error: {exc}" time.sleep delay return all rows The delay is there to avoid hitting APIs too aggressively. Respect rate limits. A script that gets rate-limited in five seconds is not automation. It is a tiny cannon with billing attached. Now save the results as CSV. python import pandas as pd def save snapshot rows : today = date.today .isoformat filename = f"paa snapshot {today}.csv" df = pd.DataFrame rows df.to csv filename, index=False print f"Saved snapshot: {filename}" return filename Every time you run the script, you get a file like: paa snapshot 2026-01-01.csv That is useful for weekly or monthly comparison. Here is the full version. python import os import re import time import requests import pandas as pd from datetime import date from dotenv import load dotenv load dotenv SERP API KEY = os.getenv "SERP API KEY" SERP API URL = os.getenv "SERP API URL" def clean text value : if not value: return "" if not isinstance value, str : value = str value value = re.sub r"\s+", " ", value return value.strip def normalize question key question : question = question.lower .strip question = re.sub r" ^\w\s ", "", question question = re.sub r"\s+", " ", question return question def fetch serp query, location="United States", language="en" : if not SERP API KEY: raise ValueError "Missing SERP API KEY" if not SERP API URL: raise ValueError "Missing SERP API URL" params = { "api key": SERP API KEY, "engine": "google", "q": query, "location": location, "language": language, "output": "json", } response = requests.get SERP API URL, params=params, timeout=30, response.raise for status return response.json def load keywords filename="keywords.txt" : with open filename, "r", encoding="utf-8" as file: return line.strip for line in file if line.strip def get paa items data : possible keys = "people also ask", "related questions", "paa results", "questions", for key in possible keys: value = data.get key if isinstance value, list : return value serp = data.get "serp", {} if isinstance serp, dict : for key in possible keys: value = serp.get key if isinstance value, list : return value return def normalize paa item keyword, item, date string : question = item.get "question" or item.get "title" or item.get "query" or "" answer snippet = item.get "snippet" or item.get "answer" or item.get "description" or "" source title = item.get "source title" or item.get "title" or "" source url = item.get "link" or item.get "url" or item.get "source url" or "" return { "date": date string, "keyword": keyword, "question": clean text question , "answer snippet": clean text answer snippet , "source title": clean text source title , "source url": source url, } def is valid paa row row : if not row "question" : return False if len row "question" < 5: return False return True def dedupe paa rows rows : seen = set unique rows = for row in rows: key = row "keyword" .lower .strip , normalize question key row "question" , if key in seen: continue seen.add key unique rows.append row return unique rows def fetch paa for keyword keyword, location="United States", language="en" : date string = date.today .isoformat data = fetch serp query=keyword, location=location, language=language, paa items = get paa items data rows = normalize paa item keyword, item, date string for item in paa items rows = row for row in rows if is valid paa row row return dedupe paa rows rows def fetch all paa keywords, location="United States", language="en", delay=1 : all rows = for keyword in keywords: print f"Fetching PAA for: {keyword}" try: rows = fetch paa for keyword keyword=keyword, location=location, language=language, all rows.extend rows except Exception as exc: print f"Failed keyword: {keyword}" print f"Error: {exc}" time.sleep delay return all rows def save snapshot rows : today = date.today .isoformat filename = f"paa snapshot {today}.csv" df = pd.DataFrame rows df.to csv filename, index=False print f"Saved snapshot: {filename}" return filename def main : keywords = load keywords "keywords.txt" rows = fetch all paa keywords=keywords, location="United States", language="en", delay=1, save snapshot rows print f"Collected {len rows } PAA rows." if name == " main ": main Run it: python track paa.py You should get a CSV snapshot. Tracking becomes useful when you compare snapshots. Let's say you have: paa snapshot 2026-01-01.csv paa snapshot 2026-01-08.csv Create a new file called compare paa snapshots.py . python import re import pandas as pd def normalize question key question : question = str question .lower .strip question = re.sub r" ^\w\s ", "", question question = re.sub r"\s+", " ", question return question def add compare key df : df = df.copy df "question key" = df "question" .apply normalize question key df "compare key" = df "keyword" .str.lower .str.strip + "||" + df "question key" return df def compare snapshots old file, new file : old df = pd.read csv old file new df = pd.read csv new file old df = add compare key old df new df = add compare key new df old keys = set old df "compare key" new keys = set new df "compare key" added keys = new keys - old keys removed keys = old keys - new keys unchanged keys = old keys & new keys added = new df new df "compare key" .isin added keys removed = old df old df "compare key" .isin removed keys unchanged = new df new df "compare key" .isin unchanged keys return added, removed, unchanged def main : old file = "paa snapshot 2026-01-01.csv" new file = "paa snapshot 2026-01-08.csv" added, removed, unchanged = compare snapshots old file, new file added.to csv "paa added.csv", index=False removed.to csv "paa removed.csv", index=False unchanged.to csv "paa unchanged.csv", index=False print f"Added questions: {len added }" print f"Removed questions: {len removed }" print f"Unchanged questions: {len unchanged }" if name == " main ": main Run: python compare paa snapshots.py Now you get: paa added.csv paa removed.csv paa unchanged.csv This is where the PAA tracking starts to become useful. New PAA questions can mean several things. They may show: new search intent new concerns new comparison angles new competitor awareness new product category language new educational gaps For content teams, new PAA questions can become: For AI teams, they can become: For SEO teams, they can become: Not every PAA question deserves content. Some are too broad. Some are repetitive. Some are weird little search mushrooms. But repeated PAA questions are worth watching. You can quickly count how many PAA questions each keyword returns. df = pd.read csv "paa snapshot 2026-01-08.csv" summary = df.groupby "keyword" .size .reset index name="paa count" .sort values "paa count", ascending=False summary.to csv "paa keyword summary.csv", index=False print summary This helps you find which topics have more question depth. A keyword with many PAA results may be good for educational content. A keyword with no PAA results may still matter, but it may be more direct or transactional. A simple word count can reveal repeated patterns. python from collections import Counter def tokenize text : text = str text .lower text = re.sub r" ^\w\s ", "", text stopwords = { "what", "how", "is", "are", "the", "a", "an", "to", "for", "of", "and", "in", "with", "does", } words = word for word in text.split if word not in stopwords and len word 2 return words df = pd.read csv "paa snapshot 2026-01-08.csv" counter = Counter for question in df "question" : counter.update tokenize question print counter.most common 20 This is basic, but useful. If words like: pricing alternative free compare best tools api keep appearing, you know what users are circling around. Search intent leaves fingerprints. People Also Ask can change by location. If your SERP API supports location parameters, track them. Modify rows to include location: python def normalize paa item keyword, item, date string, location : question = item.get "question" or item.get "title" or item.get "query" or "" answer snippet = item.get "snippet" or item.get "answer" or item.get "description" or "" source title = item.get "source title" or item.get "title" or "" source url = item.get "link" or item.get "url" or item.get "source url" or "" return { "date": date string, "location": location, "keyword": keyword, "question": clean text question , "answer snippet": clean text answer snippet , "source title": clean text source title , "source url": source url, } Then your comparison key should include location: df "compare key" = df "location" .str.lower .str.strip + "||" + df "keyword" .str.lower .str.strip + "||" + df "question key" This is useful for local SEO and regional content planning. CSV is fine at the beginning. Once snapshots pile up, use SQLite. python import sqlite3 def save to sqlite rows, database="paa tracking.db" : df = pd.DataFrame rows with sqlite3.connect database as connection: df.to sql "paa results", connection, if exists="append", index=False, Then you can query historical data: SELECT keyword, question, COUNT as appearances FROM paa results GROUP BY keyword, question ORDER BY appearances DESC; This helps find persistent questions. Persistent questions are usually more valuable than one-day surprises. Most SERP API providers can return some form of Google result data, but the response shapes differ. When testing providers, check: Does it return People Also Ask data? What is the field name? Does it include answer snippets? Does it include source URLs? Does location targeting work? Are empty PAA blocks common? Is the response easy to normalize? One keyword proves very little. Test at least 20 to 50 keywords. Include: commercial queries informational queries comparison queries branded queries local queries long-tail queries Some questions are not worth a full article. Some belong in a FAQ section. Some belong inside an existing post. Some are just noise wearing a question mark. If your audience is local or regional, test location-specific SERPs. PAA can change across countries and cities. If you only look at today's results, you cannot detect changes. Save snapshots. The value is in the history. If you use PAA data for AI workflows, clean it first. Use a source-aware format: Question 1 Keyword: serp api Question: What is a SERP API? Source URL: https://example.com Snippet: ... Do not dump raw JSON into the prompt unless you enjoy token confetti. People Also Ask tracking is not complicated. The basic workflow is: keywords → SERP API → PAA questions → clean rows → daily snapshot → compare changes The useful part is not one export. The useful part is watching the questions change over time. New questions can reveal fresh intent. Repeated questions can reveal stable content opportunities. Removed questions can show shifting SERP behavior. For SEO, PAA tracking helps with content planning and search intent research. For AI apps, it can help generate better test prompts and search-grounded answer flows. Start small. Use 20 keywords. Collect once a week. Save the CSV. Compare snapshots. Once the pattern is working, add locations, SQLite, alerts, and dashboards. Search intent moves quietly. PAA tracking gives you a small radar.