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.
@@ -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,6 @@
1
+
2
+ [langchain]
3
+ langchain-core>=0.2
4
+
5
+ [openai]
6
+ openai-agents>=0.1
@@ -0,0 +1 @@
1
+ scrapercity
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+