如何使用HtmlAgilityPack进行异步调用?

时间:2018-07-21 15:06:55

标签: c# html-agility-pack

我正尝试使用here来获得ID为table-matches的表。问题是表是使用ajax加载的,因此下载页面时我没有完整的html代码:

string url = "http://www.oddsportal.com/matches/soccer/20180701/";

using (HttpClient client = new HttpClient())
{
    using (HttpResponseMessage response = client.GetAsync(url).Result)
    {
        using (HttpContent content = response.Content)
        {
            string result = content.ReadAsStringAsync().Result;
        }
    }
}

返回的html不包含任何表,因此我尝试查看该库是否存在问题,实际上是我在Chrome(特别是在Dev控制台F12上)上设置的javascript,然后在浏览器上得到相同的结果。

Fox解决了这个问题,尽管我使用WebBrowser,尤其是:

webBrowser.Navigate("oddsportal.com/matches/soccer/20140221/"); 
HtmlElementCollection elements = webBrowser.Document.GetElementsByTagName("table");

但是我想问一下我是否也可以加载完整的html进行异步调用,有人遇到过类似的问题吗?

能否请您分享解决方案?谢谢。

1 个答案:

答案 0 :(得分:4)

此页面的主要问题是table-matches中的内容是通过ajax加载的。 HttpClientHtmlAgilityPack都无法等待ajax被执行。因此,您需要不同的方法。

方法1 -使用任何无头浏览器,例如PuppeteerSharp

using PuppeteerSharp;
using System;
using System.Threading.Tasks;

namespace PuppeteerSharpDemo
{
    class Program
    {
        private static String url = "http://www.oddsportal.com/matches/soccer/20180701/";

        static void Main(string[] args)
        {
            var htmlAsTask = LoadAndWaitForSelector(url, "#table-matches .table-main");
            htmlAsTask.Wait();
            Console.WriteLine(htmlAsTask.Result);

            Console.ReadKey();
        }

        public static async Task<string> LoadAndWaitForSelector(String url, String selector)
        {
            var browser = await Puppeteer.LaunchAsync(new LaunchOptions
            {
                Headless = true,
                ExecutablePath = @"c:\Program Files (x86)\Google\Chrome\Application\chrome.exe"
            });
            using (Page page = await browser.NewPageAsync())
            {
                await page.GoToAsync(url);
                await page.WaitForSelectorAsync(selector);
                return await page.GetContentAsync();
            }
        }
    }
}

为了简洁起见,我在此处here中发布了输出。并且一旦获得html内容,您就可以parse it with HtmlAgilityPack

方法2 -使用纯Selenium WebDriver。可以在headless mode中启动。

using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Support.UI;
using System;

namespace SeleniumDemo
{
    class Program
    {
        private static IWebDriver webDriver;
        private static TimeSpan defaultWait = TimeSpan.FromSeconds(10);
        private static String targetUrl = "http://www.oddsportal.com/matches/soccer/20180701/";
        private static String driversDir = @"../../Drivers/";

        static void Main(string[] args)
        {
            webDriver = new ChromeDriver(driversDir);
            webDriver.Navigate().GoToUrl(targetUrl);
            IWebElement table = webDriver.FindElement(By.Id("table-matches"));
            var innerHtml = table.GetAttribute("innerHTML");
        }

        #region (!) I didn't even use this, but it can be useful (!)
        public static IWebElement FindElement(By by)
        {
            try
            {
                WaitForAjax();
                var wait = new WebDriverWait(webDriver, defaultWait);
                return wait.Until(driver => driver.FindElement(by));
            }
            catch
            {
                return null;
            }
        }

        public static void WaitForAjax()
        {
            var wait = new WebDriverWait(webDriver, defaultWait);
            wait.Until(d => (bool)(d as IJavaScriptExecutor).ExecuteScript("return jQuery.active == 0"));
        }
        #endregion
    }
}

方法3 -模拟Ajax请求

如果使用Fiddler或浏览器的探查器(F12)分析页面加载,则可以看到所有数据都来自以下两个请求:

fiddler requests oddsportal scraping 因此,您可以尝试使用HttpClient直接执行它们。但是在这种情况下,您可能需要在每个HTTP请求中跟踪授权标头以及其他内容。