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
41 changes: 40 additions & 1 deletion examples/Businesses.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,32 @@ result = client.businesses.search(
]
)

# Search with enrichments (recommended dict format):
result = client.businesses.search(
filters=filters,
limit=25,
fields=['name', 'website', 'phone'],
enrichments={
'contacts_n_leads': {
'contacts_per_company': 3,
'emails_per_contact': 1,
},
'company_insights': {},
},
)

# Search with enrichments (list format):
result = client.businesses.search(
filters=filters,
enrichments=['contacts_n_leads', 'company_insights'],
)

# Search with enrichments (single string format):
result = client.businesses.search(
filters=filters,
enrichments='contacts_n_leads',
)

# Search with dict filters (alternative)
result = client.businesses.search(
filters={
Expand All @@ -87,6 +113,13 @@ json = {
'cursor': None,
'include_total': False,
'fields': ['name', 'types', 'address', 'state', 'postal_code', 'country', 'website', 'phone', 'rating', 'reviews', 'photo'],
'enrichments': {
'contacts_n_leads': {
'contacts_per_company': 2,
'emails_per_contact': 1
},
'company_insights': {},
},
'filters': {
'country_code': 'US',
'states': [
Expand Down Expand Up @@ -115,7 +148,13 @@ filters = BusinessFilters(country_code='US', states=['NY'], business_statuses=['
for business in client.businesses.iter_search(
filters=filters,
limit=100,
fields=['name', 'phone', 'address', 'rating', 'reviews']
fields=['name', 'phone', 'address', 'rating', 'reviews'],
enrichments={
'contacts_n_leads': {
'contacts_per_company': 2,
'emails_per_contact': 1,
}
},
):
# business is a Business dataclass instance
print(business)
Expand Down
82 changes: 79 additions & 3 deletions outscraper/businesses.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,19 @@


FiltersLike = Union[BusinessFilters, Mapping[str, Any], None]
EnrichmentsLike = Optional[Union[
dict[str, Union[dict[str, Any], None, bool]],
list[str],
str,
]]


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

def search(self, *, filters: FiltersLike = None, limit: int = 10, cursor: Optional[str] = None, include_total: bool = False,
fields: Optional[list[str]] = None, query: str = '') -> BusinessSearchResult:
fields: Optional[list[str]] = None, enrichments: EnrichmentsLike = None, query: str = '') -> BusinessSearchResult:
'''
Retrieve business records with optional enrichment data.

Expand All @@ -30,6 +35,19 @@ def search(self, *, filters: FiltersLike = None, limit: int = 10, cursor: Option
include_total (bool): Whether to include the total count of matching records in the response. This could increase response time.
Default: False.
fields (list[str] | None): List of fields to include in the response. If not specified, all fields will be returned.
enrichments (dict | list[str] | str | None): Optional enrichments to apply.
Preferred format is dict with per-enrichment params:
{
"contacts_n_leads": {
"contacts_per_company": 3,
"emails_per_contact": 1,
},
"company_insights": {},
}
Backward-compatible formats are also supported:
- ["contacts_n_leads", "company_insights"]
- "contacts_n_leads"
In those forms, each enrichment is sent with empty params.
query (str): natural language search.

Returns:
Expand Down Expand Up @@ -57,6 +75,11 @@ def search(self, *, filters: FiltersLike = None, limit: int = 10, cursor: Option
if fields:
payload['fields'] = list(fields)

normalized_enrichments = self._normalize_enrichments(enrichments=enrichments)

if normalized_enrichments:
payload['enrichments'] = normalized_enrichments

if query:
payload['query'] = query

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

def iter_search(self, *, filters: FiltersLike = None, limit: int = 10, start_cursor: Optional[str] = None,
include_total: bool = False, fields: Optional[list[str]] = None) -> Iterator[dict]:
include_total: bool = False, fields: Optional[list[str]] = None,
enrichments: EnrichmentsLike = None, query: str = '') -> Iterator[dict]:
'''
Iterate over businesses across all pages (auto-pagination).

Expand All @@ -91,6 +115,9 @@ def iter_search(self, *, filters: FiltersLike = None, limit: int = 10, start_cur
include_total (bool): Passed to `search()` (if supported by API).
Default: False.
fields (list[str] | None): Passed to `search()`.
enrichments (dict | list[str] | str | None): Passed to `search()`.
Supports the same formats as `search()`.
query (str): Passed to `search()`.

Yields:
item (dict): Each business record from all pages.
Expand All @@ -105,7 +132,9 @@ def iter_search(self, *, filters: FiltersLike = None, limit: int = 10, start_cur
limit=limit,
cursor=cursor,
include_total=include_total,
fields=fields)
fields=fields,
enrichments=enrichments,
query=query)

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

return data

def _normalize_enrichments(self, enrichments: EnrichmentsLike = None) -> dict[str, dict[str, Any]]:
normalized_enrichments = {}

if enrichments is None:
return normalized_enrichments

if isinstance(enrichments, str):
if not enrichments:
raise ValueError('enrichment name must be a non-empty string')
normalized_enrichments[enrichments] = {}

elif isinstance(enrichments, dict):
for name, params in enrichments.items():
if not isinstance(name, str) or not name:
raise ValueError('enrichment name must be a non-empty string')

if params is None or params is True:
params = {}
elif params is False:
raise ValueError(f'enrichment "{name}" cannot be False; omit it instead')

if not isinstance(params, dict):
raise ValueError(f'params for enrichment "{name}" must be a dict, None or True')

normalized_enrichments[name] = dict(params)

elif isinstance(enrichments, list):
for name in enrichments:
if not isinstance(name, str) or not name:
raise ValueError('enrichment name must be a non-empty string')
normalized_enrichments[name] = {}
else:
raise ValueError('enrichments must be a dict, list[str], string, or None')

contacts_n_leads = normalized_enrichments.get('contacts_n_leads', {})
if 'contacts_per_company' in contacts_n_leads:
contacts_per_company = contacts_n_leads['contacts_per_company']
if not isinstance(contacts_per_company, int) or contacts_per_company < 1:
raise ValueError('contacts_per_company must be an int >= 1')

if 'emails_per_contact' in contacts_n_leads:
emails_per_contact = contacts_n_leads['emails_per_contact']
if not isinstance(emails_per_contact, int) or emails_per_contact < 1:
raise ValueError('emails_per_contact must be an int >= 1')

return normalized_enrichments
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ def readme():

setup(
name='outscraper',
version='6.0.2',
version='6.0.3',
description='Python bindings for the Outscraper API',
long_description=readme(),
classifiers = ['Programming Language :: Python',
Expand Down