Files
extractions-actions/import_ibex35.php
T

1854 lines
26 KiB
PHP
Raw Normal View History

2026-08-19 15:32:30 +00:00
<?php
declare(strict_types=1);
/*
* ============================================================
* IMPORT IBEX 35
* Investing.com -> 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);