L'API Temps réél Transilen sans API ! Episode 2

Le service "transilien.mobi" a été arrêté il y a quelques mois. Il n'est plus possible d'exploiter le module "Prochains Départs" de cet ancien portail officiel.
Le nouveau module "Prochains Départs" est hébergé au sein du portail Transilien.com.

En inspectant les requêtes XHR renvoyées lors de la validation du formulaire de recherche, on voit apparaitre deux requêtes :

  • stopareas : https://www.transilien.com/aidesaisie/autocompletion/stopareas
    Cette requête permet d'obtenir la liste des arrêts au format JSON. Elle contient, entre autre, les noms des gares, le type de ligne (rer, train, metro, bus) et le code uic.
  • search : https://www.transilien.com/api/nextDeparture/search
    Cette requête permet d'obtenir la liste des prochains départs avec, cerise sur le gâteau, la liste des gares desservies ainsi que les heures de passages.

 

Requête stopareas

Il s'agit d'une requête de type GET, qui n'a pas besoin d'arguments particuliers.

En shell, avec cURL :

curl -i \
-H "Accept: application/json" \
-H "Content-Type:application/json" \
"https://www.transilien.com/aidesaisie/autocompletion/stopareas"

Réponse :

{"content":[{"label":"LE VESINET CENTRE","value":null,"coordonneeX":null,"coordonneeY":null,"name":null,"typeRue":null,"city":null,"coordLambertX":null,"coordLambertY":null,"uic":"8775807","train":false,"rer":true,"tramway":false,"metro":false,"bateau":false,"navette":false,"bus":true,"stopPoint":null,"ratp":false,"keywords":["LE VESINET CENTRE"],"nbAutomates":0,"sncf":false,"tempsReel":true,"abcdGare":false,"lignes":false,"entryPointType":null},{"label":"LE VESINET LE PECQ","value":null,"coordonneeX":null,"coordonneeY":null,"name":null,"typeRue":null,"city":null,"coordLambertX":null,"coordLambertY":null,"uic":"8775808","train":false,"rer":true,"tramway":false,"metro":false,"bateau":false,"navette":false,"bus":true,"stopPoint":null,"ratp":false,"keywords":["LE VESINET LE PECQ"],"nbAutomates":0,"sncf":false,"tempsReel":true,"abcdGare":false,"lignes":false,"entryPointType":null},{"label":"LES ARDOINES","value":null,"coordonneeX":null,"coordonneeY":null,"name":null,"typeRue":null,"city":null,"coordLambertX":null,"coordLambertY":null,"uic":"8749210","train":false,"rer":true,"tramway":false,"metro":false,"bateau":false,"navette":false,"bus":true,"stopPoint":null,"ratp":false,"keywords":["LES ARDOINES"],"nbAutomates":1,"sncf":false,"tempsReel":true,"abcdGare":false,"lignes":false,"entryPointType":null}, ...

 

En php, avec cURL :

<?php

$url = "https://www.transilien.com/aidesaisie/autocompletion/stopareas";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(                                                                          
    'Accept: application/json',
    'Content-Type:application/json')
);    

$output = curl_exec($ch);
$info = curl_getinfo($ch);
curl_close($ch);

if( $info['http_code'] == 200 ) {
    $json = json_decode($output);
    echo "<pre>";
    print_r($json);
    echo "</pre>";
}

Réponse :

stdClass Object
(
    [content] => Array
        (
            [0] => stdClass Object
                (
                    [label] => LE VESINET CENTRE
                    [value] => 
                    [coordonneeX] => 
                    [coordonneeY] => 
                    [name] => 
                    [typeRue] => 
                    [city] => 
                    [coordLambertX] => 
                    [coordLambertY] => 
                    [uic] => 8775807
                    [train] => 
                    [rer] => 1
                    [tramway] => 
                    [metro] => 
                    [bateau] => 
                    [navette] => 
                    [bus] => 1
                    [stopPoint] => 
                    [ratp] => 
                    [keywords] => Array
                        (
                            [0] => LE VESINET CENTRE
                        )

                    [nbAutomates] => 0
                    [sncf] => 
                    [tempsReel] => 1
                    [abcdGare] => 
                    [lignes] => 
                    [entryPointType] => 
                )
...

 

Requête search

Il s'agit d'une requête de type POST. Les arguments sont au format JSON.

Les arguments sont :

  • departure : la gare de départ
  • uicDeparture : le code uic de la gare de départ
  • uicArrival : le code uic de la gare d'arrvivée (peut être vide)
  • pmr : à priori, l'option "Infos accessibilité". On peut le positionner à "false"

 

En shell, avec cURL, la liste des prochains départs depuis la gare d'Herblay :

curl -i \
-H "Accept: application/json" \
-H "Content-Type:application/json" \
-X POST --data '{"departure":"HERBLAY","uicDeparture":"8738188","uicArrival":"","pmr":false}' "https://www.transilien.com/api/nextDeparture/search"

Réponse :

{"platformAvailable":true,"disruptionsAvailable":true,"arrivalTimeAvailable":false,"nextTrainsList":[{"modeTransportEnum":"TRAIN","lineTransportEnum":"TRAIN_J","codeMission":"MOCA","cancelled":false,"delayed":false,"departureTime":"10:10","arrivalTime":"10:59","destinationMission":"MANTES LA JOLIE","platform":"1","deservedStations":[{"label":"CONFLANS SAINTE-HONORINE","time":"10:16"},{"label":"CONFLANS FIN D'OISE","time":"10:19"},{"label":"MAURECOURT","time":"10:22"},{"label":"ANDRESY","time":"10:25"},{"label":"CHANTELOUP LES VIGNES","time":"10:28"},{"label":"TRIEL SUR SEINE","time":"10:32"},{"label":"VAUX SUR SEINE","time":"10:36"},{"label":"THUN LE PARADIS","time":"10:40"},{"label":"MEULAN HARDRICOURT","time":"10:42"},{"label":"JUZIERS","time":"10:47"},{"label":"GARGENVILLE","time":""},{"label":"ISSOU PORCHEVILLE","time":"10:50"},{"label":"LIMAY","time":"10:53"},{"label":"MANTES STATION","time":"10:57"}, ...

 

En PHP, avec cURL :

$url = "https://www.transilien.com/api/nextDeparture/search";
$data_string = '{"departure":"HERBLAY","uicDeparture":"8738188","uicArrival":"8738400","pmr":false}';

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_HEADER, false);

curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string); 
curl_setopt($ch, CURLOPT_HTTPHEADER, array(                                                                          
    'Accept: application/json',
    'Content-Type:application/json')
);    

$output = curl_exec($ch);
$info = curl_getinfo($ch);
$error = curl_error($ch);
curl_close($ch);

if( $info['http_code'] == 200 ) {
    $json = json_decode($output);
    echo "<pre>";
    print_r($json);
    echo "</pre>";
}

Réponse :

stdClass Object
(
    [platformAvailable] => 1
    [disruptionsAvailable] => 1
    [arrivalTimeAvailable] => 1
    [nextTrainsList] => Array
        (
            [0] => stdClass Object
                (
                    [modeTransportEnum] => TRAIN
                    [lineTransportEnum] => TRAIN_J
                    [codeMission] => PUCA
                    [cancelled] => 
                    [delayed] => 
                    [departureTime] => 10:17
                    [arrivalTime] => 10:43
                    [destinationMission] => GARE DE PARIS SAINT-LAZARE
                    [platform] => 2
                    [deservedStations] => Array
                        (
                            [0] => stdClass Object
                                (
                                    [label] => LA FRETTE MONTIGNY
                                    [time] => 10:20
                                )

                            [1] => stdClass Object
                                (
                                    [label] => CORMEILLES EN PARISIS
                                    [time] => 10:24
                                )

                            [2] => stdClass Object
                                (
                                    [label] => VAL D ARGENTEUIL
                                    [time] => 10:28
                                )

                            [3] => stdClass Object
                                (
                                    [label] => ARGENTEUIL
                                    [time] => 10:32
                                )

                            [4] => stdClass Object
                                (
                                    [label] => GARE DE PARIS SAINT-LAZARE
                                    [time] => 10:43
                                )

                        )

                    [hasTraficDisruption] => 
                    [hasTravauxDisruption] => 1
                    [disruptions] => Array
                        (
                            [0] => stdClass Object
                                (
                                    [id] => 
                                    [creationDate] => 
                                    [updateDate] => 2019-02-28T14:18:00
                                    [title] => Ligne J : Paris / Mantes 2,3,9,10,30 et 31/03
                                    [type] => TRAVAUX
                                    [validityPeriods] => Array
                                        (
                                            [0] => stdClass Object
                                                (
                                                    [startDate] => 2019-03-02T02:30:00
                                                    [endDate] => 2019-03-04T02:59:00
                                                    [now] => 
                                                )

                                            [1] => stdClass Object
                                                (
                                                    [startDate] => 2019-03-09T02:30:00
                                                    [endDate] => 2019-03-11T02:59:00
                                                    [now] => 1
                                                )

                                            [2] => stdClass Object
                                                (
                                                    [startDate] => 2019-03-30T02:30:00
                                                    [endDate] => 2019-04-01T02:59:00
                                                    [now] => 
                                                )

                                        )

                                    [detail] => ...
                                    [startingApplicationDate] => 2019-03-02T02:30:00
                                    [hasSubstitutionBus] => 
                                    [line] => J
                                    [transport] => 
                                )

                        )

                )
...

 

En local, cela marche très bien. Cependant, depuis un serveur distant, il y a un petit bémol. Voici le contenu de la réponse :

{"url_captcha":"/displayCaptchaPage.html"}

Les informations du transfert cURL :

Array
(
    [url] => https://www.transilien.com/api/nextDeparture/search
    [content_type] => application/json
    [http_code] => 429
    [header_size] => 160
    [request_size] => 229
    [filetime] => -1
    [ssl_verify_result] => 0
    [redirect_count] => 0
    [total_time] => 1.601726
    [namelookup_time] => 1.510025
    [connect_time] => 1.520981
    [pretransfer_time] => 1.549964
    [size_upload] => 83
    [size_download] => 42
    [speed_download] => 26
    [speed_upload] => 51
    [download_content_length] => 42
    [upload_content_length] => 83
    [starttransfer_time] => 1.601705
    [redirect_time] => 0
    [redirect_url] => 
    [primary_ip] => xx.xxx.xxx.xxx
    [certinfo] => Array
        (
        )

    [primary_port] => 443
    [local_ip] => xx.xxx.xxx.xxx
    [local_port] => 56596
)

Code HTTP 429 : Too Many Requests ! Ce qui explique certainement le captcha.

 

Pour contourner cela, la première idée est de "spoofer" le host et l'ip. Vous pouvez essayer, personnellement je n'ai pas réussi à obtenir la réponse voulue : j'obtiens soit une erreur 429, soit une erreur 400.

Deuxième idée, utiliser un proxy public :

<?php

$url = "https://www.transilien.com/api/nextDeparture/search";
$data_string = '{"departure":"HERBLAY","uicDeparture":"8738188","uicArrival":"8738400","pmr":false}';
$proxy_host = '151.80.147.76:8080';

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_HEADER, false);

curl_setopt($ch, CURLOPT_PROXY, $proxy_host);

curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string); 
curl_setopt($ch, CURLOPT_HTTPHEADER, array(                                                                          
    'Accept: application/json',
    'Content-Type:application/json')
);    

$output = curl_exec($ch);
$info = curl_getinfo($ch);
$error = curl_error($ch);
curl_close($ch);

if( $info['http_code'] == 200 ) {
    $json = json_decode($output);
    echo "<pre>";
    print_r($json);
    echo "</pre>";
}

Et, le tour est joué :

Array
(
    [url] => https://www.transilien.com/api/nextDeparture/search
    [content_type] => application/json;charset=UTF-8
    [http_code] => 200
    [header_size] => 681
    [request_size] => 332
    [filetime] => -1
    [ssl_verify_result] => 0
    [redirect_count] => 0
    [total_time] => 0.597784
    [namelookup_time] => 2.5E-5
    [connect_time] => 0.000403
    [pretransfer_time] => 0.28495
    [size_upload] => 83
    [size_download] => 36365
    [speed_download] => 60833
    [speed_upload] => 138
    [download_content_length] => -1
    [upload_content_length] => 83
    [starttransfer_time] => 0.593032
    [redirect_time] => 0
    [redirect_url] => 
    [primary_ip] => 151.80.147.76
    [certinfo] => Array
        (
        )

    [primary_port] => 8080
    [local_ip] => xx.xxx.xxx.xxx
    [local_port] => 50736
)

Le statut http est bien 200 et le primary_ip est bien l'adresse du proxy utilisé !

La réponse peut être exploitée... jusqu'à la prochaine mise à jour du module.

 

Cependant, pour une utilisation en production, je ne peux que vous conseiller l'utilisation de l'API officielle, même si elle ne permet pas une telle richesse de données.

 

Étiquettes