From 03278dbff35f6b1a9510e6349b2b1b35c41b9574 Mon Sep 17 00:00:00 2001 From: jfgiraud Date: Wed, 19 Aug 2026 15:32:30 +0000 Subject: [PATCH] import cac40 et ibex35 --- import_ibex35.php | 1853 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1853 insertions(+) create mode 100644 import_ibex35.php diff --git a/import_ibex35.php b/import_ibex35.php new file mode 100644 index 0000000..ff9b566 --- /dev/null +++ b/import_ibex35.php @@ -0,0 +1,1853 @@ + MySQL GF + * + * Source : + * https://www.investing.com/equities/spain + * + * Référentiel : + * indice + * + * Données : + * cours_actions + * + * IMPORTANT : + * + * - Le nom Investing sert uniquement à retrouver la valeur + * dans la table indice. + * + * - Le symbole Investing n'est JAMAIS utilisé comme référence. + * + * - nom / isin / symbole proviennent exclusivement de indice. + * + * - Aucun changement n'est effectué dans la table indice. + * + * - Les dates sont générées en UTC. + * + * ============================================================ + */ + + +/* + * ============================================================ + * CONFIGURATION + * ============================================================ + */ + +$dbHost = 'localhost'; +$dbName = 'GF'; +$dbUser = 'root'; +$dbPass = 'sysadm-1963'; + +$url = 'https://www.investing.com/equities/spain'; + + +/* + * ============================================================ + * UTC + * + * MySQL travaille en UTC chez toi. + * ============================================================ + */ + +date_default_timezone_set('UTC'); + +$logPrefix = date('Y-m-d H:i:s'); + +echo "$logPrefix - Début import IBEX35\n"; + + +/* + * ============================================================ + * MYSQL + * ============================================================ + */ + +try { + + $pdo = new PDO( + "mysql:host=$dbHost;dbname=$dbName;charset=utf8mb4", + $dbUser, + $dbPass, + [ + PDO::ATTR_ERRMODE => + PDO::ERRMODE_EXCEPTION, + + PDO::ATTR_DEFAULT_FETCH_MODE => + PDO::FETCH_ASSOC, + + PDO::ATTR_EMULATE_PREPARES => + false + ] + ); +} catch (PDOException $e) { + + die("$logPrefix - ERREUR MYSQL : " . + $e->getMessage() . + PHP_EOL); +} + + +/* + * ============================================================ + * CHARGEMENT DU REFERENTIEL IBEX35 + * + * La table indice contient : + * + * indice + * nom + * isin + * symbole + * actif + * + * ============================================================ + */ + +$sqlIndice = " + + SELECT + nom, + isin, + symbole + + FROM indice + + WHERE indice = 'IBEX35' + + AND actif = 1 + +"; + +$stmtIndice = $pdo->query( + $sqlIndice +); + +$referentiel = []; + + +while ($row = $stmtIndice->fetch()) { + + /* + * La clé du tableau est le nom normalisé. + */ + + $cle = normalizeName( + (string)$row['nom'] + ); + + $referentiel[$cle] = [ + + 'nom' => + $row['nom'], + + 'isin' => + $row['isin'], + + 'symbole' => + $row['symbole'] + + ]; +} + + +echo +"$logPrefix - Référentiel IBEX35 : " . + count($referentiel) . + " valeurs\n"; + + +if (count($referentiel) !== 35) { + + echo + "$logPrefix - ATTENTION : le référentiel IBEX35 " . + "contient " . + count($referentiel) . + " valeurs au lieu de 35.\n"; +} + + +/* + * ============================================================ + * CURL + * ============================================================ + */ + +$ch = curl_init($url); + +curl_setopt_array( + $ch, + [ + + CURLOPT_RETURNTRANSFER => + true, + + CURLOPT_FOLLOWLOCATION => + true, + + CURLOPT_MAXREDIRS => + 10, + + CURLOPT_CONNECTTIMEOUT => + 15, + + CURLOPT_TIMEOUT => + 45, + + CURLOPT_ENCODING => + '', + + CURLOPT_SSL_VERIFYPEER => + true, + + CURLOPT_SSL_VERIFYHOST => + 2, + + CURLOPT_HTTP_VERSION => + CURL_HTTP_VERSION_2TLS, + + /* + * User-Agent navigateur réel. + */ + + CURLOPT_USERAGENT => + 'Mozilla/5.0 (X11; Linux x86_64; rv:153.0) ' . + 'Gecko/20100101 Firefox/153.0', + + CURLOPT_HTTPHEADER => + [ + + 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8', + + 'Accept-Language: fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7', + + 'Accept-Encoding: gzip, deflate, br', + + 'Cache-Control: no-cache', + + 'Pragma: no-cache', + + 'Upgrade-Insecure-Requests: 1', + + 'Sec-Fetch-Dest: document', + + 'Sec-Fetch-Mode: navigate', + + 'Sec-Fetch-Site: none', + + 'Sec-Fetch-User: ?1' + + ] + + ] +); + + +$html = curl_exec( + $ch +); + + +$httpCode = curl_getinfo( + $ch, + CURLINFO_HTTP_CODE +); + + +$curlError = curl_error( + $ch +); + + +curl_close( + $ch +); + + +/* + * ============================================================ + * VERIFICATION CURL + * ============================================================ + */ + +if ( + $html === false || + $html === '' +) { + + die("$logPrefix - ERREUR CURL : " . + $curlError . + PHP_EOL); +} + + +if ($httpCode !== 200) { + + /* + * Sauvegarde de la page reçue pour diagnostic. + */ + + file_put_contents( + '/tmp/investing_ibex35_http_error.html', + $html + ); + + die("$logPrefix - ERREUR HTTP : " . + $httpCode . + PHP_EOL); +} + + +echo +"$logPrefix - Page récupérée : " . + strlen($html) . + " octets\n"; + + +/* + * ============================================================ + * DOM + * ============================================================ + */ + +libxml_use_internal_errors( + true +); + + +$dom = new DOMDocument(); + + +if ( + !$dom->loadHTML( + $html + ) +) { + + die("$logPrefix - ERREUR : impossible de parser le HTML\n"); +} + + +libxml_clear_errors(); + + +$xpath = new DOMXPath( + $dom +); + + +/* + * ============================================================ + * RECHERCHE DU TABLEAU IBEX35 + * + * Investing utilise : + * + * Name + * Last + * High + * Low + * Chg. + * Chg. % + * Vol. + * Time + * + * ============================================================ + */ + +$tables = $xpath->query( + '//table' +); + + +$targetTable = null; + + +foreach ($tables as $table) { + + /* + * IMPORTANT : + * + * $table vient de DOMXPath::query(). + * On ne fait aucun getAttribute() dessus. + */ + + $rowsTable = $xpath->query( + './/tr', + $table + ); + + + if ( + $rowsTable === false || + $rowsTable->length === 0 + ) { + + continue; + } + + + $firstRow = + $rowsTable->item(0); + + + if ($firstRow === null) { + + continue; + } + + + $cells = $xpath->query( + './th|./td', + $firstRow + ); + + + if ( + $cells === false + ) { + + continue; + } + + + $headers = []; + + + foreach ($cells as $cell) { + + $headers[] = + cleanText( + $cell->textContent + ); + } + + + /* + * Tableau IBEX35 recherché. + */ + + if ( + + in_array( + 'Name', + $headers, + true + ) + + && + + in_array( + 'Last', + $headers, + true + ) + + && + + in_array( + 'High', + $headers, + true + ) + + && + + in_array( + 'Low', + $headers, + true + ) + + && + + in_array( + 'Chg. %', + $headers, + true + ) + + && + + in_array( + 'Vol.', + $headers, + true + ) + + && + + in_array( + 'Time', + $headers, + true + ) + + ) { + + $targetTable = + $table; + + + echo + "$logPrefix - Tableau IBEX35 trouvé\n"; + + + echo + "$logPrefix - Colonnes : " . + implode( + '|', + $headers + ) . + "\n"; + + + break; + } +} + + +/* + * ============================================================ + * TABLEAU NON TROUVE + * ============================================================ + */ + +if ( + $targetTable === null +) { + + file_put_contents( + '/tmp/investing_ibex35_error.html', + $html + ); + + + die("$logPrefix - ERREUR : tableau IBEX35 introuvable\n"); +} + + +/* + * ============================================================ + * COLONNES + * ============================================================ + */ + +$rows = $xpath->query( + './/tr', + $targetTable +); + + +if ( + $rows === false || + $rows->length === 0 +) { + + die("$logPrefix - ERREUR : aucune ligne trouvée\n"); +} + + +$headerCells = $xpath->query( + './th|./td', + $rows->item(0) +); + + +$columns = []; + + +if ($headerCells !== false) { + + foreach ( + $headerCells as $index => $cell + ) { + + $name = + cleanText( + $cell->textContent + ); + + + $columns[$name] = + $index; + } +} + + +/* + * ============================================================ + * FONCTIONS + * ============================================================ + */ + + +/* + * ------------------------------------------------------------ + * Nettoyage texte + * ------------------------------------------------------------ + */ + +function cleanText( + string $text +): string { + + $text = + html_entity_decode( + $text, + ENT_QUOTES | + ENT_HTML5, + 'UTF-8' + ); + + + $text = + preg_replace( + '/\s+/u', + ' ', + $text + ); + + + return trim( + $text + ); +} + + +/* + * ------------------------------------------------------------ + * Normalisation des noms + * + * Exemple : + * + * "ACS derived" + * ↓ + * "acs" + * + * "Téléfónica" + * ↓ + * "telefonica" + * + * "Caixabank" + * ↓ + * "caixabank" + * + * ------------------------------------------------------------ + */ + +function normalizeName( + string $name +): string { + + $name = + cleanText( + $name + ); + + + /* + * Supprimer "derived" ajouté par Investing. + * + * On accepte aussi d'éventuels espaces avant. + */ + + $name = + preg_replace( + '/\s+derived\s*$/iu', + '', + $name + ); + + + /* + * Minuscules. + */ + + $name = + mb_strtolower( + $name, + 'UTF-8' + ); + + + /* + * Suppression des accents. + */ + + $name = + strtr( + $name, + [ + + 'à' => 'a', + 'á' => 'a', + 'â' => 'a', + 'ä' => 'a', + 'ã' => 'a', + 'å' => 'a', + + 'ç' => 'c', + + 'è' => 'e', + 'é' => 'e', + 'ê' => 'e', + 'ë' => 'e', + + 'ì' => 'i', + 'í' => 'i', + 'î' => 'i', + 'ï' => 'i', + + 'ñ' => 'n', + + 'ò' => 'o', + 'ó' => 'o', + 'ô' => 'o', + 'ö' => 'o', + 'õ' => 'o', + + 'ù' => 'u', + 'ú' => 'u', + 'û' => 'u', + 'ü' => 'u', + + 'ý' => 'y', + 'ÿ' => 'y', + + 'œ' => 'oe', + 'æ' => 'ae' + ] + ); + + + /* + * Apostrophes. + */ + + $name = + str_replace( + [ + "'", + '’', + '`' + ], + '', + $name + ); + + + /* + * Tout ce qui n'est pas alphanumérique + * devient un espace. + */ + + $name = + preg_replace( + '/[^a-z0-9]+/u', + ' ', + $name + ); + + + /* + * Espaces multiples. + */ + + $name = + preg_replace( + '/\s+/u', + ' ', + $name + ); + + + return trim( + $name + ); +} + + +/* + * ------------------------------------------------------------ + * Nombre + * ------------------------------------------------------------ + */ + +function parseNumber( + ?string $value +): ?float { + + if ( + $value === null + ) { + + return null; + } + + + $value = + cleanText( + $value + ); + + + if ( + $value === '' + ) { + + return null; + } + + + /* + * Espaces insécables et espaces. + */ + + $value = + str_replace( + [ + "\xc2\xa0", + ' ', + '%' + ], + '', + $value + ); + + + /* + * Gestion décimale. + * + * Investing utilise généralement le point. + * + * Exemple : + * 24.755 + * -0.74% + */ + + if ( + + str_contains( + $value, + ',' + ) + + && + + !str_contains( + $value, + '.' + ) + + ) { + + $value = + str_replace( + ',', + '.', + $value + ); + } else { + + $value = + str_replace( + ',', + '', + $value + ); + } + + + /* + * Garder uniquement nombre. + */ + + $value = + preg_replace( + '/[^0-9.\-+]/', + '', + $value + ); + + + if ( + + $value === '' + + || + + !is_numeric( + $value + ) + + ) { + + return null; + } + + + return (float)$value; +} + + +/* + * ------------------------------------------------------------ + * Volume + * + * Exemples : + * + * 1.25K + * 3.4M + * 1.2B + * + * ------------------------------------------------------------ + */ + +function parseVolume( + ?string $value +): ?int { + + if ( + $value === null + ) { + + return null; + } + + + $value = + cleanText( + $value + ); + + + if ( + $value === '' + ) { + + return null; + } + + + /* + * Espaces. + */ + + $value = + str_replace( + [ + "\xc2\xa0", + ' ' + ], + '', + $value + ); + + + $multiplier = 1; + + + $last = + strtoupper( + substr( + $value, + -1 + ) + ); + + + if ( + $last === 'K' + ) { + + $multiplier = + 1000; + + + $value = + substr( + $value, + 0, + -1 + ); + } elseif ( + $last === 'M' + ) { + + $multiplier = + 1000000; + + + $value = + substr( + $value, + 0, + -1 + ); + } elseif ( + $last === 'B' + ) { + + $multiplier = + 1000000000; + + + $value = + substr( + $value, + 0, + -1 + ); + } + + + /* + * Décimale. + */ + + if ( + + str_contains( + $value, + ',' + ) + + && + + !str_contains( + $value, + '.' + ) + + ) { + + $value = + str_replace( + ',', + '.', + $value + ); + } else { + + $value = + str_replace( + ',', + '', + $value + ); + } + + + $value = + preg_replace( + '/[^0-9.]/', + '', + $value + ); + + + if ( + + $value === '' + + || + + !is_numeric( + $value + ) + + ) { + + return null; + } + + + return (int)round( + (float)$value * + $multiplier + ); +} + + +/* + * ============================================================ + * ALIAS INVESTING -> NOM DE LA TABLE indice + * + * Les clés sont déjà normalisées. + * + * ============================================================ + */ + +$aliases = [ + + /* + * ACS + */ + + 'acs' => + 'acs', + + + /* + * BBVA + */ + + 'bbva' => + 'banco bilbao vizcaya argentaria', + + + /* + * Caixabank + */ + + 'caixabank' => + 'caixabank', + + + /* + * Enagas + */ + + 'enagas' => + 'enagas', + + + /* + * Naturgy + */ + + 'naturgy energy' => + 'naturgy energy group', + + + /* + * Redeia + */ + + 'redeia corporacion' => + 'redeia', + + + /* + * Indra + */ + + 'indra a' => + 'indra sistemas', + + + /* + * Telefonica + */ + + 'telefonica' => + 'telefonica', + + + /* + * Banco Sabadell + */ + + 'banco de sabadell' => + 'banco sabadell', + + + /* + * Santander + */ + + 'santander' => + 'banco santander', + + + /* + * IAG + */ + + 'iag' => + 'international airlines group', + + + /* + * Amadeus + */ + + 'amadeus' => + 'amadeus it group', + + + /* + * Rovi + */ + + 'laborat rovi' => + 'laboratorios rovi', + + + /* + * Merlin + */ + + 'merlin properties sa' => + 'merlin properties', + + + /* + * Acciona Energía + */ + + 'corporacion acciona energias renovables' => + 'acciona energia', + + + /* + * Aena + */ + + 'aena' => + 'aena', + + + /* + * Cellnex + */ + + 'cellnex telecom' => + 'cellnex telecom', + + + /* + * Unicaja + */ + + 'unicaja banco' => + 'unicaja banco', + + + /* + * ArcelorMittal + */ + + 'arcelormittal' => + 'arcelormittal', + + + /* + * Fluidra + */ + + 'fluidra' => + 'fluidra', + + + /* + * Logista + */ + + 'logista' => + 'logista', + + + /* + * Solaria + */ + + 'solaria' => + 'solaria', + + + /* + * Puig + */ + + 'puig brands' => + 'puig brands' +]; + + +/* + * ============================================================ + * PREPARATION SQL + * ============================================================ + */ + +$sql = " + +INSERT INTO cours_actions +( + indice, + nom, + isin, + symbole, + cours, + plus_haut, + plus_bas, + variation, + variation_pct, + volume, + date_cours, + source +) + +VALUES +( + :indice, + :nom, + :isin, + :symbole, + :cours, + :plus_haut, + :plus_bas, + :variation, + :variation_pct, + :volume, + :date_cours, + :source +) + +ON DUPLICATE KEY UPDATE + + nom = + VALUES(nom), + + isin = + VALUES(isin), + + symbole = + VALUES(symbole), + + cours = + VALUES(cours), + + plus_haut = + VALUES(plus_haut), + + plus_bas = + VALUES(plus_bas), + + variation = + VALUES(variation), + + variation_pct = + VALUES(variation_pct), + + volume = + VALUES(volume), + + date_cours = + VALUES(date_cours), + + source = + VALUES(source) + +"; + + +$stmt = + $pdo->prepare( + $sql + ); + + +/* + * ============================================================ + * DATE COURS + * + * UTC. + * ============================================================ + */ + +$dateCours = + gmdate( + 'Y-m-d H:i:00' + ); + + +/* + * ============================================================ + * TRAITEMENT DES 35 VALEURS + * ============================================================ + */ + +$count = 0; + +$errors = 0; + +$notFound = []; + + +/* + * Ligne 0 = entêtes. + */ + +for ( + $r = 1; + $r < $rows->length; + $r++ +) { + + $row = + $rows->item( + $r + ); + + + if ( + $row === null + ) { + + continue; + } + + + /* + * Récupération des cellules. + */ + + $cells = + $xpath->query( + './td', + $row + ); + + + if ( + + $cells === false + + || + + $cells->length < 7 + + ) { + + continue; + } + + + /* + * -------------------------------------------------------- + * VALEURS DES CELLULES + * -------------------------------------------------------- + */ + + $values = []; + + + foreach ( + $cells as $cell + ) { + + $values[] = + cleanText( + $cell->textContent + ); + } + + + /* + * -------------------------------------------------------- + * NOM INVESTING + * -------------------------------------------------------- + */ + + $nomInvesting = + $values[$columns['Name'] ?? 0] ?? ''; + + + if ( + $nomInvesting === '' + ) { + + continue; + } + + + /* + * -------------------------------------------------------- + * RECHERCHE PAR NOM + * + * JAMAIS par symbole Investing. + * -------------------------------------------------------- + */ + + $cle = + normalizeName( + $nomInvesting + ); + + + $ref = + $referentiel[$cle] ?? null; + + + /* + * -------------------------------------------------------- + * ALIAS + * -------------------------------------------------------- + */ + + if ( + $ref === null && + isset( + $aliases[$cle] + ) + ) { + + $cleAlias = + normalizeName( + $aliases[$cle] + ); + + + $ref = + $referentiel[$cleAlias] ?? null; + } + + + /* + * -------------------------------------------------------- + * REFERENTIEL INCONNU + * -------------------------------------------------------- + */ + + if ( + $ref === null + ) { + + $notFound[] = + $nomInvesting; + + + echo + "$logPrefix - ATTENTION : " . + "référentiel inconnu : " . + $nomInvesting . + "\n"; + + + continue; + } + + + /* + * -------------------------------------------------------- + * IDENTITE DE LA VALEUR + * + * TOUT vient de indice. + * -------------------------------------------------------- + */ + + $nom = + (string)$ref['nom']; + + + $isin = + (string)$ref['isin']; + + + $symbole = + (string)$ref['symbole']; + + + /* + * -------------------------------------------------------- + * COURS + * -------------------------------------------------------- + */ + + $cours = + parseNumber( + $values[$columns['Last']] ?? null + ); + + + /* + * -------------------------------------------------------- + * PLUS HAUT + * -------------------------------------------------------- + */ + + $plusHaut = + parseNumber( + $values[$columns['High']] ?? null + ); + + + /* + * -------------------------------------------------------- + * PLUS BAS + * -------------------------------------------------------- + */ + + $plusBas = + parseNumber( + $values[$columns['Low']] ?? null + ); + + + /* + * -------------------------------------------------------- + * VARIATION % + * -------------------------------------------------------- + */ + + $variationPct = + parseNumber( + $values[$columns['Chg. %']] ?? null + ); + + + /* + * -------------------------------------------------------- + * VOLUME + * -------------------------------------------------------- + */ + + $volume = + parseVolume( + $values[$columns['Vol.']] ?? null + ); + + + /* + * -------------------------------------------------------- + * VARIATION ABSOLUE + * + * Si le cours actuel vaut C et que la variation + * est P %, alors : + * + * cours précédent = C / (1 + P/100) + * + * variation = C - cours précédent + * + * -------------------------------------------------------- + */ + + $variation = null; + + + if ( + + $cours !== null + + && + + $variationPct !== null + + && + + (100 + $variationPct) != 0 + + ) { + + $variation = + $cours * + $variationPct / + (100 + $variationPct); + } + + + /* + * -------------------------------------------------------- + * INSERTION + * -------------------------------------------------------- + */ + + try { + + $stmt->execute( + + [ + + ':indice' => + 'IBEX35', + + ':nom' => + $nom, + + ':isin' => + $isin, + + ':symbole' => + $symbole, + + ':cours' => + $cours, + + ':plus_haut' => + $plusHaut, + + ':plus_bas' => + $plusBas, + + ':variation' => + $variation, + + ':variation_pct' => + $variationPct, + + ':volume' => + $volume, + + ':date_cours' => + $dateCours, + + ':source' => + 'Investing' + + ] + ); + + + $count++; + + + echo + "$logPrefix - OK : " . + + str_pad( + (string)$count, + 2, + '0', + STR_PAD_LEFT + ) . + + " - " . + + $nom . + + " [" . + + $symbole . + + "] " . + + $isin . + + " - " . + + ( + $cours !== null + ? $cours + : 'NULL' + ) . + + " - " . + + ( + $variationPct !== null + ? $variationPct + : 'NULL' + ) . + + "%\n"; + } catch ( + PDOException $e + ) { + + $errors++; + + + echo + "$logPrefix - ERREUR SQL : " . + + $nom . + + " : " . + + $e->getMessage() . + + "\n"; + } +} + + +/* + * ============================================================ + * RESULTAT + * ============================================================ + */ + +echo "\n"; + + +echo +"$logPrefix - IBEX35 : " . + $count . + "/35 valeurs importées\n"; + + +/* + * ------------------------------------------------------------ + * VALEURS NON TROUVEES + * ------------------------------------------------------------ + */ + +if ( + count($notFound) > 0 +) { + + echo + "$logPrefix - VALEURS NON TROUVÉES DANS indice :\n"; + + + foreach ( + $notFound as $nom + ) { + + echo + " - " . + $nom . + "\n"; + } +} + + +/* + * ------------------------------------------------------------ + * ERREURS SQL + * ------------------------------------------------------------ + */ + +if ( + $errors > 0 +) { + + echo + "$logPrefix - Erreurs SQL : " . + $errors . + "\n"; +} + + +/* + * ============================================================ + * SECURITE + * + * L'import est considéré comme valide uniquement + * si les 35 valeurs ont été correctement importées. + * ============================================================ + */ + +if ( + $count !== 35 +) { + + echo + "$logPrefix - ATTENTION : import incomplet !\n"; + + + exit(1); +} + + +echo +"$logPrefix - IMPORT IBEX35 OK\n"; + + +exit(0);