scrapercity 1.0.0__tar.gz
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.
- scrapercity-1.0.0/LICENSE +21 -0
- scrapercity-1.0.0/PKG-INFO +127 -0
- scrapercity-1.0.0/README.md +101 -0
- scrapercity-1.0.0/pyproject.toml +39 -0
- scrapercity-1.0.0/scrapercity/__init__.py +11 -0
- scrapercity-1.0.0/scrapercity/client.py +225 -0
- scrapercity-1.0.0/scrapercity/langchain_tools.py +122 -0
- scrapercity-1.0.0/scrapercity/openai_tools.py +343 -0
- scrapercity-1.0.0/scrapercity.egg-info/PKG-INFO +127 -0
- scrapercity-1.0.0/scrapercity.egg-info/SOURCES.txt +12 -0
- scrapercity-1.0.0/scrapercity.egg-info/dependency_links.txt +1 -0
- scrapercity-1.0.0/scrapercity.egg-info/requires.txt +6 -0
- scrapercity-1.0.0/scrapercity.egg-info/top_level.txt +1 -0
- scrapercity-1.0.0/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ScraperCity
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: scrapercity
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: B2B lead generation for AI agents. 20+ tools: Apollo, Google Maps, email finder, skip trace, and more.
|
|
5
|
+
Author-email: ScraperCity <support@scrapercity.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://scrapercity.com
|
|
8
|
+
Project-URL: Documentation, https://scrapercity.com/agents/AGENT_DOCS.txt
|
|
9
|
+
Project-URL: Repository, https://github.com/scrapercity/scrapercity-python
|
|
10
|
+
Project-URL: Issues, https://github.com/scrapercity/scrapercity-python/issues
|
|
11
|
+
Keywords: leads,apollo,scraping,b2b,email-finder,sales,lead-generation,google-maps,skip-trace,langchain,openai-agents,ai-agent,prospecting,contact-enrichment,email-validation,mcp
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Topic :: Office/Business
|
|
17
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
18
|
+
Requires-Python: >=3.9
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Provides-Extra: langchain
|
|
22
|
+
Requires-Dist: langchain-core>=0.2; extra == "langchain"
|
|
23
|
+
Provides-Extra: openai
|
|
24
|
+
Requires-Dist: openai-agents>=0.1; extra == "openai"
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# ScraperCity Python SDK
|
|
28
|
+
|
|
29
|
+
B2B lead generation for AI agents and Python developers. 20+ tools including Apollo scraping, Google Maps, email finder/validator, mobile lookup, skip trace, ecommerce store data, and more.
|
|
30
|
+
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install scrapercity
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Quick Start
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from scrapercity import ScraperCity
|
|
41
|
+
|
|
42
|
+
sc = ScraperCity("your-api-key")
|
|
43
|
+
|
|
44
|
+
# Check balance
|
|
45
|
+
print(sc.wallet())
|
|
46
|
+
|
|
47
|
+
# Scrape Google Maps
|
|
48
|
+
result = sc.scrape_maps("plumbers", "Denver, CO", limit=200)
|
|
49
|
+
print(result["runId"])
|
|
50
|
+
|
|
51
|
+
# Query lead database (requires $649/mo plan)
|
|
52
|
+
leads = sc.query_leads(title="CEO", state="California", limit=50)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## LangChain / LangGraph
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
from scrapercity import langchain_tools
|
|
59
|
+
from langgraph.prebuilt import create_react_agent
|
|
60
|
+
|
|
61
|
+
tools = langchain_tools(api_key="your-key")
|
|
62
|
+
agent = create_react_agent(llm, tools)
|
|
63
|
+
result = agent.invoke({"messages": [{"role": "user", "content": "Find 50 CEOs in New York"}]})
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## OpenAI Agents SDK
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from agents import Agent
|
|
70
|
+
from scrapercity.openai_tools import get_tools, init
|
|
71
|
+
|
|
72
|
+
init("your-api-key")
|
|
73
|
+
agent = Agent(name="Lead Gen Agent", tools=get_tools())
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## OpenAI Function Calling
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
from scrapercity.openai_tools import get_function_definitions, handle_tool_call, init
|
|
80
|
+
|
|
81
|
+
init("your-api-key")
|
|
82
|
+
tools = get_function_definitions()
|
|
83
|
+
|
|
84
|
+
response = client.chat.completions.create(
|
|
85
|
+
model="gpt-4o",
|
|
86
|
+
messages=messages,
|
|
87
|
+
tools=tools
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# Handle the tool call
|
|
91
|
+
for call in response.choices[0].message.tool_calls:
|
|
92
|
+
result = handle_tool_call(call.function.name, json.loads(call.function.arguments))
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Available Methods
|
|
96
|
+
|
|
97
|
+
| Method | Description | Cost |
|
|
98
|
+
|--------|-------------|------|
|
|
99
|
+
| `scrape_apollo(url, count)` | B2B contacts from Apollo.io | $0.0039/lead |
|
|
100
|
+
| `scrape_maps(query, location)` | Google Maps businesses | $0.01/place |
|
|
101
|
+
| `validate_emails(emails)` | Email deliverability check | $0.0036/email |
|
|
102
|
+
| `find_emails(contacts)` | Email from name + company | $0.05/contact |
|
|
103
|
+
| `find_mobiles(inputs)` | Phone from LinkedIn/email | $0.25/input |
|
|
104
|
+
| `find_people(**params)` | Skip trace by name/email/phone | $0.02/result |
|
|
105
|
+
| `scrape_store_leads(**params)` | Shopify/WooCommerce stores | $0.0039/lead |
|
|
106
|
+
| `scrape_builtwith(technology)` | Sites using a technology | $4.99/search |
|
|
107
|
+
| `search_criminal_records(name)` | Background check | $1.00 if found |
|
|
108
|
+
| `query_leads(**params)` | 4M+ B2B database (instant) | Included ($649 plan) |
|
|
109
|
+
| `scrape_yelp(**params)` | Yelp business listings | varies |
|
|
110
|
+
| `scrape_angi(keyword, zips)` | Angi service providers | varies |
|
|
111
|
+
| `scrape_zillow_agents(location)` | Zillow real estate agents | varies |
|
|
112
|
+
| `scrape_airbnb(**params)` | Airbnb host emails | varies |
|
|
113
|
+
| `scrape_youtube_email(channels)` | YouTube channel emails | varies |
|
|
114
|
+
|
|
115
|
+
## Authentication
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
export SCRAPERCITY_API_KEY="your-key"
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Or pass directly: `ScraperCity(api_key="your-key")`
|
|
122
|
+
|
|
123
|
+
Get your API key at [scrapercity.com](https://scrapercity.com)
|
|
124
|
+
|
|
125
|
+
## License
|
|
126
|
+
|
|
127
|
+
MIT
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# ScraperCity Python SDK
|
|
2
|
+
|
|
3
|
+
B2B lead generation for AI agents and Python developers. 20+ tools including Apollo scraping, Google Maps, email finder/validator, mobile lookup, skip trace, ecommerce store data, and more.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install scrapercity
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from scrapercity import ScraperCity
|
|
15
|
+
|
|
16
|
+
sc = ScraperCity("your-api-key")
|
|
17
|
+
|
|
18
|
+
# Check balance
|
|
19
|
+
print(sc.wallet())
|
|
20
|
+
|
|
21
|
+
# Scrape Google Maps
|
|
22
|
+
result = sc.scrape_maps("plumbers", "Denver, CO", limit=200)
|
|
23
|
+
print(result["runId"])
|
|
24
|
+
|
|
25
|
+
# Query lead database (requires $649/mo plan)
|
|
26
|
+
leads = sc.query_leads(title="CEO", state="California", limit=50)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## LangChain / LangGraph
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
from scrapercity import langchain_tools
|
|
33
|
+
from langgraph.prebuilt import create_react_agent
|
|
34
|
+
|
|
35
|
+
tools = langchain_tools(api_key="your-key")
|
|
36
|
+
agent = create_react_agent(llm, tools)
|
|
37
|
+
result = agent.invoke({"messages": [{"role": "user", "content": "Find 50 CEOs in New York"}]})
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## OpenAI Agents SDK
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from agents import Agent
|
|
44
|
+
from scrapercity.openai_tools import get_tools, init
|
|
45
|
+
|
|
46
|
+
init("your-api-key")
|
|
47
|
+
agent = Agent(name="Lead Gen Agent", tools=get_tools())
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## OpenAI Function Calling
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from scrapercity.openai_tools import get_function_definitions, handle_tool_call, init
|
|
54
|
+
|
|
55
|
+
init("your-api-key")
|
|
56
|
+
tools = get_function_definitions()
|
|
57
|
+
|
|
58
|
+
response = client.chat.completions.create(
|
|
59
|
+
model="gpt-4o",
|
|
60
|
+
messages=messages,
|
|
61
|
+
tools=tools
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# Handle the tool call
|
|
65
|
+
for call in response.choices[0].message.tool_calls:
|
|
66
|
+
result = handle_tool_call(call.function.name, json.loads(call.function.arguments))
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Available Methods
|
|
70
|
+
|
|
71
|
+
| Method | Description | Cost |
|
|
72
|
+
|--------|-------------|------|
|
|
73
|
+
| `scrape_apollo(url, count)` | B2B contacts from Apollo.io | $0.0039/lead |
|
|
74
|
+
| `scrape_maps(query, location)` | Google Maps businesses | $0.01/place |
|
|
75
|
+
| `validate_emails(emails)` | Email deliverability check | $0.0036/email |
|
|
76
|
+
| `find_emails(contacts)` | Email from name + company | $0.05/contact |
|
|
77
|
+
| `find_mobiles(inputs)` | Phone from LinkedIn/email | $0.25/input |
|
|
78
|
+
| `find_people(**params)` | Skip trace by name/email/phone | $0.02/result |
|
|
79
|
+
| `scrape_store_leads(**params)` | Shopify/WooCommerce stores | $0.0039/lead |
|
|
80
|
+
| `scrape_builtwith(technology)` | Sites using a technology | $4.99/search |
|
|
81
|
+
| `search_criminal_records(name)` | Background check | $1.00 if found |
|
|
82
|
+
| `query_leads(**params)` | 4M+ B2B database (instant) | Included ($649 plan) |
|
|
83
|
+
| `scrape_yelp(**params)` | Yelp business listings | varies |
|
|
84
|
+
| `scrape_angi(keyword, zips)` | Angi service providers | varies |
|
|
85
|
+
| `scrape_zillow_agents(location)` | Zillow real estate agents | varies |
|
|
86
|
+
| `scrape_airbnb(**params)` | Airbnb host emails | varies |
|
|
87
|
+
| `scrape_youtube_email(channels)` | YouTube channel emails | varies |
|
|
88
|
+
|
|
89
|
+
## Authentication
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
export SCRAPERCITY_API_KEY="your-key"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Or pass directly: `ScraperCity(api_key="your-key")`
|
|
96
|
+
|
|
97
|
+
Get your API key at [scrapercity.com](https://scrapercity.com)
|
|
98
|
+
|
|
99
|
+
## License
|
|
100
|
+
|
|
101
|
+
MIT
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "scrapercity"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "B2B lead generation for AI agents. 20+ tools: Apollo, Google Maps, email finder, skip trace, and more."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "MIT"}
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
authors = [{name = "ScraperCity", email = "support@scrapercity.com"}]
|
|
13
|
+
keywords = [
|
|
14
|
+
"leads", "apollo", "scraping", "b2b", "email-finder", "sales",
|
|
15
|
+
"lead-generation", "google-maps", "skip-trace", "langchain",
|
|
16
|
+
"openai-agents", "ai-agent", "prospecting", "contact-enrichment",
|
|
17
|
+
"email-validation", "mcp"
|
|
18
|
+
]
|
|
19
|
+
classifiers = [
|
|
20
|
+
"Development Status :: 4 - Beta",
|
|
21
|
+
"Intended Audience :: Developers",
|
|
22
|
+
"License :: OSI Approved :: MIT License",
|
|
23
|
+
"Programming Language :: Python :: 3",
|
|
24
|
+
"Topic :: Office/Business",
|
|
25
|
+
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
Homepage = "https://scrapercity.com"
|
|
30
|
+
Documentation = "https://scrapercity.com/agents/AGENT_DOCS.txt"
|
|
31
|
+
Repository = "https://github.com/scrapercity/scrapercity-python"
|
|
32
|
+
Issues = "https://github.com/scrapercity/scrapercity-python/issues"
|
|
33
|
+
|
|
34
|
+
[project.optional-dependencies]
|
|
35
|
+
langchain = ["langchain-core>=0.2"]
|
|
36
|
+
openai = ["openai-agents>=0.1"]
|
|
37
|
+
|
|
38
|
+
[tool.setuptools.packages.find]
|
|
39
|
+
include = ["scrapercity*"]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""ScraperCity - B2B lead generation for AI agents and Python developers."""
|
|
2
|
+
from scrapercity.client import ScraperCity
|
|
3
|
+
|
|
4
|
+
__version__ = "1.0.0"
|
|
5
|
+
__all__ = ["ScraperCity", "langchain_tools"]
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def langchain_tools(api_key=None):
|
|
9
|
+
"""Lazy import to avoid requiring langchain-core for basic usage."""
|
|
10
|
+
from scrapercity.langchain_tools import get_tools
|
|
11
|
+
return get_tools(api_key)
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"""ScraperCity API client."""
|
|
2
|
+
import os
|
|
3
|
+
import json
|
|
4
|
+
import time
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, Optional
|
|
7
|
+
from urllib.request import Request, urlopen
|
|
8
|
+
from urllib.error import HTTPError
|
|
9
|
+
from urllib.parse import urlencode
|
|
10
|
+
|
|
11
|
+
BASE_URL = "https://app.scrapercity.com"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ScraperCity:
|
|
15
|
+
"""ScraperCity API client for B2B lead generation.
|
|
16
|
+
|
|
17
|
+
Usage:
|
|
18
|
+
from scrapercity import ScraperCity
|
|
19
|
+
sc = ScraperCity("your-api-key")
|
|
20
|
+
result = sc.wallet()
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, api_key: Optional[str] = None):
|
|
24
|
+
self.api_key = api_key or os.environ.get("SCRAPERCITY_API_KEY") or self._load_rc()
|
|
25
|
+
if not self.api_key:
|
|
26
|
+
raise ValueError(
|
|
27
|
+
"No API key. Pass api_key= or set SCRAPERCITY_API_KEY env var. "
|
|
28
|
+
"Get yours at scrapercity.com"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
def _load_rc(self) -> Optional[str]:
|
|
32
|
+
rc = Path.home() / ".scrapercityrc"
|
|
33
|
+
if rc.exists():
|
|
34
|
+
data = json.loads(rc.read_text())
|
|
35
|
+
return data.get("apiKey")
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
def _request(self, method: str, path: str, body: Optional[dict] = None) -> dict:
|
|
39
|
+
url = path if path.startswith("http") else f"{BASE_URL}{path}"
|
|
40
|
+
data = json.dumps(body).encode() if body and method != "GET" else None
|
|
41
|
+
req = Request(url, data=data, method=method)
|
|
42
|
+
req.add_header("Authorization", f"Bearer {self.api_key}")
|
|
43
|
+
req.add_header("Content-Type", "application/json")
|
|
44
|
+
req.add_header("User-Agent", "scrapercity-python/1.0")
|
|
45
|
+
try:
|
|
46
|
+
with urlopen(req) as resp:
|
|
47
|
+
return json.loads(resp.read())
|
|
48
|
+
except HTTPError as e:
|
|
49
|
+
text = e.read().decode()
|
|
50
|
+
try:
|
|
51
|
+
err = json.loads(text)
|
|
52
|
+
except Exception:
|
|
53
|
+
err = {"raw": text}
|
|
54
|
+
raise RuntimeError(err.get("error", err.get("message", f"HTTP {e.code}"))) from e
|
|
55
|
+
|
|
56
|
+
def _get(self, path: str) -> dict:
|
|
57
|
+
return self._request("GET", path)
|
|
58
|
+
|
|
59
|
+
def _post(self, path: str, body: dict) -> dict:
|
|
60
|
+
return self._request("POST", path, body)
|
|
61
|
+
|
|
62
|
+
# ── Wallet & Runs ──────────────────────────────────────────
|
|
63
|
+
def wallet(self) -> dict:
|
|
64
|
+
"""Check account balance, plan, and billing info."""
|
|
65
|
+
return self._get("/api/v1/wallet")
|
|
66
|
+
|
|
67
|
+
def runs(self, hours: int = 24, limit: int = 50) -> dict:
|
|
68
|
+
"""List recent scraper runs."""
|
|
69
|
+
return self._get(f"/api/v1/runs?hours={hours}&limit={limit}")
|
|
70
|
+
|
|
71
|
+
# ── Status / Download / Cancel ─────────────────────────────
|
|
72
|
+
def status(self, run_id: str) -> dict:
|
|
73
|
+
"""Check status of a scraper run."""
|
|
74
|
+
return self._get(f"/api/v1/scrape/status/{run_id}")
|
|
75
|
+
|
|
76
|
+
def cancel(self, run_id: str) -> dict:
|
|
77
|
+
"""Cancel a running scraper job."""
|
|
78
|
+
return self._post(f"/api/v1/scrape/cancel/{run_id}", {})
|
|
79
|
+
|
|
80
|
+
def download(self, run_id: str, output_path: Optional[str] = None) -> dict:
|
|
81
|
+
"""Download CSV results of a completed run."""
|
|
82
|
+
url = f"{BASE_URL}/api/downloads/{run_id}"
|
|
83
|
+
req = Request(url)
|
|
84
|
+
req.add_header("Authorization", f"Bearer {self.api_key}")
|
|
85
|
+
with urlopen(req) as resp:
|
|
86
|
+
data = resp.read()
|
|
87
|
+
out = output_path or f"{run_id}.csv"
|
|
88
|
+
Path(out).write_bytes(data)
|
|
89
|
+
return {"path": out, "bytes": len(data)}
|
|
90
|
+
|
|
91
|
+
# ── Apollo ─────────────────────────────────────────────────
|
|
92
|
+
def scrape_apollo(self, url: str, count: int = 1000, file_name: str = "") -> dict:
|
|
93
|
+
"""Scrape B2B contacts from Apollo.io using a search URL."""
|
|
94
|
+
return self._post("/api/v1/scrape/apollo", {"url": url, "count": count, "fileName": file_name})
|
|
95
|
+
|
|
96
|
+
def scrape_apollo_filters(self, **filters) -> dict:
|
|
97
|
+
"""Scrape Apollo.io using filter parameters."""
|
|
98
|
+
return self._post("/api/v1/scrape/apollo-filters", filters)
|
|
99
|
+
|
|
100
|
+
# ── Maps ───────────────────────────────────────────────────
|
|
101
|
+
def scrape_maps(self, query: str, location: str, limit: int = 500) -> dict:
|
|
102
|
+
"""Scrape businesses from Google Maps."""
|
|
103
|
+
q = [query] if isinstance(query, str) else query
|
|
104
|
+
return self._post("/api/v1/scrape/maps", {
|
|
105
|
+
"searchStringsArray": q,
|
|
106
|
+
"locationQuery": location,
|
|
107
|
+
"maxCrawledPlacesPerSearch": limit
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
# ── Email Validator ────────────────────────────────────────
|
|
111
|
+
def validate_emails(self, emails: list[str]) -> dict:
|
|
112
|
+
"""Validate email addresses for deliverability."""
|
|
113
|
+
return self._post("/api/v1/scrape/email-validator", {"emails": emails})
|
|
114
|
+
|
|
115
|
+
# ── Email Finder ───────────────────────────────────────────
|
|
116
|
+
def find_emails(self, contacts: list[dict], validate: bool = False, mobiles: bool = False) -> dict:
|
|
117
|
+
"""Find business email addresses from name + company."""
|
|
118
|
+
return self._post("/api/v1/scrape/email-finder", {
|
|
119
|
+
"contacts": contacts,
|
|
120
|
+
"autoValidateEmails": validate,
|
|
121
|
+
"autoFindMobiles": mobiles
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
# ── Mobile Finder ──────────────────────────────────────────
|
|
125
|
+
def find_mobiles(self, inputs: list[str]) -> dict:
|
|
126
|
+
"""Find mobile numbers from LinkedIn URLs or work emails."""
|
|
127
|
+
return self._post("/api/v1/scrape/mobile-finder", {"inputs": inputs})
|
|
128
|
+
|
|
129
|
+
# ── People Finder / Skip Trace ─────────────────────────────
|
|
130
|
+
def find_people(self, **params) -> dict:
|
|
131
|
+
"""Skip trace - look up personal info by name, email, phone, or address."""
|
|
132
|
+
return self._post("/api/v1/scrape/people-finder", params)
|
|
133
|
+
|
|
134
|
+
# ── Store Leads (Ecommerce) ────────────────────────────────
|
|
135
|
+
def scrape_store_leads(self, **params) -> dict:
|
|
136
|
+
"""Get ecommerce store data (Shopify, WooCommerce, etc)."""
|
|
137
|
+
return self._post("/api/v1/scrape/store-leads", params)
|
|
138
|
+
|
|
139
|
+
# ── BuiltWith ──────────────────────────────────────────────
|
|
140
|
+
def scrape_builtwith(self, technology: str, file_name: str = "") -> dict:
|
|
141
|
+
"""Find websites using a specific technology."""
|
|
142
|
+
return self._post("/api/v1/scrape/builtwith", {"technology": technology, "fileName": file_name})
|
|
143
|
+
|
|
144
|
+
# ── Criminal Records ───────────────────────────────────────
|
|
145
|
+
def search_criminal_records(self, name: str, state: str = "", dob: str = "") -> dict:
|
|
146
|
+
"""Search criminal records by name."""
|
|
147
|
+
return self._post("/api/v1/scrape/criminal-records", {"name": name, "state": state, "dob": dob})
|
|
148
|
+
|
|
149
|
+
# ── Airbnb ─────────────────────────────────────────────────
|
|
150
|
+
def scrape_airbnb(self, **params) -> dict:
|
|
151
|
+
"""Scrape Airbnb host emails by city or listing URL."""
|
|
152
|
+
return self._post("/api/v1/scrape/airbnb-email", params)
|
|
153
|
+
|
|
154
|
+
# ── YouTube Email ──────────────────────────────────────────
|
|
155
|
+
def scrape_youtube_email(self, channels: list[str]) -> dict:
|
|
156
|
+
"""Find business emails for YouTube channels."""
|
|
157
|
+
return self._post("/api/v1/scrape/youtube-email", {"channels": channels})
|
|
158
|
+
|
|
159
|
+
# ── Website Finder ─────────────────────────────────────────
|
|
160
|
+
def scrape_website_finder(self, domains: list[str], job_title: str = "") -> dict:
|
|
161
|
+
"""Find contact info from website domains."""
|
|
162
|
+
return self._post("/api/v1/scrape/website-finder", {"domains": domains, "jobTitle": job_title})
|
|
163
|
+
|
|
164
|
+
# ── Yelp ───────────────────────────────────────────────────
|
|
165
|
+
def scrape_yelp(self, **params) -> dict:
|
|
166
|
+
"""Scrape business listings from Yelp."""
|
|
167
|
+
return self._post("/api/v1/scrape/yelp-scraper", params)
|
|
168
|
+
|
|
169
|
+
# ── Angi ───────────────────────────────────────────────────
|
|
170
|
+
def scrape_angi(self, keyword: str, zip_codes: list[str] = None, max_items: int = 100) -> dict:
|
|
171
|
+
"""Scrape from Angi (Angie's List)."""
|
|
172
|
+
return self._post("/api/v1/scrape/angi-angies-list-scraper", {
|
|
173
|
+
"keyword": keyword, "zipCodes": zip_codes or [], "maxItems": max_items
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
# ── Zillow Agents ──────────────────────────────────────────
|
|
177
|
+
def scrape_zillow_agents(self, location: str, **opts) -> dict:
|
|
178
|
+
"""Scrape real estate agents from Zillow."""
|
|
179
|
+
return self._post("/api/v1/scrape/zillow-agents", {"location": location, **opts})
|
|
180
|
+
|
|
181
|
+
# ── BizBuySell ─────────────────────────────────────────────
|
|
182
|
+
def scrape_bizbuysell(self, start_urls: list[str], max_items: int = 100) -> dict:
|
|
183
|
+
"""Scrape business-for-sale listings."""
|
|
184
|
+
return self._post("/api/v1/scrape/bizbuysell-scraper", {"startUrls": start_urls, "maxItems": max_items})
|
|
185
|
+
|
|
186
|
+
# ── Crexi ──────────────────────────────────────────────────
|
|
187
|
+
def scrape_crexi(self, start_urls: list[str]) -> dict:
|
|
188
|
+
"""Scrape commercial real estate from Crexi."""
|
|
189
|
+
return self._post("/api/v1/scrape/crexi-scraper", {"startUrls": start_urls})
|
|
190
|
+
|
|
191
|
+
# ── Property Lookup ────────────────────────────────────────
|
|
192
|
+
def scrape_property_lookup(self, addresses: list[str], include_owner: bool = False) -> dict:
|
|
193
|
+
"""Look up property data by address."""
|
|
194
|
+
return self._post("/api/v1/scrape/property-lookup", {
|
|
195
|
+
"addresses": addresses, "includeOwnerContact": include_owner
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
# ── Lead Database ($649 plan) ──────────────────────────────
|
|
199
|
+
def query_leads(self, **params) -> dict:
|
|
200
|
+
"""Query 4M+ B2B contacts instantly."""
|
|
201
|
+
qs = urlencode({k: v for k, v in params.items() if v}, doseq=True)
|
|
202
|
+
return self._get(f"/api/v1/database/leads?{qs}")
|
|
203
|
+
|
|
204
|
+
def query_local_businesses(self, **params) -> dict:
|
|
205
|
+
"""Query local business database."""
|
|
206
|
+
qs = urlencode({k: v for k, v in params.items() if v}, doseq=True)
|
|
207
|
+
return self._get(f"/api/v1/database/local-businesses?{qs}")
|
|
208
|
+
|
|
209
|
+
def query_ecommerce(self, **params) -> dict:
|
|
210
|
+
"""Query ecommerce store database."""
|
|
211
|
+
qs = urlencode({k: v for k, v in params.items() if v}, doseq=True)
|
|
212
|
+
return self._get(f"/api/v1/database/ecommerce?{qs}")
|
|
213
|
+
|
|
214
|
+
# ── Poll ───────────────────────────────────────────────────
|
|
215
|
+
def poll_until_done(self, run_id: str, interval: int = 15, timeout: int = 3600) -> dict:
|
|
216
|
+
"""Poll a run until it completes. Returns final status."""
|
|
217
|
+
start = time.time()
|
|
218
|
+
while time.time() - start < timeout:
|
|
219
|
+
s = self.status(run_id)
|
|
220
|
+
if s.get("status") == "SUCCEEDED":
|
|
221
|
+
return s
|
|
222
|
+
if s.get("status") in ("FAILED", "CANCELLED"):
|
|
223
|
+
raise RuntimeError(f"Run {run_id} ended: {s.get('status')} - {s.get('statusMessage', '')}")
|
|
224
|
+
time.sleep(interval)
|
|
225
|
+
raise TimeoutError(f"Timed out after {timeout}s waiting for {run_id}")
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""LangChain tool wrappers for ScraperCity.
|
|
2
|
+
|
|
3
|
+
Usage with LangChain:
|
|
4
|
+
from scrapercity import langchain_tools
|
|
5
|
+
tools = langchain_tools()
|
|
6
|
+
# Use with any LangChain agent
|
|
7
|
+
|
|
8
|
+
Usage with LangGraph:
|
|
9
|
+
from scrapercity import langchain_tools
|
|
10
|
+
from langgraph.prebuilt import create_react_agent
|
|
11
|
+
tools = langchain_tools()
|
|
12
|
+
agent = create_react_agent(llm, tools)
|
|
13
|
+
"""
|
|
14
|
+
import json
|
|
15
|
+
from typing import Optional
|
|
16
|
+
|
|
17
|
+
from scrapercity.client import ScraperCity
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_tools(api_key: Optional[str] = None) -> list:
|
|
21
|
+
"""Return a list of LangChain tools for ScraperCity.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
api_key: ScraperCity API key. Falls back to SCRAPERCITY_API_KEY env var.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
List of LangChain tools ready to use with any agent.
|
|
28
|
+
"""
|
|
29
|
+
try:
|
|
30
|
+
from langchain_core.tools import tool as langchain_tool
|
|
31
|
+
except ImportError:
|
|
32
|
+
raise ImportError("Install langchain-core: pip install scrapercity[langchain]")
|
|
33
|
+
|
|
34
|
+
sc = ScraperCity(api_key)
|
|
35
|
+
|
|
36
|
+
@langchain_tool
|
|
37
|
+
def check_wallet() -> str:
|
|
38
|
+
"""Check ScraperCity account balance, plan, and billing info. Call this FIRST to verify credits."""
|
|
39
|
+
return json.dumps(sc.wallet(), indent=2)
|
|
40
|
+
|
|
41
|
+
@langchain_tool
|
|
42
|
+
def list_runs(hours: int = 24, limit: int = 20) -> str:
|
|
43
|
+
"""List recent ScraperCity scraper runs with status, lead counts, and costs."""
|
|
44
|
+
return json.dumps(sc.runs(hours, limit), indent=2)
|
|
45
|
+
|
|
46
|
+
@langchain_tool
|
|
47
|
+
def scrape_apollo(url: str, count: int = 1000) -> str:
|
|
48
|
+
"""Scrape B2B contacts from Apollo.io using a search URL. Delivery takes up to 4 days."""
|
|
49
|
+
return json.dumps(sc.scrape_apollo(url, count), indent=2)
|
|
50
|
+
|
|
51
|
+
@langchain_tool
|
|
52
|
+
def scrape_maps(query: str, location: str, limit: int = 500) -> str:
|
|
53
|
+
"""Scrape businesses from Google Maps. Returns names, addresses, phones, emails, ratings."""
|
|
54
|
+
return json.dumps(sc.scrape_maps(query, location, limit), indent=2)
|
|
55
|
+
|
|
56
|
+
@langchain_tool
|
|
57
|
+
def validate_emails(emails: list[str]) -> str:
|
|
58
|
+
"""Validate email addresses. Returns deliverability status, catch-all detection, MX records."""
|
|
59
|
+
return json.dumps(sc.validate_emails(emails), indent=2)
|
|
60
|
+
|
|
61
|
+
@langchain_tool
|
|
62
|
+
def find_emails(contacts: list[dict]) -> str:
|
|
63
|
+
"""Find business email addresses from name + company. Each contact needs name AND company/domain."""
|
|
64
|
+
return json.dumps(sc.find_emails(contacts), indent=2)
|
|
65
|
+
|
|
66
|
+
@langchain_tool
|
|
67
|
+
def find_mobiles(inputs: list[str]) -> str:
|
|
68
|
+
"""Find mobile phone numbers from LinkedIn URLs or work emails."""
|
|
69
|
+
return json.dumps(sc.find_mobiles(inputs), indent=2)
|
|
70
|
+
|
|
71
|
+
@langchain_tool
|
|
72
|
+
def find_people(name: list[str] = None, email: list[str] = None, phone_number: list[str] = None) -> str:
|
|
73
|
+
"""Skip trace / people finder. Look up personal info by name, email, or phone."""
|
|
74
|
+
params = {}
|
|
75
|
+
if name:
|
|
76
|
+
params["name"] = name
|
|
77
|
+
if email:
|
|
78
|
+
params["email"] = email
|
|
79
|
+
if phone_number:
|
|
80
|
+
params["phone_number"] = phone_number
|
|
81
|
+
return json.dumps(sc.find_people(**params), indent=2)
|
|
82
|
+
|
|
83
|
+
@langchain_tool
|
|
84
|
+
def scrape_store_leads(platform: str = "shopify", country_code: str = "", total_leads: int = 1000) -> str:
|
|
85
|
+
"""Get ecommerce store data (Shopify, WooCommerce, etc). Instant results from cached database."""
|
|
86
|
+
return json.dumps(sc.scrape_store_leads(platform=platform, countryCode=country_code, totalLeads=total_leads), indent=2)
|
|
87
|
+
|
|
88
|
+
@langchain_tool
|
|
89
|
+
def query_lead_database(title: str = "", industry: str = "", state: str = "", city: str = "", limit: int = 50) -> str:
|
|
90
|
+
"""Query 4M+ B2B contacts instantly by title, industry, and location. Requires $649/mo plan."""
|
|
91
|
+
return json.dumps(sc.query_leads(title=title, industry=industry, state=state, city=city, limit=limit), indent=2)
|
|
92
|
+
|
|
93
|
+
@langchain_tool
|
|
94
|
+
def check_run_status(run_id: str) -> str:
|
|
95
|
+
"""Check status of a scraper run. Returns RUNNING/SUCCEEDED/FAILED and download URL when done."""
|
|
96
|
+
return json.dumps(sc.status(run_id), indent=2)
|
|
97
|
+
|
|
98
|
+
@langchain_tool
|
|
99
|
+
def download_results(run_id: str, output_path: str = "") -> str:
|
|
100
|
+
"""Download CSV results of a completed scraper run."""
|
|
101
|
+
return json.dumps(sc.download(run_id, output_path or None), indent=2)
|
|
102
|
+
|
|
103
|
+
@langchain_tool
|
|
104
|
+
def cancel_run(run_id: str) -> str:
|
|
105
|
+
"""Cancel a running scraper job."""
|
|
106
|
+
return json.dumps(sc.cancel(run_id), indent=2)
|
|
107
|
+
|
|
108
|
+
return [
|
|
109
|
+
check_wallet,
|
|
110
|
+
list_runs,
|
|
111
|
+
scrape_apollo,
|
|
112
|
+
scrape_maps,
|
|
113
|
+
validate_emails,
|
|
114
|
+
find_emails,
|
|
115
|
+
find_mobiles,
|
|
116
|
+
find_people,
|
|
117
|
+
scrape_store_leads,
|
|
118
|
+
query_lead_database,
|
|
119
|
+
check_run_status,
|
|
120
|
+
download_results,
|
|
121
|
+
cancel_run,
|
|
122
|
+
]
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
"""OpenAI Agents SDK tool definitions for ScraperCity.
|
|
2
|
+
|
|
3
|
+
Usage with OpenAI Agents SDK:
|
|
4
|
+
from scrapercity.openai_tools import get_tools
|
|
5
|
+
from agents import Agent
|
|
6
|
+
|
|
7
|
+
tools = get_tools()
|
|
8
|
+
agent = Agent(name="Lead Gen Agent", tools=tools)
|
|
9
|
+
|
|
10
|
+
Usage with OpenAI function calling:
|
|
11
|
+
from scrapercity.openai_tools import get_function_definitions, handle_tool_call
|
|
12
|
+
functions = get_function_definitions()
|
|
13
|
+
# Pass to chat.completions.create(tools=functions)
|
|
14
|
+
# Then: result = handle_tool_call(function_name, arguments)
|
|
15
|
+
"""
|
|
16
|
+
import json
|
|
17
|
+
from typing import Optional
|
|
18
|
+
|
|
19
|
+
from scrapercity.client import ScraperCity
|
|
20
|
+
|
|
21
|
+
_client: Optional[ScraperCity] = None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _get_client() -> ScraperCity:
|
|
25
|
+
global _client
|
|
26
|
+
if _client is None:
|
|
27
|
+
_client = ScraperCity()
|
|
28
|
+
return _client
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def init(api_key: Optional[str] = None):
|
|
32
|
+
"""Initialize the ScraperCity client. Call before using tools."""
|
|
33
|
+
global _client
|
|
34
|
+
_client = ScraperCity(api_key)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# ── Tool handler functions ─────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
def _check_wallet(**kwargs) -> str:
|
|
40
|
+
return json.dumps(_get_client().wallet(), indent=2)
|
|
41
|
+
|
|
42
|
+
def _list_runs(hours: int = 24, limit: int = 20, **kwargs) -> str:
|
|
43
|
+
return json.dumps(_get_client().runs(hours, limit), indent=2)
|
|
44
|
+
|
|
45
|
+
def _scrape_apollo(url: str, count: int = 1000, **kwargs) -> str:
|
|
46
|
+
return json.dumps(_get_client().scrape_apollo(url, count), indent=2)
|
|
47
|
+
|
|
48
|
+
def _scrape_maps(query: str, location: str, limit: int = 500, **kwargs) -> str:
|
|
49
|
+
return json.dumps(_get_client().scrape_maps(query, location, limit), indent=2)
|
|
50
|
+
|
|
51
|
+
def _validate_emails(emails: list, **kwargs) -> str:
|
|
52
|
+
return json.dumps(_get_client().validate_emails(emails), indent=2)
|
|
53
|
+
|
|
54
|
+
def _find_emails(contacts: list, **kwargs) -> str:
|
|
55
|
+
return json.dumps(_get_client().find_emails(contacts), indent=2)
|
|
56
|
+
|
|
57
|
+
def _find_mobiles(inputs: list, **kwargs) -> str:
|
|
58
|
+
return json.dumps(_get_client().find_mobiles(inputs), indent=2)
|
|
59
|
+
|
|
60
|
+
def _find_people(**params) -> str:
|
|
61
|
+
return json.dumps(_get_client().find_people(**params), indent=2)
|
|
62
|
+
|
|
63
|
+
def _scrape_store_leads(platform: str = "shopify", country_code: str = "", total_leads: int = 1000, **kwargs) -> str:
|
|
64
|
+
return json.dumps(_get_client().scrape_store_leads(platform=platform, countryCode=country_code, totalLeads=total_leads), indent=2)
|
|
65
|
+
|
|
66
|
+
def _query_lead_database(title: str = "", industry: str = "", state: str = "", city: str = "", limit: int = 50, **kwargs) -> str:
|
|
67
|
+
return json.dumps(_get_client().query_leads(title=title, industry=industry, state=state, city=city, limit=limit), indent=2)
|
|
68
|
+
|
|
69
|
+
def _check_run_status(run_id: str, **kwargs) -> str:
|
|
70
|
+
return json.dumps(_get_client().status(run_id), indent=2)
|
|
71
|
+
|
|
72
|
+
def _download_results(run_id: str, output_path: str = "", **kwargs) -> str:
|
|
73
|
+
return json.dumps(_get_client().download(run_id, output_path or None), indent=2)
|
|
74
|
+
|
|
75
|
+
def _cancel_run(run_id: str, **kwargs) -> str:
|
|
76
|
+
return json.dumps(_get_client().cancel(run_id), indent=2)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
_HANDLERS = {
|
|
80
|
+
"check_wallet": _check_wallet,
|
|
81
|
+
"list_runs": _list_runs,
|
|
82
|
+
"scrape_apollo": _scrape_apollo,
|
|
83
|
+
"scrape_maps": _scrape_maps,
|
|
84
|
+
"validate_emails": _validate_emails,
|
|
85
|
+
"find_emails": _find_emails,
|
|
86
|
+
"find_mobiles": _find_mobiles,
|
|
87
|
+
"find_people": _find_people,
|
|
88
|
+
"scrape_store_leads": _scrape_store_leads,
|
|
89
|
+
"query_lead_database": _query_lead_database,
|
|
90
|
+
"check_run_status": _check_run_status,
|
|
91
|
+
"download_results": _download_results,
|
|
92
|
+
"cancel_run": _cancel_run,
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def handle_tool_call(function_name: str, arguments: dict) -> str:
|
|
97
|
+
"""Handle a tool call from OpenAI function calling.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
function_name: The function name from the model's tool call.
|
|
101
|
+
arguments: The parsed arguments dict.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
JSON string result.
|
|
105
|
+
"""
|
|
106
|
+
handler = _HANDLERS.get(function_name)
|
|
107
|
+
if not handler:
|
|
108
|
+
return json.dumps({"error": f"Unknown function: {function_name}"})
|
|
109
|
+
return handler(**arguments)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def get_function_definitions() -> list[dict]:
|
|
113
|
+
"""Return OpenAI function calling tool definitions.
|
|
114
|
+
|
|
115
|
+
Use with: openai.chat.completions.create(tools=get_function_definitions())
|
|
116
|
+
"""
|
|
117
|
+
return [
|
|
118
|
+
{
|
|
119
|
+
"type": "function",
|
|
120
|
+
"function": {
|
|
121
|
+
"name": "check_wallet",
|
|
122
|
+
"description": "Check ScraperCity account balance, plan, and billing info. Call FIRST to verify credits.",
|
|
123
|
+
"parameters": {"type": "object", "properties": {}}
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"type": "function",
|
|
128
|
+
"function": {
|
|
129
|
+
"name": "list_runs",
|
|
130
|
+
"description": "List recent scraper runs with status, lead counts, and costs.",
|
|
131
|
+
"parameters": {
|
|
132
|
+
"type": "object",
|
|
133
|
+
"properties": {
|
|
134
|
+
"hours": {"type": "integer", "description": "Hours back to look (default 24)", "default": 24},
|
|
135
|
+
"limit": {"type": "integer", "description": "Max runs to return (default 20)", "default": 20}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"type": "function",
|
|
142
|
+
"function": {
|
|
143
|
+
"name": "scrape_apollo",
|
|
144
|
+
"description": "Scrape B2B contacts from Apollo.io using a search URL. Delivery takes up to 4 days.",
|
|
145
|
+
"parameters": {
|
|
146
|
+
"type": "object",
|
|
147
|
+
"properties": {
|
|
148
|
+
"url": {"type": "string", "description": "Full Apollo.io search URL"},
|
|
149
|
+
"count": {"type": "integer", "description": "Number of leads (min 500, max 50000)", "default": 1000}
|
|
150
|
+
},
|
|
151
|
+
"required": ["url"]
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
"type": "function",
|
|
157
|
+
"function": {
|
|
158
|
+
"name": "scrape_maps",
|
|
159
|
+
"description": "Scrape businesses from Google Maps with phones, emails, ratings.",
|
|
160
|
+
"parameters": {
|
|
161
|
+
"type": "object",
|
|
162
|
+
"properties": {
|
|
163
|
+
"query": {"type": "string", "description": "Search keyword e.g. 'plumbers'"},
|
|
164
|
+
"location": {"type": "string", "description": "City/area e.g. 'Denver, CO'"},
|
|
165
|
+
"limit": {"type": "integer", "description": "Max places (default 500)", "default": 500}
|
|
166
|
+
},
|
|
167
|
+
"required": ["query", "location"]
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
"type": "function",
|
|
173
|
+
"function": {
|
|
174
|
+
"name": "validate_emails",
|
|
175
|
+
"description": "Validate email deliverability, catch-all detection, MX records.",
|
|
176
|
+
"parameters": {
|
|
177
|
+
"type": "object",
|
|
178
|
+
"properties": {
|
|
179
|
+
"emails": {"type": "array", "items": {"type": "string"}, "description": "Emails to validate"}
|
|
180
|
+
},
|
|
181
|
+
"required": ["emails"]
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
"type": "function",
|
|
187
|
+
"function": {
|
|
188
|
+
"name": "find_emails",
|
|
189
|
+
"description": "Find business email addresses from name + company.",
|
|
190
|
+
"parameters": {
|
|
191
|
+
"type": "object",
|
|
192
|
+
"properties": {
|
|
193
|
+
"contacts": {
|
|
194
|
+
"type": "array",
|
|
195
|
+
"items": {
|
|
196
|
+
"type": "object",
|
|
197
|
+
"properties": {
|
|
198
|
+
"first_name": {"type": "string"},
|
|
199
|
+
"last_name": {"type": "string"},
|
|
200
|
+
"domain": {"type": "string"},
|
|
201
|
+
"company_name": {"type": "string"}
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
"description": "Contacts to find emails for"
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
"required": ["contacts"]
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
"type": "function",
|
|
213
|
+
"function": {
|
|
214
|
+
"name": "find_mobiles",
|
|
215
|
+
"description": "Find mobile phone numbers from LinkedIn URLs or work emails.",
|
|
216
|
+
"parameters": {
|
|
217
|
+
"type": "object",
|
|
218
|
+
"properties": {
|
|
219
|
+
"inputs": {"type": "array", "items": {"type": "string"}, "description": "LinkedIn URLs or work emails"}
|
|
220
|
+
},
|
|
221
|
+
"required": ["inputs"]
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
"type": "function",
|
|
227
|
+
"function": {
|
|
228
|
+
"name": "find_people",
|
|
229
|
+
"description": "Skip trace / people finder. Look up by name, email, phone, or address.",
|
|
230
|
+
"parameters": {
|
|
231
|
+
"type": "object",
|
|
232
|
+
"properties": {
|
|
233
|
+
"name": {"type": "array", "items": {"type": "string"}, "description": "Full names"},
|
|
234
|
+
"email": {"type": "array", "items": {"type": "string"}, "description": "Emails"},
|
|
235
|
+
"phone_number": {"type": "array", "items": {"type": "string"}, "description": "Phone numbers"}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
"type": "function",
|
|
242
|
+
"function": {
|
|
243
|
+
"name": "scrape_store_leads",
|
|
244
|
+
"description": "Get ecommerce store data (Shopify, WooCommerce, etc). Instant results.",
|
|
245
|
+
"parameters": {
|
|
246
|
+
"type": "object",
|
|
247
|
+
"properties": {
|
|
248
|
+
"platform": {"type": "string", "description": "e.g. shopify, woocommerce", "default": "shopify"},
|
|
249
|
+
"country_code": {"type": "string", "description": "e.g. US, GB"},
|
|
250
|
+
"total_leads": {"type": "integer", "description": "Number of leads", "default": 1000}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
"type": "function",
|
|
257
|
+
"function": {
|
|
258
|
+
"name": "query_lead_database",
|
|
259
|
+
"description": "Query 4M+ B2B contacts instantly by title, industry, location. Requires $649/mo plan.",
|
|
260
|
+
"parameters": {
|
|
261
|
+
"type": "object",
|
|
262
|
+
"properties": {
|
|
263
|
+
"title": {"type": "string", "description": "Job title filter"},
|
|
264
|
+
"industry": {"type": "string", "description": "Company industry"},
|
|
265
|
+
"state": {"type": "string", "description": "Person state"},
|
|
266
|
+
"city": {"type": "string", "description": "Person city"},
|
|
267
|
+
"limit": {"type": "integer", "description": "Results per page (max 100)", "default": 50}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
"type": "function",
|
|
274
|
+
"function": {
|
|
275
|
+
"name": "check_run_status",
|
|
276
|
+
"description": "Check status of a scraper run. Returns RUNNING/SUCCEEDED/FAILED.",
|
|
277
|
+
"parameters": {
|
|
278
|
+
"type": "object",
|
|
279
|
+
"properties": {
|
|
280
|
+
"run_id": {"type": "string", "description": "The run ID"}
|
|
281
|
+
},
|
|
282
|
+
"required": ["run_id"]
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
"type": "function",
|
|
288
|
+
"function": {
|
|
289
|
+
"name": "download_results",
|
|
290
|
+
"description": "Download CSV results of a completed run.",
|
|
291
|
+
"parameters": {
|
|
292
|
+
"type": "object",
|
|
293
|
+
"properties": {
|
|
294
|
+
"run_id": {"type": "string", "description": "The run ID"},
|
|
295
|
+
"output_path": {"type": "string", "description": "File path to save CSV"}
|
|
296
|
+
},
|
|
297
|
+
"required": ["run_id"]
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
"type": "function",
|
|
303
|
+
"function": {
|
|
304
|
+
"name": "cancel_run",
|
|
305
|
+
"description": "Cancel a running scraper job.",
|
|
306
|
+
"parameters": {
|
|
307
|
+
"type": "object",
|
|
308
|
+
"properties": {
|
|
309
|
+
"run_id": {"type": "string", "description": "The run ID to cancel"}
|
|
310
|
+
},
|
|
311
|
+
"required": ["run_id"]
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
]
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def get_tools():
|
|
319
|
+
"""Return tools compatible with OpenAI Agents SDK.
|
|
320
|
+
|
|
321
|
+
Usage:
|
|
322
|
+
from agents import Agent
|
|
323
|
+
from scrapercity.openai_tools import get_tools, init
|
|
324
|
+
|
|
325
|
+
init("your-api-key")
|
|
326
|
+
agent = Agent(name="Lead Gen", tools=get_tools())
|
|
327
|
+
"""
|
|
328
|
+
try:
|
|
329
|
+
from agents import FunctionTool
|
|
330
|
+
except ImportError:
|
|
331
|
+
raise ImportError("Install openai-agents: pip install openai-agents")
|
|
332
|
+
|
|
333
|
+
tools = []
|
|
334
|
+
for defn in get_function_definitions():
|
|
335
|
+
fn = defn["function"]
|
|
336
|
+
handler = _HANDLERS[fn["name"]]
|
|
337
|
+
tools.append(FunctionTool(
|
|
338
|
+
name=fn["name"],
|
|
339
|
+
description=fn["description"],
|
|
340
|
+
params_json_schema=fn["parameters"],
|
|
341
|
+
on_invoke_tool=lambda ctx, args, h=handler: h(**json.loads(args))
|
|
342
|
+
))
|
|
343
|
+
return tools
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: scrapercity
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: B2B lead generation for AI agents. 20+ tools: Apollo, Google Maps, email finder, skip trace, and more.
|
|
5
|
+
Author-email: ScraperCity <support@scrapercity.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://scrapercity.com
|
|
8
|
+
Project-URL: Documentation, https://scrapercity.com/agents/AGENT_DOCS.txt
|
|
9
|
+
Project-URL: Repository, https://github.com/scrapercity/scrapercity-python
|
|
10
|
+
Project-URL: Issues, https://github.com/scrapercity/scrapercity-python/issues
|
|
11
|
+
Keywords: leads,apollo,scraping,b2b,email-finder,sales,lead-generation,google-maps,skip-trace,langchain,openai-agents,ai-agent,prospecting,contact-enrichment,email-validation,mcp
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Topic :: Office/Business
|
|
17
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
18
|
+
Requires-Python: >=3.9
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Provides-Extra: langchain
|
|
22
|
+
Requires-Dist: langchain-core>=0.2; extra == "langchain"
|
|
23
|
+
Provides-Extra: openai
|
|
24
|
+
Requires-Dist: openai-agents>=0.1; extra == "openai"
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# ScraperCity Python SDK
|
|
28
|
+
|
|
29
|
+
B2B lead generation for AI agents and Python developers. 20+ tools including Apollo scraping, Google Maps, email finder/validator, mobile lookup, skip trace, ecommerce store data, and more.
|
|
30
|
+
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install scrapercity
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Quick Start
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from scrapercity import ScraperCity
|
|
41
|
+
|
|
42
|
+
sc = ScraperCity("your-api-key")
|
|
43
|
+
|
|
44
|
+
# Check balance
|
|
45
|
+
print(sc.wallet())
|
|
46
|
+
|
|
47
|
+
# Scrape Google Maps
|
|
48
|
+
result = sc.scrape_maps("plumbers", "Denver, CO", limit=200)
|
|
49
|
+
print(result["runId"])
|
|
50
|
+
|
|
51
|
+
# Query lead database (requires $649/mo plan)
|
|
52
|
+
leads = sc.query_leads(title="CEO", state="California", limit=50)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## LangChain / LangGraph
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
from scrapercity import langchain_tools
|
|
59
|
+
from langgraph.prebuilt import create_react_agent
|
|
60
|
+
|
|
61
|
+
tools = langchain_tools(api_key="your-key")
|
|
62
|
+
agent = create_react_agent(llm, tools)
|
|
63
|
+
result = agent.invoke({"messages": [{"role": "user", "content": "Find 50 CEOs in New York"}]})
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## OpenAI Agents SDK
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from agents import Agent
|
|
70
|
+
from scrapercity.openai_tools import get_tools, init
|
|
71
|
+
|
|
72
|
+
init("your-api-key")
|
|
73
|
+
agent = Agent(name="Lead Gen Agent", tools=get_tools())
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## OpenAI Function Calling
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
from scrapercity.openai_tools import get_function_definitions, handle_tool_call, init
|
|
80
|
+
|
|
81
|
+
init("your-api-key")
|
|
82
|
+
tools = get_function_definitions()
|
|
83
|
+
|
|
84
|
+
response = client.chat.completions.create(
|
|
85
|
+
model="gpt-4o",
|
|
86
|
+
messages=messages,
|
|
87
|
+
tools=tools
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# Handle the tool call
|
|
91
|
+
for call in response.choices[0].message.tool_calls:
|
|
92
|
+
result = handle_tool_call(call.function.name, json.loads(call.function.arguments))
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Available Methods
|
|
96
|
+
|
|
97
|
+
| Method | Description | Cost |
|
|
98
|
+
|--------|-------------|------|
|
|
99
|
+
| `scrape_apollo(url, count)` | B2B contacts from Apollo.io | $0.0039/lead |
|
|
100
|
+
| `scrape_maps(query, location)` | Google Maps businesses | $0.01/place |
|
|
101
|
+
| `validate_emails(emails)` | Email deliverability check | $0.0036/email |
|
|
102
|
+
| `find_emails(contacts)` | Email from name + company | $0.05/contact |
|
|
103
|
+
| `find_mobiles(inputs)` | Phone from LinkedIn/email | $0.25/input |
|
|
104
|
+
| `find_people(**params)` | Skip trace by name/email/phone | $0.02/result |
|
|
105
|
+
| `scrape_store_leads(**params)` | Shopify/WooCommerce stores | $0.0039/lead |
|
|
106
|
+
| `scrape_builtwith(technology)` | Sites using a technology | $4.99/search |
|
|
107
|
+
| `search_criminal_records(name)` | Background check | $1.00 if found |
|
|
108
|
+
| `query_leads(**params)` | 4M+ B2B database (instant) | Included ($649 plan) |
|
|
109
|
+
| `scrape_yelp(**params)` | Yelp business listings | varies |
|
|
110
|
+
| `scrape_angi(keyword, zips)` | Angi service providers | varies |
|
|
111
|
+
| `scrape_zillow_agents(location)` | Zillow real estate agents | varies |
|
|
112
|
+
| `scrape_airbnb(**params)` | Airbnb host emails | varies |
|
|
113
|
+
| `scrape_youtube_email(channels)` | YouTube channel emails | varies |
|
|
114
|
+
|
|
115
|
+
## Authentication
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
export SCRAPERCITY_API_KEY="your-key"
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Or pass directly: `ScraperCity(api_key="your-key")`
|
|
122
|
+
|
|
123
|
+
Get your API key at [scrapercity.com](https://scrapercity.com)
|
|
124
|
+
|
|
125
|
+
## License
|
|
126
|
+
|
|
127
|
+
MIT
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
scrapercity/__init__.py
|
|
5
|
+
scrapercity/client.py
|
|
6
|
+
scrapercity/langchain_tools.py
|
|
7
|
+
scrapercity/openai_tools.py
|
|
8
|
+
scrapercity.egg-info/PKG-INFO
|
|
9
|
+
scrapercity.egg-info/SOURCES.txt
|
|
10
|
+
scrapercity.egg-info/dependency_links.txt
|
|
11
|
+
scrapercity.egg-info/requires.txt
|
|
12
|
+
scrapercity.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
scrapercity
|