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);
}
}
由于 IDocument
的 All
属性返回文档中包含的所有 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 交互
使用 AngleSharp
与 JavaScript
没有问题。当前状态下,我们也可以轻松地进行 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
触发事件后,我们会识别到来自两个已注册事件监听器(来自 JavaScript
和 C#
)的输出。