Attendi AJAX con HtmlAgilityPack in Xamarin

ajax c# html-agility-pack xamarin

Domanda

Ho una domanda che sembra essere stata chiesta in precedenza, ma è un po 'diversa. Sto cercando di raschiare i dati da questo sito, ma il problema è che sembra essere caricato con AJAX. Per questo motivo la mia applicazione non è in grado di trovare l'ID e le classi nell'HTML che sto cercando.

È possibile riprodurre questo ispezionando un elemento o visualizzando la fonte. Mentre osservo la fonte, sto vedendo molto meno rispetto al controllo di un elemento.

Ho pensato di rintracciare il file che contiene AJAX per caricare questo html premendo F12, andando alla scheda di rete e selezionando XHR, ma non riesco a trovarlo.

La mia domanda è: come posso recuperare questi dati o scoprire quale file viene utilizzato per raccogliere i dati?

Un esempio del mio codice (non riesco a trovare il 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)
            {
            }
        }

Risposta accettata

Soluzione in cui si chiama il metodo Ajax utilizzando una richiesta web.

Quindi mi sono annoiato e ho capito la maggior parte di esso. Quello che manca di seguito è come identificare la Klase tramite id. L'esempio seguente recupererà la '1GLD' di klase. Il motivo per cui abbiamo bisogno di cookie è in ordine per la richiesta di sapere da quale scuola andiamo a prendere la Klase. Anche il codice seguente restituisce solo JSON - e non HTML poiché è un metodo ajax che chiamiamo.

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);
}

Altra soluzione con Selenium con driver per Firefox.

Questo è molto più facile da fare. ma ci vuole anche del tempo. Non sono necessari tutti i thread di sospensione. Questo darà un HTML per funzionare con isntead proprio come richiesto. Ma l'ho trovato necessario nell'ultimo ciclo foreach.

public static void Main(string[] args)
{
    HtmlDocument doc = new HtmlDocument();
    //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;
    string webAddr = "https://roosters.windesheim.nl/WebUntis/Timetable.do?request.preventCache="+unixTimeStamp.ToString();
    var ffOptions = new FirefoxOptions();
    ffOptions.BrowserExecutableLocation = @"C:\Program Files (x86)\Mozilla Firefox\firefox.exe";
    ffOptions.LogLevel = FirefoxDriverLogLevel.Default;
    ffOptions.Profile = new FirefoxProfile { AcceptUntrustedCertificates = true };
    var service = FirefoxDriverService.CreateDefaultService();

    var driver = new FirefoxDriver(service, ffOptions, TimeSpan.FromSeconds(120));


    driver.Navigate().GoToUrl(webAddr);


    driver.FindElement(By.XPath("//input[@id='school']")).SendKeys("Windesheim"+Keys.Enter);
    Thread.Sleep(2000);
    driver.FindElement(By.XPath("//span[@id='dijit_PopupMenuBarItem_0_text' and text() ='Lesrooster']")).Click();

    driver.FindElement(By.XPath("//td[@id='dijit_MenuItem_0_text' and text() ='Klassen']")).Click();
    Thread.Sleep(2000);

    driver.FindElement(By.XPath("//div[@id='widget_Timetable_toolbar_elementSelect']//input[@class='dijitReset dijitInputField dijitArrowButtonInner']")).Click();

    //we get all the options for Klase
    doc.LoadHtml(driver.PageSource);
    HtmlNodeCollection nodes = doc.DocumentNode.SelectNodes("//div[@id='Timetable_toolbar_elementSelect_popup']/div[@item]");
    List<String> options = new List<String>();
    foreach (HtmlNode n in nodes)
    {
        options.Add(n.InnerText);
    }

    foreach(string s in options)
    {
        driver.FindElement(By.XPath("//input[@id='Timetable_toolbar_elementSelect']")).Clear();
        driver.FindElement(By.XPath("//input[@id='Timetable_toolbar_elementSelect']")).SendKeys(s);
        Thread.Sleep(2000);
        driver.FindElement(By.XPath("//body")).SendKeys(Keys.Enter);
        Thread.Sleep(2000);
        doc.LoadHtml(driver.PageSource);
        //Console.WriteLine(driver.Url); //Now we can see the id of the current Klase
    }

    Console.WriteLine(doc.DocumentNode.InnerHtml);

    Console.ReadKey();
}

Ultimo aggiornamento

Usando la soluzione Selenium sono riuscito a ottenere gli ID di tutti i corsi. Ho incluso il file qui in modo da poterlo utilizzare con le tue richieste su ajax e web.


Risposta popolare

Stavo per lasciare questo come commento. Ma è diventato troppo grande e troppo mal formattato. Quindi eccoci qui.

In primo luogo. Il sito viene aggiornato dinamicamente usando javascript che viene chiamato con un comando ajax.

Se è possibile aprire una sessione e memorizzare il cookie contenente il SESSIONID e il nome della scuola ora "crittografato", è possibile chiamare i comandi ajax come tali.

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

Ciò tuttavia richiede che tu sappia cos'è elementType e che cos'è elementId.

In questo caso elementId si riferisce a Klas quando è uguale a 1GLD. E formatID (7) si riferisce a Roosterformaat quando è uguale a "Beknopt". Devi capire cosa fanno le variabili rimanenti. Ancora più importante è che se riesci a essere in grado di rendere validi i comandi ajax al server, non otterrai indietro HTML come risposta riceverai i dati in JSON.

Il modo più semplice per fare ciò che vuoi è avere tutte le classi in un file separato. E usalo come punto di riferimento. Lo stesso vale per le altre opzioni.

E poi usa un browser senza testa come phantomjs.org con Selenium . In questo modo puoi trovare e cliccare sulle classi che vuoi analizzare. Carica il codice html in HtmlAgilityPack.HtmlDocument e poi fai ciò che devi fare. Selenium / PhantomJS fino a tenere traccia dei tuoi cookie. Questo metodo è più lento, ma molto più facile da fare.

MODIFICA Memorizzare i cookie da una richiesta web - il modo semplice.

Non sono entusiasta di questo argomento. Ma OP ha chiesto. Se qualcuno ha un modo migliore di farlo, per favore modifica.

CookieContainer cookies = new CookieContainer();
try
{
    string webAddr = "https://roosters.windesheim.nl/WebUntis/";

    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");
    using (var streamWriter = new StreamWriter(httpWebRequest.GetRequestStream()))
    {
        string json = "ajaxCommand=getWeeklyTimetable&elementType=1&elementId=13092&date=20171126&formatId=7&departmentId=0&filterId=-2";

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


    var httpResponse = (HttpWebResponse)httpWebRequest.GetResponse();
    using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
    {
        cookies.Add(httpWebRequest.CookieContainer.GetCookies(httpWebRequest.RequestUri));
        //cookies.Add(httpResponse.Cookies);
        var responseText = streamReader.ReadToEnd();
        doc.LoadHtml(responseText);
        foreach(Cookie c in httpResponse.Cookies)
        {
            Console.WriteLine(c.ToString());
        } 
    }
}
catch (WebException ex)
{
    Console.WriteLine(ex.Message);
}
    Console.WriteLine(doc.DocumentNode.InnerHtml);

    Console.ReadKey();


Related

Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow