解析HTML文档:正则表达式还是LINQ?

8

尝试解析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建议也有效。我希望我能将两个答案都标记为“答案”,但我显然不能。它们都是解决问题的有效方法。

这是一个C#控制台应用程序,使用Jeff建议的正则表达式。它可以正确读取字符串,并且不会包含任何没有以.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解析器? - Jeff
@JD:当然可以!正如Matthew所建议的那样,HTML Agility Pack听起来值得一试。您想要建议它还是其他工具? - p.campbell
1
@Philoushka 我本来想建议使用HTML Agility Pack...它非常好用。 - Jeff
4个回答

13

都不需要。将它加载到(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 https://dev59.com/mHRC5IYBdhLWcg3wG9bp
        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);

2
@Matthew:HTML Agility Pack 在大约 5 分钟的实现时间内为我提供了所需的功能。它附带了示例和源代码。向 Simon Mourier 致敬! - p.campbell
现在Agility包中也支持“LINQ to HTML”。 - Pete Montgomery

1

我建议使用正则表达式。为什么呢?

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

只要你能编写正则表达式,阅读正则表达式就不会很难。

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

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
}

1
@Jeff:这是一个非常优秀的代码示例。感谢您的贡献! - p.campbell
4
如果OP明确表示“以...结尾”,那么匹配href中任何位置的.txt是不恰当的。我认为,在这里使用正则表达式是不合适的。 - Matthew Flaschen
@Matthew:不,它只会匹配以(".txt")结尾的HREF。我认为HREF中间没有引号。 - Dmitri Farkov
1
不要尝试使用正则表达式来解析非正则语言。 - Svante
1
我理解从DOM / XPath角度处理这个问题的愿望 - 但我的理由是,正则表达式实现对输入数据假设非常少。显然,如果OP可以做出假设,特别是像格式良好的文档之类的,那么DOM方法会更加“干净”。@Svante:我认为正则表达式非常适合从非规则数据中查找已知模式。想想你用正则表达式多少次grep了某些内容。此外,OP想要一个正则表达式示例。 - Jeff Meatball Yang
我误读了你的正则表达式。然而,如果它们被单引号包围,href实际上可以包含“'s”。<a href='foo.txt".html'>foo</a>尽管这有点反常,但确实是有效的。无论如何,重要的是OP得到了适用于其当前数据的有效解决方案。 - Matthew Flaschen

0

正则表达式不快,事实上比 .NET 中的本机字符串解析要慢。别信我,亲自试试。

以上示例中没有一个比直接访问 DOM 更快。

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

0

除了Matthew Flaschen的建议,DOM也是一个选择(例如,如果你患有X?L过敏反应)

它有时会被贬低 - 我想这是因为实现有时候确实很有趣,而本地COM接口在没有一些(轻微的)智能助手的情况下有点笨拙,但我发现它是一种稳健、稳定和直观/可探索的解析和操作HTML的方式。


2
你实际上建议他通过COM互操作从.NET使用IE的HTML解析器吗?... - Matthew Flaschen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接