last_edit / server /serper_client.py
Moharek
Deploy Moharek GEO Platform
a74b879
"""Serper.dev client — Google SERP data for keyword ranking & competitor analysis."""
import os
import requests
from typing import List, Dict
SERPER_KEY = os.getenv("SERPER_KEY", "")
BASE_URL = "https://google.serper.dev"
def _key() -> str:
"""Return first available serper key, rotating across SERPER_KEY, SERPER_KEY_2, etc."""
for suffix in ["", "_2", "_3", "_4", "_5"]:
k = os.environ.get(f"SERPER_KEY{suffix}", "").strip()
if k:
return k
return SERPER_KEY
def search(query: str, gl: str = "sa", hl: str = "ar", num: int = 10) -> Dict:
"""Run a Google search via serper.dev. Returns organic results + answerBox."""
key = _key()
if not key:
return {"error": "no_serper_key"}
try:
r = requests.post(
f"{BASE_URL}/search",
headers={"X-API-KEY": key, "Content-Type": "application/json"},
json={"q": query, "gl": gl, "hl": hl, "num": num},
timeout=12,
)
r.raise_for_status()
return r.json()
except Exception as e:
return {"error": str(e)}
def rank_check(site_url: str, keywords: List[str], gl: str = "sa", hl: str = "ar") -> List[Dict]:
"""
For each keyword, search Google and find where site_url appears.
Returns list of {kw, position, url, title, snippet, found}.
"""
domain = site_url.replace("https://", "").replace("http://", "").rstrip("/").split("/")[0]
results = []
for kw in keywords[:10]: # cap at 10 to save credits
data = search(kw, gl=gl, hl=hl, num=10)
organic = data.get("organic", [])
found = None
for item in organic:
link = item.get("link", "")
if domain in link:
found = item
break
results.append({
"kw": kw,
"position": found["position"] if found else None,
"url": found["link"] if found else None,
"title": found["title"] if found else None,
"snippet": found["snippet"] if found else None,
"found": bool(found),
"top_competitor": organic[0] if organic and not found else None,
})
return results
def competitor_serp(site_url: str, keyword: str, gl: str = "sa", hl: str = "ar") -> Dict:
"""
Search for a keyword and return top 10 competitors with their positions.
"""
domain = site_url.replace("https://", "").replace("http://", "").rstrip("/").split("/")[0]
data = search(keyword, gl=gl, hl=hl, num=10)
organic = data.get("organic", [])
competitors = []
site_position = None
for item in organic:
link = item.get("link", "")
is_self = domain in link
if is_self:
site_position = item["position"]
competitors.append({
"position": item.get("position"),
"title": item.get("title"),
"link": link,
"snippet": item.get("snippet", ""),
"is_self": is_self,
})
return {
"keyword": keyword,
"site_position": site_position,
"competitors": competitors,
"answer_box": data.get("answerBox"),
"knowledge_graph": data.get("knowledgeGraph"),
"related_searches": data.get("relatedSearches", []),
}