Files
extractions-actions/import_ibex35.php
T
2026-08-19 15:32:30 +00:00

1854 lines
26 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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);