joedac-netvigie commited on
Commit
bf67806
·
1 Parent(s): a14e062

add description

Browse files
Files changed (1) hide show
  1. app.py +74 -16
app.py CHANGED
@@ -6,7 +6,42 @@ import random
6
  import pandas as pd
7
 
8
 
9
- def scrape_linkedin_jobs(keyword, location, num_pages=1):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  """
11
  Scrape les offres d'emploi sur LinkedIn en fonction des mots-clés et de l'emplacement spécifiés.
12
 
@@ -14,6 +49,7 @@ def scrape_linkedin_jobs(keyword, location, num_pages=1):
14
  keyword (str): Le mot-clé pour la recherche d'emploi (ex: Python, Data Scientist)
15
  location (str): L'emplacement pour la recherche d'emploi (ex: Paris, France)
16
  num_pages (int, optional): Le nombre de pages à scraper. Par défaut à 1.
 
17
 
18
  Returns:
19
  pandas.DataFrame ou str: Un DataFrame contenant les offres d'emploi trouvées ou un message d'erreur
@@ -25,6 +61,7 @@ def scrape_linkedin_jobs(keyword, location, num_pages=1):
25
  }
26
 
27
  for page in range(num_pages):
 
28
  params = {
29
  "keywords": keyword,
30
  "location": location,
@@ -42,25 +79,37 @@ def scrape_linkedin_jobs(keyword, location, num_pages=1):
42
  if not job_cards:
43
  return f"Avertissement: Aucune offre d'emploi trouvée sur la page {page + 1}. La structure de la page a peut-être changé."
44
 
45
- for card in job_cards:
 
 
46
  title = card.find('h3', class_='base-search-card__title')
47
  company = card.find('h4', class_='base-search-card__subtitle')
48
- location = card.find('span', class_='job-search-card__location')
49
  link = card.find('a', class_='base-card__full-link')
50
 
51
- if title and company and location and link:
52
- jobs.append({
53
  'Titre': title.text.strip(),
54
  'Entreprise': company.text.strip(),
55
- 'Lieu': location.text.strip(),
56
  'Lien': link['href']
57
- })
 
 
58
 
59
- time.sleep(random.uniform(1, 3))
 
 
 
 
 
 
 
60
 
61
  except requests.RequestException as e:
62
  return f"Une erreur s'est produite lors de la récupération de la page {page + 1}: {str(e)}"
63
 
 
64
  return pd.DataFrame(jobs)
65
 
66
 
@@ -77,10 +126,10 @@ def dataframe_to_html(df):
77
  if not isinstance(df, pd.DataFrame):
78
  return f"<p>{df}</p>"
79
 
80
- html = "<table border='1'><thead><tr>"
81
 
82
  for col in df.columns:
83
- html += f"<th>{col}</th>"
84
 
85
  html += "</tr></thead><tbody>"
86
 
@@ -88,9 +137,13 @@ def dataframe_to_html(df):
88
  html += "<tr>"
89
  for col in df.columns:
90
  if col == "Lien":
91
- html += f'<td><a href="{row[col]}" target="_blank">{row[col]}</a></td>'
 
 
 
 
92
  else:
93
- html += f"<td>{row[col]}</td>"
94
  html += "</tr>"
95
 
96
  html += "</tbody></table>"
@@ -98,7 +151,7 @@ def dataframe_to_html(df):
98
  return html
99
 
100
 
101
- def linkedin_scraper_interface(keyword, location, num_pages):
102
  """
103
  Interface pour le scraper LinkedIn qui valide les entrées et formate les résultats.
104
 
@@ -106,6 +159,7 @@ def linkedin_scraper_interface(keyword, location, num_pages):
106
  keyword (str): Le mot-clé pour la recherche d'emploi
107
  location (str): L'emplacement pour la recherche d'emploi
108
  num_pages (int): Le nombre de pages à scraper
 
109
 
110
  Returns:
111
  str: Résultats formatés en HTML avec des liens cliquables ou message d'erreur
@@ -120,7 +174,10 @@ def linkedin_scraper_interface(keyword, location, num_pages):
120
  except ValueError:
