Spaces:
Sleeping
Sleeping
Download data_enrichment.py from StrategyStack/README: direct link, hf CLI and curl.
- Browser
- Download file 16.9 kB
-
https://huggingface.co/spaces/StrategyStack/README/resolve/main/data_enrichment.py
- Command line
-
hf download hf://spaces/StrategyStack/README/data_enrichment.py
-
curl -L -o data_enrichment.py https://huggingface.co/spaces/StrategyStack/README/resolve/main/data_enrichment.py
16.9 kB
| """ | |
| StrategyStack Multi-API Data Enrichment Module | |
| Integrates multiple RapidAPI sources for comprehensive contact/lead enrichment: | |
| - LinkedIn Data APIs (3 providers) | |
| - Apollo.io | |
| - Social Media (Twitter, Facebook) | |
| - Mediafy API | |
| - Google Search | |
| - LetScrape (Local Business Data + Website Contacts) | |
| Development version with fallback support and multi-provider redundancy. | |
| """ | |
| import os | |
| import requests | |
| import json | |
| import time | |
| from datetime import datetime, timedelta | |
| from pathlib import Path | |
| from typing import Dict, Optional, List, Any | |
| import logging | |
| logging.basicConfig(level=logging.INFO) | |
| logger = logging.getLogger(__name__) | |
| class DataEnrichmentManager: | |
| """ | |
| Manages data enrichment across multiple API providers. | |
| Implements fallback logic, caching, and rate limiting. | |
| """ | |
| def __init__(self, username: str): | |
| self.username = username | |
| self.cache_dir = Path(f"/data/users/{username}/enrichment_cache") | |
| self.cache_dir.mkdir(parents=True, exist_ok=True) | |
| # API Keys from environment | |
| self.api_keys = { | |
| 'linkedin_fresh': os.getenv('RAPIDAPI_KEY_LINKEDIN_FRESH'), | |
| 'linkedin_rockapis': os.getenv('RAPIDAPI_KEY_LINKEDIN_ROCKAPIS'), | |
| 'linkedin_liscraper': os.getenv('RAPIDAPI_KEY_LISCRAPER'), | |
| 'linkedin_scraper': os.getenv('RAPIDAPI_KEY_LINKEDIN_SCRAPER'), | |
| 'apollo': os.getenv('RAPIDAPI_KEY_APOLLO'), | |
| 'mediafy': os.getenv('RAPIDAPI_KEY_MEDIAFY'), | |
| 'twitter': os.getenv('RAPIDAPI_KEY_TWITTER'), | |
| 'facebook': os.getenv('RAPIDAPI_KEY_FACEBOOK'), | |
| 'google': os.getenv('RAPIDAPI_KEY_GOOGLE'), | |
| 'letscrape': os.getenv('RAPIDAPI_KEY_LETSCRAPE') | |
| } | |
| # RapidAPI host configurations | |
| self.api_hosts = { | |
| 'linkedin_fresh': 'fresh-linkedin-profile-data.p.rapidapi.com', | |
| 'linkedin_rockapis': 'linkedin-data-api.p.rapidapi.com', | |
| 'linkedin_liscraper': 'li-data-scraper.p.rapidapi.com', | |
| 'linkedin_scraper': 'linkedin-data-scraper.p.rapidapi.com', | |
| 'apollo': 'apollo-io-no-cookies-required.p.rapidapi.com', | |
| 'mediafy': 'mediafy-api.p.rapidapi.com', | |
| 'twitter': 'twitter241.p.rapidapi.com', | |
| 'facebook': 'facebook-scraper3.p.rapidapi.com', | |
| 'google': 'google-search74.p.rapidapi.com', | |
| 'letscrape_business': 'local-business-data.p.rapidapi.com', | |
| 'letscrape_contacts': 'website-contacts-scraper.p.rapidapi.com' | |
| } | |
| self.cache_ttl = timedelta(days=30) | |
| def _make_request(self, api_name: str, url: str, params: Dict = None, headers: Dict = None) -> Optional[Dict]: | |
| """Make API request with error handling""" | |
| try: | |
| api_key = self.api_keys.get(api_name) or self.api_keys.get('letscrape') | |
| if not api_key: | |
| logger.warning(f"API key not found for {api_name}") | |
| return None | |
| # Determine host | |
| host = self.api_hosts.get(api_name, '') | |
| request_headers = { | |
| 'x-rapidapi-key': api_key, | |
| 'x-rapidapi-host': host | |
| } | |
| if headers: | |
| request_headers.update(headers) | |
| response = requests.get(url, headers=request_headers, params=params, timeout=30) | |
| response.raise_for_status() | |
| return response.json() | |
| except Exception as e: | |
| logger.error(f"API request failed for {api_name}: {str(e)}") | |
| return None | |
| def _get_cache_path(self, identifier: str, data_type: str) -> Path: | |
| """Get cache file path for identifier""" | |
| safe_id = identifier.replace('/', '_').replace(':', '_') | |
| return self.cache_dir / f"{data_type}_{safe_id}.json" | |
| def _load_from_cache(self, identifier: str, data_type: str) -> Optional[Dict]: | |
| """Load data from cache if valid""" | |
| cache_path = self._get_cache_path(identifier, data_type) | |
| if not cache_path.exists(): | |
| return None | |
| try: | |
| with open(cache_path, 'r') as f: | |
| cached = json.load(f) | |
| cached_time = datetime.fromisoformat(cached.get('cached_at', '2020-01-01')) | |
| if datetime.now() - cached_time < self.cache_ttl: | |
| logger.info(f"Cache hit for {data_type}: {identifier}") | |
| return cached.get('data') | |
| except Exception as e: | |
| logger.error(f"Cache read error: {str(e)}") | |
| return None | |
| def _save_to_cache(self, identifier: str, data_type: str, data: Dict): | |
| """Save data to cache""" | |
| cache_path = self._get_cache_path(identifier, data_type) | |
| try: | |
| cache_data = { | |
| 'cached_at': datetime.now().isoformat(), | |
| 'identifier': identifier, | |
| 'data': data | |
| } | |
| with open(cache_path, 'w') as f: | |
| json.dump(cache_data, f, indent=2) | |
| logger.info(f"Cached {data_type}: {identifier}") | |
| except Exception as e: | |
| logger.error(f"Cache write error: {str(e)}") | |
| # LinkedIn Enrichment Methods | |
| def enrich_linkedin_profile(self, linkedin_url: str) -> Optional[Dict]: | |
| """Enrich LinkedIn profile with fallback across multiple providers""" | |
| cached = self._load_from_cache(linkedin_url, 'linkedin') | |
| if cached: | |
| return cached | |
| providers = ['linkedin_fresh', 'linkedin_rockapis', 'linkedin_liscraper', 'linkedin_scraper'] | |
| for provider in providers: | |
| try: | |
| if provider == 'linkedin_fresh': | |
| url = 'https://fresh-linkedin-profile-data.p.rapidapi.com/enrich-lead' | |
| params = {'linkedin_url': linkedin_url} | |
| elif provider == 'linkedin_rockapis': | |
| url = 'https://linkedin-data-api.p.rapidapi.com/get-profile-data-by-url' | |
| params = {'url': linkedin_url} | |
| elif provider == 'linkedin_liscraper': | |
| url = 'https://li-data-scraper.p.rapidapi.com/get-profile-data-by-url' | |
| params = {'url': linkedin_url} | |
| else: | |
| url = 'https://linkedin-data-scraper.p.rapidapi.com/person' | |
| params = {'url': linkedin_url} | |
| data = self._make_request(provider, url, params) | |
| if data: | |
| self._save_to_cache(linkedin_url, 'linkedin', data) | |
| logger.info(f"LinkedIn enrichment successful via {provider}") | |
| return data | |
| except Exception as e: | |
| logger.warning(f"Provider {provider} failed: {str(e)}") | |
| continue | |
| return None | |
| def enrich_company(self, domain: str) -> Optional[Dict]: | |
| """Enrich company data by domain""" | |
| cached = self._load_from_cache(domain, 'company') | |
| if cached: | |
| return cached | |
| url = 'https://linkedin-data-api.p.rapidapi.com/get-company-by-domain' | |
| params = {'domain': domain} | |
| data = self._make_request('linkedin_rockapis', url, params) | |
| if data: | |
| self._save_to_cache(domain, 'company', data) | |
| return data | |
| # Apollo.io Enrichment | |
| def enrich_with_apollo(self, email: str = None, domain: str = None) -> Optional[Dict]: | |
| """Enrich contact using Apollo.io API""" | |
| cache_key = email or domain | |
| cached = self._load_from_cache(cache_key, 'apollo') | |
| if cached: | |
| return cached | |
| url = 'https://apollo-io-no-cookies-required.p.rapidapi.com/enrich' | |
| params = {} | |
| if email: | |
| params['email'] = email | |
| if domain: | |
| params['domain'] = domain | |
| data = self._make_request('apollo', url, params) | |
| if data: | |
| self._save_to_cache(cache_key, 'apollo', data) | |
| return data | |
| # Social Media Enrichment | |
| def enrich_twitter_profile(self, username: str) -> Optional[Dict]: | |
| """Enrich Twitter profile data""" | |
| cached = self._load_from_cache(username, 'twitter') | |
| if cached: | |
| return cached | |
| url = 'https://twitter241.p.rapidapi.com/user' | |
| params = {'username': username} | |
| data = self._make_request('twitter', url, params) | |
| if data: | |
| self._save_to_cache(username, 'twitter', data) | |
| return data | |
| def enrich_facebook_profile(self, profile_id: str) -> Optional[Dict]: | |
| """Enrich Facebook profile data""" | |
| cached = self._load_from_cache(profile_id, 'facebook') | |
| if cached: | |
| return cached | |
| url = 'https://facebook-scraper3.p.rapidapi.com/profile' | |
| params = {'profile_id': profile_id} | |
| data = self._make_request('facebook', url, params) | |
| if data: | |
| self._save_to_cache(profile_id, 'facebook', data) | |
| return data | |
| # Mediafy API | |
| def enrich_with_mediafy(self, query: str) -> Optional[Dict]: | |
| """Enrich with Mediafy media/social data""" | |
| cached = self._load_from_cache(query, 'mediafy') | |
| if cached: | |
| return cached | |
| url = 'https://mediafy-api.p.rapidapi.com/search' | |
| params = {'query': query} | |
| data = self._make_request('mediafy', url, params) | |
| if data: | |
| self._save_to_cache(query, 'mediafy', data) | |
| return data | |
| # Google Search | |
| def search_google(self, query: str, num_results: int = 10) -> Optional[Dict]: | |
| """Perform Google search for contact information""" | |
| cached = self._load_from_cache(query, 'google_search') | |
| if cached: | |
| return cached | |
| url = 'https://google-search74.p.rapidapi.com/' | |
| params = {'query': query, 'limit': str(num_results)} | |
| data = self._make_request('google', url, params) | |
| if data: | |
| self._save_to_cache(query, 'google_search', data) | |
| return data | |
| # LetScrape APIs - Local Business & Website Contacts | |
| def scrape_local_business(self, query: str, location: str = None, limit: int = 20) -> Optional[Dict]: | |
| """ | |
| Scrape local business data from Google Maps. | |
| Returns business information including address, phone, email, ratings, reviews. | |
| """ | |
| cache_key = f"{query}_{location or 'any'}" | |
| cached = self._load_from_cache(cache_key, 'local_business') | |
| if cached: | |
| return cached | |
| url = 'https://local-business-data.p.rapidapi.com/search' | |
| params = {'query': query, 'limit': limit} | |
| if location: | |
| params['subtypes'] = location | |
| data = self._make_request('letscrape_business', url, params) | |
| if data: | |
| self._save_to_cache(cache_key, 'local_business', data) | |
| logger.info(f"Local business data retrieved for: {query}") | |
| return data | |
| def scrape_website_contacts(self, domain: str) -> Optional[Dict]: | |
| """ | |
| Scrape and extract email addresses, phone numbers, and social profile links from a website. | |
| Supports Facebook, Instagram, TikTok, LinkedIn, Twitter, YouTube, Pinterest, Snapchat, GitHub. | |
| """ | |
| cached = self._load_from_cache(domain, 'website_contacts') | |
| if cached: | |
| return cached | |
| url = 'https://website-contacts-scraper.p.rapidapi.com/scrape-contacts' | |
| params = {'query': domain} | |
| data = self._make_request('letscrape_contacts', url, params) | |
| if data: | |
| self._save_to_cache(domain, 'website_contacts', data) | |
| logger.info(f"Website contacts scraped for: {domain}") | |
| return data | |
| # Comprehensive Enrichment | |
| def enrich_contact_full(self, contact_data: Dict) -> Dict: | |
| """ | |
| Perform comprehensive enrichment using all available data sources. | |
| Input contact_data can include: email, linkedin_url, twitter, facebook, domain, name, company | |
| """ | |
| enriched = {'original': contact_data, 'enrichments': {}, 'sources': []} | |
| # LinkedIn enrichment | |
| if contact_data.get('linkedin_url'): | |
| linkedin_data = self.enrich_linkedin_profile(contact_data['linkedin_url']) | |
| if linkedin_data: | |
| enriched['enrichments']['linkedin'] = linkedin_data | |
| enriched['sources'].append('linkedin') | |
| # Apollo enrichment | |
| if contact_data.get('email') or contact_data.get('domain'): | |
| apollo_data = self.enrich_with_apollo( | |
| email=contact_data.get('email'), | |
| domain=contact_data.get('domain') | |
| ) | |
| if apollo_data: | |
| enriched['enrichments']['apollo'] = apollo_data | |
| enriched['sources'].append('apollo') | |
| # Company enrichment | |
| if contact_data.get('domain'): | |
| company_data = self.enrich_company(contact_data['domain']) | |
| if company_data: | |
| enriched['enrichments']['company'] = company_data | |
| enriched['sources'].append('company') | |
| # Website contacts scraping | |
| if contact_data.get('domain'): | |
| website_contacts = self.scrape_website_contacts(contact_data['domain']) | |
| if website_contacts: | |
| enriched['enrichments']['website_contacts'] = website_contacts | |
| enriched['sources'].append('website_contacts') | |
| # Local business data (if company name and location provided) | |
| if contact_data.get('company') and contact_data.get('location'): | |
| business_data = self.scrape_local_business( | |
| query=contact_data['company'], | |
| location=contact_data['location'] | |
| ) | |
| if business_data: | |
| enriched['enrichments']['local_business'] = business_data | |
| enriched['sources'].append('local_business') | |
| # Twitter enrichment | |
| if contact_data.get('twitter_username'): | |
| twitter_data = self.enrich_twitter_profile(contact_data['twitter_username']) | |
| if twitter_data: | |
| enriched['enrichments']['twitter'] = twitter_data | |
| enriched['sources'].append('twitter') | |
| # Facebook enrichment | |
| if contact_data.get('facebook_id'): | |
| facebook_data = self.enrich_facebook_profile(contact_data['facebook_id']) | |
| if facebook_data: | |
| enriched['enrichments']['facebook'] = facebook_data | |
| enriched['sources'].append('facebook') | |
| # Google search for additional info | |
| if contact_data.get('name'): | |
| search_query = f"{contact_data['name']}" | |
| if contact_data.get('company'): | |
| search_query += f" {contact_data['company']}" | |
| google_results = self.search_google(search_query, num_results=5) | |
| if google_results: | |
| enriched['enrichments']['google_search'] = google_results | |
| enriched['sources'].append('google') | |
| enriched['enriched_at'] = datetime.now().isoformat() | |
| enriched['total_sources'] = len(enriched['sources']) | |
| return enriched | |
| def get_enrichment_stats(self) -> Dict: | |
| """Get cache statistics and API usage""" | |
| stats = { | |
| 'cache_directory': str(self.cache_dir), | |
| 'cached_profiles': 0, | |
| 'cache_types': {}, | |
| 'total_api_calls': 0, | |
| 'cache_hits': 0, | |
| 'cache_misses': 0, | |
| 'total_enrichments': 0, | |
| 'successful_enrichments': 0, | |
| 'failed_enrichments': 0 | |
| } | |
| if self.cache_dir.exists(): | |
| cache_files = list(self.cache_dir.glob('*.json')) | |
| stats['cached_profiles'] = len(cache_files) | |
| for cache_file in cache_files: | |
| cache_type = cache_file.stem.split('_')[0] | |
| stats['cache_types'][cache_type] = stats['cache_types'].get(cache_type, 0) + 1 | |
| stats['configured_apis'] = [k for k, v in self.api_keys.items() if v] | |
| stats['total_apis'] = len(stats['configured_apis']) | |
| return stats | |
| # Convenience functions | |
| def create_enrichment_manager(username: str) -> DataEnrichmentManager: | |
| """Create a new enrichment manager instance""" | |
| return DataEnrichmentManager(username) | |
| def enrich_contact(username: str, contact_data: Dict) -> Dict: | |
| """Quick enrichment for a single contact""" | |
| manager = DataEnrichmentManager(username) | |
| return manager.enrich_contact_full(contact_data) | |