| """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]: |
| 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", []), |
| } |
|
|