Files
extractions-actions/import_dowjones.php
T

1501 lines
23 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 DOW JONES INDUSTRIAL AVERAGE
* Investing.com -> MySQL GF
*
* Source :
* https://www.investing.com/equities/americas
*
* Référentiel :
* indice
*
* Données :
* cours_actions
*
* IMPORTANT :
*
* - NE MODIFIE JAMAIS la table indice
* - nom / ISIN / symbole viennent de indice
* - la correspondance se fait sur le NOM
* - "derived" est supprimé du nom Investing
*
* ============================================================
*/
$dbHost = 'localhost';
$dbName = 'GF';
$dbUser = 'root';
$dbPass = 'sysadm-1963';
$url = 'https://www.investing.com/equities/americas';
$logPrefix = date('Y-m-d H:i:s');
echo "$logPrefix - Début import DOWJONES\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
*
* On récupère uniquement les valeurs actives.
*
* Alphabet pourra être présent dans indice avec :
*
* actif = 0
*
* tant que le changement n'est pas effectif.
*
* ============================================================
*/
$sqlIndice = "
SELECT
nom,
isin,
symbole
FROM indice
WHERE indice = 'DOWJONES'
AND actif = 1
";
$stmtIndice = $pdo->query($sqlIndice);
$referentiel = [];
while ($row = $stmtIndice->fetch()) {
$cle = normalizeName(
$row['nom']
);
$referentiel[$cle] = [
'nom' =>
$row['nom'],
'isin' =>
$row['isin'],
'symbole' =>
$row['symbole']
];
}
echo
"$logPrefix - Référentiel DOWJONES : " .
count($referentiel) .
" valeurs\n";
if (count($referentiel) !== 30) {
echo
"$logPrefix - ATTENTION : le référentiel " .
"DOWJONES contient " .
count($referentiel) .
" valeurs au lieu de 30.\n";
}
/*
* ============================================================
* CURL
* ============================================================
*
* Investing peut répondre 403 selon le User-Agent.
*
* On utilise ici une requête proche d'un navigateur réel.
*
* ============================================================
*/
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER =>
true,
CURLOPT_FOLLOWLOCATION =>
true,
CURLOPT_MAXREDIRS =>
5,
CURLOPT_CONNECTTIMEOUT =>
15,
CURLOPT_TIMEOUT =>
30,
CURLOPT_ENCODING =>
'',
CURLOPT_SSL_VERIFYPEER =>
true,
CURLOPT_SSL_VERIFYHOST =>
2,
CURLOPT_USERAGENT =>
'Mozilla/5.0 (X11; Linux x86_64) ' .
'AppleWebKit/537.36 ' .
'(KHTML, like Gecko) ' .
'Chrome/139.0.0.0 Safari/537.36',
CURLOPT_HTTPHEADER => [
'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
'Accept-Language: en-US,en;q=0.9',
'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) {
file_put_contents(
'/tmp/investing_dowjones_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
*
* Colonnes attendues :
*
* Name
* Last
* High
* Low
* Chg.
* Chg. %
* Vol.
* Time
*
* ============================================================
*/
$tables = $xpath->query('//table');
$targetTable = null;
foreach ($tables as $table) {
$rowsTable = $xpath->query(
'.//tr',
$table
);
if ($rowsTable->length === 0) {
continue;
}
$firstRow =
$rowsTable->item(0);
$cells = $xpath->query(
'./th|./td',
$firstRow
);
$headers = [];
foreach ($cells as $cell) {
$headers[] = cleanText(
$cell->textContent
);
}
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 DOWJONES trouvé\n";
echo
"$logPrefix - Colonnes : " .
implode(
'|',
$headers
) .
"\n";
break;
}
}
/*
* ============================================================
* TABLEAU NON TROUVE
* ============================================================
*/
if ($targetTable === null) {
file_put_contents(
'/tmp/investing_dowjones_error.html',
$html
);
die("$logPrefix - ERREUR : tableau DOWJONES introuvable\n");
}
/*
* ============================================================
* COLONNES
* ============================================================
*/
$rows = $xpath->query(
'.//tr',
$targetTable
);
$headerCells = $xpath->query(
'./th|./td',
$rows->item(0)
);
$columns = [];
foreach ($headerCells as $index => $cell) {
$name = cleanText(
$cell->textContent
);
$columns[$name] = $index;
}
/*
* ============================================================
* PREPARATION MYSQL
* ============================================================
*/
$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 / HEURE
*
* MySQL utilise UTC.
*
* On demande explicitement à MySQL son heure UTC.
*
* ============================================================
*/
$stmtDate = $pdo->query(
"SELECT UTC_TIMESTAMP() AS utc_now"
);
$dateCours =
$stmtDate->fetch()['utc_now'];
$dateCours =
substr(
$dateCours,
0,
16
) .
':00';
echo
"$logPrefix - Date cours UTC : " .
$dateCours .
"\n";
/*
* ============================================================
* TRAITEMENT DES VALEURS
* ============================================================
*/
$count = 0;
$errors = 0;
$notFound = [];
/*
* ============================================================
* LIGNE 0 = ENTETES
* ============================================================
*/
for (
$r = 1;
$r < $rows->length;
$r++
) {
$row =
$rows->item($r);
$cells = $xpath->query(
'./td',
$row
);
if ($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;
}
/*
* --------------------------------------------------------
* SUPPRESSION DE "derived"
*
* Exemples :
*
* ACS derived
* Apple derived
*
* devient :
*
* ACS
* Apple
*
* --------------------------------------------------------
*/
$nomNettoye =
preg_replace(
'/\s+derived\s*$/iu',
'',
$nomInvesting
);
$nomNettoye =
trim(
$nomNettoye
);
/*
* --------------------------------------------------------
* RECHERCHE PAR NOM
* --------------------------------------------------------
*/
$cle =
normalizeName(
$nomNettoye
);
$ref =
$referentiel[$cle]
?? null;
/*
* --------------------------------------------------------
* ALIAS
*
* Les noms Investing peuvent légèrement différer.
* --------------------------------------------------------
*/
if ($ref === null) {
$aliases = [
'3m' =>
'3M',
'amazon.com' =>
'Amazon.com',
'american express' =>
'American Express',
'amgen' =>
'Amgen',
'apple' =>
'Apple',
'boeing' =>
'Boeing',
'caterpillar' =>
'Caterpillar',
'chevron' =>
'Chevron',
'cisco' =>
'Cisco Systems',
'coca cola' =>
'Coca-Cola',
'disney' =>
'Disney',
'goldman sachs' =>
'Goldman Sachs',
'home depot' =>
'Home Depot',
'honeywell' =>
'Honeywell International',
'ibm' =>
'IBM',
'johnson johnson' =>
'Johnson & Johnson',
'jpmorgan' =>
'JPMorgan Chase',
'mcdonalds' =>
'McDonald\'s',
'merck' =>
'Merck & Co.',
'microsoft' =>
'Microsoft',
'nike' =>
'Nike',
'nvidia' =>
'NVIDIA',
'procter gamble' =>
'Procter & Gamble',
'salesforce' =>
'Salesforce',
'sherwin williams' =>
'Sherwin-Williams',
'travelers' =>
'Travelers',
'unitedhealth' =>
'UnitedHealth Group',
'verizon' =>
'Verizon Communications',
'visa' =>
'Visa',
'walmart' =>
'Walmart',
'alphabet' =>
'Alphabet'
];
if (
isset(
$aliases[$cle]
)
) {
$cleAlias =
normalizeName(
$aliases[$cle]
);
$ref =
$referentiel[$cleAlias] ?? null;
}
}
/*
* --------------------------------------------------------
* ABSENT DU REFERENTIEL
* --------------------------------------------------------
*/
if ($ref === null) {
$notFound[] =
$nomInvesting .
' / ' .
$nomNettoye;
echo
"$logPrefix - ATTENTION : " .
"référentiel inconnu : " .
$nomInvesting .
" -> " .
$nomNettoye .
"\n";
continue;
}
/*
* --------------------------------------------------------
* REFERENTIEL
* --------------------------------------------------------
*/
$nom =
$ref['nom'];
$isin =
$ref['isin'];
$symbole =
$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
* --------------------------------------------------------
*/
$variation = null;
if (
$cours !== null &&
$variationPct !== null &&
(100 + $variationPct) != 0
) {
$variation =
$cours *
$variationPct /
(100 + $variationPct);
}
/*
* --------------------------------------------------------
* INSERTION
* --------------------------------------------------------
*/
try {
$stmt->execute([
':indice' =>
'DOWJONES',
':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') .
" - " .
($variationPct ?? 'NULL') .
"%\n";
} catch (PDOException $e) {
$errors++;
echo
"$logPrefix - ERREUR SQL : " .
$nom .
" : " .
$e->getMessage() .
"\n";
}
}
/*
* ============================================================
* RESULTAT
* ============================================================
*/
echo "\n";
echo
"$logPrefix - DOWJONES : " .
$count .
"/30 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
* ============================================================
*/
if ($count !== 30) {
echo
"$logPrefix - ATTENTION : import incomplet !\n";
exit(1);
}
echo
"$logPrefix - IMPORT DOWJONES OK\n";
exit(0);
/*
* ============================================================
* 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 NOM
* ------------------------------------------------------------
*/
function normalizeName(
string $name
): string {
$name =
cleanText(
$name
);
/*
* Apostrophes
*/
$name =
str_replace(
[
'',
'`'
],
"'",
$name
);
/*
* Tirets
*/
$name =
str_replace(
[
'',
'—'
],
'-',
$name
);
/*
* Espaces autour des tirets
*/
$name =
preg_replace(
'/\s*-\s*/u',
'-',
$name
);
/*
* Suppression de certains caractères
* de ponctuation pour faciliter
* les correspondances.
*/
$name =
preg_replace(
'/[.,]/u',
'',
$name
);
/*
* Espaces multiples
*/
$name =
preg_replace(
'/\s+/u',
' ',
$name
);
return mb_strtolower(
trim($name),
'UTF-8'
);
}
/*
* ------------------------------------------------------------
* PARSE NUMBER
* ------------------------------------------------------------
*/
function parseNumber(
?string $value
): ?float {
if ($value === null) {
return null;
}
$value =
cleanText(
$value
);
if ($value === '') {
return null;
}
$value =
str_replace(
[
"\xc2\xa0",
' ',
'%'
],
'',
$value
);
/*
* Format français
*/
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 (float)$value;
}
/*
* ------------------------------------------------------------
* PARSE VOLUME
* ------------------------------------------------------------
*/
function parseVolume(
?string $value
): ?int {
if ($value === null) {
return null;
}
$value =
cleanText(
$value
);
if ($value === '') {
return null;
}
$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
);
}
$value =
str_replace(
',',
'.',
$value
);
$value =
preg_replace(
'/[^0-9.]/',
'',
$value
);
if (
$value === ''
||
!is_numeric(
$value
)
) {
return null;
}
return (int)round(
(float)$value *
$multiplier
);
}