Skip to content

Repository files navigation

openserp

PyPI versionPython versionsLicense

pip install openserp

Cloud:

importosfromopenserpimportOpenSERPclient=OpenSERP(api_key=os.environ["OPENSERP_API_KEY"])
resp=client.search(engine="google", text="openserp")
print(resp.results[0].title, resp.results[0].url)

Self-hosted:

fromopenserpimportOpenSERPclient=OpenSERP(base_url="http://localhost:7000")
resp=client.search(engine="bing", text="openserp")
print(resp.results[0].title, resp.results[0].url)

Python SDK for the OpenSERP multi-engine SERP API - Google, Bing, Yandex, Baidu, DuckDuckGo, and Ecosia results in a single call. Works against the self-hosted open-source server and against OpenSERP Cloud with the same code.

Use it for AI grounding, RAG pipelines, LLM tool use, agent tool use, LangChain / LlamaIndex integrations, SEO rank tracking, competitor analysis, and search-powered automations. Open-source alternative to SerpAPI, DataForSEO, ScrapingBee, Bright Data SERP, Oxylabs SERP, and Zenserp.

Also available for TypeScript / JavaScript: @openserp/sdk.

Alpha - the API may change before 1.0.0. Pin a version in production.

Contents

Install

pip install openserp

DataFrame export is an optional extra:

pip install "openserp[pandas]"

Requires Python 3.10+.

Why OpenSERP

NeedOpenSERP fit
Local developmentRun the OSS server and use the same SDK surface as Cloud.
AI groundingPull fresh SERP snippets and optional extracted page text for prompts, RAG, or agents.
SEO checksQuery Google, Bing, Yandex, Baidu, DuckDuckGo, and Ecosia with typed models.
Migration pathStart self-hosted, then switch to Cloud by adding OPENSERP_API_KEY.

Compared with hosted-only SERP APIs, OpenSERP keeps the client contract portable. You can test locally without an API key, then use the hosted API when you want managed infrastructure.

Quickstart - OSS (self-hosted)

Run the open-source server locally, no API key required:

docker run -p 7000:7000 karust/openserp serve
fromopenserpimportOpenSERPclient=OpenSERP(base_url="http://localhost:7000")
resp=client.search(
engine="google",
text="openserp",
limit=10,
region="US",
)
print(resp.results[0].title, resp.results[0].url)

If you pass no options, the client defaults to http://localhost:7000.

Quickstart - Cloud

Get an API key from the API keys section in the dashboard. When api_key is set, the SDK defaults base_url to https://api.openserp.org/v1 and sends Authorization: Bearer ... for you.

importosfromopenserpimportOpenSERPclient=OpenSERP(api_key=os.environ["OPENSERP_API_KEY"])
resp=client.search(engine="google", text="openserp")
print(resp.results[0].title)
print(client.last_response.credits) # CreditInfo(used=..., remaining=...)

If both base_url and api_key are set, base_url wins and the key is still sent. Use this for an authenticated self-hosted deployment. Add backend="oss" when you also need OSS-only methods such as stats() or health().

Why two backends?

OpenSERP Cloud uses the same public HTTP contract as the OSS server, with a /v1/ prefix and bearer auth. The same SDK call works on both; you only change base_url / api_key. Start with OSS locally, then move to Cloud when you want the hosted API. See openserp.org/docs/oss-vs-cloud for the full comparison.

Search

single=client.search(engine="bing", text="golang", limit=10, region="US")
mega=client.mega_search(
text="golang",
engines=["google", "bing", "yandex"],
mode="balanced",
limit=20,
)
fast=client.fast_search(text="golang", engines=["google", "bing"])
any_=client.any_search(text="golang", engines=["google", "yandex"])

mega_search aggregates multiple engines. mode is "balanced" (default, merged and deduplicated), "any" (first successful engine wins), or "fast" (engines reordered by recent health). fast_search / any_search are sugar for the matching mode.

To enrich top search results with cleaned page content, pass the extraction flags:

grounded=client.search(
engine="google",
text="openserp docs",
extract=3,
extract_mode="auto",
min_runes=500,
)
print(grounded.results[0].extracted.content)

Extract

page=client.extract(
url="https://openserp.org/docs",
mode="auto",
clean=True,
)
print(page.markdown)

Use min_runes to set the auto-mode escalation floor, clean=False for whole-page readable extraction, and use_llms_txt=True to prefer /llms-full.txt or /llms.txt for site-root URLs. Non-JSON formats are returned as strings:

markdown=client.extract(url="https://openserp.org", format="markdown")

Batch extract

batch_extract takes up to 20 URLs in one request. A URL that fails becomes an item with an error instead of failing the whole call, so one dead link never costs you the other results:

