🔧 Programmierung 🕛 vor 2 Monaten 17 Min Lesezeit
0

How to Track People Also Ask Results with a SERP API

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht

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:




CODE
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:




CODE
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.






What are People Also Ask results?



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:




CODE
best project management software






might show questions such as:




CODE
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.






Why track PAA results?



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:




CODE
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:




  • content planning

  • SEO research

  • featured snippet research

  • competitor monitoring

  • topic clustering

  • AI agent search context

  • FAQ generation

  • market research



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.






What we will build



We will write a Python script that:




  1. Reads a list of keywords

  2. Calls a SERP API for each keyword

  3. Extracts People Also Ask questions

  4. Normalizes the response

  5. Saves results to CSV

  6. Saves snapshots by date

  7. Compares today's questions with a previous snapshot

  8. Finds new and removed questions



The output will look like this:




CODE
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:




CODE
people_also_ask
related_questions
paa_results
questions






So we will write the parser defensively.






Install dependencies



Create a new folder and install the packages:




CODE
pip install requests python-dotenv pandas






We will use:




CODE
requests → call the API
python-dotenv → load API keys
pandas → save and compare CSV files









Create a .env file



Create a .env file:




CODE
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:




CODE
q
query
engine
location
gl
hl
device






Always check your provider's docs and adjust the request function.






Create a keyword file



Create keywords.txt:




CODE
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.






Step 1: Call the SERP API



Create a file called track_paa.py.




CODE
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.






Step 2: Load keywords



Add a helper to load keywords from a text file.




CODE
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.






Step 3: Find the PAA block



Different APIs may return PAA data under different keys.



Let's support several possible shapes.




CODE
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:




CODE
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.






Step 4: Normalize PAA items



A PAA item may include a question, answer snippet, title, link, and sometimes more fields.



We will normalize each item into one stable format.




CODE
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:




CODE
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.






Step 5: Remove weak rows



Sometimes the API may return an item without a real question.



Skip those.




CODE
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.






Step 6: Deduplicate questions



The same question may appear more than once.



Deduplicate by keyword plus normalized question.




CODE
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.






Step 7: Fetch PAA for one keyword



Now combine the API call and parser.




CODE
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.






Step 8: Fetch all keywords



Add a loop for the full keyword list.




CODE
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.






Step 9: Save a daily snapshot



Now save the results as CSV.




CODE
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:




CODE
paa_snapshot_2026-01-01.csv






That is useful for weekly or monthly comparison.






Full tracking script



Here is the full version.




CODE
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:




CODE
python track_paa.py






You should get a CSV snapshot.






Compare two snapshots



Tracking becomes useful when you compare snapshots.



Let's say you have:




CODE
paa_snapshot_2026-01-01.csv
paa_snapshot_2026-01-08.csv






Create a new file called compare_paa_snapshots.py.




CODE
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:




CODE
python compare_paa_snapshots.py






Now you get:




CODE
paa_added.csv
paa_removed.csv
paa_unchanged.csv






This is where the PAA tracking starts to become useful.






What to do with new PAA questions



New PAA questions can mean several things.



They may show:




CODE
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:




  • FAQ sections

  • article headings

  • comparison page ideas

  • support docs

  • glossary entries

  • video scripts

  • social post topics



For AI teams, they can become:




  • evaluation questions

  • agent test prompts

  • retrieval queries

  • topic expansion data



For SEO teams, they can become:




  • keyword clusters

  • on-page optimization ideas

  • featured snippet targets

  • content refresh signals



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.





Group questions by keyword



You can quickly count how many PAA questions each keyword returns.




CODE
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.






Group questions by words



A simple word count can reveal repeated patterns.




CODE
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:




CODE
pricing
alternative
free
compare
best
tools
api






keep appearing, you know what users are circling around.



Search intent leaves fingerprints.






Add location tracking



People Also Ask can change by location.



If your SERP API supports location parameters, track them.



Modify rows to include location:




CODE
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:




CODE
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.






Save to SQLite instead of CSV



CSV is fine at the beginning.



Once snapshots pile up, use SQLite.




CODE
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:




CODE
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.






A note on SERP API providers



Most SERP API providers can return some form of Google result data, but the response shapes differ.



When testing providers, check:




CODE
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?









Common mistakes






Only testing one keyword



One keyword proves very little.



Test at least 20 to 50 keywords.



Include:




CODE
commercial queries
informational queries
comparison queries
branded queries
local queries
long-tail queries









Treating every PAA question as content-worthy



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.






Ignoring location



If your audience is local or regional, test location-specific SERPs.



PAA can change across countries and cities.






Not saving snapshots



If you only look at today's results, you cannot detect changes.



Save snapshots.



The value is in the history.






Sending PAA data directly to an LLM without cleaning



If you use PAA data for AI workflows, clean it first.



Use a source-aware format:




CODE
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.






Final thoughts



People Also Ask tracking is not complicated.



The basic workflow is:




CODE
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.

Vollständiger Original-Artikel
Den kompletten Beitrag mit allen Details direkt auf dev.to lesen.
↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 0%
🟡 In Evaluierung 0%
🟢 Keine Auswirkung 0%
Spannende Innovation 0%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
2 Quellen
CVE-2024-45058 | portabilis i-educar up to 2.8 Setting educar_usuario_cad.php authorization
1 Quelle
Kompakte 10.000-mAh-Powerbank für weniger als 10 Euro bei Amazon Haul
1 Quelle
Bessere Grafik in Spielen: So steigern Sie die Bildqualität ohne FPS-Verlust
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten How to Track People Also Ask Results with a SERP API

Thematisch verwandte Begriffe: Track, People, Also, Results · 6 Treffer

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...