stackConsult commited on
Commit
d7a5f99
·
verified ·
1 Parent(s): 4406729

Create linkedin_data.py

Browse files

LinkedIn data fetching module with RapidAPI integration.

Features:
- Fetches LinkedIn profile data via RapidAPI Fresh LinkedIn Profile Data API
- Automatic caching per user (30-day cache expiration)
- Rate limit handling and error management
- User-isolated cache directories
- Convenience functions for easy integration

Requires RAPIDAPI_KEY environment variable to be set in Space Secrets.

Files changed (1) hide show
  1. linkedin_data.py +268 -0
linkedin_data.py ADDED
@@ -0,0 +1,268 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """LinkedIn Data Fetching Module
2
+
3
+ Provides functions to fetch and enrich LinkedIn profile data using RapidAPI.
4
+ Includes caching to minimize API usage and respect rate limits.
5
+
6
+ Usage:
7
+ from linkedin_data import fetch_linkedin_profile, enrich_lead
8
+
9
+ profile = fetch_linkedin_profile("https://linkedin.com/in/username")
10
+ enriched = enrich_lead(lead_data)
11
+ """
12
+
13
+ import os
14
+ import json
15
+ import time
16
+ import requests
17
+ from pathlib import Path
18
+ from typing import Dict, Optional, Any
19
+
20
+
21
+ class LinkedInDataFetcher:
22
+ """Handles LinkedIn profile data fetching via RapidAPI"""
23
+
24
+ def __init__(self, api_key: Optional[str] = None, username: Optional[str] = None):
25
+ """Initialize LinkedIn data fetcher
26
+
27
+ Args:
28
+ api_key: RapidAPI key (defaults to RAPIDAPI_KEY env var)
29
+ username: Current user's username for cache directory
30
+ """
31
+ self.api_key = api_key or os.environ.get('RAPIDAPI_KEY', '')
32
+ self.username = username
33
+ self.base_url = "https://fresh-linkedin-profile-data.p.rapidapi.com"
34
+ self.headers = {
35
+ "x-rapidapi-key": self.api_key,
36
+ "x-rapidapi-host": "fresh-linkedin-profile-data.p.rapidapi.com"
37
+ }
38
+
39
+ # Set up cache directory for current user
40
+ if self.username:
41
+ self.cache_dir = Path(f"/data/users/{self.username}/linkedin_cache")
42
+ self.cache_dir.mkdir(parents=True, exist_ok=True)
43
+ else:
44
+ self.cache_dir = None
45
+
46
+ def _get_cache_path(self, linkedin_url: str) -> Optional[Path]:
47
+ """Get cache file path for a LinkedIn URL"""
48
+ if not self.cache_dir:
49
+ return None
50
+
51
+ # Create a safe filename from the LinkedIn URL
52
+ safe_name = linkedin_url.replace('https://', '').replace('/', '_').replace(':', '_')
53
+ return self.cache_dir / f"{safe_name}.json"
54
+
55
+ def _load_from_cache(self, linkedin_url: str) -> Optional[Dict]:
56
+ """Load profile data from cache if available and not expired"""
57
+ cache_path = self._get_cache_path(linkedin_url)
58
+ if not cache_path or not cache_path.exists():
59
+ return None
60
+
61
+ try:
62
+ with open(cache_path, 'r') as f:
63
+ cached_data = json.load(f)
64
+
65
+ # Check if cache is still valid (30 days)
66
+ cache_time = cached_data.get('cached_at', 0)
67
+ if time.time() - cache_time < (30 * 24 * 60 * 60): # 30 days in seconds
68
+ return cached_data.get('data')
69
+ except Exception as e:
70
+ print(f"Error loading cache: {e}")
71
+
72
+ return None
73
+
74
+ def _save_to_cache(self, linkedin_url: str, data: Dict) -> None:
75
+ """Save profile data to cache"""
76
+ cache_path = self._get_cache_path(linkedin_url)
77
+ if not cache_path:
78
+ return
79
+
80
+ try:
81
+ cached_data = {
82
+ 'cached_at': time.time(),
83
+ 'url': linkedin_url,
84
+ 'data': data
85
+ }
86
+ with open(cache_path, 'w') as f:
87
+ json.dump(cached_data, f, indent=2)
88
+ except Exception as e:
89
+ print(f"Error saving cache: {e}")
90
+
91
+ def fetch_profile(self, linkedin_url: str, use_cache: bool = True) -> Dict[str, Any]:
92
+ """Fetch LinkedIn profile data
93
+
94
+ Args:
95
+ linkedin_url: Full LinkedIn profile URL
96
+ use_cache: Whether to use cached data if available
97
+
98
+ Returns:
99
+ Dictionary containing profile data or error information
100
+ """
101
+ # Check cache first
102
+ if use_cache:
103
+ cached = self._load_from_cache(linkedin_url)
104
+ if cached:
105
+ return {
106
+ 'success': True,
107
+ 'data': cached,
108
+ 'from_cache': True
109
+ }
110
+
111
+ # Check if API key is configured
112
+ if not self.api_key:
113
+ return {
114
+ 'success': False,
115
+ 'error': 'RapidAPI key not configured',
116
+ 'message': 'Please add RAPIDAPI_KEY to Space Secrets'
117
+ }
118
+
119
+ # Fetch from API
120
+ try:
121
+ querystring = {"linkedin_url": linkedin_url}
122
+ response = requests.get(
123
+ f"{self.base_url}/get-linkedin-profile",
124
+ headers=self.headers,
125
+ params=querystring,
126
+ timeout=30
127
+ )
128
+
129
+ if response.status_code == 200:
130
+ data = response.json()
131
+
132
+ # Save to cache
133
+ if use_cache:
134
+ self._save_to_cache(linkedin_url, data)
135
+
136
+ return {
137
+ 'success': True,
138
+ 'data': data,
139
+ 'from_cache': False
140
+ }
141
+ elif response.status_code == 429:
142
+ return {
143
+ 'success': False,
144
+ 'error': 'Rate limit exceeded',
145
+ 'message': 'You have reached your API rate limit. Consider upgrading or waiting.'
146
+ }
147
+ else:
148
+ return {
149
+ 'success': False,
150
+ 'error': f'API error: {response.status_code}',
151
+ 'message': response.text
152
+ }
153
+
154
+ except requests.exceptions.Timeout:
155
+ return {
156
+ 'success': False,
157
+ 'error': 'Request timeout',
158
+ 'message': 'The API request timed out. Please try again.'
159
+ }
160
+ except Exception as e:
161
+ return {
162
+ 'success': False,
163
+ 'error': 'Fetch error',
164
+ 'message': str(e)
165
+ }
166
+
167
+ def enrich_lead(self, lead_data: Dict) -> Dict:
168
+ """Enrich lead data with LinkedIn profile information
169
+
170
+ Args:
171
+ lead_data: Dictionary containing lead information with 'linkedin_url' key
172
+
173
+ Returns:
174
+ Enriched lead data dictionary
175
+ """
176
+ linkedin_url = lead_data.get('linkedin_url', '')
177
+ if not linkedin_url:
178
+ return lead_data
179
+
180
+ # Fetch LinkedIn profile
181
+ result = self.fetch_profile(linkedin_url)
182
+
183
+ if result['success']:
184
+ profile = result['data']
185
+
186
+ # Add LinkedIn data to lead
187
+ enriched = lead_data.copy()
188
+ enriched['linkedin_profile'] = {
189
+ 'full_name': profile.get('full_name', ''),
190
+ 'headline': profile.get('headline', ''),
191
+ 'location': profile.get('location', ''),
192
+ 'summary': profile.get('summary', ''),
193
+ 'experiences': profile.get('experiences', []),
194
+ 'education': profile.get('education', []),
195
+ 'skills': profile.get('skills', []),
196
+ 'from_cache': result.get('from_cache', False)
197
+ }
198
+ enriched['linkedin_enriched'] = True
199
+ enriched['linkedin_enriched_at'] = time.time()
200
+ else:
201
+ # Add error info
202
+ enriched = lead_data.copy()
203
+ enriched['linkedin_enriched'] = False
204
+ enriched['linkedin_error'] = result.get('error', 'Unknown error')
205
+
206
+ return enriched
207
+
208
+ def get_cache_stats(self) -> Dict:
209
+ """Get cache statistics
210
+
211
+ Returns:
212
+ Dictionary with cache stats
213
+ """
214
+ if not self.cache_dir or not self.cache_dir.exists():
215
+ return {'cached_profiles': 0}
216
+
217
+ cached_files = list(self.cache_dir.glob('*.json'))
218
+ return {
219
+ 'cached_profiles': len(cached_files),
220
+ 'cache_directory': str(self.cache_dir)
221
+ }
222
+
223
+
224
+ # Convenience functions for easy import
225
+ def fetch_linkedin_profile(linkedin_url: str, api_key: Optional[str] = None,
226
+ username: Optional[str] = None, use_cache: bool = True) -> Dict:
227
+ """Fetch LinkedIn profile data (convenience function)
228
+
229
+ Args:
230
+ linkedin_url: Full LinkedIn profile URL
231
+ api_key: RapidAPI key (optional, uses env var if not provided)
232
+ username: Current user's username for caching
233
+ use_cache: Whether to use cached data
234
+
235
+ Returns:
236
+ Dictionary with profile data or error information
237
+ """
238
+ fetcher = LinkedInDataFetcher(api_key=api_key, username=username)
239
+ return fetcher.fetch_profile(linkedin_url, use_cache=use_cache)
240
+
241
+
242
+ def enrich_lead(lead_data: Dict, api_key: Optional[str] = None,
243
+ username: Optional[str] = None) -> Dict:
244
+ """Enrich lead data with LinkedIn information (convenience function)
245
+
246
+ Args:
247
+ lead_data: Lead dictionary with 'linkedin_url' key
248
+ api_key: RapidAPI key (optional)
249
+ username: Current user's username for caching
250
+
251
+ Returns:
252
+ Enriched lead data dictionary
253
+ """
254
+ fetcher = LinkedInDataFetcher(api_key=api_key, username=username)
255
+ return fetcher.enrich_lead(lead_data)
256
+
257
+
258
+ def get_cache_stats(username: str) -> Dict:
259
+ """Get LinkedIn cache statistics (convenience function)
260
+
261
+ Args:
262
+ username: Username to check cache for
263
+
264
+ Returns:
265
+ Dictionary with cache statistics
266
+ """
267
+ fetcher = LinkedInDataFetcher(username=username)
268
+ return fetcher.get_cache_stats()