batch=client.batch_extract(
urls=[
"https://openserp.org/docs",
"https://openserp.org/blog",
],
mode="auto",
)
foriteminbatch.results:
ifitem.error:
print(item.url, "failed:", item.error)
else:
print(item.url, item.page_content[:120])

On the hosted API, billing is per URL and matches calling extract that many times - successful extractions bill their mode, failed and empty ones are free.

Regions

Pass region (a two-letter country code) to extract as a visitor from that country - useful for geo-fenced or localized pages. On the hosted API this adds 1 credit per successfully extracted URL:

page=client.extract(url="https://example.com/pricing", region="DE")

Images

images=client.image(engine="bing", text="golang logo", limit=20)
mega_images=client.mega_image(text="golang logo", engines=["bing", "google"])

Async

importasyncio, osfromopenserpimportAsyncOpenSERPasyncdefmain() ->None:
asyncwithAsyncOpenSERP(api_key=os.environ["OPENSERP_API_KEY"]) asclient:
resp=awaitclient.search(engine="google", text="openserp")
print(resp.results[0].title)
asyncio.run(main())

Run hundreds of queries concurrently with a semaphore:

importasynciofromopenserpimportAsyncOpenSERPasyncdefmain() ->None:
sem=asyncio.Semaphore(20)
queries= [f"keyword {i}"foriinrange(500)]
asyncwithAsyncOpenSERP() asclient:
asyncdefrun(query: str):
asyncwithsem:
returnawaitclient.search(engine="google", text=query, limit=10)
responses=awaitasyncio.gather(*(run(q) forqinqueries))
print(len(responses))
asyncio.run(main())

Endpoint availability

OSS-only operational methods raise OssOnlyError when the client is configured for Cloud:

client.parse_google(html="<html>...</html>")
client.stats()
client.health()

Cloud-only account methods raise CloudOnlyError when the client is configured for OSS:

client.me()
client.pricing()
client.engines_status()
client.engines_capabilities()

The backend is inferred from base_url and api_key. Pass backend="oss" or backend="cloud" to the constructor to override.

Telemetry

client.last_response is updated after every HTTP response:

client.last_response.credits# Cloud - CreditInfo(used, remaining)client.last_response.engine_used# both - X-Engine-Usedclient.last_response.fallback_engine# OSS onlyclient.last_response.cache# OSS onlyclient.last_response.headers# raw response headers (lower-cased)

Some self-hosted operational headers are not part of the Cloud response contract, so expect those fields to be None against api.openserp.org. credits is Cloud-specific.

Error handling

fromopenserpimportOpenSERP, RateLimitError, CaptchaError, SERPErrorclient=OpenSERP(api_key="...")
try:
client.search(engine="google", text="openserp")
exceptRateLimitError:
# slow down or queue the request
...
exceptCaptchaError:
# inspect the upstream search failure and retry later
...
exceptSERPErroraserr:
print(err.status, err.code, err.reason, err.request_id)

Retry hook

The SDK does not apply a retry policy. Provide a hook when you want one:

importos, random, timefromopenserpimportOpenSERP, SERPErrorRETRYABLE= {408, 429, 500, 502, 503}
client: OpenSERPdefshould_retry(err: Exception, attempt: int) ->bool:
ifattempt>=3ornotisinstance(err, SERPError) orerr.statusnotinRETRYABLE:
returnFalseheaders=client.last_response.headersifclient.last_responseelse {}
retry_after=float(headers.get("retry-after", 0) or0)
wait=retry_afterormin(2**attempt*0.25, 8.0)
time.sleep(wait+random.random() *0.25)
returnTrueclient=OpenSERP(api_key=os.environ["OPENSERP_API_KEY"], retry=should_retry)
client.search(engine="google", text="openserp")

Use cases

  • AI grounding / RAG - feed top-N results into an LLM prompt (OpenAI, Anthropic, Ollama) for up-to-date answers.
  • LLM tool use - expose client.search as a tool to your agent.
  • SEO monitoring - daily rank tracking across multiple engines and regions, export to a DataFrame or Sheets.
  • Competitor analysis - weekly diff of top-10 results for a keyword set.
  • Data pipelines - stream SERPs to ClickHouse, BigQuery, or a DataFrame for NLP on snippets.

Quick SEO rank report with pandas:

importpandasaspdfromopenserpimportOpenSERPclient=OpenSERP()
keywords= ["openserp", "serp api", "google search api"]
frames= []
forkeywordinkeywords:
resp=client.search(engine="google", text=keyword, region="US", limit=10)
frame=resp.to_pandas()
frame["keyword"] =keywordframes.append(frame)
pd.concat(frames, ignore_index=True).to_csv("rank-report.csv", index=False)

About

Python SDK for the OpenSERP multi-engine SERP API - Google, Bing, Yandex, Baidu, DuckDuckGo, and Ecosia results in a single call

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages