Библиотека С# для разбора HTML?

Библиотека должна поддерживать как минимум .NET framework версии 3.5 и 4.0; поддержка версии 4.5 и будущих версий тоже была бы отличной.

Он также должен обрабатывать «грязный» HTML, если это возможно.

Ответы (5)

АнглШарп :

  • Активно развивается/поддерживается
  • Встроенная поддержка селекторов CSS

Пакет гибкости HTML

Я использовал Html Agility Pack , и хотя на его домашней странице явно упоминается только версия 2.0, он отлично работает с версией 4.0 .NET framework. Я подозреваю, что он отлично работает и с версией 4.5.

Вот пример кода, использующего Html Agility Pack с LINQ:

var document = new HtmlDocument();
document.Load(@"C:\Documents and Settings\Kenny\My Documents\project\document.html");

var table = document.GetElementbyId("table5");
var tableRows = table.ChildNodes
                    .Where(cn => cn.NodeType == HtmlNodeType.Element)
                    .Skip(2);

К сожалению, это не похоже на активный проект. Последнее изменение, согласно странице проекта CodePlex, относится к июлю 2012 года. Возможно, не так много возможностей для улучшения, и, судя по моему предварительному использованию, он кажется стабильным и быстрым.

Физзлер

Fizzler построен на основе Html Agility Pack и обеспечивает поддержку использования селекторов CSS для доступа к проанализированным HTML-документам.

К сожалению, как и Html Agility Pack, он также выглядит неактивным; последнее изменение, согласно исходному списку Google Code, последнее изменение было в январе 2013 года. Также возможно, что оно также стабильно и не нуждается в постоянной разработке или обслуживании.

Другие источники

HAP по-прежнему популярен, но это причудливый мусор с ошибками, и его следует избегать в современном коде. Ошибка некорректного парсинга HTML-тегов с необязательными закрывающими тегами осталась, спустя много-много лет. Он по-прежнему возвращает нули вместо пустых коллекций, что делает код многословным. Также HAP — это Ms-PL, а Fizzler — это LGPL, а не MIT/BSD, что в некоторых случаях может быть важно. Просто не используйте его, есть варианты получше.

CsQuery также является очень хорошим парсером HTML с селекторами CSS. Он генерирует тот же DOM, что и браузеры на основе Gecko. У него также гораздо лучшая лицензия (MIT), чем Html Agility Pack (MS-PL), которая несовместима с GPL.

Эта библиотека также очень проста в использовании, потому что у нее есть API, похожий на jQuery.

РЕДАКТИРОВАТЬ: В настоящее время (25 июня 2016 г.) он активно не поддерживается. Так что есть лучшая альтернатива, такая как AngleSharp.

CefSharp

Почему?

  • Активно поддерживается
  • Вы получаете мощь хрома
  • Давайте запустим любой JavaScript. Таким образом гораздо проще разработать синтаксический анализ. Вы переходите в консоль браузера на базе Chromium и разрабатываете нужный сценарий. Когда вы написали небольшой базовый код C#, ваш путь разработки заключается в том, чтобы просто вставлять код Javascript из консоли без необходимости писать циклы C# и запросы.
  • Позволяет запускать событие C# из кода JavaScript. Это чрезвычайно полезно, когда вы хотите запустить событие успеха AJAX, чтобы получить результаты.

Существует три вида CefSharp:

  • CefSharp.WinForms
  • CefSharp.Wpf
  • CefSharp.OffScreen

Первые два используются как WebBrowser на базе IE в Windows.Forms. Но это на основе хрома. А для разбора следует использовать CefSharp.OffScreen.

Установите его через Nuget и используйте.
Install-Package CefSharp.OffScreen -Version 57.0.0


Код

Приведенные примеры не должны быть короткими, но они облегчат программирование с использованием CefSharp.

Я буду использовать jQuery для вызовов Javascript для демонстрации и упрощения примера, предполагая, что на целевом сайте есть эта библиотека. Вы можете выполнить простой JS или выбрать любой из доступных на целевом сайте.

Прежде всего, результаты javascript возвращаются через JavascriptResponseсвойство type Result. objectМассивы Javascript сопоставляются с List<object>. Другие сопоставления типов результатов очевидны: string, int, boolно все они будут храниться в object Resultсвойстве. Чтобы сделать методы Javascript универсальными, я использую следующие файлы ConvertHelper.

public static class ConvertHelper
{
    public static T[] GetArrayFromObjectList<T>(object obj)
    {
        return ((IEnumerable<object>)obj)
            .Cast<T>()
            .ToArray();
    }

    public static List<T> GetListFromObjectList<T>(object obj)
    {
        return ((IEnumerable<object>)obj)
            .Cast<T>()
            .ToList();
    }

    public static T ToTypedVariable<T>(object obj)
    {
        if (obj == null)
        {
            dynamic dynamicResult = null;
            return dynamicResult;
        }

        Type type = typeof(T);
        if (type.IsArray)
        {
            dynamic dynamicResult = typeof(ConvertHelper).GetMethod(nameof(GetArrayFromObjectList))
                .MakeGenericMethod(type.GetElementType())
                .Invoke(null, new[] { obj });
            return dynamicResult;
        }

        if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>))
        {
            dynamic dynamicResult = typeof(ConvertHelper).GetMethod(nameof(GetListFromObjectList))
                .MakeGenericMethod(type.GetGenericArguments().Single())
                .Invoke(null, new[] { obj });
            return dynamicResult;
        }

        return (T)obj;
    }
}

