HTML Agility Pack按类别获取所有元素


74

我在html敏捷包中遇到了麻烦,无法找到正确的方法来解决这个问题。

例如:

var findclasses = _doc.DocumentNode.Descendants("div").Where(d => d.Attributes.Contains("class"));

但是,显然您可以将类添加到比divs更多的类,所以我尝试了这一点。

var allLinksWithDivAndClass = _doc.DocumentNode.SelectNodes("//*[@class=\"float\"]");

但这不能解决您添加多个类并且“ float”只是这样的情况之一的情况。

class="className float anotherclassName"

有没有办法处理所有这些?我基本上想选择所有具有class =且包含float的节点。

**答案已记录在我的博客上,并在以下位置提供了完整的解释:Html Agility Pack按类别获取所有元素

Answers:


94

(更新2018-03-17)

问题:

正如您所发现的,问题是String.Contains不执行单词边界检查,因此对于“ foo float bar”(正确)和“ unfloating”(不正确)都Contains("float")将返回true

解决方案是确保在两端的单词边界旁边显示“ float”(或任何所需的类名)。字边界是字符串(或行)的开头(或结尾),空格,某些标点符号等。在大多数正则表达式中,它是\b。因此,您想要的正则表达式很简单:\bfloat\b

使用Regex实例的不利之处在于,如果不使用该.Compiled选项,它们的运行速度可能会很慢-并且它们的编译速度可能会很慢。因此,您应该缓存正则表达式实例。如果要在运行时查找的类名发生更改,这将更加困难。

另外,您也可以通过将正则表达式实现为C#字符串处理函数,而无需使用正则表达式来按单词边界在字符串中搜索单词,请注意不要引起任何新的字符串或其他对象分配(例如,不使用String.Split)。

方法1:使用正则表达式:

假设您只想使用一个在设计时指定的类名来查找元素:

class Program {

    private static readonly Regex _classNameRegex = new Regex( @"\bfloat\b", RegexOptions.Compiled );

    private static IEnumerable<HtmlNode> GetFloatElements(HtmlDocument doc) {
        return doc
            .Descendants()
            .Where( n => n.NodeType == NodeType.Element )
            .Where( e => e.Name == "div" && _classNameRegex.IsMatch( e.GetAttributeValue("class", "") ) );
    }
}

如果您需要在运行时选择单个类名,则可以构建一个正则表达式:

private static IEnumerable<HtmlNode> GetElementsWithClass(HtmlDocument doc, String className) {

    Regex regex = new Regex( "\\b" + Regex.Escape( className ) + "\\b", RegexOptions.Compiled );

    return doc
        .Descendants()
        .Where( n => n.NodeType == NodeType.Element )
        .Where( e => e.Name == "div" && regex.IsMatch( e.GetAttributeValue("class", "") ) );
}

如果您有多个类名,并且想要匹配所有的类名,则可以创建一个Regex对象数组并确保它们都匹配,或者Regex使用环顾四周将它们组合成一个单一的对象,但这会导致极其复杂的表达式-因此使用一个Regex[]可能更好:

using System.Linq;

private static IEnumerable<HtmlNode> GetElementsWithClass(HtmlDocument doc, String[] classNames) {

    Regex[] exprs = new Regex[ classNames.Length ];
    for( Int32 i = 0; i < exprs.Length; i++ ) {
        exprs[i] = new Regex( "\\b" + Regex.Escape( classNames[i] ) + "\\b", RegexOptions.Compiled );
    }

    return doc
        .Descendants()
        .Where( n => n.NodeType == NodeType.Element )
        .Where( e =>
            e.Name == "div" &&
            exprs.All( r =>
                r.IsMatch( e.GetAttributeValue("class", "") )
            )
        );
}

方法2:使用非正则表达式字符串匹配:

假设使用自定义C#方法进行字符串匹配而不是使用正则表达式的好处是,可以提高性能并减少内存使用(尽管Regex在某些情况下可能会更快-始终请孩子们分析代码!)

下面的方法:CheapClassListContains提供快速的单词边界检查字符串匹配功能,该功能可以与以下方式相同使用regex.IsMatch

private static IEnumerable<HtmlNode> GetElementsWithClass(HtmlDocument doc, String className) {

    return doc
        .Descendants()
        .Where( n => n.NodeType == NodeType.Element )
        .Where( e =>
            e.Name == "div" &&
            CheapClassListContains(
                e.GetAttributeValue("class", ""),
                className,
                StringComparison.Ordinal
            )
        );
}

