Background
Marketing teams view SERP data through Notion dashboards (collaboration, views, filters are strong). Sync data from serpbase directly to Notion, 5-minute setup.
1. Setup
Notion Setup
- Create a Notion database
- Fields:
-
Date(Date) -
Keyword(Title) -
Rank(Number) -
URL(URL) -
Title(Text) -
Snippet(Text)
-
- Create Internal Integration: https://www.notion.so/my-integrations
- Copy Integration Token
- Share Notion database with Integration (Share → Add connection)
2. Full Code (50 lines)
import os
import requests
from notion_client import Client
from datetime import datetime
NOTION_TOKEN = os.environ["NOTION_TOKEN"]
DATABASE_ID = "xxx_your_database_id"
notion = Client(auth=NOTION_TOKEN)
def call_serpbase(query, gl="us", num=10):
r = requests.post(
"https://api.serpbase.dev/google/search",
headers={"X-API-Key": os.environ["SERPBASE_KEY"]},
json={"q": query, "gl": gl, "num": num},
timeout=10,
)
r.raise_for_status()
return r.json()
def save_to_notion(query, data):
today = datetime.now().strftime("%Y-%m-%d")
for i, item in enumerate(data.get("organic", [])[:5], 1):
existing = notion.databases.query(
database_id=DATABASE_ID,
filter={
"and": [
{"property": "Date", "date": {"equals": today}},
{"property": "Keyword", "title": {"equals": query}},
{"property": "Rank", "number": {"equals": i}},
]
},
).get("results", [])
properties = {
"Date": {"date": {"start": today}},
"Keyword": {"title": [{"text": {"content": query}}]},
"Rank": {"number": i},
"URL": {"url": item.get("link", "")},
"Title": {"rich_text": [{"text": {"content": item.get("title", "")[:100]}}]},
"Snippet": {"rich_text": [{"text": {"content": item.get("snippet", "")}}]},
}
if existing:
notion.pages.update(page_id=existing[0]["id"], properties=properties)
else:
notion.pages.create(parent={"database_id": DATABASE_ID}, properties=properties)
def main():
queries = [
"SERP API",
"cheap SERP API",
"SERP API selection",
]
for q in queries:
try:
data = call_serpbase(q)
save_to_notion(q, data)
print(f"OK {q}: synced to Notion")
except Exception as e:
print(f"FAIL {q}: {e}")
if __name__ == "__main__":
main()
3. Advanced Features (2)
Feature 1: Daily Report Notion Page
def create_daily_report():
today = datetime.now().strftime("%Y-%m-%d")
blocks = [
{
"object": "block",
"type": "heading_1",
"heading_1": {
"rich_text": [{"type": "text", "text": {"content": f"SERP Daily Report {today}"}}]
}
},
{
"object": "block",
"type": "paragraph",
"paragraph": {
"rich_text": [{"type": "text", "text": {"content": "Today's 50 keyword rank changes"}}]
}
}
]
for kw in queries:
blocks.append({
"object": "block",
"type": "paragraph",
"paragraph": {
"rich_text": [{"type": "text", "text": {"content": f"**{kw}**: data in table below"}}]
}
})
blocks.append({
"object": "block",
"type": "embed",
"embed": {
"url": f"https://www.notion.so/{DATABASE_ID}?v=serp_{today}"
}
})
notion.pages.create(
parent={"page_id": "parent_page_id"},
properties={"title": {"title": [{"text": {"content": f"SERP Daily Report {today}"}}]}},
children=blocks,
)
Feature 2: Notion Views
In Notion database create Views:
- View 1: Top 10 Opportunities (
Rank ≤ 10) - View 2: Top 3 Rank (
Rank ≤ 3) - View 3: Historical Trend (group by keyword, sort by date)
- View 4: Competitor Compare (group by domain)
4. Scheduled Sync
import schedule
schedule.every().day.at("09:00").do(main)
Or GitHub Actions:
on:
schedule:
- cron: '0 9 * * *'
jobs:
sync:
runs-on: ubuntu-latest
steps:
- name: Sync
env:
SERPBASE_KEY: ${{ secrets.SERPBASE_KEY }}
NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }}
DATABASE_ID: ${{ secrets.NOTION_DB_ID }}
run: python sync_to_notion.py
5. 5 Engineering Details
Detail 1: Batch Creation
# Notion API rate limit 3 req/sec
import time
for item in items:
notion.pages.create(...)
time.sleep(0.4) # ~2.5 req/sec
Detail 2: Incremental Update
existing = notion.databases.query(
database_id=DATABASE_ID,
filter={"property": "Keyword", "title": {"equals": query}},
).get("results", [])
if existing:
notion.pages.update(page_id=existing[0]["id"], properties=...)
else:
notion.pages.create(...)
Detail 3: Error Handling
from notion_client.errors import APIResponseError
def safe_create(properties):
try:
return notion.pages.create(...)
except APIResponseError as e:
if e.status == 429:
time.sleep(60) # rate limit
return notion.pages.create(...)
raise
Detail 4: Batch Processing
# 100 queries then pause 1 minute
BATCH_SIZE = 100
for i in range(0, len(queries), BATCH_SIZE):
batch = queries[i:i+BATCH_SIZE]
for q in batch:
sync_one(q)
time.sleep(60)
Detail 5: Notion Database Relations
properties = {
"Date": {"date": {"start": today}},
"Project": {"relation": [{"id": project_page_id}]},
"Rank": {"number": i},
}
6. Real Data (My 1-Month Project)
| Metric | Value |
|---|---|
| Syncs | 30 (1/day) |
| Total records | 1,500 |
| Notion pages | 31 (1 report + 30 data) |
| serpbase monthly cost | $0.45 |
| Engineer maintenance | 0 |
7. vs Airtable
| Dimension | Notion | Airtable |
|---|---|---|
| Free tier | 5 users | 1 base, 1000 rows |
| Database capability | Strong (views, relations) | Stronger (automation) |
| API docs | Good | Good |
| Integrations | Many (API / Webhook) | Many |
| Performance | Slow (network) | Fast |
| Best for | Team collaboration | Complex workflows |
8. 4 Use Cases
Use Case 1: SEO Monitoring
# 50 keywords synced to Notion daily
for kw in 50_keywords:
sync_one(kw)
Use Case 2: Competitor Comparison
# Same keyword, 5 competitors
competitors = ["competitor1.com", "competitor2.com", ...]
for comp in competitors:
sync_competitor_rank(comp, keyword)
Use Case 3: Content Opportunities
# PAA to Notion
for kw in queries:
data = call_serpbase(kw)
for paa in data.get("people_also_ask", []):
sync_paa_to_notion(kw, paa)
Use Case 4: Client Reports
# Weekly client SERP reports
for client in clients:
create_client_serp_report(client)
Summary
serpbase + Notion 5 steps to SERP dashboard:
- Setup Notion database
- Pull SERP data
- Write Notion database
- Incremental update (avoid duplicates)
- Scheduled sync
50 lines of Python + Notion integration + 1 cron job, zero engineer maintenance, monthly cost $0.45.
Suits 5-50 person marketing teams that already use Notion for collaboration.
Top comments (0)