【问题标题】:HtmlAgilityPack & Selenium Webdriver returns random resultsHtmlAgilityPack 和 Selenium Webdriver 返回随机结果
【发布时间】:2017-12-27 19:45:22
【问题描述】:

我正在尝试从网站上抓取产品名称。奇怪的是,我似乎只随机抓取 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 的代码:

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 个。我怎样才能抓取该网站上的所有项目?

【问题讨论】:

  • 尝试不同的用户代理?其他人是否加载了ajax?
  • 因为页面在滚动时加载,所以爬虫不是人类。
  • @DanielA.White 你还有什么推荐的代理?
  • 我不知道。实验和尝试。这取决于那个网站。我们无法合理地为您回答。
  • @AlexK。有没有办法强制 HAP 先加载整个页面?

标签: c# selenium-webdriver web-scraping web-crawler html-agility-pack


【解决方案1】:

对于大多数单页应用程序或动态加载内容的页面,您最好使用实际浏览器来导航页面。我建议为这种类型的设置研究 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。
  • 我认为大多数人都会同意,在没有任何重大贡献的情况下将其他答案添加到您自己的答案中是一种不好的做法。
  • @JeffC 抱歉,我调整以从其他答案中删除对 size 参数的引用。至于滚动部分,我只是在回答他关于为什么他没有得到所有这些的评论。我没有从你的回答中明白这一点。与你在我之后建议硒没有什么不同..
  • @CodingKuma 非常不同。我不只是说“使用 Selenium”,我对问题进行了描述,然后提供了包括代码在内的解决方案。您的答案来自一个半星期前,并且您最近编辑了您的答案,并且方便地包含了其他两个答案的 cmets。
  • @JeffC 很好,我删除了我的更新,尽管在回复和添加之前我什至没有阅读您的答案。
【解决方案2】:

所以有几个问题会阻止计数正确。

  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】:

    正如其他人所说,该站点的页面使用一些 javascript 动态加载,因此 Html Agility Pack 只获取第一个项目。

    Web Scraping 可能很困难,尤其是对于使用越来越多的 javascript 的现代网站,而且它通常非常特定于目标网站(我什至没有谈论法律问题..)。您可以使用各种技术来确定如何获取所需的信息。

    在这种情况下,如果您使用任何网络分析器,您会很快看到该站点使用了一个 'sz'(我猜是大小)查询字符串参数,该参数允许您指定所需的项目数。

    所以,只需为此修改您的网址:

    var url = @"http://www.roots.com/ca/en/men/tops/shirts-and-polos/?sz=9999";
    

    并获得您想要的任意数量的项目。

    【讨论】:

    • 虽然这是有用的信息,但它不能回答问题。他已经获得了 20 种产品,但只看到了前 12 种。获得 9999 种产品并不能解决这个问题。
    • @JeffC - ???如果没有 sz 参数,您不会在一个 HTTP GET 中获得所有产品,只有一部分,这正是问题所在。用大值定义 sz 将在一个 GET 中获得最大可能的项目数(在我的示例中最多为 9999),即此查询为 20。尝试这两个网址都会提琴手,你会明白的。
    • 不,问题是,“嘿……页面上有 20 种产品,而我只有 12 种,这是为什么呢?”如果 OP 使用您的答案,下一个问题将是,“嘿......页面上有 9999 种产品,而我只有 12 种,这是为什么呢?”参考:There are over 20 items on that page, but I seem to only get a random 12.
    【解决方案4】:

    从 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,不,我没有尝试。然而,由于他可以访问 WebBrowser 并且可以使用一些 API,例如 webBrowser.Document.Window.ScrollTo(0, webBrowser.Document.Body.ScrollRectangle.Height);,因此可以获得相同的结果
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-01-22
    • 2010-11-23
    • 2015-01-14
    • 2022-11-24
    • 2013-05-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多