【问题标题】:Parsing HTML document: Regular expression or LINQ?解析 HTML 文档:正则表达式还是 LINQ?
【发布时间】:2023-04-02 02:10:01
【问题描述】:

尝试解析 HTML 文档并提取一些元素(任何指向文本文件的链接)。

当前的策略是将 HTML 文档加载到字符串中。然后找到文本文件链接的所有实例。它可以是任何文件类型,但对于这个问题,它是一个文本文件。

最终目标是拥有一个IEnumerable 字符串对象列表。这部分很简单,但解析数据是个问题。

<html>
<head><title>Blah</title>
</head>
<body>
<br/>
<div>Here is your first text file: <a href="http://myServer.com/blah.txt"></div>
<span>Here is your second text file: <a href="http://myServer.com/blarg2.txt"></span>
<div>Here is your third text file: <a href="http://myServer.com/bat.txt"></div>
<div>Here is your fourth text file: <a href="http://myServer.com/somefile.txt"></div>
<div>Thanks for visiting!</div>
</body>
</html>

最初的方法是:

  • 将字符串加载到 XML 文档中,并以 Linq-To-Xml 方式对其进行攻击。
  • 创建一个正则表达式,查找以href= 开头并以.txt 结尾的字符串

问题是:

  • 那个正则表达式会是什么样子?我是一个正则表达式新手,这是我学习正则表达式的一部分。
  • 您会使用哪种方法来提取标签列表?
  • 哪种方式最高效?
  • 哪种方法可读性/可维护性最高?


更新: 感谢 Matthew 关于 HTML Agility Pack 的建议。它工作得很好! XPath 建议也适用。我希望我可以将两个答案都标记为“答案”,但我显然不能。它们都是解决问题的有效方法。

这是一个使用 Jeff 建议的正则表达式的 C# 控制台应用程序。它可以很好地读取字符串,并且不会包含任何不以 .txt 结尾的 href。对于给定的示例,它不会在结果中正确包含 .txt.snarg 文件(如 HTML 字符串函数中提供的那样)。

using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;

namespace ParsePageLinks
{
    class Program
    {
        static void Main(string[] args)
        {
            GetAllLinksFromStringByRegex();
        }

        static List<string> GetAllLinksFromStringByRegex()
        {
            string myHtmlString = BuildHtmlString();
            string txtFileExp = "href=\"([^\\\"]*\\.txt)\"";

            List<string> foundTextFiles = new List<string>();

            MatchCollection textFileLinkMatches = Regex.Matches(myHtmlString, txtFileExp, RegexOptions.IgnoreCase);
            foreach (Match m in textFileLinkMatches)
            {
                foundTextFiles.Add( m.Groups[1].ToString()); // this is your captured group
            }

            return files;
        }

            static string BuildHtmlString()
            {
                return new StringReader(@"<html><head><title>Blah</title></head><body><br/>
<div>Here is your first text file: <a href=""http://myServer.com/blah.txt""></div>
<span>Here is your second text file: <a href=""http://myServer.com/blarg2.txt""></span>
<div>Here is your third text file: <a href=""http://myServer.com/bat.txt.snarg""></div>
<div>Here is your fourth text file: <a href=""http://myServer.com/somefile.txt""></div>
<div>Thanks for visiting!</div></body></html>").ReadToEnd();
            }       
        }
    }

【问题讨论】:

  • 您愿意使用开源 HTML 解析器吗?
  • @JD:绝对!正如 Matthew 所建议的,HTML Agility Pack 听起来值得一看。你会建议这个还是另一个?
  • @Philoushka 我本来打算推荐 HTML Agility Pack ......它太棒了。

标签: c# regex linq parsing linq-to-xml


【解决方案1】:

两者都没有。将其加载到 (X/HT)MLDocument 中并使用 XPath,这是一种操作 XML 的标准方法并且非常强大。要查看的函数是SelectNodesSelectSingleNode

由于您显然使用的是 HTML(不是 XHTML),因此您应该使用 HTML Agility Pack。大多数方法和属性都与相关的 XML 类匹配。

使用 XPath 的示例实现:

