Html Agility Pack получить все элементы по классам


Я беру удар на html agility pack и возникли проблемы с поиском правильного пути, чтобы пойти об этом.

например:

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"

есть ли способ справиться со всем этим? Я в основном хочу выбрать все узлы, которые имеют класс = и содержит float.

**ответ был задокументирован в моем блоге с полным объяснением по адресу:Html Agility Pack получить все элементы по классам

5 61

5 ответов:

(Обновлено 2018-03-17)

проблема:

проблема, как вы заметили, заключается в том, что String.Contains не выполняет проверку границы слова, поэтому Contains("float") вернутся true как для " Foo float bar "(правильно), так и для" unfloating " (что неверно).

решение состоит в том, чтобы гарантировать, что" 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 использовать lookarounds, но это приводит в ужасно сложных выражениях - так что с помощью 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 с ним, а именно:Fizzler и CssSelectors. И 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  );
}

вы можете решить свою проблему с помощью функции "содержит" в вашем запросе Xpath, как показано ниже:

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

чтобы повторно использовать это в функции, сделайте что-то похожее на следующее:

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

я использовал этот метод расширения много в моем проекте. Надеюсь, это поможет одному из вас, ребята.

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

вы можете использовать следующий скрипт:

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