Attendez-vous à AJAX avec HtmlAgilityPack dans Xamarin

ajax c# html-agility-pack xamarin

Question

J'ai une question qui semble avoir été posée auparavant, mais qui est un peu différente. J'essaie d'extraire des données de ce site Web, mais le problème, c'est qu'il semble être chargé d'AJAX. À cause de cela, mon application est incapable de trouver les identifiants et les classes dans le HTML que je cherche.

Vous pouvez reproduire ceci en inspectant un élément ou en visualisant la source. En regardant la source, je vois beaucoup moins qu'en inspectant un élément.

Je pensais pouvoir retrouver le fichier contenant AJAX pour charger ce code HTML en appuyant sur F12, accéder à l'onglet Réseau et en sélectionnant XHR, mais je ne parviens pas à le trouver.

Ma question est la suivante: comment récupérer ces données ou savoir quel fichier est utilisé pour collecter les données?

Un exemple de mon code (je ne parviens pas à trouver Timetable_toolbar_elementSelect_popup0 ):

private async Task GetHtmlDocument(string url)
        {
            HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);
            //request.Credentials = new LoginCredentials().Credentials;

            try
            {
                WebResponse myResponse = await request.GetResponseAsync();
                HtmlDocument htmlDoc = new HtmlDocument();
                htmlDoc.OptionFixNestedTags = true;
                htmlDoc.Load(myResponse.GetResponseStream());
                var test = htmlDoc.GetElementbyId("Timetable_toolbar_elementSelect_popup0");
            }
            catch (Exception e)
            {
            }
        }

Réponse acceptée

Solution où vous appelez la méthode ajax en utilisant une requête en ligne.

Alors je me suis ennuyé et j'ai compris l'essentiel. Ce qui manque ci-dessous, c'est comment identifier le Klase par son identifiant. L'exemple ci-dessous va chercher la klase '1GLD'. Nous avons besoin de cookies pour pouvoir demander à quelle école nous allons chercher le Klase. De plus, le code ci-dessous ne renvoie que JSON - et non HTML, car il s’agit d’une méthode ajax que nous appelons.

CookieContainer cookies = new CookieContainer();
try
{
    string webAddr = "https://roosters.windesheim.nl/";
    var httpWebRequest = (HttpWebRequest)WebRequest.Create(webAddr);
    httpWebRequest.ContentType = "application/json; charset=utf-8";
    httpWebRequest.Method = "POST";
    httpWebRequest.CookieContainer = cookies;        
    httpWebRequest.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
    httpWebRequest.Headers.Add("X-Requested-With", "XMLHttpRequest");

    var httpResponse = (HttpWebResponse)httpWebRequest.GetResponse();
    using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
    {
        cookies.Add(httpWebRequest.CookieContainer.GetCookies(httpWebRequest.RequestUri));
    }
}
catch (WebException ex)
{
    Console.WriteLine(ex.Message);
}

//According to my web debugger the cookie will last until the 10th of December. So need to fix a new cookie until then.
//I noticed the url used unixtimestamps at the end of the url. So we just add the unixtimestamp at the end for each request.
long unixTimeStamp = new DateTimeOffset(DateTime.Now).ToUnixTimeMilliseconds() - 100;

//we are now ready to call the ajax method and get the JSON.
try
{
    string webAddr = "https://roosters.windesheim.nl/WebUntis/Timetable.do?request.preventCache="+unixTimeStamp.ToString();
    var httpWebRequest = (HttpWebRequest)WebRequest.Create(webAddr);
    httpWebRequest.ContentType = "application/x-www-form-urlencoded; charset=utf-8";
    httpWebRequest.Method = "POST";
    httpWebRequest.CookieContainer = cookies;
    httpWebRequest.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
    httpWebRequest.Headers.Add("X-Requested-With", "XMLHttpRequest");

    using (var streamWriter = new StreamWriter(httpWebRequest.GetRequestStream()))
    {
        string json = "ajaxCommand=getWeeklyTimetable&elementType=1&elementId=13090&date=20171126&formatId=7&departmentId=0&filterId=-2";

        //The command below will return a JSON datastructure containing all the klases and their relevant ID.
        //string otherJson = "ajaxCommand=getPageConfig&type=1&filter=-2"


        streamWriter.Write(json);
        streamWriter.Flush();
    }


    var httpResponse = (HttpWebResponse)httpWebRequest.GetResponse();
    using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
    {
        var responseText = streamReader.ReadToEnd();
        //THE RESULTS GETS PRINTED HERE.
        Console.Write(responseText);
    }
}
catch (WebException ex)
{
    Console.WriteLine(ex.Message);
}

Autre solution avec Selenium avec le pilote Firefox.

C'est beaucoup plus facile à faire. mais cela prend aussi du temps. Tous les threads dorment ne sont pas nécessaires. Cela donnera un code HTML pour travailler avec isntead, exactement comme vous l'avez demandé. Mais je l'ai trouvé nécessaire dans la dernière boucle foreach.

