Cet article présente une méthode détaillée pour extraire des données chiffrées à partir d'une plateforme de surveillance de la qualité de l'air en ligne. La plateforme, accessible via https://www.aqistudy.cn/html/city_detail.html, affiche des informations essentielles telles que la température, l'humidité, la concentration de PM2.5 et l'indice de qualité de l'air (AQI) sous forme de graphiques interactifs. Le défi principal réside dans le fait que ces données sont chargées dynamiquement et transmises sous forme chiffrée, ce qui complique les méthodes d'extraction classiques basées sur l'interface utilisateur ou des requêtes directes.
Analyse du Trafic Réseau et Identification de l'API
L'observation du comportement du site web révèle que les données sont récupérées suite à une interaction utilisateur spécifique : la sélection d'une ville et d'une période, suivie d'un clic sur le bouton de rehcerche. En utilisant les outils de développement du navigateur (par exemple, l'onglet "Réseau"), on peut intercepter une requête AJAX de type POST. Cette requête est envoyée à une URL d'API et contient un paramètre unique, généralement nommé d, dont la valeur est une chaîne de caractères chiffrée. De manière similaire, la réponse du serveur à cette requête est également un texte chiffré.
La nature chiffrée et dynamique du paramètre d constitue le premier obstacle. Sa valeur change à chaque requête, empêchant une simple réutilisation. Le deuxième obstacle est la réponse chiffrée, qui nécessite un mécanisme de déchiffrement pour être interprétée. Ces observations nous orientent vers une stratégie d'ingénierie inverse du code JavaScript client pour comprendre comment ces opérations de chiffrement et de déchiffrement sont effectuées.
Ingénierie Inverse des Fonctions JavaScript
Le processus commence par l'analyse du code JavaScript exécuté lors de l'interaction utilisateur. En inspcetant le bouton de recherche via les outils de développement, il est possible de localiser le gestionnaire d'événements attaché à son clic.
- Le suivi du code JavaScript à partir de cet événement permet d'identifier la fonction principale déclenchée, souvent une fonction de haut niveau comme
getData(). - En explorant la mise en œuvre de cette fonction, on découvre qu'elle délègue la gestion des requêtes à des fonctions internes. Ces dernières convergent vers une fonction clé, ici nommée
getServerData(), qui est responsable de l'initiation de la requête AJAX. Cette fonction reçoit des paramètres tels que la méthode de l'API (par exemple, "GETCITYWEATHER") et un objet contenant les critères de recherche (ville, période). - Un examen plus approfondi de
getServerData()révèle qu'elle appelle d'autres fonctions pour préparer le corps de la requête et pour traiter la réponse.
Gestion de l'Obfuscation de Code
Il est courant que le code JavaScript des sites web soit obfusqué pour des raisons de performence ou de sécurité. Si les noms des fonctions comme getServerData et d'autres apparaissent sous une forme difficilement compréhensible (par exemple, a.b.c au lieu de getServerData), il peut être nécessaire d'utiliser un désobfuscateur JavaScript en ligne pour obtenir une version plus lisible du code source.
Découverte des Algorithmes de Chiffrement
Après avoir désobfusqué et analysé le code, deux fonctions essentielles sont identifiées :
getParam(api_method_str, query_object): Cette fonction est invoquée pour générer le paramètredchiffré. Elle prend en entrée une chaîne représentant la méthode d'API (par exemple, "GETCITYWEATHER") et un objetquery_objectqui contient les détails de la requête (city,typede données,startTime,endTime). Le code révèle quegetParamapplique une combinaison des algorithmes Base64 et AES pour chiffrerquery_objectet retourner la chaîne chiffrée.decodeData(encrypted_response_str): Cette fonction est le mécanisme de déchiffrement pour les données reçues du serveur. Elle est appelée dans la fonction de rappel de succès de la requête AJAX.decodeDataprend la chaîneencrypted_response_strchiffrée et utilise une séquence de déchiffrement combinant Base64, AES et DES pour restituer les données brutes, au format JSON.
La connaissance de ces fonctions et de leurs mécanismes de chiffrement/déchiffrement est cruciale pour automatiser l'extraction des données.
Implémentation Python pour l'Extraction de Données
Pour reproduire le comportement du navigateur et interagir avec l'API, nous utiliserons la bibliothèque Python PyExecJS pour exécuter le code JavaScript trouvé et requests pour envoyer les requêtes HTTP.
Préparation de l'environnement
Assurez-vous que les bibliothèques nécessaires sont installées et qu'un runtime JavaScript (comme Node.js) est disponible pour PyExecJS :
pip install PyExecJS requests
# Si Node.js n'est pas installé, PyExecJS peut utiliser d'autres runtimes ou le demander.
Fichier JavaScript des Fonctions API
Créez un fichier nommé api_functions.js. Copiez-y le code JavaScript désobfusqué contenant les implémentations de getParam et decodeData, ainsi que toutes leurs dépendances. Ajoutez-y également une fonction d'aide pour simplifier la génération du payload chiffré depuis Python :
// api_functions.js
// ... Début du code désobfusqué (getParam, decodeData, et leurs fonctions auxiliaires) ...
function generateEncryptedPayload(apiMethod, targetCity, dataType, startDate, endDate) {
var requestParams = {};
requestParams.city = targetCity;
requestParams.type = dataType;
requestParams.startTime = startDate;
requestParams.endTime = endDate;
return getParam(apiMethod, requestParams);
}
// ... Fin du code JavaScript ...
Script Python d'Extraction
Voici un exemple de script Python qui utilise les fonctions JavaScript définies pour chiffrer la requête, envoyer une demande à l'API, puis déchiffrer la réponse :
import execjs
import requests
import json
def recuperer_donnees_aqi_chiffrees(nom_ville, date_debut_str, date_fin_str):
"""
Récupère et déchiffre les données de qualité de l'air pour une ville et une période spécifiées.
"""
js_runtime_env = execjs.get() # Obtient l'environnement d'exécution JavaScript
# Paramètres de l'API
methode_api = 'GETCITYWEATHER'
type_donnees = 'HOUR' # Peut être 'DAY' ou 'MONTH' selon les besoins
# Charge et compile le code JavaScript
fichier_js = 'api_functions.js'
try:
with open(fichier_js, 'r', encoding='utf-8') as f:
contexte_js = js_runtime_env.compile(f.read())
except FileNotFoundError:
print(f"Erreur: Le fichier '{fichier_js}' n'a pas été trouvé.")
return None
except Exception as e:
print(f"Erreur lors de la compilation JavaScript: {e}")
return None
# Appelle la fonction JS pour générer le payload chiffré
commande_js_chiffrer = (
f'generateEncryptedPayload("{methode_api}", "{nom_ville}", "{type_donnees}", '
f'"{date_debut_str}", "{date_fin_str}")'
)
payload_chiffre = contexte_js.eval(commande_js_chiffrer)
# URL de l'API cible
url_api = 'https://www.aqistudy.cn/apinew/aqistudyapi.php'
# Envoie la requête POST à l'API
try:
reponse_http = requests.post(url_api, data={'d': payload_chiffre})
reponse_http.raise_for_status() # Lève une exception pour les codes d'état HTTP d'erreur
texte_reponse_chiffree = reponse_http.text
except requests.exceptions.RequestException as e:
print(f"Erreur réseau ou HTTP lors de la requête: {e}")
return None
# Appelle la fonction JS pour déchiffrer la réponse de l'API
commande_js_dechiffrer = f'decodeData("{texte_reponse_chiffree}")'
donnees_dechiffrees_str = contexte_js.eval(commande_js_dechiffrer)
# Tente de parser la chaîne JSON déchiffrée
try:
donnees_finales = json.loads(donnees_dechiffrees_str)
return donnees_finales
except json.JSONDecodeError as e:
print(f"Erreur lors du parsing JSON des données déchiffrées: {e}")
return None
# Exemple d'utilisation du script
if __name__ == '__main__':
ville_recherche = '北京' # Pékin
heure_debut = '2018-01-25 00:00:00'
heure_fin = '2018-01-25 23:00:00'
resultats_aqi = recuperer_donnees_aqi_chiffrees(ville_recherche, heure_debut, heure_fin)
if resultats_aqi:
# Affichage des données déchiffrées, formaté pour la lisibilité
print(json.dumps(resultats_aqi, indent=2, ensure_ascii=False))
# Vous pouvez ensuite parcourir et traiter les 'rows' dans 'resultats_aqi['result']['data']['rows']'
L'exécution de ce script fournira un objet JSON contenant les données de qualité de l'air déchiffrées, prêtes à être analysées ou stockées.