Html Agility Pack按类获取所有元素

时间:2012-12-07 21:15:48

标签: c# html html-agility-pack

我正在攻击html敏捷包并且无法找到合适的方法来解决这个问题。

例如:

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

然而,显然你可以添加更多的类然后div,所以我尝试了..

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

但是,这并没有处理你添加多个类和"浮动"只是其中一个像这样......

class="className float anotherclassName"

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

**我的博客上已经记录了答案,并在Html Agility Pack Get All Elements by Class

上提供了完整的说明

5 个答案:

答案 0 :(得分:89)

(2018-03-17更新)

问题:

正如您所发现的那样,问题是String.Contains没有执行字边界检查,因此Contains("float")将为“foo float bar”返回true(正确)和“解除”(这是不正确的)。

解决方案是确保“浮动”(或任何您想要的类名称)在两端出现和字边界。单词边界是字符串(或行)的开头(或结尾),空格,某些标点符号等。在大多数正则表达式中,这是\b。所以你想要的正则表达式只是:\bfloat\b

使用Regex实例的一个缺点是,如果不使用.Compiled选项,它们可能会运行缓慢 - 而且编译速度很慢。所以你应该缓存正则表达式实例。如果您要查找的类名在运行时发生更改,则会更加困难。

或者,您可以通过将正则表达式实现为C#字符串处理函数,而不使用正则表达式来按字边界搜索字符串,注意不要导致任何新字符串或其他对象分配(例如,不使用{{1 }})。

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

假设您只想查找具有单个设计时指定类名的元素:

String.Split

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

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使用lookarounds,但结果为in horrendously complicated expressions - 所以使用Regex可能更好:

Regex[]

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

使用自定义C#方法进行字符串匹配而不是正则表达式的优势在于假设性能更快且内存使用率更低(尽管在某些情况下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", "") ) ) ); } 可能更快 - 总是先编写代码,小孩!)

下面的方法:Regex提供了一个快速的字边界检查字符串匹配函数,可以像CheapClassListContains一样使用:

regex.IsMatch

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

HtmlAgilityPack有些停滞不支持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; } .querySelector,但有第三方库用它扩展HtmlAgilityPack:即FizzlerCssSelectors。 Fizzler和CssSelectors都实现.querySelectorAll,因此您可以这样使用它:

QuerySelectorAll

使用运行时定义的类:

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

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

答案 1 :(得分:86)

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

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

要在函数中重复使用它,请执行以下类似操作:

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

答案 2 :(得分:3)

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

-------+-------+-------+----//---+-------+-------+-----
      May     June    July      May     June    July
              2015                      2016

答案 3 :(得分: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;
    }      

答案 4 :(得分:-7)

您可以使用以下脚本:

var findclasses = _doc.DocumentNode.Descendants("div").Where(d => 
    d.Attributes.Contains("class") && d.Attributes["class"].Value.Contains("float")
);