stackConsult commited on
Commit
e8f65e8
·
verified ·
1 Parent(s): 3fefa5f

Update data_enrichment.py

Browse files

Nov 07, 2025: updated syntax error and missing code with full rewrite via comet production, dev copy and paste and commit

Files changed (1) hide show
  1. data_enrichment.py +341 -1
data_enrichment.py CHANGED
@@ -1 +1,341 @@
1
- """\nStrategyStack Multi-API Data Enrichment Module\n\nIntegrates multiple RapidAPI sources for comprehensive contact/lead enrichment:\n- LinkedIn Data APIs (3 providers)\n- Apollo.io\n- Social Media (Twitter, Facebook)\n- Mediafy API\n- Google Search\n- LetScrape (Local Business Data + Website Contacts)\n\nDevelopment version with fallback support and multi-provider redundancy.\n"""\n\nimport os\nimport requests\nimport json\nimport time\nfrom datetime import datetime, timedelta\nfrom pathlib import Path\nfrom typing import Dict, Optional, List, Any\nimport logging\n\nlogging.basicConfig(level=logging.INFO)\nlogger = logging.getLogger(__name__)\n\n\nclass DataEnrichmentManager:\n """\n Manages data enrichment across multiple API providers.\n Implements fallback logic, caching, and rate limiting.\n """\n \n def __init__(self, username: str):\n self.username = username\n self.cache_dir = Path(f"/data/users/{username}/enrichment_cache")\n self.cache_dir.mkdir(parents=True, exist_ok=True)\n \n # API Keys from environment\n self.api_keys = {\n 'linkedin_fresh': os.getenv('RAPIDAPI_KEY_LINKEDIN_FRESH'),\n 'linkedin_rockapis': os.getenv('RAPIDAPI_KEY_LINKEDIN_ROCKAPIS'),\n 'linkedin_liscraper': os.getenv('RAPIDAPI_KEY_LISCRAPER'),\n 'linkedin_scraper': os.getenv('RAPIDAPI_KEY_LINKEDIN_SCRAPER'),\n 'apollo': os.getenv('RAPIDAPI_KEY_APOLLO'),\n 'mediafy': os.getenv('RAPIDAPI_KEY_MEDIAFY'),\n 'twitter': os.getenv('RAPIDAPI_KEY_TWITTER'),\n 'facebook': os.getenv('RAPIDAPI_KEY_FACEBOOK'),\n 'google': os.getenv('RAPIDAPI_KEY_GOOGLE'),\n 'letscrape': os.getenv('RAPIDAPI_KEY_LETSCRAPE')\n }\n \n # RapidAPI host configurations\n self.api_hosts = {\n 'linkedin_fresh': 'fresh-linkedin-profile-data.p.rapidapi.com',\n 'linkedin_rockapis': 'linkedin-data-api.p.rapidapi.com',\n 'linkedin_liscraper': 'li-data-scraper.p.rapidapi.com',\n 'linkedin_scraper': 'linkedin-data-scraper.p.rapidapi.com',\n 'apollo': 'apollo-io-no-cookies-required.p.rapidapi.com',\n 'mediafy': 'mediafy-api.p.rapidapi.com',\n 'twitter': 'twitter241.p.rapidapi.com',\n 'facebook': 'facebook-scraper3.p.rapidapi.com',\n 'google': 'google-search74.p.rapidapi.com',\n 'letscrape_business': 'local-business-data.p.rapidapi.com',\n 'letscrape_contacts': 'website-contacts-scraper.p.rapidapi.com'\n }\n \n self.cache_ttl = timedelta(days=30)\n\n \n def _make_request(self, api_name: str, url: str, params: Dict = None, headers: Dict = None) -> Optional[Dict]:\n """Make API request with error handling"""\n try:\n api_key = self.api_keys.get(api_name) or self.api_keys.get('letscrape')\n if not api_key:\n logger.warning(f"API key not found for {api_name}")\n return None\n \n # Determine host\n host = self.api_hosts.get(api_name, '')\n \n request_headers = {\n 'x-rapidapi-key': api_key,\n 'x-rapidapi-host': host\n }\n if headers:\n request_headers.update(headers)\n \n response = requests.get(url, headers=request_headers, params=params, timeout=30)\n response.raise_for_status()\n return response.json()\n except Exception as e:\n logger.error(f"API request failed for {api_name}: {str(e)}")\n return None\n \n def _get_cache_path(self, identifier: str, data_type: str) -> Path:\n """Get cache file path for identifier"""\n safe_id = identifier.replace('/', '_').replace(':', '_')\n return self.cache_dir / f"{data_type}_{safe_id}.json"\n \n def _load_from_cache(self, identifier: str, data_type: str) -> Optional[Dict]:\n """Load data from cache if valid"""\n cache_path = self._get_cache_path(identifier, data_type)\n if not cache_path.exists():\n return None\n \n try:\n with open(cache_path, 'r') as f:\n cached = json.load(f)\n \n cached_time = datetime.fromisoformat(cached.get('cached_at', '2020-01-01'))\n if datetime.now() - cached_time < self.cache_ttl:\n logger.info(f"Cache hit for {data_type}: {identifier}")\n return cached.get('data')\n except Exception as e:\n logger.error(f"Cache read error: {str(e)}")\n \n return None\n \n def _save_to_cache(self, identifier: str, data_type: str, data: Dict):\n """Save data to cache"""\n cache_path = self._get_cache_path(identifier, data_type)\n try:\n cache_data = {\n 'cached_at': datetime.now().isoformat(),\n 'identifier': identifier,\n 'data': data\n }\n with open(cache_path, 'w') as f:\n json.dump(cache_data, f, indent=2)\n logger.info(f"Cached {data_type}: {identifier}")\n except Exception as e:\n logger.error(f"Cache write error: {str(e)}")\n\n # LinkedIn Enrichment Methods\n def enrich_linkedin_profile(self, linkedin_url: str) -> Optional[Dict]:\n """Enrich LinkedIn profile with fallback across multiple providers"""\n cached = self._load_from_cache(linkedin_url, 'linkedin')\n if cached:\n return cached\n \n providers = ['linkedin_fresh', 'linkedin_rockapis', 'linkedin_liscraper', 'linkedin_scraper']\n \n for provider in providers:\n try:\n if provider == 'linkedin_fresh':\n url = 'https://fresh-linkedin-profile-data.p.rapidapi.com/enrich-lead'\n params = {'linkedin_url': linkedin_url}\n elif provider == 'linkedin_rockapis':\n url = 'https://linkedin-data-api.p.rapidapi.com/get-profile-data-by-url'\n params = {'url': linkedin_url}\n elif provider == 'linkedin_liscraper':\n url = 'https://li-data-scraper.p.rapidapi.com/get-profile-data-by-url'\n params = {'url': linkedin_url}\n else:\n url = 'https://linkedin-data-scraper.p.rapidapi.com/person'\n params = {'url': linkedin_url}\n \n data = self._make_request(provider, url, params)\n if data:\n self._save_to_cache(linkedin_url, 'linkedin', data)\n logger.info(f"LinkedIn enrichment successful via {provider}")\n return data\n except Exception as e:\n logger.warning(f"Provider {provider} failed: {str(e)}")\n continue\n \n return None\n \n def enrich_company(self, domain: str) -> Optional[Dict]:\n """Enrich company data by domain"""\n cached = self._load_from_cache(domain, 'company')\n if cached:\n return cached\n \n url = 'https://linkedin-data-api.p.rapidapi.com/get-company-by-domain'\n params = {'domain': domain}\n data = self._make_request('linkedin_rockapis', url, params)\n \n if data:\n self._save_to_cache(domain, 'company', data)\n return data\n \n # Apollo.io Enrichment\n def enrich_with_apollo(self, email: str = None, domain: str = None) -> Optional[Dict]:\n """Enrich contact using Apollo.io API"""\n cache_key = email or domain\n cached = self._load_from_cache(cache_key, 'apollo')\n if cached:\n return cached\n \n url = 'https://apollo-io-no-cookies-required.p.rapidapi.com/enrich'\n params = {}\n if email:\n params['email'] = email\n if domain:\n params['domain'] = domain\n \n data = self._make_request('apollo', url, params)\n if data:\n self._save_to_cache(cache_key, 'apollo', data)\n return data\n\n # Social Media Enrichment\n def enrich_twitter_profile(self, username: str) -> Optional[Dict]:\n """Enrich Twitter profile data"""\n cached = self._load_from_cache(username, 'twitter')\n if cached:\n return cached\n \n url = 'https://twitter241.p.rapidapi.com/user'\n params = {'username': username}\n data = self._make_request('twitter', url, params)\n \n if data:\n self._save_to_cache(username, 'twitter', data)\n return data\n \n def enrich_facebook_profile(self, profile_id: str) -> Optional[Dict]:\n """Enrich Facebook profile data"""\n cached = self._load_from_cache(profile_id, 'facebook')\n if cached:\n return cached\n \n url = 'https://facebook-scraper3.p.rapidapi.com/profile'\n params = {'profile_id': profile_id}\n data = self._make_request('facebook', url, params)\n \n if data:\n self._save_to_cache(profile_id, 'facebook', data)\n return data\n \n # Mediafy API\n def enrich_with_mediafy(self, query: str) -> Optional[Dict]:\n """Enrich with Mediafy media/social data"""\n cached = self._load_from_cache(query, 'mediafy')\n if cached:\n return cached\n \n url = 'https://mediafy-api.p.rapidapi.com/search'\n params = {'query': query}\n data = self._make_request('mediafy', url, params)\n \n if data:\n self._save_to_cache(query, 'mediafy', data)\n return data\n \n # Google Search\n def search_google(self, query: str, num_results: int = 10) -> Optional[Dict]:\n """Perform Google search for contact information"""\n cached = self._load_from_cache(query, 'google_search')\n if cached:\n return cached\n \n url = 'https://google-search74.p.rapidapi.com/'\n params = {'query': query, 'limit': str(num_results)}\n data = self._make_request('google', url, params)\n \n if data:\n self._save_to_cache(query, 'google_search', data)\n return data\n\n # LetScrape APIs - Local Business & Website Contacts\n def scrape_local_business(self, query: str, location: str = None, limit: int = 20) -> Optional[Dict]:\n """\n Scrape local business data from Google Maps.\n Returns business information including address, phone, email, ratings, reviews.\n """\n cache_key = f"{query}_{location or 'any'}"\n cached = self._load_from_cache(cache_key, 'local_business')\n if cached:\n return cached\n \n url = 'https://local-business-data.p.rapidapi.com/search'\n params = {'query': query, 'limit': limit}\n if location:\n params['subtypes'] = location\n \n data = self._make_request('letscrape_business', url, params)\n \n if data:\n self._save_to_cache(cache_key, 'local_business', data)\n logger.info(f"Local business data retrieved for: {query}")\n return data\n \n def scrape_website_contacts(self, domain: str) -> Optional[Dict]:\n """\n Scrape and extract email addresses, phone numbers, and social profile links from a website.\n Supports Facebook, Instagram, TikTok, LinkedIn, Twitter, YouTube, Pinterest, Snapchat, GitHub.\n """\n cached = self._load_from_cache(domain, 'website_contacts')\n if cached:\n return cached\n \n url = 'https://website-contacts-scraper.p.rapidapi.com/scrape-contacts'\n params = {'query': domain}\n \n data = self._make_request('letscrape_contacts', url, params)\n \n if data:\n self._save_to_cache(domain, 'website_contacts', data)\n logger.info(f"Website contacts scraped for: {domain}")\n return data\n\n # Comprehensive Enrichment\n def enrich_contact_full(self, contact_data: Dict) -> Dict:\n """\n Perform comprehensive enrichment using all available data sources.\n Input contact_data can include: email, linkedin_url, twitter, facebook, domain, name, company\n """\n enriched = {'original': contact_data, 'enrichments': {}, 'sources': []}\n \n # LinkedIn enrichment\n if contact_data.get('linkedin_url'):\n linkedin_data = self.enrich_linkedin_profile(contact_data['linkedin_url'])\n if linkedin_data:\n enriched['enrichments']['linkedin'] = linkedin_data\n enriched['sources'].append('linkedin')\n \n # Apollo enrichment\n if contact_data.get('email') or contact_data.get('domain'):\n apollo_data = self.enrich_with_apollo(\n email=contact_data.get('email'),\n domain=contact_data.get('domain')\n )\n if apollo_data:\n enriched['enrichments']['apollo'] = apollo_data\n enriched['sources'].append('apollo')\n \n # Company enrichment\n if contact_data.get('domain'):\n company_data = self.enrich_company(contact_data['domain'])\n if company_data:\n enriched['enrichments']['company'] = company_data\n enriched['sources'].append('company')\n \n # Website contacts scraping\n if contact_data.get('domain'):\n website_contacts = self.scrape_website_contacts(contact_data['domain'])\n if website_contacts:\n enriched['enrichments']['website_contacts'] = website_contacts\n enriched['sources'].append('website_contacts')\n \n # Local business data (if company name and location provided)\n if contact_data.get('company') and contact_data.get('location'):\n business_data = self.scrape_local_business(\n query=contact_data['company'],\n location=contact_data['location']\n )\n if business_data:\n enriched['enrichments']['local_business'] = business_data\n enriched['sources'].append('local_business')\n \n # Twitter enrichment\n if contact_data.get('twitter_username'):\n twitter_data = self.enrich_twitter_profile(contact_data['twitter_username'])\n if twitter_data:\n enriched['enrichments']['twitter'] = twitter_data\n enriched['sources'].append('twitter')\n \n # Facebook enrichment\n if contact_data.get('facebook_id'):\n facebook_data = self.enrich_facebook_profile(contact_data['facebook_id'])\n if facebook_data:\n enriched['enrichments']['facebook'] = facebook_data\n enriched['sources'].append('facebook')\n \n # Google search for additional info\n if contact_data.get('name'):\n search_query = f\"{contact_data['name']}\"\n if contact_data.get('company'):\n search_query += f\" {contact_data['company']}\"\n google_results = self.search_google(search_query, num_results=5)\n if google_results:\n enriched['enrichments']['google_search'] = google_results\n enriched['sources'].append('google')\n \n enriched['enriched_at'] = datetime.now().isoformat()\n enriched['total_sources'] = len(enriched['sources'])\n \n return enriched\n \n def get_enrichment_stats(self) -> Dict:\n """Get cache statistics and API usage"""\n stats = {\n 'cache_directory': str(self.cache_dir),\n 'cached_profiles': 0,\n 'cache_types': {}\n }\n \n if self.cache_dir.exists():\n cache_files = list(self.cache_dir.glob('*.json'))\n stats['cached_profiles'] = len(cache_files)\n \n for cache_file in cache_files:\n cache_type = cache_file.stem.split('_')[0]\n stats['cache_types'][cache_type] = stats['cache_types'].get(cache_type, 0) + 1\n \n stats['configured_apis'] = [k for k, v in self.api_keys.items() if v]\n stats['total_apis'] = len(stats['configured_apis'])\n \n return stats\n\n\n# Convenience functions\ndef create_enrichment_manager(username: str) -> DataEnrichmentManager:\n """Create a new enrichment manager instance"""\n return DataEnrichmentManager(username)\n\n\ndef enrich_contact(username: str, contact_data: Dict) -> Dict:\n """Quick enrichment for a single contact"""\n manager = DataEnrichmentManager(username)\n return manager.enrich_contact_full(contact_data)\n
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ StrategyStack Multi-API Data Enrichment Module
3
+
4
+ Integrates multiple RapidAPI sources for comprehensive contact/lead enrichment:
5
+ - LinkedIn Data APIs (3 providers)
6
+ - Apollo.io
7
+ - Social Media (Twitter, Facebook)
8
+ - Mediafy API
9
+ - Google Search
10
+ - LetScrape (Local Business Data + Website Contacts)
11
+
12
+ Development version with fallback support and multi-provider redundancy.
13
+ """
14
+
15
+ import os
16
+ import requests
17
+ import json
18
+ import time
19
+ from datetime import datetime, timedelta
20
+ from pathlib import Path
21
+ from typing import Dict, Optional, List, Any
22
+ import logging
23
+
24
+ logging.basicConfig(level=logging.INFO)
25
+ logger = logging.getLogger(__name__)
26
+
27
+
28
+ class DataEnrichmentManager:
29
+ """
30
+ Manages data enrichment across multiple API providers.
31
+ Implements fallback logic, caching, and rate limiting.
32
+ """
33
+
34
+ def __init__(self, username: str):
35
+ self.username = username
36
+ self.cache_dir = Path(f"/data/users/{username}/enrichment_cache")
37
+ self.cache_dir.mkdir(parents=True, exist_ok=True)
38
+
39
+ # API Keys from environment
40
+ self.api_keys = {
41
+ 'linkedin_fresh': os.getenv('RAPIDAPI_KEY_LINKEDIN_FRESH'),
42
+ 'linkedin_rockapis': os.getenv('RAPIDAPI_KEY_LINKEDIN_ROCKAPIS'),
43
+ 'linkedin_liscraper': os.getenv('RAPIDAPI_KEY_LISCRAPER'),
44
+ 'linkedin_scraper': os.getenv('RAPIDAPI_KEY_LINKEDIN_SCRAPER'),
45
+ 'apollo': os.getenv('RAPIDAPI_KEY_APOLLO'),
46
+ 'mediafy': os.getenv('RAPIDAPI_KEY_MEDIAFY'),
47
+ 'twitter': os.getenv('RAPIDAPI_KEY_TWITTER'),
48
+ 'facebook': os.getenv('RAPIDAPI_KEY_FACEBOOK'),
49
+ 'google': os.getenv('RAPIDAPI_KEY_GOOGLE'),
50
+ 'letscrape': os.getenv('RAPIDAPI_KEY_LETSCRAPE')
51
+ }
52
+
53
+ # RapidAPI host configurations
54
+ self.api_hosts = {
55
+ 'linkedin_fresh': 'fresh-linkedin-profile-data.p.rapidapi.com',
56
+ 'linkedin_rockapis': 'linkedin-data-api.p.rapidapi.com',
57
+ 'linkedin_liscraper': 'li-data-scraper.p.rapidapi.com',
58
+ 'linkedin_scraper': 'linkedin-data-scraper.p.rapidapi.com',
59
+ 'apollo': 'apollo-io-no-cookies-required.p.rapidapi.com',
60
+ 'mediafy': 'mediafy-api.p.rapidapi.com',
61
+ 'twitter': 'twitter241.p.rapidapi.com',
62
+ 'facebook': 'facebook-scraper3.p.rapidapi.com',
63
+ 'google': 'google-search74.p.rapidapi.com',
64
+ 'letscrape_business': 'local-business-data.p.rapidapi.com',
65
+ 'letscrape_contacts': 'website-contacts-scraper.p.rapidapi.com'
66
+ }
67
+
68
+ self.cache_ttl = timedelta(days=30)
69
+
70
+
71
+ def _make_request(self, api_name: str, url: str, params: Dict = None, headers: Dict = None) -> Optional[Dict]:
72
+ """Make API request with error handling"""
73
+ try:
74
+ api_key = self.api_keys.get(api_name) or self.api_keys.get('letscrape')
75
+ if not api_key:
76
+ logger.warning(f"API key not found for {api_name}")
77
+ return None
78
+
79
+ # Determine host
80
+ host = self.api_hosts.get(api_name, '')
81
+
82
+ request_headers = {
83
+ 'x-rapidapi-key': api_key,
84
+ 'x-rapidapi-host': host
85
+ }
86
+ if headers:
87
+ request_headers.update(headers)
88
+
89
+ response = requests.get(url, headers=request_headers, params=params, timeout=30)
90
+ response.raise_for_status()
91
+ return response.json()
92
+ except Exception as e:
93
+ logger.error(f"API request failed for {api_name}: {str(e)}")
94
+ return None
95
+
96
+ def _get_cache_path(self, identifier: str, data_type: str) -> Path:
97
+ """Get cache file path for identifier"""
98
+ safe_id = identifier.replace('/', '_').replace(':', '_')
99
+ return self.cache_dir / f"{data_type}_{safe_id}.json"
100
+
101
+ def _load_from_cache(self, identifier: str, data_type: str) -> Optional[Dict]:
102
+ """Load data from cache if valid"""
103
+ cache_path = self._get_cache_path(identifier, data_type)
104
+ if not cache_path.exists():
105
+ return None
106
+
107
+ try:
108
+ with open(cache_path, 'r') as f:
109
+ cached = json.load(f)
110
+
111
+ cached_time = datetime.fromisoformat(cached.get('cached_at', '2020-01-01'))
112
+ if datetime.now() - cached_time < self.cache_ttl:
113
+ logger.info(f"Cache hit for {data_type}: {identifier}")
114
+ return cached.get('data')
115
+ except Exception as e:
116
+ logger.error(f"Cache read error: {str(e)}")
117
+
118
+ return None
119
+
120
+ def _save_to_cache(self, identifier: str, data_type: str, data: Dict):
121
+ """Save data to cache"""
122
+ cache_path = self._get_cache_path(identifier, data_type)
123
+ try:
124
+ cache_data = {
125
+ 'cached_at': datetime.now().isoformat(),
126
+ 'identifier': identifier,
127
+ 'data': data
128
+ }
129
+ with open(cache_path, 'w') as f:
130
+ json.dump(cache_data, f, indent=2)
131
+ logger.info(f"Cached {data_type}: {identifier}")
132
+ except Exception as e:
133
+ logger.error(f"Cache write error: {str(e)}")
134
+
135
+ # LinkedIn Enrichment Methods
136
+ def enrich_linkedin_profile(self, linkedin_url: str) -> Optional[Dict]:
137
+ """Enrich LinkedIn profile with fallback across multiple providers"""
138
+ cached = self._load_from_cache(linkedin_url, 'linkedin')
139
+ if cached:
140
+ return cached
141
+
142
+ providers = ['linkedin_fresh', 'linkedin_rockapis', 'linkedin_liscraper', 'linkedin_scraper']
143
+
144
+ for provider in providers:
145
+ try:
146
+ if provider == 'linkedin_fresh':
147
+ url = 'https://fresh-linkedin-profile-data.p.rapidapi.com/enrich-lead'
148
+ params = {'linkedin_url': linkedin_url}
149
+ elif provider == 'linkedin_rockapis':
150
+ url = 'https://linkedin-data-api.p.rapidapi.com/get-profile-data-by-url'
151
+ params = {'url': linkedin_url}
152
+ elif provider == 'linkedin_liscraper':
153
+ url = 'https://li-data-scraper.p.rapidapi.com/get-profile-data-by-url'
154
+ params = {'url': linkedin_url}
155
+ else:
156
+ url = 'https://linkedin-data-scraper.p.rapidapi.com/person'
157
+ params = {'url': linkedin_url}
158
+
159
+ data = self._make_request(provider, url, params)
160
+ if data:
161
+ self._save_to_cache(linkedin_url, 'linkedin', data)
162
+ logger.info(f"LinkedIn enrichment successful via {provider}")
163
+ return data
164
+ except Exception as e:
165
+ logger.warning(f"Provider {provider} failed: {str(e)}")
166
+ continue
167
+
168
+ return None
169
+
170
+ def enrich_company(self, domain: str) -> Optional[Dict]:
171
+ """Enrich company data by domain"""
172
+ cached = self._load_from_cache(domain, 'company')
173
+ if cached:
174
+ return cached
175
+
176
+ url = 'https://linkedin-data-api.p.rapidapi.com/get-company-by-domain'
177
+ params = {'domain': domain}
178
+ data = self._make_request('linkedin_rockapis', url, params)
179
+
180
+ if data:
181
+ self._save_to_cache(domain, 'company', data)
182
+ return data
183
+
184
+ # Apollo.io Enrichment
185
+ def enrich_with_apollo(self, email: str = None, domain: str = None) -> Optional[Dict]:
186
+ """Enrich contact using Apollo.io API"""
187
+ cache_key = email or domain
188
+ cached = self._load_from_cache(cache_key, 'apollo')
189
+ if cached:
190
+ return cached
191
+
192
+ url = 'https://apollo-io-no-cookies-required.p.rapidapi.com/enrich'
193
+ params = {}
194
+ if email:
195
+ params['email'] = email
196
+ if domain:
197
+ params['domain'] = domain
198
+
199
+ data = self._make_request('apollo', url, params)
200
+ if data:
201
+ self._save_to_cache(cache_key, 'apollo', data)
202
+ return data
203
+
204
+ # Social Media Enrichment
205
+ def enrich_twitter_profile(self, username: str) -> Optional[Dict]:
206
+ """Enrich Twitter profile data"""
207
+ cached = self._load_from_cache(username, 'twitter')
208
+ if cached:
209
+ return cached
210
+
211
+ url = 'https://twitter241.p.rapidapi.com/user'
212
+ params = {'username': username}
213
+ data = self._make_request('twitter', url, params)
214
+
215
+ if data:
216
+ self._save_to_cache(username, 'twitter', data)
217
+ return data
218
+
219
+ def enrich_facebook_profile(self, profile_id: str) -> Optional[Dict]:
220
+ """Enrich Facebook profile data"""
221
+ cached = self._load_from_cache(profile_id, 'facebook')
222
+ if cached:
223
+ return cached
224
+
225
+ url = 'https://facebook-scraper3.p.rapidapi.com/profile'
226
+ params = {'profile_id': profile_id}
227
+ data = self._make_request('facebook', url, params)
228
+
229
+ if data:
230
+ self._save_to_cache(profile_id, 'facebook', data)
231
+ return data
232
+
233
+ # Mediafy API
234
+ def enrich_with_mediafy(self, query: str) -> Optional[Dict]:
235
+ """Enrich with Mediafy media/social data"""
236
+ cached = self._load_from_cache(query, 'mediafy')
237
+ if cached:
238
+ return cached
239
+
240
+ url = 'https://mediafy-api.p.rapidapi.com/search'
241
+ params = {'query': query}
242
+ data = self._make_request('mediafy', url, params)
243
+
244
+ if data:
245
+ self._save_to_cache(query, 'mediafy', data)
246
+ return data
247
+
248
+ # Google Search
249
+ def search_google(self, query: str, num_results: int = 10) -> Optional[Dict]:
250
+ """Perform Google search for contact information"""
251
+ cached = self._load_from_cache(query, 'google_search')
252
+ if cached:
253
+ return cached
254
+
255
+ url = 'https://google-search74.p.rapidapi.com/'
256
+ params = {'query': query, 'limit': str(num_results)}
257
+ data = self._make_request('google', url, params)
258
+
259
+ if data:
260
+ self._save_to_cache(query, 'google_search', data)
261
+ return data
262
+
263
+ # LetScrape APIs - Local Business & Website Contacts
264
+ def scrape_local_business(self, query: str, location: str = None, limit: int = 20) -> Optional[Dict]:
265
+ """
266
+ Scrape local business data from Google Maps.
267
+ Returns business information including address, phone, email, ratings, reviews.
268
+ """
269
+ cache_key = f"{query}_{location or 'any'}"
270
+ cached = self._load_from_cache(cache_key, 'local_business')
271
+ if cached:
272
+ return cached
273
+
274
+ url = 'https://local-business-data.p.rapidapi.com/search'
275
+ params = {'query': query, 'limit': limit}
276
+ if location:
277
+ params['subtypes'] = location
278
+
279
+ data = self._make_request('letscrape_business', url, params)
280
+
281
+ if data:
282
+ self._save_to_cache(cache_key, 'local_business', data)
283
+ logger.info(f"Local business data retrieved for: {query}")
284
+ return data
285
+
286
+ def scrape_website_contacts(self, domain: str) -> Optional[Dict]:
287
+ """
288
+ Scrape and extract email addresses, phone numbers, and social profile links from a website.
289
+ Supports Facebook, Instagram, TikTok, LinkedIn, Twitter, YouTube, Pinterest, Snapchat, GitHub.
290
+ """
291
+ cached = self._load_from_cache(domain, 'website_contacts')
292
+ if cached:
293
+ return cached
294
+
295
+ url = 'https://website-contacts-scraper.p.rapidapi.com/scrape-contacts'
296
+ params = {'query': domain}
297
+
298
+ data = self._make_request('letscrape_contacts', url, params)
299
+
300
+ if data:
301
+ self._save_to_cache(domain, 'website_contacts', data)
302
+ logger.info(f"Website contacts scraped for: {domain}")
303
+ return data
304
+
305
+ # Comprehensive Enrichment
306
+ def enrich_contact_full(self, contact_data: Dict) -> Dict:
307
+ """
308
+ Perform comprehensive enrichment using all available data sources.
309
+ Input contact_data can include: email, linkedin_url, twitter, facebook, domain, name, company
310
+ """
311
+ enriched = {'original': contact_data, 'enrichments': {}, 'sources': []}
312
+
313
+ # LinkedIn enrichment
314
+ if contact_data.get('linkedin_url'):
315
+ linkedin_data = self.enrich_linkedin_profile(contact_data['linkedin_url'])
316
+ if linkedin_data:
317
+ enriched['enrichments']['linkedin'] = linkedin_data
318
+ enriched['sources'].append('linkedin')
319
+
320
+ # Apollo enrichment
321
+ if contact_data.get('email') or contact_data.get('domain'):
322
+ apollo_data = self.enrich_with_apollo(
323
+ email=contact_data.get('email'),
324
+ domain=contact_data.get('domain')
325
+ )
326
+ if apollo_data:
327
+ enriched['enrichments']['apollo'] = apollo_data
328
+ enriched['sources'].append('apollo')
329
+
330
+ # Company enrichment
331
+ if contact_data.get('domain'):
332
+ company_data = self.enrich_company(contact_data['domain'])
333
+ if company_data:
334
+ enriched['enrichments']['company'] = company_data
335
+ enriched['sources'].append('company')
336
+
337
+ # Website contacts scraping
338
+ if contact_data.get('domain'):
339
+ website_contacts = self.scrape_website_contacts(contact_data['domain'])
340
+ if website_contacts:
341
+ enriched['enrichments