/// <summary>Performs optionally-whitespace-padded string search without new string allocations.</summary>
/// <remarks>A regex might also work, but constructing a new regex every time this method is called would be expensive.</remarks>
private static Boolean CheapClassListContains(String haystack, String needle, StringComparison comparison)
{
    if( String.Equals( haystack, needle, comparison ) ) return true;
    Int32 idx = 0;
    while( idx + needle.Length <= haystack.Length )
    {
        idx = haystack.IndexOf( needle, idx, comparison );
        if( idx == -1 ) return false;

        Int32 end = idx + needle.Length;

        // Needle must be enclosed in whitespace or be at the start/end of string
        Boolean validStart = idx == 0               || Char.IsWhiteSpace( haystack[idx - 1] );
        Boolean validEnd   = end == haystack.Length || Char.IsWhiteSpace( haystack[end] );
        if( validStart && validEnd ) return true;

        idx++;
    }
    return false;
}

方法3:使用CSS选择器库:

HtmlAgilityPack有点停滞,不支持.querySelector并且.querySelectorAll,但是有一些第三方库通过它扩展HtmlAgilityPack:FizzlerCssSelectors。Fizzler和CssSelectors都实现QuerySelectorAll,因此您可以像这样使用它:

private static IEnumerable<HtmlNode> GetDivElementsWithFloatClass(HtmlDocument doc) {

    return doc.QuerySelectorAll( "div.float" );
}

使用运行时定义的类:

private static IEnumerable<HtmlNode> GetDivElementsWithClasses(HtmlDocument doc, IEnumerable<String> classNames) {

    String selector = "div." + String.Join( ".", classNames );

    return doc.QuerySelectorAll( selector  );
}

这会不会导致只找到Divs?如果我将该类添加到<span class =“ someClass float someClass2”,该怎么办。
亚当

1
然后删除“ div”谓词。

14
Contains()属性上不存在,请替换d.Attributes["class"].Contains("float")d.Attributes["class"].Value.Split(' ').Any(b => b.Equals("float"))
maxp,2014年

2
如果有一个名为的课程,floating那么它Value.Contains("float")也将
tic

1
@RobertOschlerCheapClassListContains可能比正则表达式便宜,并且实现相同的逻辑-但是,这也是一种选择。
Dai)

92

您可以通过在Xpath查询中使用“包含”功能来解决问题,如下所示:

var allElementsWithClassFloat = 
   _doc.DocumentNode.SelectNodes("//*[contains(@class,'float')]")

要在函数中重用此功能,请执行以下操作:

string classToFind = "float";    
var allElementsWithClassFloat = 
   _doc.DocumentNode.SelectNodes(string.Format("//*[contains(@class,'{0}')]", classToFind));

对象类型是allElementsWithClassFloat什么?
Adromil Balais

allElementsWithClassFloat是一个HtmlNodeCollection
feztheforeigner

除了string.Format,您还可以使用$"//*[contains(@class,'{classToFind}')]"
feztheforeigner

5
如果您有一个名称为float-xs的类,该怎么办?
Sameera Kumarasingha

@SameeraKumarasingha类'float-xs'和'unfloating'都将包含在allElementsWithClassFloat列表中。请改为查看
@Dai

4

我在项目中经常使用这种扩展方法。希望它能帮助你们中的一个。

public static bool HasClass(this HtmlNode node, params string[] classValueArray)
    {
        var classValue = node.GetAttributeValue("class", "");
        var classValues = classValue.Split(' ');
        return classValueArray.All(c => classValues.Contains(c));
    }

3
ToLower()当您真正想要的是IgnoreCase比较时不要使用。通过StringComparison.CultureIgnoreCase更干净,并且显示出更明确的意图。
PauliØsterø17年

0
public static List<HtmlNode> GetTagsWithClass(string html,List<string> @class)
    {
        // LoadHtml(html);           
        var result = htmlDocument.DocumentNode.Descendants()
            .Where(x =>x.Attributes.Contains("class") && @class.Contains(x.Attributes["class"].Value)).ToList();          
        return result;
    }      

-7

您可以使用以下脚本:

var findclasses = _doc.DocumentNode.Descendants("div").Where(d => 
    d.Attributes.Contains("class") && d.Attributes["class"].Value.Contains("float")
);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.