121
  return "Le nombre de pages doit être un nombre entier."
122
 
123
- results = scrape_linkedin_jobs(keyword, location, num_pages)
 
 
 
124
 
125
  if isinstance(results, str):
126
  return results
@@ -143,14 +200,15 @@ with gr.Blocks(title="Job Scraper") as demo:
143
  with gr.Column():
144
  keyword_input = gr.Textbox(label="Mot-clé (ex: Python, Data Scientist)", placeholder="Entrez un mot-clé")
145
  location_input = gr.Textbox(label="Lieu (ex: Paris, France)", placeholder="Entrez un lieu")
146
- pages_input = gr.Number(label="Nombre de pages à scraper", value=1, minimum=1, maximum=10)
 
147
  submit_button = gr.Button("Rechercher")
148
 
149
  output = gr.HTML(label="Résultats")
150
 
151
  submit_button.click(
152
  fn=linkedin_scraper_interface,
153
- inputs=[keyword_input, location_input, pages_input],
154
  outputs=output
155
  )
156
 
 
6
  import pandas as pd
7
 
8
 
9
+ def get_job_description(job_url, headers):
10
+ """
11
+ Récupère la description d'une offre d'emploi à partir de son URL.
12
+
13
+ Args:
14
+ job_url (str): L'URL de l'offre d'emploi
15
+ headers (dict): Les headers pour la requête HTTP
16
+
17
+ Returns:
18
+ str: La description de l'offre d'emploi ou un message d'erreur
19
+ """
20
+ try:
21
+ response = requests.get(job_url, headers=headers)
22
+ response.raise_for_status()
23
+ soup = BeautifulSoup(response.content, 'html.parser')
24
+
25
+ description_container = soup.find('div', class_='show-more-less-html__markup')
26
+
27
+ if description_container:
28
+ description = description_container.get_text(strip=True, separator=' ')
29
+ print(f"✅ Description récupérée pour {job_url[:50]}...")
30
+ print(f"Aperçu: {description[:200]}...")
31
+ return description
32
+ else:
33
+ print(f"❌ Balise de description non trouvée pour {job_url}")
34
+ return "Description non disponible"
35
+
36
+ except requests.RequestException as e:
37
+ print(f"❌ Erreur lors de la récupération de {job_url}: {str(e)}")
38
+ return f"Erreur lors de la récupération: {str(e)}"
39
+ except Exception as e:
40
+ print(f"❌ Erreur inattendue pour {job_url}: {str(e)}")
41
+ return f"Erreur inattendue: {str(e)}"
42
+
43
+
44
+ def scrape_linkedin_jobs(keyword, location, num_pages=1, include_description=True):
45
  """
46
  Scrape les offres d'emploi sur LinkedIn en fonction des mots-clés et de l'emplacement spécifiés.
47
 
 
49
  keyword (str): Le mot-clé pour la recherche d'emploi (ex: Python, Data Scientist)
50
  location (str): L'emplacement pour la recherche d'emploi (ex: Paris, France)
51
  num_pages (int, optional): Le nombre de pages à scraper. Par défaut à 1.
52
+ include_description (bool, optional): Si True, récupère aussi les descriptions. Par défaut à True.
53
 
54
  Returns:
55
  pandas.DataFrame ou str: Un DataFrame contenant les offres d'emploi trouvées ou un message d'erreur
 
61
  }
62
 
63
  for page in range(num_pages):
64
+ print(f"🔍 Scraping page {page + 1}...")
65
  params = {
66
  "keywords": keyword,
67
  "location": location,
 
79
  if not job_cards:
80
  return f"Avertissement: Aucune offre d'emploi trouvée sur la page {page + 1}. La structure de la page a peut-être changé."
81
 
82
+ print(f"📋 {len(job_cards)} offres trouvées sur la page {page + 1}")
83
+
84
+ for i, card in enumerate(job_cards):
85
  title = card.find('h3', class_='base-search-card__title')
86
  company = card.find('h4', class_='base-search-card__subtitle')
87
+ location_elem = card.find('span', class_='job-search-card__location')
88
  link = card.find('a', class_='base-card__full-link')
89
 
90
+ if title and company and location_elem and link:
91
+ job_data = {
92
  'Titre': title.text.strip(),
93
  'Entreprise': company.text.strip(),
94
+ 'Lieu': location_elem.text.strip(),
95
  'Lien': link['href']
96
+ }
97
+
98
+ print(f"📄 Traitement de l'offre {i + 1}: {job_data['Titre']} chez {job_data['Entreprise']}")
99
 
100
+ if include_description:
101
+ print(f"🔗 Récupération de la description...")
102
+ job_data['Description'] = get_job_description(job_data['Lien'], headers)
103
+ time.sleep(random.uniform(1, 2))
104
+
105
+ jobs.append(job_data)
106
+
107
+ time.sleep(random.uniform(1, 2))
108
 
109
  except requests.RequestException as e:
110
  return f"Une erreur s'est produite lors de la récupération de la page {page + 1}: {str(e)}"
111
 
112
+ print(f"✅ Scraping terminé! {len(jobs)} offres récupérées au total")
113
  return pd.DataFrame(jobs)
114
 
115
 
 
126
  if not isinstance(df, pd.DataFrame):
127
  return f"<p>{df}</p>"
128
 
129
+ html = "<table border='1' style='border-collapse: collapse; width: 100%;'><thead><tr>"
130
 
131
  for col in df.columns:
132
+ html += f"<th style='padding: 8px; background-color: #f2f2f2;'>{col}</th>"
133
 
134
  html += "</tr></thead><tbody>"
135
 
 
137
  html += "<tr>"
138
  for col in df.columns:
139
  if col == "Lien":
140
+ html += f'<td style="padding: 8px;"><a href="{row[col]}" target="_blank">Voir l\'offre</a></td>'
141
+ elif col == "Description":
142
+ # Limiter l'affichage de la description pour le tableau
143
+ desc = str(row[col])[:200] + "..." if len(str(row[col])) > 200 else str(row[col])
144
+ html += f"<td style='padding: 8px; max-width: 300px;'>{desc}</td>"
145
  else:
146
+ html += f"<td style='padding: 8px;'>{row[col]}</td>"
147
  html += "</tr>"
148
 
149
  html += "</tbody></table>"
 
151
  return html
152
 
153
 
154
+ def linkedin_scraper_interface(keyword, location, num_pages, include_description):
155
  """