CookieContainer cookies = new CookieContainer();
try
{
    string webAddr = "https://roosters.windesheim.nl/";
    var httpWebRequest = (HttpWebRequest)WebRequest.Create(webAddr);
    httpWebRequest.ContentType = "application/json; charset=utf-8";
    httpWebRequest.Method = "POST";
    httpWebRequest.CookieContainer = cookies;        
    httpWebRequest.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
    httpWebRequest.Headers.Add("X-Requested-With", "XMLHttpRequest");

    var httpResponse = (HttpWebResponse)httpWebRequest.GetResponse();
    using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
    {
        cookies.Add(httpWebRequest.CookieContainer.GetCookies(httpWebRequest.RequestUri));
    }
}
catch (WebException ex)
{
    Console.WriteLine(ex.Message);
}

//According to my web debugger the cookie will last until the 10th of December. So need to fix a new cookie until then.
//I noticed the url used unixtimestamps at the end of the url. So we just add the unixtimestamp at the end for each request.
long unixTimeStamp = new DateTimeOffset(DateTime.Now).ToUnixTimeMilliseconds() - 100;

//we are now ready to call the ajax method and get the JSON.
try
{
    string webAddr = "https://roosters.windesheim.nl/WebUntis/Timetable.do?request.preventCache="+unixTimeStamp.ToString();
    var httpWebRequest = (HttpWebRequest)WebRequest.Create(webAddr);
    httpWebRequest.ContentType = "application/x-www-form-urlencoded; charset=utf-8";
    httpWebRequest.Method = "POST";
    httpWebRequest.CookieContainer = cookies;
    httpWebRequest.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
    httpWebRequest.Headers.Add("X-Requested-With", "XMLHttpRequest");

    using (var streamWriter = new StreamWriter(httpWebRequest.GetRequestStream()))
    {
        string json = "ajaxCommand=getWeeklyTimetable&elementType=1&elementId=13090&date=20171126&formatId=7&departmentId=0&filterId=-2";

        //The command below will return a JSON datastructure containing all the klases and their relevant ID.
        //string otherJson = "ajaxCommand=getPageConfig&type=1&filter=-2"


        streamWriter.Write(json);
        streamWriter.Flush();
    }


    var httpResponse = (HttpWebResponse)httpWebRequest.GetResponse();
    using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
    {
        var responseText = streamReader.ReadToEnd();
        //THE RESULTS GETS PRINTED HERE.
        Console.Write(responseText);
    }
}
catch (WebException ex)
{
    Console.WriteLine(ex.Message);
}

Dernière mise à jour

En utilisant la solution Selenium, j'ai pu obtenir les identifiants pour tous les cours. J'ai inclus le fichier ici pour que vous puissiez l'utiliser avec vos requêtes ajax et web.


Réponse populaire

J'allais laisser cela comme un commentaire. Mais il est devenu trop gros et trop mal formaté. Alors on y va.

D'abord. Le site est mis à jour dynamiquement à l'aide de javascript appelé avec une commande ajax.

Si vous pouvez ouvrir une session et stocker le cookie contenant le SESSIONID et le pseudonyme désormais "chiffré", vous pouvez appeler les commandes ajax en tant que telles.

    https://roosters.windesheim.nl/ajaxCommand=getWeeklyTimetable&elementType=1&elementId=13090&date=20171126&formatId=7&departmentId=0&filterId=-2

Cela nécessite toutefois de savoir ce que elementType est et quel est elementId.

Dans ce cas, elementId fait référence à Klas lorsqu'il est égal à 1GLD. Et formatID (7) fait référence à Roosterformaat quand il est égal à "Beknopt". Vous devez comprendre ce que font les variables restantes. Plus important encore, si vous parvenez à exécuter des commandes ajax valides sur le serveur, vous ne recevrez pas de retour HTML en réponse, vous recevrez les données au format JSON.

Le moyen le plus simple de faire ce que vous voulez est de placer toutes les classes dans un fichier séparé. Et utilisez cela comme point de référence. Il en va de même pour les autres options.

Et puis utilisez un navigateur sans tête comme phantomjs.org avec Selenium . De cette façon, vous pouvez trouver et cliquer sur les classes que vous voulez gratter. Chargez le code HTML dans un fichier HtmlAgilityPack.HtmlDocument, puis faites ce que vous devez faire. Selenium / PhantomJS jusqu’à garder trace de vos cookies. Cette méthode est plus lente - mais beaucoup plus facile à faire.

EDIT Stocker des cookies à partir d'une requête Web - le moyen le plus simple.

Je ne suis pas passionné par ce sujet. Mais OP a demandé. Si quelqu'un a un meilleur moyen de le faire, éditez-le.

    https://roosters.windesheim.nl/ajaxCommand=getWeeklyTimetable&elementType=1&elementId=13090&date=20171126&formatId=7&departmentId=0&filterId=-2



Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi