Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,22 @@ Documentation is [available on Read the Docs](https://serpapi-python.readthedocs

Change history is [available on GitHub](https://github.com/serpapi/serpapi-python/blob/master/HISTORY.md).

### Markdown output for AI agents

For AI agents and LLM workflows, use `output="md"`. It returns search results with clean headings, links, and tables while using roughly half the tokens of JSON on average.

```python
markdown = client.search({
"engine": "google",
"q": "coffee",
"output": "md",
})
```

Markdown works across SerpApi APIs and is returned as a plain Python string. See [Markdown Output for AI Agents](https://serpapi.com/markdown-output.md) for details.

Raw HTML is also available with `output="html"` and is returned as a plain Python string.

## Basic Examples in Python

### Search Bing
Expand Down
6 changes: 3 additions & 3 deletions serpapi/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def __repr__(self):
return "<SerpApi Client>"

def search(self, params: dict = None, **kwargs):
"""Fetch a page of results from SerpApi. Returns a :class:`SerpResults <serpapi.client.SerpResults>` object, or unicode text (*e.g.* if ``'output': 'html'`` was passed).
"""Fetch a page of results from SerpApi. Returns a :class:`SerpResults <serpapi.client.SerpResults>` object for JSON responses, or unicode text for HTML and Markdown responses.

The following three calls are equivalent:

Expand All @@ -56,7 +56,7 @@ def search(self, params: dict = None, **kwargs):

:param q: typically, this is the parameter for the search engine query.
:param engine: the search engine to use. Defaults to ``google``.
:param output: the output format desired (``html`` or ``json``). Defaults to ``json``.
:param output: the output format desired (``html``, ``json``, or ``md``). Defaults to ``json``.
:param api_key: the API Key to use for SerpApi.com.
:param **: any additional parameters to pass to the API.

Expand Down Expand Up @@ -84,7 +84,7 @@ def search_archive(self, params: dict = None, **kwargs):

:param search_id: the Search ID of the search to retrieve from the archive.
:param api_key: the API Key to use for SerpApi.com.
:param output: the output format desired (``html`` or ``json``). Defaults to ``json``.
:param output: the output format desired (``html``, ``json``, or ``md``). Defaults to ``json``.
:param **: any additional parameters to pass to the API.

**Learn more**: https://serpapi.com/search-archive-api
Expand Down
4 changes: 4 additions & 0 deletions serpapi/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ def from_http_response(cls, r, *, client=None):
Otherwise, the raw text (as a properly decoded unicode string) is returned.
"""

content_type = r.headers.get("Content-Type", "").split(";", 1)[0].lower()
if content_type in {"text/html", "text/markdown"}:
return r.text

try:
cls = cls(r.json(), client=client)

Expand Down
52 changes: 52 additions & 0 deletions tests/test_output_formats.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from unittest.mock import Mock

import pytest
import requests

import serpapi


def response(content_type, content):
response = requests.Response()
response.status_code = 200
response.headers["Content-Type"] = f"{content_type}; charset=utf-8"
response._content = content.encode("utf-8")
return response


def test_search_json_returns_serp_results():
client = serpapi.Client(api_key="test-api-key")
client.session.request = Mock(
return_value=response(
"application/json",
'{"search_metadata": {"id": "search-123"}}',
)
)

result = client.search(engine="google", q="Coffee", output="json")

assert isinstance(result, serpapi.SerpResults)
assert result["search_metadata"]["id"] == "search-123"
assert result.client is client
_, request_kwargs = client.session.request.call_args
assert request_kwargs["params"]["output"] == "json"


@pytest.mark.parametrize(
("output", "content_type"),
[
("html", "text/html"),
("md", "text/markdown"),
],
)
def test_search_text_outputs_return_raw_text(output, content_type):
content = '{"looks": "like JSON"}'
client = serpapi.Client(api_key="test-api-key")
client.session.request = Mock(return_value=response(content_type, content))

result = client.search(engine="google", q="Coffee", output=output)

assert result == content
assert isinstance(result, str)
_, request_kwargs = client.session.request.call_args
assert request_kwargs["params"]["output"] == output
Loading