
    hi,)                         d Z ddlZddlZddlmZ ddlmZmZmZ ddlm	Z	m
Z
mZmZ ddlmZ ddlZddlmZ  ej$                  e      Z e         G d d	      Z e       Zy)
z&
Service LinkedIn avec scraping Apify
    N)datetime)DictOptionalList)urlparseparse_qs	urlencode
urlunparse)ApifyClient)load_dotenvc                       e Zd ZdZg dZd ZdedefdZdedefdZ	dedefdZ
dedee   fd	Zd
edefdZd
edefdZdedefdZdededefdZdefdZdedeeeee   f   fdZy)LinkedInServicez&Service LinkedIn - Scraping avec Apify)z3^https?://(www\.)?linkedin\.com/in/[\w-]+/?(\?.*)?$z"^linkedin\.com/in/[\w-]+/?(\?.*)?$z*^(www\.)?linkedin\.com/in/[\w-]+/?(\?.*)?$c                    t        j                  d      | _        t        | j                        | _        | j                  r0t        | j                        | _        t        j                  d       y t        j                  d       y )NAPIFY_API_TOKENu%   ✅ LinkedIn Scraping activé (Apify)u=   ⚠️ LinkedIn Scraping désactivé (pas de APIFY_API_TOKEN))
osgetenvapify_tokenbooluse_scrapingr   clientloggerinfowarningselfs    E/home/www/therecruiter.miabetepe.com/app/services/linkedin_service.py__init__zLinkedInService.__init__   s\    99%67 !1!12%d&6&67DKKK?@NNZ[    urlreturnc                    	 t        |      }t        |j                  |j                  |j                  dddf      }|j                  d      S # t        $ r3}t        j                  d|        |j                  d      cY d}~S d}~ww xY w)u  
        Nettoie l'URL LinkedIn en enlevant les paramètres UTM et autres tracking
        
        Exemple:
        IN: https://www.linkedin.com/in/omar-farouk?utm_source=share&utm_campaign=share_via
        OUT: https://www.linkedin.com/in/omar-farouk
         /u   ⚠️ Erreur nettoyage URL: N)	r   r
   schemenetlocpathrstrip	Exceptionr   r   )r   r   parsed	clean_urles        r   clean_linkedin_urlz"LinkedInService.clean_linkedin_url&   s    	#c]F #$ I ##C(( 	#NN:1#>?::c?"	#s   A
A 	B	(B>B	B	c                 z    |sy| j                   D ])  }t        j                  ||t        j                        s) y y)u@   Vérifie si l'URL LinkedIn est valide (avec ou sans paramètres)FT)LINKEDIN_URL_PATTERNSrematch
IGNORECASE)r   r   patterns      r   is_valid_linkedin_urlz%LinkedInService.is_valid_linkedin_urlC   s9    11 	Gxxbmm4	 r   c                    |j                         }|j                  d      sd|z   }| j                  |      }|j                  dd      }d|v r|j                  dd      }|j	                  d      }t
        j                  d|        |S )	zNormalise l'URL LinkedInhttpzhttps://zlinkedin.comzwww.linkedin.comzwww.www.zwww.r#   u   🔗 URL normalisée: )strip
startswithr,   replacer'   r   r   )r   r   s     r   normalize_linkedin_urlz&LinkedInService.normalize_linkedin_urlN   s    iik ~~f%s"C %%c* kk.*<=++j&1C jjo,SE23
r   c                 v    t        j                  d|t         j                        }|r|j                  d      S dS )z%Extrait le nom d'utilisateur de l'URLzlinkedin\.com/in/([\w-]+)   N)r/   searchr1   group)r   r   r0   s      r   extract_usernamez LinkedInService.extract_usernamee   s.    		6R]]K!&u{{1~0D0r   linkedin_urlc                 6   | j                  |      }| j                  |      }| j                  |      st        d      | j                  s't
        j                  d       | j                  ||      S 	 t
        j                  d|        | j                  |      }t
        j                  d|        ||t        j                         j                         |dS # t        $ r4}t
        j                  d|        | j                  ||      cY d}~S d}~ww xY w)z|
        Scrape le profil LinkedIn avec Apify
        
        Utilise l'Actor: dev_fusion/linkedin-profile-scraper
        zURL LinkedIn invalideu4   ⚠️ Scraping désactivé, stockage URL uniquementu#   🔍 Scraping LinkedIn avec Apify: u   ✅ Profil LinkedIn scrapé: r   usernameadded_atscraped_datau   ❌ Erreur scraping LinkedIn: N)r9   r>   r3   
ValueErrorr   r   r   _create_minimal_profiler   _scrape_with_apifyr   utcnow	isoformatr(   error)r   r?   normalized_urlrB   rD   r+   s         r   scrape_profilezLinkedInService.scrape_profilej   s    44\B((8)).9455   NNQR//II	JKK=n=MNO  22>BLKK7zBC &$$OO-779 ,	   	JLL9!=>//II	Js   3A'C 	D$)DDDc                 l   	 d|gi}| j                   j                  d      j                  |      }|d   }t        | j                   j	                  |      j                               }|st        d      |d   }| j                  |      S # t        $ r}t        j                  d|         d}~ww xY w)	u   
        Scrape avec Apify Actor
        Actor: dev_fusion/linkedin-profile-scraper
        
        Coût: ~0.01$ par profil
        profileUrlsz#dev_fusion/linkedin-profile-scraper)	run_inputdefaultDatasetIdu#   Aucune donnée retournée par Apifyr   u   ❌ Erreur Apify: N)r   actorcalllistdatasetiterate_itemsrE   _transform_apify_datar(   r   rJ   )r   r?   rO   run
dataset_iditemsprofile_datar+   s           r   rG   z"LinkedInService._scrape_with_apify   s    	 ~I
 ++##$IJOOZcOdC /0J,,Z8FFHIE !FGG !8L --l;; 	LL-aS12	s   B	B 	B3B..B3
apify_datac                 T   	 g }|j                  dg       dd D ]  }|j                  |j                  dd      |j                  dd      |j                  di       j                  dd       d	|j                  d
i       j                  dd       |j                  dd      |j                  d      r|j                  dd      dd ndd        g }|j                  dg       dd D ]  }|j                  |j                  dd      |j                  dd      |j                  dd      |j                  di       j                  dd       d	|j                  d
i       j                  dd       d        |j                  dg       D cg c]  }|s|	 c}dd }g }|j                  dg       dd D ]f  }	|j                  |	j                  dd      |	j                  dd      |	j                  di       j                  di       j                  d      d       h g }
|j                  dg       D ]#  }|
j                  |j                  dd             % |j                  dd      |j                  d d      |j                  dd      |||||
|j                  d!d      |j                  d"d      |j                  d#d      |j                  d$d%      |j                  d&d%      d'S c c}w # t        $ r2}t        j	                  d(|        | j                         cY d}~S d}~ww xY w))u-   Transforme les données Apify en notre format
experienceN
   titler"   companyNamestartyearz - endPresentlocationdescription   )r_   companydurationre   rf   	education   
schoolName
degreeNamefieldOfStudy)schooldegreefield_of_studyyearsskills   certificationsname	authority
timePeriod	startDate)rv   issuerdate	languagesheadlinesummaryfullName
occupationprofilePictureconnectionsCountr   followersCountr}   r~   re   r]   rj   rs   ru   r|   	full_namer   profile_pic_urlconnections	followersu*   ❌ Erreur transformation données Apify: )getappendr(   r   rJ   _get_empty_scraped_data)r   r[   experiencesexprj   eduskillrs   ru   certr|   langr+   s                r   rV   z%LinkedInService._transform_apify_data   s%   9	2K!~~lB7< "" WWWb1"ww}b9#&777B#7#;#;FB#G"HCGGTY[]L^LbLbciktLuKv w #
B 7GJww}G]377="#=ds#Cce$  I!~~k26r:   !gglB7!gglB7&)ggnb&A # 4 8 8 DESQVXZI[I_I_`fhjIkHlm	"  *4")EOeOPSQSTF  N"'7<SbA %% HHVR0"hh{B7 HH\26::;KOOPVW'  I"{B7 7  &"!567 'NN:r:%>>)R8&NN:r:)& "0&'^^J;(nn\2>#->>2BB#G)~~.@!D'^^,<a@ ! P@  	2LLEaSIJ//11	2s7   F	K, K'K'EK, 'K, ,	L'5'L"L'"L'rB   c                 l    ||t        j                         j                         | j                         dS )zProfil minimal sans scrapingrA   )r   rH   rI   r   )r   r   rB   s      r   rF   z'LinkedInService._create_minimal_profile   s3       )335 88:	
 	
r   c                 "    dddg g g g g ddddddS )u&   Structure vide pour données scrapéesNr    r   s    r   r   z'LinkedInService._get_empty_scraped_data   s5      #
 	
r   c                 ,   	 | j                  |      }t        |j                  di       j                  d            }|rd}nd}d||fS # t        $ r}dt	        |      dfcY d}~S d}~wt
        $ r"}t        j                  d|        Y d}~y	d}~ww xY w)
z Valide l'URL et scrape le profilrD   r}   u,   ✅ Profil LinkedIn récupéré avec succèsu   ✅ URL LinkedIn enregistréeTFNu)   ❌ Erreur validation/scraping LinkedIn: )Fu*   Erreur lors de la récupération du profilN)rL   r   r   rE   strr(   r   rJ   )r   r   linkedin_datahas_datamessager+   s         r   validate_and_scrapez#LinkedInService.validate_and_scrape  s    	M //4M M--nbAEEjQRHH9-// 	'#a&$&& 	MLLDQCHIL	Ms*   AA	 		BA%B%B1BBN)__name__
__module____qualname____doc__r.   r   r   r,   r   r3   r9   r   r>   r   rL   rG   rV   rF   r   tupler   r   r   r   r   r      s    0	\#c #c #:	 	 	# # .1C 1HSM 1
#J3 #J4 #JJs t B;2 ;2 ;2z
3 
# 
$ 

 
$Ms MuT35N/O Mr   r   )r   r/   loggingr   typingr   r   r   urllib.parser   r   r	   r
   apify_clientr   r   dotenvr   	getLoggerr   r   r   linkedin_servicer   r   r   <module>r      s[    
   ' ' B B $ 	 			8	$ IM IMZ #$ r   