如何使用 HtmlAgilityPack 进行异步调用?
How to make asynchronous calls using HtmlAgilityPack?
我正在尝试获取 ID 为 table-matches
的 table here。问题是 table 是使用 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
不包含任何table,所以我试图查看库是否有问题,实际上我设置了Chrome
(具体在Dev控制台 F12) javascript 关闭,浏览器上的结果相同。
Fox 解决了这个问题我虽然使用 WebBrowser
,特别是:
webBrowser.Navigate("oddsportal.com/matches/soccer/20140221/");
HtmlElementCollection elements = webBrowser.Document.GetElementsByTagName("table");
但我想问一下我是否也可以加载完整的 html
进行异步调用,有人遇到过类似的问题吗?
能否请您分享解决方案?谢谢。
此页面的主要问题是 table-matches
中的内容是通过 ajax 加载的。而且 HttpClient
和 HtmlAgilityPack
都无法等待 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. And once you get html content you are able to parse it with HtmlAgilityPack。
方法 #2 - 使用纯 Selenium WebDriver. Can be launched in 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) 分析页面加载,您可以看到所有数据都来自这两个请求:
所以你可以尝试使用 HttpClient 直接执行它们。但在这种情况下,您可能需要跟踪授权 headers 以及每个 HTTP 请求的其他内容。
我正在尝试获取 ID 为 table-matches
的 table here。问题是 table 是使用 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
不包含任何table,所以我试图查看库是否有问题,实际上我设置了Chrome
(具体在Dev控制台 F12) javascript 关闭,浏览器上的结果相同。
Fox 解决了这个问题我虽然使用 WebBrowser
,特别是:
webBrowser.Navigate("oddsportal.com/matches/soccer/20140221/");
HtmlElementCollection elements = webBrowser.Document.GetElementsByTagName("table");
但我想问一下我是否也可以加载完整的 html
进行异步调用,有人遇到过类似的问题吗?
能否请您分享解决方案?谢谢。
此页面的主要问题是 table-matches
中的内容是通过 ajax 加载的。而且 HttpClient
和 HtmlAgilityPack
都无法等待 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. And once you get html content you are able to parse it with HtmlAgilityPack。
方法 #2 - 使用纯 Selenium WebDriver. Can be launched in 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) 分析页面加载,您可以看到所有数据都来自这两个请求: