HtmlAgilityPack和Selenium Webdriver返回随机结果

15

我正在尝试从网站上抓取产品名称。奇怪的是,我似乎只能抓取随机的12个项目。我尝试了HtmlAgilityPack和HTTPClient,但都得到了相同的随机结果。这是我的HtmlAgilityPack代码:

using HtmlAgilityPack;
using System.Net.Http;

var url = @"http://www.roots.com/ca/en/men/tops/shirts-and-polos/";
HtmlWeb web = new HtmlWeb();
var doc = web.Load(url, "GET", proxy, new NetworkCredential(PROXY_UID, PROXY_PWD, PROXY_DMN));
var nodes = doc.DocumentNode.Descendants("div")
            .Where(div => div.GetAttributeValue("class", string.Empty) == "product-name")
            .Select(div => div.InnerText.Trim())
            ;

[更新1] @CodingKuma建议我尝试使用Selenium Webdriver。这是我的代码,使用了Selenium Webdriver:

[UPDATE 1] @CodingKuma建議我嘗試使用Selenium Webdriver。這是我的程式碼,使用了Selenium Webdriver:

IWebDriver chromeDriver = new ChromeDriver(@"C:\TEMP\Projects\Chrome\chromedriver_win32");
chromeDriver.Url = "http://www.roots.com/ca/en/men/tops/shirts-and-polos/";
var items = chromeDriver.FindElements(By.ClassName("product-name"));
items.Count().Dump();
chromeDriver.Quit();

我尝试了这段代码但仍然没有运气。那个页面上有20多个项目,但我似乎只得到了随机的12个。如何才能爬取该网站上的所有项目?


我不知道。实验和尝试。这取决于那个网站。我们不能为您合理地回答这个问题。 - Daniel A. White
我认为httpclient也不能与JavaScript一起使用。 - inquisitive_one
有没有一个好用的C#网络爬虫可以实现这个功能?你能提供一些例子或资源吗? - inquisitive_one
我想知道是否可以使用Web浏览器控件加载页面,通过编程滚动它以加载源代码,然后从控件中获取HTML... - JuanR
顺便说一下,它不是返回随机结果...每次都是前12个元素。 - JeffC
显示剩余5条评论
4个回答

5
自从v1.5.0-beta92版本以来,HtmlAgilityPack已经拥有了一个名为FromBrowser的方法,可以让你等待所有需要的元素都准备好后再进行操作。文档详见:http://html-agility-pack.net/from-browser
string url = "http://html-agility-pack/from-browser";

var web1 = new HtmlWeb();
var doc1 = web1.LoadFromBrowser(url, o =>
{
    var webBrowser = (WebBrowser) o;

    // WAIT until the dynamic text is set
    return !string.IsNullOrEmpty(webBrowser.Document.GetElementById("uiDynamicText").InnerText);
});
var t1 = doc1.DocumentNode.SelectSingleNode("//div[@id='uiDynamicText']").InnerText

var web2 = new HtmlWeb();
var doc2 = web2.LoadFromBrowser(url, html =>
{
    // WAIT until the dynamic text is set
    return !html.Contains("<div id=\"uiDynamicText\"></div>");
});
var t2 = doc2.DocumentNode.SelectSingleNode("//div[@id='uiDynamicText']").InnerText

Console.WriteLine("Text 1: " + t1);
Console.WriteLine("Text 2: " + t2);

这里的诀窍是找到一个能够告诉你页面何时准备好的东西,因为库无法知道。


你在 OP 发布的网站上试过这个吗?我认为这不会起作用,因为它使用了懒加载。页面已经加载完成,你必须向下滚动到底部,然后等待页面完成加载...请查看我的答案以获取更多详细信息。 - JeffC
@JeffC,我没有尝试过。但是由于他可以访问WebBrowser并且可以使用一些API,例如webBrowser.Document.Window.ScrollTo(0, webBrowser.Document.Body.ScrollRectangle.Height),因此可以实现相同的结果。 - Jonathan Magnan

4
所以有几个问题会导致计数不正确。
  1. 页面采用延迟加载。您必须向下滚动才能触发加载12个以上的项目。
  2. 页面使用AJAX调用来加载12个以上的项目。
因此,您需要导航到页面,滚动到页面底部,等待AJAX完成,然后爬取页面。下面的代码经过测试,返回20个项目。
脚本如下:
String url = "http://www.roots.com/ca/en/men/tops/shirts-and-polos/";
driver.navigate().to(url);
JavascriptExecutor js = ((JavascriptExecutor) driver);
int height = 1;
int lastHeight = 0;
while (lastHeight != height)
{
    lastHeight = height;
    js.executeScript("window.scrollTo(0, document.body.scrollHeight);");
    height = (int) (long) js.executeScript("return document.body.scrollHeight;");
}

