Erwarte AJAX mit HtmlAgilityPack in Xamarin

ajax c# html-agility-pack xamarin

Frage

Ich habe eine Frage, die schon einmal gestellt wurde, aber ein bisschen anders ist. Ich versuche, Daten von dieser Website zu schaben, aber das Problem ist, dass es scheint, dass es mit AJAX geladen ist. Aus diesem Grund kann meine Anwendung die IDs und Klassen im von mir gesuchten HTML nicht finden.

Sie können dies reproduzieren, indem Sie ein Element untersuchen oder die Quelle anzeigen. Während ich die Quelle sehe, sehe ich viel weniger als bei der Inspektion eines Elements.

Ich dachte, dass ich die Datei, die den AJAX enthält, aufspüren könnte, um diesen HTML-Code zu laden, indem ich F12 drücke, auf den Netzwerk-Tab gehe und XHR auswähle, aber ich kann ihn nicht finden.

Meine Frage ist: Wie kann ich diese Daten abrufen oder herausfinden, welche Datei zum Sammeln der Daten verwendet wird?

Ein Beispiel für meinen Code (Ich finde das 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)
            {
            }
        }

Akzeptierte Antwort

Lösung, bei der Sie die Ajax-Methode mit einer Webrequest aufrufen.

Also langweilte ich mich und dachte mir das meiste aus. Was hier fehlt, ist, wie man die Klase mit id identifiziert. Das folgende Beispiel ruft die KLASE '1GLD' ab. Der Grund, warum wir Cookies brauchen, ist, dass wir wissen, von welcher Schule wir die Klase beziehen. Auch der folgende Code gibt nur JSON - und nicht HTML zurück, da es sich um eine Ajax-Methode handelt, die wir aufrufen.

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

Andere Lösung mit Selenium mit Firefox-Treiber.

Das ist viel einfacher. aber es braucht auch etwas Zeit. Nicht alle Thread-Betten sind notwendig. Dadurch erhalten Sie einen HTML-Code, mit dem Sie arbeiten können. Aber ich fand es in der letzten foreach Schleife notwendig.

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

Letztes Update

Mit der Selenium-Lösung konnte ich die IDs für alle Kurse erhalten. Ich habe die Datei hier eingefügt, damit Sie sie mit Ihren Ajax- und Web-Anfragen verwenden können.


Beliebte Antwort

Ich würde das als Kommentar hinterlassen. Aber es wurde zu groß und zu schlecht formatiert. Auf geht's.

Zuerst. Die Site wird dynamisch mit JavaScript aktualisiert, das mit einem Ajax-Befehl aufgerufen wird.

Wenn Sie eine Sitzung öffnen und den Cookie speichern können, der die SESSIONID und den nun "verschlüsselten" Schulnamen enthält, können Sie die AJAX-Befehle als solche aufrufen.

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

Dies erfordert jedoch, dass Sie wissen, was elementType ist und was elementId ist.

In diesem Fall bezieht sich elementId auf Klas, wenn es gleich 1GLD ist. Und formatID (7) verweist auf Roosterformaat, wenn es gleich "Beknopt" ist. Sie müssen herausfinden, was die verbleibenden Variablen tun. Noch wichtiger ist, dass, wenn es Ihnen gelingt, gültige Ajax-Befehle an den Server zu senden, Sie HTML nicht zurückbekommen werden, als Antwort erhalten Sie die Daten in JSON.

Der einfachste Weg zu tun, was Sie wollen, ist, alle Klassen in einer separaten Datei zu haben . Und das als Bezugspunkt verwenden. Gleiches gilt für die anderen Optionen.

Und dann benutze einen kopflosen Browser wie phantomjs.org mit Selenium . Auf diese Weise können Sie die Klassen finden und anklicken, die Sie scrappen möchten. Laden Sie die HTML-Datei in ein HtmlAgilityPack.HtmlDocument und führen Sie dann aus, was Sie tun müssen. Selenium / PhantomJS, um Ihre Cookies im Auge zu behalten. Diese Methode ist langsamer - aber viel einfacher.

Bearbeiten Cookies von einem Webantrag speichern - der einfache Weg.

Ich bin nicht scharf auf dieses Thema. Aber OP fragte. Wenn jemand einen besseren Weg hat, bitte editieren.

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


Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Ist diese KB legal? Ja, lerne warum
Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Ist diese KB legal? Ja, lerne warum