156
  Interface pour le scraper LinkedIn qui valide les entrées et formate les résultats.
157
 
 
159
  keyword (str): Le mot-clé pour la recherche d'emploi
160
  location (str): L'emplacement pour la recherche d'emploi
161
  num_pages (int): Le nombre de pages à scraper
162
+ include_description (bool): Si True, récupère aussi les descriptions
163
 
164
  Returns:
165
  str: Résultats formatés en HTML avec des liens cliquables ou message d'erreur
 
174
  except ValueError:
175
  return "Le nombre de pages doit être un nombre entier."
176
 
177
+ print(f"🚀 Début du scraping: '{keyword}' à '{location}' sur {num_pages} page(s)")
178
+ print(f"📝 Récupération des descriptions: {'Oui' if include_description else 'Non'}")
179
+
180
+ results = scrape_linkedin_jobs(keyword, location, num_pages, include_description)
181
 
182
  if isinstance(results, str):
183
  return results
 
200
  with gr.Column():
201
  keyword_input = gr.Textbox(label="Mot-clé (ex: Python, Data Scientist)", placeholder="Entrez un mot-clé")
202
  location_input = gr.Textbox(label="Lieu (ex: Paris, France)", placeholder="Entrez un lieu")
203
+ pages_input = gr.Number(label="Nombre de pages à scraper", value=1, minimum=1, maximum=5)
204
+ description_checkbox = gr.Checkbox(label="Récupérer les descriptions des offres", value=True)
205
  submit_button = gr.Button("Rechercher")
206
 
207
  output = gr.HTML(label="Résultats")
208
 
209
  submit_button.click(
210
  fn=linkedin_scraper_interface,
211
+ inputs=[keyword_input, location_input, pages_input, description_checkbox],
212
  outputs=output
213
  )
214