Scrape automatiquement les noms des responsables relations entreprises sur les sites d'écoles
Size
18.8 KB
Version
1.1.1
Created
Nov 17, 2025
Updated
25 days ago
1// ==UserScript==
2// @name Extracteur Responsables Relations Entreprises
3// @description Scrape automatiquement les noms des responsables relations entreprises sur les sites d'écoles
4// @version 1.1.1
5// @match https://*.edu/*
6// @match https://*.ac.*/*
7// @match https://*.univ-*/*
8// @match https://*.university/*
9// @match https://*.universite/*
10// @match https://*.college/*
11// @match https://*.school/*
12// @match https://*.ecole/*
13// @match https://*.enseignement/*
14// @match https://*.education/*
15// @match https://*.campus/*
16// @match https://*.iut.*/*
17// @match https://*.iae.*/*
18// @match https://*.escp.eu/*
19// @match https://*.essec.edu/*
20// @match https://*.hec.edu/*
21// @match https://*.edhec.edu/*
22// @match https://*.em-lyon.com/*
23// @match https://*.skema.edu/*
24// @match https://*.kedge.edu/*
25// @match https://*.neoma-bs.fr/*
26// @match https://*.grenoble-em.com/*
27// @match https://*.tbs-education.fr/*
28// @match https://*.audencia.com/*
29// @match https://*.rennes-sb.fr/*
30// @match https://*.bsb-education.com/*
31// @match https://*.montpellier-bs.com/*
32// @match https://*.excelia-group.com/*
33// @match https://*.inseec.com/*
34// @match https://*.psl.eu/*
35// @match https://*.polytechnique.edu/*
36// @match https://*.centralesupelec.fr/*
37// @match https://*.mines-paristech.fr/*
38// @match https://*.ensae.fr/*
39// @match https://*.sciencespo.fr/*
40// @match https://*.dauphine.psl.eu/*
41// @match https://*.vatel.fr/*
42// @match https://*.ferrandi-paris.fr/*
43// @match https://*.paul-bocuse.com/*
44// @match https://*.ieseg.fr/*
45// @match https://*.esce.fr/*
46// @match https://*.istec.fr/*
47// @match https://*.ipag.edu/*
48// @match https://*.ppa.fr/*
49// @match https://*.ebs-paris.fr/*
50// @match https://*.emlv.fr/*
51// @match https://*.icd-ecoles.com/*
52// @match https://*.efap.com/*
53// @match https://*.sup-de-pub.com/*
54// @match https://*.iscom.fr/*
55// @match https://*.iscpa.fr/*
56// @match https://*.epitech.eu/*
57// @match https://*.epita.fr/*
58// @match https://*.42.fr/*
59// @match https://*.supinfo.com/*
60// @match https://*.hetic.net/*
61// @match https://*.gobelins.fr/*
62// @match https://*.ensad.fr/*
63// @match https://*.ensci.com/*
64// @match https://*.strate.design/*
65// @match https://*.edhec.com/*
66// @match https://*.ieseg.fr/*
67// @icon https://www.vatel.fr/assets/images/favicon.ico
68// @grant GM.getValue
69// @grant GM.setValue
70// @grant GM.xmlhttpRequest
71// ==/UserScript==
72(function() {
73 'use strict';
74
75 // Configuration
76 const CONFIG = {
77 buttonText: '🔍 Extraire Responsables RE',
78 storageKey: 'responsables_relations_entreprises',
79 keywords: ['relations entreprises', 'corporate relations', 'entreprise', 'partenariats', 'partnerships', 'business relations']
80 };
81
82 // Fonction pour créer le bouton d'extraction
83 function createExtractButton() {
84 const button = document.createElement('button');
85 button.id = 'extract-responsables-btn';
86 button.textContent = CONFIG.buttonText;
87 button.style.cssText = `
88 position: fixed;
89 top: 20px;
90 right: 20px;
91 z-index: 10000;
92 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
93 color: white;
94 border: none;
95 padding: 12px 24px;
96 border-radius: 8px;
97 font-size: 14px;
98 font-weight: 600;
99 cursor: pointer;
100 box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
101 transition: all 0.3s ease;
102 `;
103
104 button.addEventListener('mouseenter', () => {
105 button.style.transform = 'translateY(-2px)';
106 button.style.boxShadow = '0 6px 20px rgba(0, 0, 0, 0.3)';
107 });
108
109 button.addEventListener('mouseleave', () => {
110 button.style.transform = 'translateY(0)';
111 button.style.boxShadow = '0 4px 15px rgba(0, 0, 0, 0.2)';
112 });
113
114 button.addEventListener('click', extractResponsables);
115 document.body.appendChild(button);
116 console.log('Bouton d\'extraction créé');
117 }
118
119 // Fonction pour afficher un indicateur de chargement
120 function showLoadingIndicator() {
121 const loader = document.createElement('div');
122 loader.id = 'extraction-loader';
123 loader.innerHTML = `
124 <div style="display: flex; flex-direction: column; align-items: center; gap: 10px;">
125 <div class="spinner"></div>
126 <div>Extraction en cours...</div>
127 </div>
128 `;
129 loader.style.cssText = `
130 position: fixed;
131 top: 80px;
132 right: 20px;
133 z-index: 10000;
134 background: white;
135 color: #333;
136 padding: 20px;
137 border-radius: 8px;
138 box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
139 font-size: 14px;
140 `;
141
142 const style = document.createElement('style');
143 style.textContent = `
144 .spinner {
145 border: 3px solid #f3f3f3;
146 border-top: 3px solid #667eea;
147 border-radius: 50%;
148 width: 30px;
149 height: 30px;
150 animation: spin 1s linear infinite;
151 }
152 @keyframes spin {
153 0% { transform: rotate(0deg); }
154 100% { transform: rotate(360deg); }
155 }
156 `;
157 document.head.appendChild(style);
158 document.body.appendChild(loader);
159 }
160
161 // Fonction pour masquer l'indicateur de chargement
162 function hideLoadingIndicator() {
163 const loader = document.getElementById('extraction-loader');
164 if (loader) loader.remove();
165 }
166
167 // Fonction pour extraire le contenu de la page
168 function getPageContent() {
169 // Récupérer le texte visible de la page
170 const bodyText = document.body.innerText;
171
172 // Récupérer aussi les sections spécifiques qui pourraient contenir des contacts
173 const contactSections = document.querySelectorAll('[class*="contact"], [class*="team"], [class*="staff"], [id*="contact"], [id*="team"]');
174 let sectionsText = '';
175 contactSections.forEach(section => {
176 sectionsText += section.innerText + '\n';
177 });
178
179 return {
180 url: window.location.href,
181 title: document.title,
182 bodyText: bodyText.substring(0, 5000), // Limiter pour ne pas surcharger
183 sectionsText: sectionsText.substring(0, 3000)
184 };
185 }
186
187 // Fonction principale d'extraction
188 async function extractResponsables() {
189 console.log('Début de l\'extraction des responsables relations entreprises');
190 showLoadingIndicator();
191
192 try {
193 const pageContent = getPageContent();
194 console.log('Contenu de la page récupéré:', pageContent.url);
195
196 // Utiliser l'API RM.aiCall pour extraire les informations
197 const prompt = `Analyse le contenu suivant d'un site web d'école et extrait UNIQUEMENT les noms et informations des responsables relations entreprises, partenariats entreprises, ou corporate relations.
198
199URL: ${pageContent.url}
200Titre: ${pageContent.title}
201
202Contenu:
203${pageContent.sectionsText || pageContent.bodyText}
204
205Cherche spécifiquement les personnes avec des titres comme:
206- Responsable relations entreprises
207- Chargé(e) de relations entreprises
208- Corporate relations manager
209- Responsable partenariats
210- Business relations
211- Développement entreprises
212
213Si tu ne trouves AUCUNE personne correspondant à ces critères, retourne un tableau vide.`;
214
215 const result = await RM.aiCall(prompt, {
216 type: 'json_schema',
217 json_schema: {
218 name: 'responsables_extraction',
219 schema: {
220 type: 'object',
221 properties: {
222 responsables: {
223 type: 'array',
224 items: {
225 type: 'object',
226 properties: {
227 nom: { type: 'string' },
228 prenom: { type: 'string' },
229 titre: { type: 'string' },
230 email: { type: 'string' },
231 telephone: { type: 'string' },
232 campus: { type: 'string' }
233 },
234 required: ['nom']
235 }
236 },
237 ecole: { type: 'string' },
238 url: { type: 'string' }
239 },
240 required: ['responsables', 'ecole', 'url']
241 }
242 }
243 });
244
245 console.log('Résultat de l\'extraction:', result);
246
247 // Sauvegarder les résultats
248 await saveResults(result);
249
250 // Afficher les résultats
251 displayResults(result);
252
253 } catch (error) {
254 console.error('Erreur lors de l\'extraction:', error);
255 showError('Erreur lors de l\'extraction: ' + error.message);
256 } finally {
257 hideLoadingIndicator();
258 }
259 }
260
261 // Fonction pour sauvegarder les résultats
262 async function saveResults(result) {
263 try {
264 // Récupérer les résultats existants
265 const existingData = await GM.getValue(CONFIG.storageKey, '[]');
266 const allResults = JSON.parse(existingData);
267
268 // Ajouter les nouveaux résultats avec timestamp
269 const newEntry = {
270 ...result,
271 timestamp: new Date().toISOString(),
272 extractedAt: new Date().toLocaleString('fr-FR')
273 };
274
275 // Vérifier si cette URL existe déjà et la remplacer
276 const existingIndex = allResults.findIndex(r => r.url === result.url);
277 if (existingIndex >= 0) {
278 allResults[existingIndex] = newEntry;
279 } else {
280 allResults.push(newEntry);
281 }
282
283 await GM.setValue(CONFIG.storageKey, JSON.stringify(allResults));
284 console.log('Résultats sauvegardés');
285 } catch (error) {
286 console.error('Erreur lors de la sauvegarde:', error);
287 }
288 }
289
290 // Fonction pour afficher les résultats
291 function displayResults(result) {
292 // Supprimer l'ancien panneau s'il existe
293 const oldPanel = document.getElementById('results-panel');
294 if (oldPanel) oldPanel.remove();
295
296 const panel = document.createElement('div');
297 panel.id = 'results-panel';
298 panel.style.cssText = `
299 position: fixed;
300 top: 80px;
301 right: 20px;
302 z-index: 10000;
303 background: white;
304 color: #333;
305 padding: 20px;
306 border-radius: 8px;
307 box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
308 max-width: 400px;
309 max-height: 500px;
310 overflow-y: auto;
311 font-family: Arial, sans-serif;
312 `;
313
314 let html = `
315 <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
316 <h3 style="margin: 0; color: #667eea;">Résultats de l'extraction</h3>
317 <button id="close-panel" style="background: none; border: none; font-size: 20px; cursor: pointer; color: #999;">×</button>
318 </div>
319 <div style="margin-bottom: 10px;">
320 <strong>École:</strong> ${result.ecole || 'Non identifiée'}
321 </div>
322 <div style="margin-bottom: 15px;">
323 <strong>URL:</strong> <a href="${result.url}" target="_blank" style="color: #667eea; font-size: 12px; word-break: break-all;">${result.url}</a>
324 </div>
325 `;
326
327 if (result.responsables && result.responsables.length > 0) {
328 html += `<div style="margin-bottom: 10px;"><strong>Responsables trouvés: ${result.responsables.length}</strong></div>`;
329
330 result.responsables.forEach((resp, index) => {
331 html += `
332 <div style="background: #f5f5f5; padding: 12px; border-radius: 6px; margin-bottom: 10px; border-left: 3px solid #667eea;">
333 <div style="font-weight: 600; color: #333; margin-bottom: 5px;">
334 ${resp.prenom || ''} ${resp.nom}
335 </div>
336 ${resp.titre ? `<div style="color: #667eea; font-size: 13px; margin-bottom: 5px;">${resp.titre}</div>` : ''}
337 ${resp.campus ? `<div style="font-size: 12px; color: #666;">📍 ${resp.campus}</div>` : ''}
338 ${resp.email ? `<div style="font-size: 12px; color: #666;">📧 ${resp.email}</div>` : ''}
339 ${resp.telephone ? `<div style="font-size: 12px; color: #666;">📞 ${resp.telephone}</div>` : ''}
340 </div>
341 `;
342 });
343
344 html += `
345 <button id="copy-results" style="
346 width: 100%;
347 background: #667eea;
348 color: white;
349 border: none;
350 padding: 10px;
351 border-radius: 6px;
352 cursor: pointer;
353 font-weight: 600;
354 margin-top: 10px;
355 ">📋 Copier les résultats</button>
356 <button id="export-all" style="
357 width: 100%;
358 background: #764ba2;
359 color: white;
360 border: none;
361 padding: 10px;
362 border-radius: 6px;
363 cursor: pointer;
364 font-weight: 600;
365 margin-top: 10px;
366 ">💾 Exporter tous les résultats</button>
367 `;
368 } else {
369 html += `
370 <div style="background: #fff3cd; padding: 12px; border-radius: 6px; color: #856404; border-left: 3px solid #ffc107;">
371 ⚠️ Aucun responsable relations entreprises trouvé sur cette page.
372 </div>
373 `;
374 }
375
376 panel.innerHTML = html;
377 document.body.appendChild(panel);
378
379 // Ajouter les événements
380 document.getElementById('close-panel').addEventListener('click', () => panel.remove());
381
382 const copyBtn = document.getElementById('copy-results');
383 if (copyBtn) {
384 copyBtn.addEventListener('click', () => copyResultsToClipboard(result));
385 }
386
387 const exportBtn = document.getElementById('export-all');
388 if (exportBtn) {
389 exportBtn.addEventListener('click', exportAllResults);
390 }
391 }
392
393 // Fonction pour copier les résultats dans le presse-papiers
394 async function copyResultsToClipboard(result) {
395 let text = `École: ${result.ecole}\nURL: ${result.url}\n\nResponsables Relations Entreprises:\n\n`;
396
397 result.responsables.forEach((resp, index) => {
398 text += `${index + 1}. ${resp.prenom || ''} ${resp.nom}\n`;
399 if (resp.titre) text += ` Titre: ${resp.titre}\n`;
400 if (resp.campus) text += ` Campus: ${resp.campus}\n`;
401 if (resp.email) text += ` Email: ${resp.email}\n`;
402 if (resp.telephone) text += ` Téléphone: ${resp.telephone}\n`;
403 text += '\n';
404 });
405
406 try {
407 await GM.setClipboard(text);
408 showNotification('✅ Résultats copiés dans le presse-papiers !');
409 } catch (error) {
410 console.error('Erreur lors de la copie:', error);
411 }
412 }
413
414 // Fonction pour exporter tous les résultats
415 async function exportAllResults() {
416 try {
417 const allData = await GM.getValue(CONFIG.storageKey, '[]');
418 const results = JSON.parse(allData);
419
420 // Créer un CSV
421 let csv = 'École,URL,Prénom,Nom,Titre,Campus,Email,Téléphone,Date extraction\n';
422
423 results.forEach(result => {
424 result.responsables.forEach(resp => {
425 csv += `"${result.ecole || ''}","${result.url}","${resp.prenom || ''}","${resp.nom}","${resp.titre || ''}","${resp.campus || ''}","${resp.email || ''}","${resp.telephone || ''}","${result.extractedAt || ''}"\n`;
426 });
427 });
428
429 // Télécharger le fichier
430 const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
431 const link = document.createElement('a');
432 link.href = URL.createObjectURL(blob);
433 link.download = `responsables_relations_entreprises_${new Date().toISOString().split('T')[0]}.csv`;
434 link.click();
435
436 showNotification('✅ Export CSV téléchargé !');
437 } catch (error) {
438 console.error('Erreur lors de l\'export:', error);
439 showError('Erreur lors de l\'export');
440 }
441 }
442
443 // Fonction pour afficher une notification
444 function showNotification(message) {
445 const notif = document.createElement('div');
446 notif.textContent = message;
447 notif.style.cssText = `
448 position: fixed;
449 top: 20px;
450 left: 50%;
451 transform: translateX(-50%);
452 z-index: 10001;
453 background: #4caf50;
454 color: white;
455 padding: 12px 24px;
456 border-radius: 6px;
457 box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
458 font-weight: 600;
459 `;
460 document.body.appendChild(notif);
461
462 setTimeout(() => notif.remove(), 3000);
463 }
464
465 // Fonction pour afficher une erreur
466 function showError(message) {
467 const error = document.createElement('div');
468 error.textContent = message;
469 error.style.cssText = `
470 position: fixed;
471 top: 80px;
472 right: 20px;
473 z-index: 10000;
474 background: #f44336;
475 color: white;
476 padding: 15px;
477 border-radius: 8px;
478 box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
479 max-width: 300px;
480 `;
481 document.body.appendChild(error);
482
483 setTimeout(() => error.remove(), 5000);
484 }
485
486 // Initialisation
487 function init() {
488 console.log('Extension Extracteur Responsables Relations Entreprises initialisée');
489
490 // Attendre que le DOM soit prêt
491 if (document.readyState === 'loading') {
492 document.addEventListener('DOMContentLoaded', createExtractButton);
493 } else {
494 createExtractButton();
495 }
496 }
497
498 // Démarrer l'extension
499 init();
500})();