Multiprocessing
Parallelize CPU-heavy or isolated SerpApi workloads with ProcessPoolExecutor.
Use multiprocessing when each search has heavy CPU-bound post-processing, when you need process isolation, or when your worker code already runs in a process pool. For simple network-bound searches, threads are usually lighter.
ProcessPoolExecutor Example
import os
import serpapi
from concurrent.futures import ProcessPoolExecutor, as_completed
def search_country(country):
client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"], timeout=20)
results = client.search(
engine="google",
q="best coffee beans",
gl=country,
hl="en",
)
organic_results = results.get("organic_results", [])
return {
"country": country,
"count": len(organic_results),
"first_title": organic_results[0].get("title") if organic_results else None,
}
if __name__ == "__main__":
countries = ["us", "gb", "ca", "au", "in"]
with ProcessPoolExecutor(max_workers=4) as executor:
futures = [executor.submit(search_country, country) for country in countries]
for future in as_completed(futures):
print(future.result())The if __name__ == "__main__" guard is important for multiprocessing, especially on macOS and Windows.
Practical Guidance
- Create the serpapi.Client inside the worker process.
- Return plain serializable data such as dictionaries, lists, strings, and numbers.
- Avoid passing open sessions, response objects, or client instances between processes.
- Set explicit timeouts.
- Prefer threading for simple concurrent HTTP requests unless CPU-heavy work makes multiprocessing worthwhile.
Combining Pagination and Processes
For Google Search, independent start offsets can be distributed across workers:
def search_offset(start):
client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"], timeout=20)
results = client.search(
engine="google",
q="coffee",
location="Austin, Texas",
start=start,
)
return results.get("organic_results", [])Check the relevant engine docs before parallelizing offsets because pagination parameters vary by engine.