    HtmlDocument doc = new HtmlDocument();
    doc.Load(new StringReader(@"<html>
<head><title>Blah</title>
</head>
<body>
<br/>
<div>Here is your first text file: <a href=""http://myServer.com/blah.txt""></div>
<span>Here is your second text file: <a href=""http://myServer.com/blarg2.txt""></span>
<div>Here is your third text file: <a href=""http://myServer.com/bat.txt""></div>
<div>Here is your fourth text file: <a href=""http://myServer.com/somefile.txt""></div>
<div>Thanks for visiting!</div>
</body>
</html>"));
        HtmlNode root = doc.DocumentNode;
        // 3 = ".txt".Length - 1.  See http://stackoverflow.com/questions/402211/how-to-use-xpath-function-in-a-xpathexpression-instance-programatically
        HtmlNodeCollection links = root.SelectNodes("//a[@href['.txt' = substring(., string-length(.)- 3)]]");
    IList<string> fileStrings;
    if(links != null)
    {
        fileStrings = new List<string>(links.Count);
        foreach(HtmlNode link in links)
        fileStrings.Add(link.GetAttributeValue("href", null));
    }
    else
        fileStrings = new List<string>(0);

【讨论】:

  • @Matthew:HTML 敏捷包在大约 5 分钟的实施时间内为我提供了我所需要的东西。它带有样品和来源。向 Simon Mourier 致敬!
  • Agility 包中现在还支持“LINQ to HTML”。
【解决方案2】:

我会推荐正则表达式。为什么?

  • 灵活(不区分大小写,易于 添加新的文件扩展名,元素到 检查等)
  • 快速编写
  • 运行速度快

正则表达式不难读,只要你会写正则表达式。

使用这个作为正则表达式:

href="([^"]*\.txt)"

解释:

  • 它有括号围绕 文件名,这将导致 您可以访问的“捕获的组” 在找到每个匹配项之后。
  • 它必须转义“.”通过使用 正则表达式转义字符,反斜杠。
  • 它必须匹配任何字符,除了 双引号:[^"] 直到找到
    “.txt”

它转换成这样的转义字符串:

string txtExp = "href=\"([^\\\"]*\\.txt)\"

然后你可以迭代你的匹配:

Matches txtMatches = Regex.Matches(input, exp, RegexOptions.IgnoreCase);
foreach(Match m in txtMatches) {
  string filename = m.Groups[1]; // this is your captured group
}

【讨论】:

  • @Jeff:这是一个出色的代码示例。感谢您的意见!
  • 当 OP 明确表示“以”结尾时,它将与 href 中的任何位置匹配 .txt。在我看来,正则表达式在这里是不合适的。
  • @Matthew:不,它只会匹配以 (.txt") 结尾的 HREF。我不认为 HREF 的中间包含引号。
  • 不要尝试使用正则表达式解析非正则语言。
  • 我理解从 DOM/XPath 的角度来处理这个问题的愿望——但我的理由是正则表达式实现对输入数据的假设很少。显然,如果 OP 可以做出假设,特别是像格式良好的文档,那么 DOM 方法会“更干净”。 @Svante:我认为正则表达式非常适合从非常规数据中找到已知模式。想想你有多少次用正则表达式来寻找东西。此外,OP 想要一个正则表达式示例。
【解决方案3】:

替代 Matthew Flaschen 的建议,DOM (例如,如果您患有 X?L 过敏症)

它有时会得到一个坏名声 - 我猜是因为实现 有时很有趣,而且原生 COM 接口在没有一些(次要)智能助手的情况下有点笨拙,但我发现它是一个健壮的,稳定且直观/可探索的方式来解析和操作 HTML。

【讨论】:

  • 您实际上是在建议他通过 COM 互操作从 .NET 使用 IE 的 HTML 解析器?....
【解决方案4】:

REGEX 并不快,实际上它比 .NET 中的原生字符串解析要慢。不信你自己看。

以上示例都没有比直接访问 DOM 更快。

HTMLDocument doc = wb.Document;
var links = doc.Links;

【讨论】:

    猜你喜欢
    • 2016-09-23
    • 1970-01-01
    • 2021-07-20
    • 2014-12-06
    • 2012-09-12
    • 2014-05-16
    • 1970-01-01
    • 2014-06-08
    相关资源
    最近更新 更多