Skip to content

Commit a632179

Browse files
Fix/yield pages extra request (#42)
* fix: prevent additional fetch when SerpResults.yield_pages is used to fetch only 1 page. * chore: improve examples and test search requests
1 parent c6f7f63 commit a632179

7 files changed

Lines changed: 55 additions & 16 deletions

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ import serpapi
196196
client = serpapi.Client(api_key=os.getenv("API_KEY"))
197197
results = client.search({
198198
'engine': 'home_depot',
199-
'q': 'table',
199+
'q': 'chair',
200200
})
201201
```
202202
- API Documentation: [serpapi.com/home-depot-search-api](https://serpapi.com/home-depot-search-api)
@@ -363,7 +363,7 @@ import serpapi
363363
client = serpapi.Client(api_key=os.getenv("API_KEY"))
364364
results = client.search({
365365
'engine': 'google_jobs',
366-
'q': 'coffee',
366+
'q': 'software engineer',
367367
})
368368
```
369369
- API Documentation: [serpapi.com/google-jobs-api](https://serpapi.com/google-jobs-api)
Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
---
22
title: "Google Shopping Price Monitoring"
3-
description: "Filter Google Shopping listings by price and read selected product fields."
3+
description: "Compare US espresso-machine listings within a budget."
44
---
55

66
# Google Shopping Price Monitoring
77

8-
Filter Google Shopping listings by price to compare offers from different merchants. The example searches for espresso machines priced between 100 and 800 in the search's currency.
8+
Search Google Shopping for a De'Longhi Stilosa espresso machine in Austin, Texas, and compare offers priced from $100 to $200. The example filters the returned prices in Python using the numeric `extracted_price` field.
99

1010
## Search Filtered Products
1111

@@ -20,17 +20,17 @@ client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"], timeout=20)
2020

2121
results = client.search(
2222
engine="google_shopping",
23-
q="espresso machine",
23+
q="DeLonghi Stilosa espresso machine",
2424
location="Austin, Texas",
2525
gl="us",
2626
hl="en",
27-
min_price=100,
28-
max_price=800,
29-
sort_by=1,
30-
json_restrictor="shopping_results[].{title, price, source, rating, reviews, link}",
27+
json_restrictor="error, shopping_results[].{title, price, extracted_price, source, rating, reviews, product_link}",
3128
)
3229

33-
for product in results.get("shopping_results", [])[:5]:
30+
for product in results.get("shopping_results", []):
31+
price = product.get("extracted_price")
32+
if price is None or not 100 <= price <= 200:
33+
continue
3434
print(product.get("title"))
3535
print(product.get("price"), product.get("source"))
3636
print(product.get("rating"), product.get("reviews"))
@@ -40,4 +40,4 @@ for product in results.get("shopping_results", [])[:5]:
4040

4141
Read product listings from `shopping_results`. Useful fields include `title`, `product_id`, `price`, `extracted_price`, `source`, `rating`, `reviews`, `thumbnail`, `delivery`, `product_link`, and `serpapi_immersive_product_api`.
4242

43-
Use `min_price`, `max_price`, `sort_by`, `free_shipping`, and `on_sale` to filter and order offers. To fetch more pages, use `serpapi_pagination.next` when it is present. The example limits response fields with `json_restrictor`. Include `serpapi_pagination` in that selector if you need pagination. See the [Google Shopping API documentation](https://serpapi.com/google-shopping-api) for the full parameter set.
43+
The example limits response fields with `json_restrictor` and keeps `error` so API errors remain visible. Use `extracted_price` for numeric comparisons and `price` for display. See the [Google Shopping API documentation](https://serpapi.com/google-shopping-api) for the full parameter set.

docs/examples/home-depot-product-search.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"], timeout=20)
2020

2121
results = client.search(
2222
engine="home_depot",
23-
q="table",
23+
q="chair",
2424
)
2525

2626
for product in results.get("products", [])[:5]:

serpapi/models.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
import json
22

3-
from pprint import pformat
43
from collections import UserDict
54

65
from .textui import prettify_json
7-
from .exceptions import HTTPError
86

97

108
class SerpResults(UserDict):
@@ -71,6 +69,8 @@ def yield_pages(self, max_pages=1_000):
7169
while current_page and current_page_count < max_pages:
7270
yield current_page
7371
current_page_count += 1
72+
if current_page_count >= max_pages:
73+
break
7474
if current_page.next_page_url:
7575
current_page = current_page.next_page()
7676
else:

tests/example_search_google_jobs_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
def test_search_google_jobs(client):
77
data = client.search({
88
'engine': 'google_jobs',
9-
'q': 'coffee',
9+
'q': 'software engineer',
1010
})
1111
assert data.get('error') is None
1212
assert data['jobs_results']

tests/example_search_home_depot_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ def test_search_home_depot(client):
77

88
data = client.search({
99
'engine': 'home_depot',
10-
'q': 'table',
10+
'q': 'chair',
1111
})
1212
assert data.get('error') is None
1313
assert data['products']

tests/test_pagination.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import json
2+
from unittest.mock import Mock
3+
4+
import pytest
5+
import requests
6+
7+
import serpapi
8+
9+
10+
@pytest.mark.parametrize(
11+
("max_pages", "available_pages", "expected_pages"),
12+
[(1, 3, 1), (2, 3, 2), (3, 3, 3), (5, 3, 3), (5, 1, 1), (0, 3, 0)],
13+
)
14+
def test_yield_pages_does_not_request_unused_pages(
15+
max_pages, available_pages, expected_pages
16+
):
17+
responses = []
18+
for page_number in range(1, available_pages + 1):
19+
data = {"search_information": {"page_number": page_number}}
20+
if page_number < available_pages:
21+
data["serpapi_pagination"] = {
22+
"next": f"https://serpapi.com/search?engine=google&q=Coffee&start={page_number * 10}"
23+
}
24+
response = requests.Response()
25+
response.status_code = 200
26+
response.headers["Content-Type"] = "application/json"
27+
response._content = json.dumps(data).encode("utf-8")
28+
responses.append(response)
29+
30+
client = serpapi.Client(api_key="test-api-key")
31+
client.session.request = Mock(side_effect=responses)
32+
results = client.search(engine="google", q="Coffee")
33+
34+
pages = list(results.yield_pages(max_pages=max_pages))
35+
36+
assert [page["search_information"]["page_number"] for page in pages] == list(
37+
range(1, expected_pages + 1)
38+
)
39+
assert client.session.request.call_count == max(1, expected_pages)

0 commit comments

Comments
 (0)