Я добавил класс для обработки ошибок Javascript:

public class JavascriptException : Exception
{
    public JavascriptException(string message): base(message) { }
}

Затем нам нужно создать наш основной CefSharpWrapperкласс для выполнения всей грязной работы с браузером.

public class CefSharpWrapper
{
    private ChromiumWebBrowser _browser;

    public void InitializeBrowser()
    {
        CefSettings settings = new CefSettings();

        // Perform dependency check to make sure all relevant resources are in our output directory.
        Cef.Initialize(new CefSettings(), performDependencyCheck: true, browserProcessHandler: null);

        _browser = new ChromiumWebBrowser();

        // wait till browser initialised
        AutoResetEvent waitHandle = new AutoResetEvent(false);

        EventHandler onBrowserInitialized = null;

        onBrowserInitialized = (sender, e) =>
        {
            _browser.BrowserInitialized -= onBrowserInitialized;

            waitHandle.Set();
        };

        _browser.BrowserInitialized += onBrowserInitialized;

        waitHandle.WaitOne();
    }

    public void ShutdownBrowser()
    {
        // Clean up Chromium objects
        Cef.Shutdown();
    }

    public Task<T> GetResultAfterPageLoad<T>(string pageUrl, Func<Task<T>> onLoadCallback)
    {
        TaskCompletionSource<T> tcs = new TaskCompletionSource<T>();

        EventHandler<LoadingStateChangedEventArgs> onPageLoaded = null;

        T t = default(T);

        // An event that is fired when the first page is finished loading.
        // This returns to us from another thread.
        onPageLoaded = async (sender, e) =>
        {
            // Check to see if loading is complete - this event is called twice, one when loading starts
            // second time when it's finished
            // (rather than an iframe within the main frame).
            if (!e.IsLoading)
            {
                // Remove the load event handler, because we only want one snapshot of the initial page.
                _browser.LoadingStateChanged -= onPageLoaded;

                t = await onLoadCallback();

                tcs.SetResult(t);
            }
        };

        _browser.LoadingStateChanged += onPageLoaded;

        _browser.Load(pageUrl);

        return tcs.Task;
    }

    // Method to get result via Javascript    
    public async Task<T> EvaluateJavascript<T>(string script)
    {
        JavascriptResponse javascriptResponse = await browser.GetMainFrame().EvaluateScriptAsync(script);

        if (javascriptResponse.Success)
        {
            object scriptResult = javascriptResponse.Result;
            return ConvertHelper.ToTypedVariable<T>(scriptResult);
        }

        throw new JavascriptException(javascriptResponse.Message);
    }
}

Затем мы вызываем наш CefSharpWrapperкласс из Mainметода, чтобы получить все a hrefс домашней страницы stackoverflow.

public class Program
{
    private static void Main()
    {
        MainAsync().Wait();
    }

    private static async Task MainAsync()
    {
        CefSharpWrapper wrapper = new CefSharpWrapper();

        wrapper.InitializeBrowser();

        string[] urls = await wrapper.GetResultAfterPageLoad("http://stackoverflow.com/", async () =>
            await wrapper.EvaluateJavascript<string[]>("$('a[href]').map((index, element) => $(element).prop('href')).toArray()"));

        wrapper.ShutdownBrowser();
    }
}

Примечание: эта библиотека не различает пустой массив nullи undefined. Все они возвращаются как null. Таким образом, чтобы избежать NullReferenceExceptionдобавления соответствующего кода в CefSharpWrapper(но тогда вам придется иметь дело с различением nullв C# означает nullили пустой массив в Javascript) или добавить следующий код в Main.

if (urls == null) urls = new string[0];
Этот вопрос не связан с рендерингом HTML. Я не смог найти ничего, относящегося к разбору HTML, в информации о проекте на GitHub.
Я знаю. Вы можете использовать эту библиотеку для синтаксического анализа, и она очень удобна для синтаксического анализа, поэтому я рекомендую ее. Вы просто вызываете метод и ChromiumWebBrowser, EvaluateScriptAsyncиспользуя Javascript, получаете желаемые результаты. Например, если целевой сайт поддерживает jQuery и вам нужны все href, которые вы можете вызвать _browser.EvaluateScriptAsync("$('a[href]').map((index, element) => $(element).prop('href')).toArray()"). Я могу показать вам примеры кода, но не уверен, что они будут уместны на этом сайте. Я использовал текущую библиотеку для анализа веб-сайтов.
Примеры кода абсолютно уместны; пожалуйста, добавьте простой пример, если можете. Например, как код C# будет извлекать данные, проанализированные кодом JavaScript? Это очень интересный ответ на вопрос. Я неявно предполагал, что HTML, который я хотел бы проанализировать, уже будет предоставлен, но возможность использовать Chromium (или любой другой браузерный движок) позволит также обрабатывать такие вещи, как одностраничные веб-приложения.
Очень интересный (и очень хакерский ) ответ!

Если вам нужно что-то действительно быстрое, загляните сюда: Majestic-12: Проекты: HTML-парсер C# (.NET)

Это будет не самый простой в использовании, но, вероятно, будет самым быстрым.