waitForJSandJQueryToLoad(10);

List<WebElement> products = driver.findElements(By.cssSelector("div.product-name"));
System.out.println(products.size());
for (WebElement e : products)
{
    System.out.println(e.getText());
}

支持功能

public boolean waitForJSandJQueryToLoad(int timeOut)
{
    WebDriverWait wait = new WebDriverWait(driver, timeOut);

    ExpectedCondition<Boolean> jQueryIsLoaded = new ExpectedCondition<Boolean>()
    {
        @Override
        public Boolean apply(WebDriver driver)
        {
            return (Boolean) ((JavascriptExecutor) driver).executeScript("return (window.jQuery != null) && (jQuery.active === 0);");
        }
    };

    ExpectedCondition<Boolean> jsIsLoaded = new ExpectedCondition<Boolean>()
    {
        @Override
        public Boolean apply(WebDriver driver)
        {
            return (Boolean) ((JavascriptExecutor) driver).executeScript("return document.readyState == 'complete'");
        }
    };

    return wait.until(jQueryIsLoaded) && wait.until(jsIsLoaded);
}

输出

20
Rideau Flannel Shirt
Westridge Denim Shirt
Rideau Flannel Shirt
Riverside Plaid Shirt
Riverside Plaid Shirt
Heritage Peppered Polo
Heritage Peppered Polo
Heritage Peppered Polo
Cedar Jersey Polo
Cedar Jersey Polo
Hope River Shirt
Hawthorne Surplus Shacket
Acadian Linen Shirt
Camp Short Sleeve Shirt
Foxley Short Sleeve Shirt
Heritage Peppered Polo
Foxley Short Sleeve Shirt
Waterway Indigo Shirt
Waterway Indigo Shirt
Resolute Flannel Shirt

3
对于大多数单页应用程序或动态加载内容的页面,最好使用实际的浏览器来浏览页面。我建议您研究一下这种类型的设置,使用Selenium可能会更好。 https://www.nuget.org/packages/Selenium.WebDriver

这也不起作用。这是我的代码: IWebDriver chromeDriver = new ChromeDriver(@"C:\TEMP\Projects\Chrome\chromedriver_win32"); chromeDriver.Url = "http://www.roots.com/ca/en/men/tops/shirts-and-polos/"; var items = chromeDriver.FindElements(By.ClassName("product-name")); items.Count().Dump(); chromeDriver.Quit(); 我仍然得到12而不是24的计数。 - inquisitive_one
我认为大多数人都会同意这是一种不好的做法,即在没有任何重要贡献的情况下将其他答案添加到自己的答案中。 - JeffC
@JeffC 抱歉,我进行了调整,去掉了其他答案中关于size参数的引用。至于滚动部分,我只是回答他对为什么没有获取所有内容的评论。我没有从你的答案中得到这个信息。这和你在我之后建议使用Selenium并无区别。 - CodingKuma
@CodingKuma 这是非常不同的。我并没有只是说,“使用Selenium”,而是对问题进行了描述,然后提供了包括代码在内的解决方案。你的回答是一周半之前的,最近你编辑了你的回答,并方便地包含了其他两个回答的评论。 - JeffC
@JeffC 好的,我撤回了我的更新,尽管在回复并添加它之前我甚至没有阅读过你的答案。 - CodingKuma

3
正如其他人所说,这个网站的页面使用一些javascript动态加载自身,因此Html Agility Pack只会获取第一批项目。
网络爬虫可能很困难,尤其是现代网站越来越多地使用javascript,而且总的来说非常特定于目标网站(我甚至没有谈论法律问题)。您可以使用各种技术来确定如何获取所需信息。
在这种情况下,如果您使用任何网络分析器,您很快就会看到该网站使用一个“sz”(我猜是大小)查询字符串参数,允许您指定要获取的项目数量。
因此,只需修改您的URL即可:
var url = @"http://www.roots.com/ca/en/men/tops/shirts-and-polos/?sz=9999";

并且可以获取任意数量的物品。


虽然这是有用的信息,但它并没有回答问题。他已经得到了20个产品,只看到了前12个。得到9999个产品并不能解决这个问题。 - JeffC
@JeffC - 没有sz参数,你无法在一个HTTP GET中获取所有产品,只能获取部分,这正是问题所在。使用大值定义sz将在一个GET中获取最大可能数量的项目(在我的示例中最多为9999),即此查询的20个项目。尝试使用Fiddler测试这两个URL,你就会明白。 - Simon Mourier
不,问题是,“嘿...页面上有20个产品,但我只能看到12个,为什么?”如果OP使用了你的答案,下一个问题将是,“嘿...页面上有9999个产品,但我只能看到12个,为什么?”参考:那个页面上有超过20个项目,但我似乎只能随机获取12个。 - JeffC

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接