使用HtmlAgilityPack解析dl

5
这是我试图在ASP.Net(C#)中使用Html Agility Pack解析的示例HTML。
<div class="content-div">
    <dl>
        <dt>
            <b><a href="1.html" title="1">1</a></b>
        </dt>
        <dd> First Entry</dd>
        <dt>
            <b><a href="2.html" title="2">2</a></b>
        </dt>
        <dd> Second Entry</dd>
        <dt>
            <b><a href="3.html" title="3">3</a></b>
        </dt>
        <dd> Third Entry</dd>
    </dl>
</div>

我想要的值如下:
  • 超链接 -> 1.html
  • 锚文本 -> 1
  • DD标签内的文本 -> 第一项
(这里只是举了第一项的例子,我需要获取列表中所有条目的这些元素的值)
这是我目前正在使用的代码:
var webGet = new HtmlWeb();
            var document = webGet.Load(url2);
var parsedValues=
   from info in document.DocumentNode.SelectNodes("//div[@class='content-div']")
   from content in info.SelectNodes("dl//dd")
   from link in info.SelectNodes("dl//dt/b/a")
       .Where(x => x.Attributes.Contains("href"))
   select new 
   {
       Text = content.InnerText,
       Url = link.Attributes["href"].Value,
       AnchorText = link.InnerText,
   };

GridView1.DataSource = parsedValues;
GridView1.DataBind();

问题在于我能正确获取链接和锚文本的值,但对于其内部文本,它只取第一个条目的值,并将相同的值填充到所有其他条目中,直到该元素出现的总次数为止,然后从第二个条目重新开始。我的解释可能不够清晰,因此这是我使用此代码获得的示例输出:
First Entry     1.html  1
First Entry     2.html  2
First Entry     3.html  3
Second Entry    1.html  1
Second Entry    2.html  2
Second Entry    3.html  3
Third Entry     1.html  1
Third Entry     2.html  2
Third Entry     3.html  3

我正试图获得的是

First Entry      1.html     1
Second Entry     2.html     2
Third Entry      3.html     3

我对HAP还不太熟悉,对xpath了解很少,我肯定在做一些错误操作,但即使我花费了几个小时也无法让它工作。非常感谢任何帮助。

2个回答

4

解决方案1

我定义了一个函数,给定一个dt节点,它会返回它后面的下一个dd节点:

private static HtmlNode GetNextDDSibling(HtmlNode dtElement)
{
    var currentNode = dtElement;

    while (currentNode != null)
    {
        currentNode = currentNode.NextSibling;

        if(currentNode.NodeType == HtmlNodeType.Element && currentNode.Name =="dd")
            return currentNode;
    }

    return null;
}

现在,LINQ 代码可以转换为以下形式:

var parsedValues =
    from info in document.DocumentNode.SelectNodes("//div[@class='content-div']")
    from dtElement in info.SelectNodes("dl/dt")
    let link = dtElement.SelectSingleNode("b/a[@href]")
    let ddElement = GetNextDDSibling(dtElement)
    where link != null && ddElement != null
    select new
    {
        Text = ddElement.InnerHtml,
        Url = link.GetAttributeValue("href", ""),
        AnchorText = link.InnerText
    };

解决方案2

不需要额外的函数:

var infoNode = 
        document.DocumentNode.SelectSingleNode("//div[@class='content-div']");

var dts = infoNode.SelectNodes("dl/dt");
var dds = infoNode.SelectNodes("dl/dd");

var parsedValues = dts.Zip(dds,
    (dt, dd) => new
    {
        Text = dd.InnerHtml,
        Url = dt.SelectSingleNode("b/a[@href]").GetAttributeValue("href", ""),
        AnchorText = dt.SelectSingleNode("b/a[@href]").InnerText
    });

非常感谢,这个方法很有效。但是我不太明白这里到底发生了什么。能否不使用函数,而是使用某种xpath/regex来完成?这个解决方案对我来说完全没问题,只是我很好奇它是如何工作的。如果您能简要解释一下,那就太好了,谢谢。 - redGREENblue
1
问题在于我们需要类似于“给定这个dt标签,给我紧随其后的dd标签”。我试图简化它,通过选择ddElement作为dtElement.NextSibling,但是HtmlAgilityPack将下一个节点视为<dt><dd>标签之间的空格。这导致我采用了稍微丑陋一些的解决方案,就像你上面看到的那样。 - Cristian Lupascu
1
我刚想出了另一个可能的解决方案,它不需要声明一个函数 - 请查看更新后的答案。 - Cristian Lupascu
谢谢,我现在明白了。我也尝试了你的第二个解决方案,但是我遇到了一个错误(如下所示:),第二个解决方案需要添加哪些引用才能正常工作?错误:'HtmlAgilityPack.HtmlNodeCollection' 不包含 'Zip' 的定义,也没有接受类型为 'HtmlAgilityPack.HtmlNodeCollection' 的第一个参数的扩展方法 'Zip' 可以找到(是否缺少 using 指令或程序集引用?) - redGREENblue
Zip是Linq扩展函数。您使用的是哪个.Net版本? - Cristian Lupascu
Zip仅在4.0中受支持。但是,您可以使用Jon Skeet的此实现:http://msmvps.com/blogs/jon_skeet/archive/2011/01/14/reimplementing-linq-to-objects-part-35-zip.aspx。 - Cristian Lupascu

2

以下是使用Html Agility Pack解析元素的示例:

public string ParseHtml()
{
    string output = null;
    HtmlDocument htmldocument = new HtmlDocument();
    htmldocument.LoadHtml(YourHTML);

    HtmlNode node = htmldocument.DocumentNode;    

    HtmlNodeCollection dds = node.SelectNodes("//dd"); //Select all dd tags
    HtmlNodeCollection anchors = node.SelectNodes("//b/a[@href]"); //Select all 'a' tags that contais href attribute

    for (int i = 0; i < dds.Count; i++)
    {
        string atributteValue = null.
        Text = dds[i].InnerText;
        Url = anchors[i].GetAttributeValue("href", atributteValue);
        AnchorText = anchors[i].InnerText;

        //Your code...
    }
    return output;
}

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