website-leads-database 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- website_leads_database/__init__.py +6 -0
- website_leads_database/client.py +112 -0
- website_leads_database/exceptions.py +13 -0
- website_leads_database-0.1.0.dist-info/METADATA +96 -0
- website_leads_database-0.1.0.dist-info/RECORD +8 -0
- website_leads_database-0.1.0.dist-info/WHEEL +5 -0
- website_leads_database-0.1.0.dist-info/licenses/LICENSE +5 -0
- website_leads_database-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"""Python SDK for the hosted Shopify & Ecommerce Store Finder Apify Actor."""
|
|
2
|
+
from .client import WebsiteLeadsDatabaseClient
|
|
3
|
+
from .exceptions import WebsiteLeadsDatabaseError, AuthenticationError, ActorRunError, ActorTimeoutError
|
|
4
|
+
|
|
5
|
+
__version__ = "0.1.0"
|
|
6
|
+
__all__ = ["WebsiteLeadsDatabaseClient", "WebsiteLeadsDatabaseError", "AuthenticationError", "ActorRunError", "ActorTimeoutError"]
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""Thin synchronous client for the Shopify & Ecommerce Store Finder Apify Actor."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import time
|
|
6
|
+
from typing import Any, Mapping
|
|
7
|
+
|
|
8
|
+
import requests
|
|
9
|
+
|
|
10
|
+
from .exceptions import ActorRunError, ActorTimeoutError, AuthenticationError, WebsiteLeadsDatabaseError
|
|
11
|
+
|
|
12
|
+
ACTOR_ID = "apivault_labs~website-leads-database"
|
|
13
|
+
APIFY_API_BASE = "https://api.apify.com/v2"
|
|
14
|
+
TERMINAL_FAIL = {"FAILED", "TIMED-OUT", "ABORTED"}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class WebsiteLeadsDatabaseClient:
|
|
18
|
+
"""Run the hosted Actor and download its Dataset results."""
|
|
19
|
+
|
|
20
|
+
def __init__(self, api_token: str | None = None, timeout: int = 600,
|
|
21
|
+
poll_interval: float = 3.0):
|
|
22
|
+
token = api_token or os.environ.get("APIFY_API_TOKEN")
|
|
23
|
+
if not token:
|
|
24
|
+
raise AuthenticationError(
|
|
25
|
+
"Pass api_token or set APIFY_API_TOKEN. Create a token at "
|
|
26
|
+
"https://console.apify.com/account/integrations"
|
|
27
|
+
)
|
|
28
|
+
self.timeout = int(timeout)
|
|
29
|
+
self.poll_interval = float(poll_interval)
|
|
30
|
+
self.base_url = APIFY_API_BASE
|
|
31
|
+
self.session = requests.Session()
|
|
32
|
+
self.session.headers.update({
|
|
33
|
+
"Authorization": f"Bearer {token}",
|
|
34
|
+
"Content-Type": "application/json",
|
|
35
|
+
"User-Agent": "website-leads-database-python/0.1.0",
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
def run(self, actor_input: Mapping[str, Any], *, actor_timeout_secs: int = 300) -> list[dict[str, Any]]:
|
|
39
|
+
"""Start one Actor run, wait for completion and return clean Dataset items."""
|
|
40
|
+
if not isinstance(actor_input, Mapping):
|
|
41
|
+
raise TypeError("actor_input must be a mapping")
|
|
42
|
+
run_id = self._start(dict(actor_input), actor_timeout_secs)
|
|
43
|
+
run = self._wait(run_id)
|
|
44
|
+
dataset_id = run.get("defaultDatasetId")
|
|
45
|
+
if not dataset_id:
|
|
46
|
+
raise ActorRunError("Successful run did not expose a Dataset ID")
|
|
47
|
+
return self._dataset(dataset_id)
|
|
48
|
+
|
|
49
|
+
def run_one(self, actor_input: Mapping[str, Any], **kwargs: Any) -> dict[str, Any]:
|
|
50
|
+
"""Return the first Dataset row, raising when no result was produced."""
|
|
51
|
+
rows = self.run(actor_input, **kwargs)
|
|
52
|
+
if not rows:
|
|
53
|
+
raise ActorRunError("Actor completed but returned no Dataset rows")
|
|
54
|
+
return rows[0]
|
|
55
|
+
|
|
56
|
+
@staticmethod
|
|
57
|
+
def estimate_cost(result_count: int, price_per_1000: float = 8.0) -> float:
|
|
58
|
+
"""Estimate result charges; actual tier pricing and platform usage may vary."""
|
|
59
|
+
if result_count < 0:
|
|
60
|
+
raise ValueError("result_count cannot be negative")
|
|
61
|
+
return round(result_count * price_per_1000 / 1000, 6)
|
|
62
|
+
|
|
63
|
+
def _start(self, payload: dict[str, Any], timeout_secs: int) -> str:
|
|
64
|
+
try:
|
|
65
|
+
response = self.session.post(
|
|
66
|
+
f"{self.base_url}/acts/{ACTOR_ID}/runs",
|
|
67
|
+
params={"timeout": int(timeout_secs)}, json=payload, timeout=30,
|
|
68
|
+
)
|
|
69
|
+
except requests.RequestException as exc:
|
|
70
|
+
raise WebsiteLeadsDatabaseError(f"Could not start Actor run: {exc}") from exc
|
|
71
|
+
if response.status_code == 401:
|
|
72
|
+
raise AuthenticationError("Apify rejected the API token")
|
|
73
|
+
if response.status_code >= 400:
|
|
74
|
+
raise ActorRunError(f"Run start failed with HTTP {response.status_code}: {response.text[:300]}")
|
|
75
|
+
run_id = (response.json().get("data") or {}).get("id")
|
|
76
|
+
if not run_id:
|
|
77
|
+
raise ActorRunError("Apify response did not contain a run ID")
|
|
78
|
+
return run_id
|
|
79
|
+
|
|
80
|
+
def _wait(self, run_id: str) -> dict[str, Any]:
|
|
81
|
+
deadline = time.monotonic() + self.timeout
|
|
82
|
+
while True:
|
|
83
|
+
try:
|
|
84
|
+
response = self.session.get(f"{self.base_url}/actor-runs/{run_id}", timeout=30)
|
|
85
|
+
except requests.RequestException as exc:
|
|
86
|
+
raise WebsiteLeadsDatabaseError(f"Could not poll Actor run: {exc}") from exc
|
|
87
|
+
if response.status_code >= 400:
|
|
88
|
+
raise ActorRunError(f"Run poll failed with HTTP {response.status_code}: {response.text[:300]}")
|
|
89
|
+
run = response.json().get("data") or {}
|
|
90
|
+
status = run.get("status")
|
|
91
|
+
if status == "SUCCEEDED":
|
|
92
|
+
return run
|
|
93
|
+
if status in TERMINAL_FAIL:
|
|
94
|
+
raise ActorRunError(f"Actor run ended with {status}: {run.get('statusMessage') or 'no details'}")
|
|
95
|
+
if time.monotonic() >= deadline:
|
|
96
|
+
raise ActorTimeoutError(f"Actor run {run_id} did not finish within {self.timeout} seconds")
|
|
97
|
+
time.sleep(self.poll_interval)
|
|
98
|
+
|
|
99
|
+
def _dataset(self, dataset_id: str) -> list[dict[str, Any]]:
|
|
100
|
+
try:
|
|
101
|
+
response = self.session.get(
|
|
102
|
+
f"{self.base_url}/datasets/{dataset_id}/items",
|
|
103
|
+
params={"clean": "true", "format": "json"}, timeout=120,
|
|
104
|
+
)
|
|
105
|
+
except requests.RequestException as exc:
|
|
106
|
+
raise WebsiteLeadsDatabaseError(f"Could not download Dataset: {exc}") from exc
|
|
107
|
+
if response.status_code >= 400:
|
|
108
|
+
raise ActorRunError(f"Dataset fetch failed with HTTP {response.status_code}: {response.text[:300]}")
|
|
109
|
+
data = response.json()
|
|
110
|
+
if not isinstance(data, list):
|
|
111
|
+
raise ActorRunError("Unexpected Dataset response type")
|
|
112
|
+
return data
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Public exception hierarchy for the Shopify & Ecommerce Store Finder SDK."""
|
|
2
|
+
|
|
3
|
+
class WebsiteLeadsDatabaseError(Exception):
|
|
4
|
+
"""Base SDK error."""
|
|
5
|
+
|
|
6
|
+
class AuthenticationError(WebsiteLeadsDatabaseError):
|
|
7
|
+
"""The Apify token is missing or rejected."""
|
|
8
|
+
|
|
9
|
+
class ActorRunError(WebsiteLeadsDatabaseError):
|
|
10
|
+
"""The Actor run or Dataset request failed."""
|
|
11
|
+
|
|
12
|
+
class ActorTimeoutError(WebsiteLeadsDatabaseError):
|
|
13
|
+
"""The client stopped waiting before the Actor completed."""
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: website-leads-database
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for Shopify & Ecommerce Store Finder on Apify. Find ecommerce and business websites by public platform, country, contact and firmographic filters through the hosted Apify Actor.
|
|
5
|
+
Author: ApiVault Labs
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/apivault-labs/website-leads-database-python
|
|
8
|
+
Project-URL: Documentation, https://github.com/apivault-labs/website-leads-database-python#readme
|
|
9
|
+
Project-URL: Apify Actor, https://apify.com/apivault_labs/website-leads-database
|
|
10
|
+
Project-URL: Bug Tracker, https://github.com/apivault-labs/website-leads-database-python/issues
|
|
11
|
+
Keywords: website-leads,shopify-leads,ecommerce-leads,b2b-leads,technology-database,apify,python-sdk
|
|
12
|
+
Requires-Python: >=3.9
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: requests>=2.31.0
|
|
16
|
+
Dynamic: license-file
|
|
17
|
+
|
|
18
|
+
# Shopify & Ecommerce Store Finder — Python SDK
|
|
19
|
+
|
|
20
|
+
Python client for the [Shopify & Ecommerce Store Finder Apify Actor](https://apify.com/apivault_labs/website-leads-database). Send public Actor inputs, wait for the hosted run, and receive clean Dataset rows without maintaining scraping infrastructure.
|
|
21
|
+
|
|
22
|
+
[](https://apify.com/apivault_labs/website-leads-database)
|
|
23
|
+
[](https://www.python.org/)
|
|
24
|
+
[](LICENSE)
|
|
25
|
+
|
|
26
|
+
## Results
|
|
27
|
+
|
|
28
|
+
- Platform and country targeting
|
|
29
|
+
- Public emails, phones and social profiles
|
|
30
|
+
- Firmographic and technology fields
|
|
31
|
+
- Sorting, pagination and count-only queries
|
|
32
|
+
|
|
33
|
+
The repository exposes only the Actor's public input and output contract. Dataset selection and processing remain inside the hosted Actor.
|
|
34
|
+
|
|
35
|
+
## Install
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install git+https://github.com/apivault-labs/website-leads-database-python.git
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Create an Apify token at [Console → Integrations](https://console.apify.com/account/integrations), then:
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from website_leads_database import WebsiteLeadsDatabaseClient
|
|
45
|
+
|
|
46
|
+
client = WebsiteLeadsDatabaseClient(api_token="apify_api_xxxxxx")
|
|
47
|
+
rows = client.run({'platforms': ['shopify_sites', 'woocommerce_sites'],
|
|
48
|
+
'country': ['US'],
|
|
49
|
+
'hasEmail': True,
|
|
50
|
+
'maxItems': 100})
|
|
51
|
+
print(rows[0] if rows else "No results")
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
You can set `APIFY_API_TOKEN` instead of passing the token in code.
|
|
55
|
+
|
|
56
|
+
## Public input options
|
|
57
|
+
|
|
58
|
+
| Field | Type | Default | Description |
|
|
59
|
+
|---|---|---|---|
|
|
60
|
+
| `platforms` | `array` | `["all"]` | Platform datasets to search, such as Shopify, WooCommerce, WordPress, Wix, Magento or all platforms. |
|
|
61
|
+
| `columns` | `array` | `[]` | Exact public output-column names. Leave empty to return all available columns. |
|
|
62
|
+
| `sortBy` | `string` | `` | Optional public column used to order rows before applying the limit. |
|
|
63
|
+
| `country` | `array` | `[]` | ISO-2 country codes such as US, GB or DE. |
|
|
64
|
+
| `keyword` | `string` | `` | Substring matched against the domain or company name. |
|
|
65
|
+
| `phoneCode` | `string` | `` | Keep sites with at least one public phone beginning with this country code. |
|
|
66
|
+
| `filters` | `array` | `[]` | Additional public column conditions using column, operator and value fields. |
|
|
67
|
+
| `hasEmail` | `boolean` | `False` | Return only records with a public email. |
|
|
68
|
+
| `hasPhone` | `boolean` | `False` | Return only records with a public phone. |
|
|
69
|
+
| `sortDesc` | `boolean` | `True` | Return highest sort values first. |
|
|
70
|
+
| `dedupeByDomain` | `boolean` | `False` | Remove repeated root domains within the selected window. |
|
|
71
|
+
| `countOnly` | `boolean` | `False` | Return match counts instead of exporting website rows. |
|
|
72
|
+
| `maxItems` | `integer` | `1000` | Maximum returned rows across selected platforms. |
|
|
73
|
+
| `offset` | `integer` | `0` | Rows to skip for stable pagination across runs. |
|
|
74
|
+
|
|
75
|
+
The complete, versioned schema is also available on the [Actor page](https://apify.com/apivault_labs/website-leads-database).
|
|
76
|
+
|
|
77
|
+
## Pricing
|
|
78
|
+
|
|
79
|
+
Pay per delivered result through Apify, starting around **$8/1,000 results** on paid tiers. Free-plan pricing and platform usage can differ; check the Actor page before large runs.
|
|
80
|
+
|
|
81
|
+
## Examples
|
|
82
|
+
|
|
83
|
+
- `examples/quickstart.py` — first run
|
|
84
|
+
- `examples/bulk_analysis.py` — expand a target list
|
|
85
|
+
- `examples/export_csv.py` — save flat result fields
|
|
86
|
+
- `examples/save_json.py` — preserve nested output
|
|
87
|
+
- `examples/cost_estimate.py` — estimate result-event charges
|
|
88
|
+
- `examples/environment_token.py` — keep credentials out of code
|
|
89
|
+
|
|
90
|
+
## Architecture and privacy
|
|
91
|
+
|
|
92
|
+
This repository is intentionally a thin API client. Collection, retries, analysis and billing run inside the hosted Apify Actor. No private implementation, credentials, scoring weights or infrastructure configuration are included.
|
|
93
|
+
|
|
94
|
+
## License
|
|
95
|
+
|
|
96
|
+
MIT. The hosted Actor is a separate paid service governed by Apify terms.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
website_leads_database/__init__.py,sha256=XfuF8ibIXac7UjwLddLKM1rYnz9Sl_TxbvSAyfG9_C8,384
|
|
2
|
+
website_leads_database/client.py,sha256=RBeB0yk_ntTWjYLalmzKXd4v30YTqI_IbPlsM1PGmVY,5235
|
|
3
|
+
website_leads_database/exceptions.py,sha256=R2X6BNwXS39Hb5_qDDdbisNgnoC5QyBL-zSwmlg5dNY,473
|
|
4
|
+
website_leads_database-0.1.0.dist-info/licenses/LICENSE,sha256=jNIGgy9DNODPurqUF05B8FchIVKrrh7q47guKrLa7Gk,441
|
|
5
|
+
website_leads_database-0.1.0.dist-info/METADATA,sha256=QdUDY7f7RWCcXfpFgOZ8KomkUvhjjThCMDsn7GPxedc,4842
|
|
6
|
+
website_leads_database-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
7
|
+
website_leads_database-0.1.0.dist-info/top_level.txt,sha256=qJx3Nr1i-sgXQUcBuniiFXC0Tj8hJum26NoxYtv7b9s,23
|
|
8
|
+
website_leads_database-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ApiVault Labs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
website_leads_database
|