Skip to content

Commit 136af05

Browse files
authored
Enrichments (#37)
* add enrichments; version increased * user query structure changed * changed readme
1 parent a05fd72 commit 136af05

3 files changed

Lines changed: 120 additions & 5 deletions

File tree

examples/Businesses.md

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,32 @@ result = client.businesses.search(
6969
]
7070
)
7171

72+
# Search with enrichments (recommended dict format):
73+
result = client.businesses.search(
74+
filters=filters,
75+
limit=25,
76+
fields=['name', 'website', 'phone'],
77+
enrichments={
78+
'contacts_n_leads': {
79+
'contacts_per_company': 3,
80+
'emails_per_contact': 1,
81+
},
82+
'company_insights': {},
83+
},
84+
)
85+
86+
# Search with enrichments (list format):
87+
result = client.businesses.search(
88+
filters=filters,
89+
enrichments=['contacts_n_leads', 'company_insights'],
90+
)
91+
92+
# Search with enrichments (single string format):
93+
result = client.businesses.search(
94+
filters=filters,
95+
enrichments='contacts_n_leads',
96+
)
97+
7298
# Search with dict filters (alternative)
7399
result = client.businesses.search(
74100
filters={
@@ -87,6 +113,13 @@ json = {
87113
'cursor': None,
88114
'include_total': False,
89115
'fields': ['name', 'types', 'address', 'state', 'postal_code', 'country', 'website', 'phone', 'rating', 'reviews', 'photo'],
116+
'enrichments': {
117+
'contacts_n_leads': {
118+
'contacts_per_company': 2,
119+
'emails_per_contact': 1
120+
},
121+
'company_insights': {},
122+
},
90123
'filters': {
91124
'country_code': 'US',
92125
'states': [
@@ -115,7 +148,13 @@ filters = BusinessFilters(country_code='US', states=['NY'], business_statuses=['
115148
for business in client.businesses.iter_search(
116149
filters=filters,
117150
limit=100,
118-
fields=['name', 'phone', 'address', 'rating', 'reviews']
151+
fields=['name', 'phone', 'address', 'rating', 'reviews'],
152+
enrichments={
153+
'contacts_n_leads': {
154+
'contacts_per_company': 2,
155+
'emails_per_contact': 1,
156+
}
157+
},
119158
):
120159
# business is a Business dataclass instance
121160
print(business)

outscraper/businesses.py

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,19 @@
55

66

77
FiltersLike = Union[BusinessFilters, Mapping[str, Any], None]
8+
EnrichmentsLike = Optional[Union[
9+
dict[str, Union[dict[str, Any], None, bool]],
10+
list[str],
11+
str,
12+
]]
813

914

1015
class BusinessesAPI:
1116
def __init__(self, client: OutscraperClient) -> None:
1217
self._client = client
1318

1419
def search(self, *, filters: FiltersLike = None, limit: int = 10, cursor: Optional[str] = None, include_total: bool = False,
15-
fields: Optional[list[str]] = None, query: str = '') -> BusinessSearchResult:
20+
fields: Optional[list[str]] = None, enrichments: EnrichmentsLike = None, query: str = '') -> BusinessSearchResult:
1621
'''
1722
Retrieve business records with optional enrichment data.
1823
@@ -30,6 +35,19 @@ def search(self, *, filters: FiltersLike = None, limit: int = 10, cursor: Option
3035
include_total (bool): Whether to include the total count of matching records in the response. This could increase response time.
3136
Default: False.
3237
fields (list[str] | None): List of fields to include in the response. If not specified, all fields will be returned.
38+
enrichments (dict | list[str] | str | None): Optional enrichments to apply.
39+
Preferred format is dict with per-enrichment params:
40+
{
41+
"contacts_n_leads": {
42+
"contacts_per_company": 3,
43+
"emails_per_contact": 1,
44+
},
45+
"company_insights": {},
46+
}
47+
Backward-compatible formats are also supported:
48+
- ["contacts_n_leads", "company_insights"]
49+
- "contacts_n_leads"
50+
In those forms, each enrichment is sent with empty params.
3351
query (str): natural language search.
3452
3553
Returns:
@@ -57,6 +75,11 @@ def search(self, *, filters: FiltersLike = None, limit: int = 10, cursor: Option
5775
if fields:
5876
payload['fields'] = list(fields)
5977

78+
normalized_enrichments = self._normalize_enrichments(enrichments=enrichments)
79+
80+
if normalized_enrichments:
81+
payload['enrichments'] = normalized_enrichments
82+
6083
if query:
6184
payload['query'] = query
6285

@@ -74,7 +97,8 @@ def search(self, *, filters: FiltersLike = None, limit: int = 10, cursor: Option
7497
)
7598

7699
def iter_search(self, *, filters: FiltersLike = None, limit: int = 10, start_cursor: Optional[str] = None,
77-
include_total: bool = False, fields: Optional[list[str]] = None) -> Iterator[dict]:
100+
include_total: bool = False, fields: Optional[list[str]] = None,
101+
enrichments: EnrichmentsLike = None, query: str = '') -> Iterator[dict]:
78102
'''
79103
Iterate over businesses across all pages (auto-pagination).
80104
@@ -91,6 +115,9 @@ def iter_search(self, *, filters: FiltersLike = None, limit: int = 10, start_cur
91115
include_total (bool): Passed to `search()` (if supported by API).
92116
Default: False.
93117
fields (list[str] | None): Passed to `search()`.
118+
enrichments (dict | list[str] | str | None): Passed to `search()`.
119+
Supports the same formats as `search()`.
120+
query (str): Passed to `search()`.
94121
95122
Yields:
96123
item (dict): Each business record from all pages.
@@ -105,7 +132,9 @@ def iter_search(self, *, filters: FiltersLike = None, limit: int = 10, start_cur
105132
limit=limit,
106133
cursor=cursor,
107134
include_total=include_total,
108-
fields=fields)
135+
fields=fields,
136+
enrichments=enrichments,
137+
query=query)
109138

110139
for item in business_search_result.items:
111140
yield item
@@ -149,3 +178,50 @@ def get(self, business_id: str, *, fields: Optional[list[str]] = None) -> dict:
149178
raise Exception(f'Unexpected response for /businesses/{business_id}: {type(data)}')
150179

151180
return data
181+
182+
def _normalize_enrichments(self, enrichments: EnrichmentsLike = None) -> dict[str, dict[str, Any]]:
183+
normalized_enrichments = {}
184+
185+
if enrichments is None:
186+
return normalized_enrichments
187+
188+
if isinstance(enrichments, str):
189+
if not enrichments:
190+
raise ValueError('enrichment name must be a non-empty string')
191+
normalized_enrichments[enrichments] = {}
192+
193+
elif isinstance(enrichments, dict):
194+
for name, params in enrichments.items():
195+
if not isinstance(name, str) or not name:
196+
raise ValueError('enrichment name must be a non-empty string')
197+
198+
if params is None or params is True:
199+
params = {}
200+
elif params is False:
201+
raise ValueError(f'enrichment "{name}" cannot be False; omit it instead')
202+
203+
if not isinstance(params, dict):
204+
raise ValueError(f'params for enrichment "{name}" must be a dict, None or True')
205+
206+
normalized_enrichments[name] = dict(params)
207+
208+
elif isinstance(enrichments, list):
209+
for name in enrichments:
210+
if not isinstance(name, str) or not name:
211+
raise ValueError('enrichment name must be a non-empty string')
212+
normalized_enrichments[name] = {}
213+
else:
214+
raise ValueError('enrichments must be a dict, list[str], string, or None')
215+
216+
contacts_n_leads = normalized_enrichments.get('contacts_n_leads', {})
217+
if 'contacts_per_company' in contacts_n_leads:
218+
contacts_per_company = contacts_n_leads['contacts_per_company']
219+
if not isinstance(contacts_per_company, int) or contacts_per_company < 1:
220+
raise ValueError('contacts_per_company must be an int >= 1')
221+
222+
if 'emails_per_contact' in contacts_n_leads:
223+
emails_per_contact = contacts_n_leads['emails_per_contact']
224+
if not isinstance(emails_per_contact, int) or emails_per_contact < 1:
225+
raise ValueError('emails_per_contact must be an int >= 1')
226+
227+
return normalized_enrichments

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ def readme():
88

99
setup(
1010
name='outscraper',
11-
version='6.0.2',
11+
version='6.0.3',
1212
description='Python bindings for the Outscraper API',
1313
long_description=readme(),
1414
classifiers = ['Programming Language :: Python',

0 commit comments

Comments
 (0)