【问题标题】:Html Agility Pack get all elements by classHtml Agility Pack 按类获取所有元素
【发布时间】:2012-11-26 02:28:51
【问题描述】:

我正在尝试使用 html 敏捷包,但无法找到正确的解决方法。

例如:

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

但是,显然你可以将类添加到更多的 div 中,所以我尝试了这个..

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

但这并不能处理您添加多个类的情况,而“float”只是其中之一......

class="className float anotherclassName"

有没有办法处理这一切?我基本上想选择所有具有 class= 并包含浮点数的节点。

**Answer 已记录在我的博客中,并在以下位置进行了完整说明:Html Agility Pack Get All Elements by Class

【问题讨论】:

    标签: c# html html-agility-pack


    【解决方案1】:

    如果您在某个标签(如或任何其他)中寻找类。 试试这个

     var spans = doc.DocumentNode.SelectNodes("//span"); //or other tag or all nodes
    
     var span_with_class = spans.Where(_ => _.Attributes["class"].Value.Split(' ').Any(b => b.Equals("someClass")));
    

    【讨论】:

    • 不鼓励使用纯代码的答案。如果答案附有关于代码如何/为何解决问题的解释,则具有更大的长期价值。
    【解决方案2】:

    (2018-03-17 更新)

    问题:

    正如您所发现的,问题在于String.Contains 不执行字边界检查,因此Contains("float") 将为“foo float bar”(正确)和“unfloating”(这是不正确的)。

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

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

    或者,您可以通过将正则表达式实现为 C# 字符串处理函数,在不使用正则表达式的情况下按单词边界搜索字符串,注意不要导致任何新字符串或其他对象分配(例如,不使用 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 对象数组并确保它们都匹配,或者使用环视将它们组合成一个 Regex,但是这个结果in horrendously complicated expressions - 所以使用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", "") )
                )
            );
    }
    

    方法二:使用非正则字符串匹配:

    使用自定义 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 吗?如果我将该类添加到
    • 然后去掉“div”谓词。
    • Contains() 在属性上不存在,因此将 d.Attributes["class"].Contains("float") 替换为 d.Attributes["class"].Value.Split(' ').Any(b =&gt; b.Equals("float"))
    • 如果有一个名为 floating 的类,那么 Value.Contains("float") 也会匹配
    • @RobertOschler CheapClassListContains 可能比正则表达式便宜并实现相同的逻辑 - 但是是的,这也是一种选择。
    【解决方案3】:

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

    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));
        }
    

    【讨论】:

    • 当你真正想要的是忽略大小写比较时,不要使用ToLower()。传递StringComparison.CultureIgnoreCase 更简洁,并显示更明确的意图。
    【解决方案4】:
    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;
        }      
    

    【讨论】:

      【解决方案5】:

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

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

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

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

      【讨论】:

      • allElementsWithClassFloat 的对象类型是什么?
      • allElementsWithClassFloat 是一个 HtmlNodeCollection
      • 你也可以使用$"//*[contains(@class,'{classToFind}')]"代替string.Format
      • 如果你有一个名为 float-xs 的类会发生什么?
      • @SameeraKumarasingha 类 'float-xs' 和 'unfloating' 都将包含在 allElementsWithClassFloat 列表中。请查看@Dai 的答案:stackoverflow.com/a/13774240/3678079
      【解决方案6】:

      您可以使用以下脚本:

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

      【讨论】:

        猜你喜欢
        • 2019-12-09
        • 1970-01-01
        • 2011-05-10
        • 1970-01-01
        • 1970-01-01
        • 2016-05-15
        • 1970-01-01
        • 1970-01-01
        • 2013-08-21
        相关资源
        最近更新 更多