项目

AngleSharp 示例代码

这是一个(不断增长)的 AngleSharp 日常使用示例列表。

解析定义良好的文档

当然,AngleSharp 能够很好地处理定义良好的文档。但是,如果一个文档看起来定义不明确,但实际上定义良好,会怎样呢?下面的文档是可行的,没有任何错误,并且实际上由 Google 在生产中使用。生成的文档序列化输出可以与 IE、Chrome 或 Firefox 等浏览器所做的序列化进行比较。

var source = @"
<!DOCTYPE html>
<html lang=en>
  <meta charset=utf-8>
  <meta name=viewport content=""initial-scale=1, minimum-scale=1, width=device-width"">
  <title>Error 404 (Not Found)!!1</title>
  <style>
    *{margin:0;padding:0}html,code{font:15px/22px arial,sans-serif}html{background:#fff;color:#222;padding:15px}body{margin:7% auto 0;max-width:390px;min-height:180px;padding:30px 0 15px}* > body{background:url(//www.google.com/images/errors/robot.png) 100% 5px no-repeat;padding-right:205px}p{margin:11px 0 22px;overflow:hidden}ins{color:#777;text-decoration:none}a img{border:0}@media screen and (max-width:772px){body{background:none;margin-top:0;max-width:none;padding-right:0}}#logo{background:url(//www.google.com/images/errors/logo_sm_2.png) no-repeat}@media only screen and (min-resolution:192dpi){#logo{background:url(//www.google.com/images/errors/logo_sm_2_hr.png) no-repeat 0% 0%/100% 100%;-moz-border-image:url(//www.google.com/images/errors/logo_sm_2_hr.png) 0}}@media only screen and (-webkit-min-device-pixel-ratio:2){#logo{background:url(//www.google.com/images/errors/logo_sm_2_hr.png) no-repeat;-webkit-background-size:100% 100%}}#logo{display:inline-block;height:55px;width:150px}
  </style>
  <a href=//www.google.com/><span id=logo aria-label=Google></span></a>
  <p><b>404.</b> <ins>那是错误。</ins>
  <p>请求的URL <code>/error</code> 在此服务器上未找到。  <ins>这就是我们知道的全部。</ins>";

// 使用AngleSharp的默认配置
IConfiguration config = Configuration.Default;

// 创建一个新的上下文,用于给定配置的网页评估
IBrowsingContext context = BrowsingContext.New(config);

// 只需获取DOM表示
IDocument document = await context.OpenAsync(req => req.Content(source));

// 将其反序列化回控制台
Console.WriteLine(document.DocumentElement.OuterHtml);

因此,我们定义了一些源代码,调用了 BrowsingContext 实例的 OpenAsync 方法。OpenAsync 方法允许我们从任何类型的请求(例如,来自 Web 服务器的请求)解析文档。回调风格称为 “虚拟请求” ,它不触发真实请求,而是停留在我们的代码中。

在这种情况下,我们使用提供的源代码来确定响应的内容。然后,将响应的内容解析为 HTML 文档。之后,我们将 DOM 序列化回字符串。最后,我们在控制台上输出这个字符串。

简单的文档操作

AngleSharp 根据官方 HTML5 规范构建 DOM。这也意味着生成的模型是完全交互式的,可用于简单的操作。下面的例子创建了一个文档,并通过插入另一个包含一些文本的段落元素来改变树结构。

static async Task FirstExample()
{
    // 使用AngleSharp的默认配置
    IConfiguration config = Configuration.Default;

    // 创建一个新的上下文,用于给定配置的网页评估
    IBrowsingContext context = BrowsingContext.New(config);

    // 从虚拟请求/响应模式的内容解析文档
    IDocument document = await context.OpenAsync(req => req.Content("<h1>一些示例源</h1><p>这是一个段落元素"));

    // 对文档做一些操作,比如以下操作
    Console.WriteLine("序列化(原始)文档:");
    Console.WriteLine(document.DocumentElement.OuterHtml);

    IElement p = document.CreateElement("p");
    p.TextContent = "这是另一个段落。";

    Console.WriteLine("在body中插入另一个元素...");
    document.Body.AppendChild(p);

    Console.WriteLine("再次序列化文档:");
    Console.WriteLine(document.DocumentElement.OuterHtml);
}

在这里,解析器将创建一个新的 IHtmlDocument 实例,然后查询以找到一些匹配的节点。在上面的示例代码中,我们还创建了另一个 IElement ,即 IHtmlParagraphElement 。然后将其附加到文档的 Body 节点。

获取特定元素

AngleSharp 将所有 DOM 列表公开为 IEnumerable<T> ,如 IEnumerable<Node> 对于 NodeList 类。这使我们可以结合使用 LINQ 和一些已经给出的 DOM 功能,如 QuerySelectorAll 方法。

static async Task UsingLinq()
{
    // 创建一个新上下文,使用默认配置评估网页
    IBrowsingContext context = BrowsingContext.New(Configuration.Default);

    // 根据虚拟请求/响应模式创建文档
    IDocument document = await context.OpenAsync(req => req.Content("<ul><li>第一项<li>第二项<li class='blue'>第三项!<li class='blue red'>最后一项!</ul>"));

    // 使用LINQ做些事情
    IEnumerable<IElement> blueListItemsLinq = document.All.Where(m => m.LocalName == "li" && m.ClassList.Contains("blue"));

    // 或直接使用CSS选择器
    IHtmlCollection<IElement> blueListItemsCssSelector = document.QuerySelectorAll("li.blue");

    Console.WriteLine("比较两种方式...");

    Console.WriteLine();
    Console.WriteLine("LINQ:");

    foreach (var item in blueListItemsLinq)
    {
        Console.WriteLine(item.TextContent);
    }

    Console.WriteLine();
    Console.WriteLine("CSS:");

    foreach (var item in blueListItemsCssSelector)
    {
        Console.WriteLine(item.TextContent);
    }
}

由于 IDocumentAll 属性返回文档中包含的所有 IElement 节点,我们可以非常高效地使用 LINQ。另一方面,QuerySelectorAll 返回的也是一个 IHtmlCollection 对象。因此,这个列表也可以用 LINQ 进行过滤!此外,这个列表已经是过滤过的。

使用选择器也可以得到与 All 相同的结果——特殊的星号 * 选择器:

// 与document.All相同
IEnumerable<IElement> blueListItemsLinq = document.QuerySelectorAll("*").Where(m => m.LocalName == "li" && m.ClassList.Contains("blue"));

这完全一样吗?实际上不是—— All 返回所谓的 “实时” DOM 列表,也就是说,如果我们把对象保存在某个地方,我们将始终能访问到最新的 DOM 更改。

获取单个元素

此外,我们还有 QuerySelector 方法。这与使用 FirstOrDefault() 生成结果的 LINQ 语句非常接近。树遍历使用 QuerySelector 方法可能更高效一些。

让我们看一些示例代码:

static async Task SingleElements()
{
    // 创建一个新上下文,使用默认配置评估网页
    IBrowsingContext context = BrowsingContext.New(Configuration.Default);

    // 创建一个新的文档
    IDocument document = await context.OpenAsync(req => req.Content("<b><i>这是一些<em>粗体<u>和</u>斜体</em>文本!</i></b>"));

    IElement emphasize = document.QuerySelector("em");

    Console.WriteLine("不同方式获取文本的差异:");
    Console.WriteLine();
    Console.WriteLine("仅来自C# / AngleSharp:");
    Console.WriteLine();
    Console.WriteLine(emphasize.ToHtml());   //<em>粗体<u>和</u>斜体</em>
    Console.WriteLine(emphasize.Text());//粗体和斜体

    Console.WriteLine();
    Console.WriteLine("来自DOM:");
    Console.WriteLine();
    Console.WriteLine(emphasize.InnerHtml);  //粗体<u>和</u>斜体
    Console.WriteLine(emphasize.OuterHtml);  //<em>粗体<u>和</u>斜体</em>
    Console.WriteLine(emphasize.TextContent);//粗体和斜体
}

输出命令试图展示从节点获取字符串的几种方式之间的差异。事实上,DOM 属性 OuterHtml 使用 ToHtml() 版本生成 HTML 代码。其他变体都是不同的。虽然 Text() 只是一个助手,用于剥离文本(并忽略不想要的文本内容,如 <script> 标签中的内容),其他两个变体的行为就像 W3C 规定的官方属性,保持一切原样。

扩展方法如 Text() 可在命名空间 AngleSharp.Dom 中找到。

连接 JavaScript 评估

该项目还包含一个基于 Jint(JavaScript 解释器)的示例 JavaScript 引擎。

示例从创建基于预定义 Configuration 类的自定义版本开始。在这里,我们仅包含另一个位于 AngleSharp.Scripting (命名空间和项目)中的引擎。重要的是也要启用脚本编写。AngleSharp 知道拥有脚本引擎和使用它们是两回事。

以下是完整的示例代码。

static async Task SimpleScriptingSample()
{
    // 我们需要一个自定义配置
    IConfiguration config = Configuration.Default.WithJs();

    // 使用给定的配置创建一个新的上下文以评估网页
    IBrowsingContext context = BrowsingContext.New(config);

    // 这是我们的示例源码,我们将设置标题并在文档中写入内容
    var source = @"<!doctype html>
        <html>
            <head><title>示例</title></head>
        <body>
            <script>
                document.title = '简单操作...';
                document.write('<span class=greeting>你好,世界!</span>');
            </script>
        </body>";

    IDocument document = await context.OpenAsync(req => req.Content(source));

    // 修改后的HTML将被输出
    Console.WriteLine(document.DocumentElement.OuterHtml);
}

此代码只是解析给定的 HTML 代码,遇到提供的 JavaScript 并执行它。JavaScript 会在此时操纵文档,改变文档的标题并附加更多的 HTML 来解析。最后,我们会看到打印(序列化)的 HTML 与原始的不同。

更复杂的 JavaScript DOM 交互

使用 AngleSharpJavaScript 没有问题。当前状态下,我们也可以轻松地进行 DOM 操作,比如创建元素、追加或移除它们。

以下示例代码执行 DOM 查询,创建新元素并移除现有元素。

static async void ExtendedScriptingSample()
{
    // 我们需要一个带有JavaScript和CSS的自定义配置
    IConfiguration config = Configuration.Default.WithJs().WithCss();

    // 使用给定的配置创建一个新的上下文以评估网页
    IBrowsingContext context = BrowsingContext.New(config);

    // 这是我们的示例源码,我们将做一些DOM操作
    var source = @"<!doctype html>
        <html>
        <head><title>示例</title></head>
        <style>
        .bold {
          font-weight: bold;
        }
        .italic {
          font-style: italic;
        }
        span {
          font-size: 12pt;
        }
        div {
          background: #777;
          color: #f3f3f3;
        }
        </style>
        <body>
        <div id=content></div>
        <script>
        (function() {
          var doc = document;
          var content = doc.querySelector('#content');
          var span = doc.createElement('span');
          span.id = 'myspan';
          span.classList.add('bold', 'italic');
          span.textContent = '一些示例文本';
          content.appendChild(span);
          var script = doc.querySelector('script');
          script.parentNode.removeChild(script);
        })();
        </script>
        </body>";

    IDocument document = await context.OpenAsync(req => req.Content(source));

    // HTML将完全改变(例如,不再有script元素)
    Console.WriteLine(document.DocumentElement.OuterHtml);
}

原则上,也可以添加其他 JavaScript 引擎。当然,手动包装对象相比基于反射的自动版本会提供更优的性能。然而,AngleSharp.Js 库(在 NuGet 上可用)展示了这种绑定现有 JavaScript 引擎到 AngleSharp 的可能性和基础。

JavaScript 和 C# 中的事件

以下示例的开头与前两个示例完全相同。我们创建了一个包含 JavaScriptEngine 引擎的自定义配置。在启用脚本编写(以及在这个例子中启用样式)之后,我们可以解析文档。

本例的样本文档由一个脚本组成,该脚本调用了 console.log 方法。一次是在添加监听器之前,另一次是在添加监听器之后。

一旦文档完全加载,就会调用监听器。这是在执行提供的 JavaScript 之后发生的,因此我们应该在最后看到这个事件。我们还注册了另一个事件监听器,一旦触发自定义事件hello,它就会被调用。

public static async void EventScriptingExample()
{
    // 我们需要一个自定义配置
    IConfiguration config = Configuration.Default.WithJs();

    // 使用给定的配置创建一个新的上下文以评估网页
    IBrowsingContext context = BrowsingContext.New(config);

    // 这是我们的示例源码,我们将触发加载事件
    var source = @"<!doctype html>
        <html>
        <head><title>事件示例</title></head>
        <body>
        <script>
        console.log('在设置处理器之前!');

        document.addEventListener('load', function() {
          console.log('文档已加载!');
        });

        document.addEventListener('hello', function() {
          console.log('来自JavaScript的问候世界!');
        });

        console.log('在设置处理器之后!');
        </script>
        </body>";

    IDocument document = await context.OpenAsync(req => req.Content(source));

    // 最终应输出HTML
    Console.WriteLine(document.DocumentElement.OuterHtml);

    // 从C#注册Hello事件监听器(我们在JS中也有一个)
    document.AddEventListener("hello", (s, ev) =>
    {
        Console.WriteLine("来自C#的问候世界!");
    });

    var e = document.CreateEvent("event");
    e.Init("hello", false, false);
    document.Dispatch(e);
}

我们还为这个自定义事件在 C# 中注册了一个事件监听器。在这里,我们有智能感知和所有其他便捷工具。通过官方 API 触发事件后,我们会识别到来自两个已注册事件监听器(来自 JavaScriptC# )的输出。

在本文档中