【问题标题】:Html Agility Pack - reading div InnerText in tableHtml Agility Pack - 读取表格中的 div InnerText
【发布时间】:2013-09-25 14:28:05
【问题描述】:

我的问题是我无法从表中获取 div InnerText。我已经成功提取了不同类型的数据,但我不知道如何从表中读取 div。

在下图中,我突出显示了 div,我需要从中获取 InnerText,在本例中为数字 3。

Click here for first picture

我正在尝试使用以下路径来完成此操作:

"//div[@class='kal']//table//tr[2]/td[1]/div[@class='cipars']"

但我收到以下错误:

Click here for Error message picture

假设其余代码编写正确,有人能指出我正确的方向吗?我一直试图弄清楚这一点,但我无法得到任何结果。

【问题讨论】:

  • 1) dateNode 可能为 null 和 2) 邮政编码。不是您的代码的图片。谢谢。 :)
  • 对不起他们链接到图片,我认为其他代码与解决我的问题无关,我发布它们来帮助我解释我的问题。我寻求帮助的原因是因为我知道该错误表明 dateNode 为空,但我认为我使用的路径是错误的。我只是不知道问题出在哪里。
  • 通常有这些问题,很难知道是什么问题。我可以看到您有一个 XPath 查询,当 HtmlAgilityPack 运行它时,我可以看到它返回了一个空对象,但我如何才能看到这个查询是否正确?我没有任何参考 XML/HTML 可以继续。我没有任何 C# 代码来显示您正在运行的代码。您的图片显示代码看起来不错,因此可能是物理 XPath 查询。
  • 带有HTML的图片来自这个页面lekcijas.va.lv/…,其余的C#代码是正确的,因为我之前已经使用过很多次了,但这是我第一次需要XPath来在表中进行对象.所以它是导致错误的 XPath.. 但对我来说似乎很好?我的任何语法错误?

标签: c# web-scraping html-agility-pack


【解决方案1】:

所以您的问题是您依赖于 XPath 中的位置。虽然在某些情况下可以这样做,但它不在这里,因为您期望给定 tr 中的 first td 与该类有 div

查看 Chrome 中的源代码,发现情况并非总是如此。您可以通过比较日历中的“1”元素与“2”和“3”来看到这一点。您会注意到“1”元素周围有许多元素,而其他元素则没有。

您的原始 XPath 查询未返回元素,这就是您收到错误的原因。如果您提供给 HtmlAgilityPack 的 XPath 查询没有生成 DOM 元素,它将返回 null。

现在,由于您没有显示整个代码,我不知道这段代码是如何运行的。但是,我猜您正在尝试遍历所有日历项目。无论如何,您有多种方法可以做到这一点,但我将向您展示使用 descendant XPath 选择器,您可以一次性获取全部内容:

//div[@class='kal']//table//descendant::div[@class='cipars']

这将返回所有个日历项目(即1到30)。

但是,要获取特定行中的所有项目,您只需将 tr 粘贴到查询中即可:

//div[@class='kal']//table//tr[3]/descendant::div[@class='cipars']

这将返回 2 到 8(日历项的第二行)。

要针对特定​​的,您必须对网站的源代码做出假设。看起来每个“cipars”div 都有一个 td 的祖先,其类为 datums....所以要从您的问题中获得“3”值:

//div[@class='kal']//table//tr[3]//td[@class='datums'][2]/div[@class='cipars']

希望这至少足以说明问题。

编辑

虽然您确实遇到了 XPath 问题,但您还有另一个问题。

该网站的创建非常奇怪。日历以一种奇怪的方式加载。当我点击那个 URL 时,日历是由一些 Javascript 调用 XML Web 服务(用 PHP 编写)创建的,然后计算完整的 table 用于日历。

由于这是 Javascript(客户端代码),HtmlAgilityPack 不会执行它。因此,HtmlAgilityPack 甚至没有“看到”该表。因此,针对它的查询返回为“未找到”(null)。

解决方法:1) 使用将调用脚本的工具。我的意思是加载浏览器。一个很好的工具叫做Selenium。这可能是更好的整体解决方案,因为这意味着站点使用的所有脚本都将被实际调用。您仍然可以将 XPath 与它一起使用,因此您的查询不会改变。

第二种方法是向页面所做的相同 Web 服务发送请求。这基本上是为了取回页面正在获取的 same HTML,并将 that 与 HtmlAgilityPack 一起使用。我们如何做到这一点?

好吧,您可以使用 C# 轻松地将数据发布到 Web 服务。只是为了便于使用,我从this SO question 窃取了代码。这样,我们可以发送与页面相同的请求,并返回相同的 HTML。

所以要发送一些 POST 数据,我们生成一个类似这样的方法.....

public static string SendPost(string url, string postData)
{
    string webpageContent = string.Empty;

    byte[] byteArray = Encoding.UTF8.GetBytes(postData);

    HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(url);
    webRequest.Method = "POST";
    webRequest.ContentType = "application/x-www-form-urlencoded";
    webRequest.ContentLength = byteArray.Length;

    using (Stream webpageStream = webRequest.GetRequestStream())
    {
        webpageStream.Write(byteArray, 0, byteArray.Length);
    }

    using (HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse())
    {
        using (StreamReader reader = new StreamReader(webResponse.GetResponseStream()))
        {
            webpageContent = reader.ReadToEnd();
        }
    }

    return webpageContent;
}

我们可以这样称呼它:

string responseBody = SendPost("http://lekcijas.va.lv/lekcijas_request.php", "nodala=IT&kurss=1&gads=2013&menesis=9&c_dala=");

我是怎么得到这个的?那么我们调用的php 文件就是页面所在的Web 服务,POST 数据也是。我通过调试 Javascript(使用 Chrome 的开发者控制台)找出它发送给服务的数据的方法,您可能会注意到它与 URL 中的内容几乎相同。这似乎是故意的。

返回的responseBody只是日历的table物理HTML

我们现在用它做什么?我们将它加载到 HtmlAgilityPack 中,因为它能够接受纯 HTML。

var document = new HtmlDocument();
document.LoadHtml(webpageContent);

现在,我们将原始 XPath 插入:

var node = document.DocumentNode.SelectSingleNode("//div[@class='kal']//table//tr[3]//td[@class='datums'][2]/div[@class='cipars']");

现在,我们打印出应该是“3”的内容:

Console.WriteLine(node.InnerText);

我在本地运行的输出确实是:3

但是,尽管这可以解决您遇到的问题,但我假设网站的其余部分都是这样的。如果是这种情况,您可能仍然可以使用上述技术解决它,但正是出于这个原因创建了 Selenium 等工具。

【讨论】:

  • 感谢您的回答,我会看看它。但是在这种情况下,我确实注意到其他元素中的元素数量不同。所以我想明确地提取数字 3。使用此代码:HtmlWeb web = new HtmlWeb(); HtmlDocument doc = web.Load("http://lekcijas.va.lv/? nodala=IT&kurss=1&gads=2013&menesis=9&c_dala=#tabs-1"); HtmlNode dateNode = doc.DocumentNode.SelectSingleNode("//div[@class='kal']//table//tbody//tr[1]/td[2]"); string date = dateNode.InnerText; date9.Text = date;
  • 我按照你的方法试过了......仍然没有运气!也许我应该尝试像stackoverflow.com/questions/14968729/…中建议的用户 Mpora 那样使用完整的 XPath@
  • 要获得具体的“3”,您想使用://div[@class='kal']//table//tr[3]//td[@class='datums'][2]/div[@class='cipars'](P.S,请记住 XPath 索引器是基于 1 的,而不是像 C# 那样基于 0)
  • 仍然,您的 Xpath 显示相同的错误。这个小问题怎么会让人这么头疼..
  • 点赞@Arran。你在这里付出了认真的工作。我现在要弄清楚的是如何在 Xamarin 中完成这一切,因为我正在开发 iOS 应用程序......
猜你喜欢
  • 1970-01-01
  • 2012-02-24
  • 1970-01-01
  • 2012-07-18
  • 2012-03-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多