解析 html -> xml 并使用 Xpath 进行查询
Posted
技术标签:
【中文标题】解析 html -> xml 并使用 Xpath 进行查询【英文标题】:Parsing html -> xml and querying with Xpath 【发布时间】:2011-07-18 14:30:13 【问题描述】:我想解析一个 html 页面来获取一些数据。 首先,我使用 SgmlReader 将其转换为 XML 文档。 然后,我将结果加载到 XMLDocument,然后通过 XPath 导航:
//contains html document
var loadedFile = LoadWebPage();
...
Sgml.SgmlReader sgmlReader = new Sgml.SgmlReader();
sgmlReader.DocType = "HTML";
sgmlReader.WhitespaceHandling = WhitespaceHandling.All;
sgmlReader.CaseFolding = Sgml.CaseFolding.ToLower;
sgmlReader.InputStream = new StringReader(loadedFile);
XmlDocument doc = new XmlDocument();
doc.PreserveWhitespace = true;
doc.XmlResolver = null;
doc.Load(sgmlReader);
此代码在大多数情况下都可以正常工作,除了在此站点上 - www.arrow.com(尝试搜索类似 OP295GS 的内容)。我可以使用以下 XPath 获得带有结果的表:
var node = doc.SelectSingleNode(".//*[@id='results-table']");
这给了我一个带有多个子节点的节点:
[0] Element, Name="thead"
[1] Element, Name="tbody"
[2] Element, Name="tbody"
FirstChild Element, Name="thead"
好的,让我们尝试使用 XPath 获取一些子节点。但这不起作用:
var childNodes = node.SelectNodes("tbody");
//childnodes.Count = 0
这也是:
var childNode = node.SelectSingleNode("thead");
// childNode = null
甚至这个:
var childNode = doc.SelectSingleNode(".//*[@id='results-table']/thead")
Xpath 查询有什么问题?
我刚刚尝试使用 Html Agility Pack 解析该 HTML 页面,并且我的 XPath 查询运行良好。但是我的应用程序内部使用了 XmlDocument,Html Agility Pack 不适合我。
我什至用 Html Agility Pack 尝试了以下技巧,但 Xpath 查询也不起作用:
//let's parse and convert HTML document using HTML Agility Pack and then load
//the result to XmlDocument
HtmlDocument xmlDocument = new HtmlDocument();
xmlDocument.OptionOutputAsXml = true;
xmlDocument.Load(new StringReader(webPage));
XmlDocument document = new XmlDocument();
document.LoadXml(xmlDocument.DocumentNode.InnerHtml);
也许,网页包含错误(并非所有标签都已关闭等等),但尽管如此,我可以看到子节点(通过 Visual Studio 中的 Quick Watch),但无法通过 XPath 访问它们。
我的 XPath 查询在 Firefox + FirePath + XPather 插件中正常工作,但在 .net XmlDocument 中不工作:(
【问题讨论】:
+1 提出一个好问题,并使用 Agility Pack 和 XML 解析器而不是正则表达式解析 HTML。 HTML Agility Pack 易于使用,但它有自己的数据类型,在集成现有逻辑时可能会出现问题。 【参考方案1】:我没有使用过 SqmlReader,但是每次我看到这个问题都是由命名空间引起的。快速浏览 www.arrow.com 上的 HTML 会发现这个节点有一个命名空间(注意 xmlns:javaurlencoder):
<form name="CatSearchForm" method="post" action="http://components.arrow.com/part/search/OP295GS" xmlns:javaurlencoder="java.net.URLEncoder">
这段代码是我如何遍历文档中的所有节点以查看哪些具有命名空间而哪些没有。如果您要查找的节点或其任何父节点具有命名空间,您必须创建一个 XmlNamespaceManager
并将其与您的调用一起传递给 SelectNodes()
。
这有点烦人,所以另一个想法可能是在将 XML 加载到 XmlDocument
之前从 XML 中去除所有 xmlns: 属性。那你就不用XmlNamespaceManager
了!
XmlDocument doc = new XmlDocument();
doc.Load(@"C:\temp\X.loadtest.xml");
Dictionary<string, string> namespaces = new Dictionary<string, string>();
XmlNodeList nlAllNodes = doc.SelectNodes("//*");
foreach (XmlNode n in nlAllNodes)
if (n.NodeType != XmlNodeType.Element) continue;
if (!String.IsNullOrEmpty(n.NamespaceURI) && !namespaces.ContainsKey(n.Name))
namespaces.Add(n.Name, n.NamespaceURI);
// Inspect the namespaces dictionary to write the code below
XmlNamespaceManager nMgr = new XmlNamespaceManager(doc.NameTable);
// Sometimes this works
nMgr.AddNamespace("ns1", doc.DocumentElement.NamespaceURI);
// You can make the first param whatever you want, it just must match in XPath queries
nMgr.AddNamespace("javaurlencoder", "java.net.URLEncoder");
XmlNodeList iter = doc.SelectNodes("//ns1:TestProfile", nMgr);
foreach (XmlNode n in iter)
// Do stuff
【讨论】:
【参考方案2】:说实话,当我尝试从使用正则表达式的网站获取信息时。 Ok Kore Nordmann(在他的 php 博客中)认为,这不好。但有些 cmets 的说法不同。
http://kore-nordmann.de/blog/0081_parse_html_extract_data_from_html.html
http://kore-nordmann.de/blog/do_NOT_parse_using_regexp.html
但它是在 php 中的,对此很抱歉 =) 无论如何希望它有所帮助。
【讨论】:
有一些很好的理由不尝试使用正则表达式解析 (X)HTML。一方面,实际上不可能正确地做到这一点。 ***.com/questions/1732348/… 在所有解密器插件的 JDownloader 中,我们也使用正则表达式。我使用正则表达式编写了一个 BrowserGame 机器人,但无论如何,这就是他们能够在几年后检测到该机器人的原因。安装了一个间隙,由于正则表达式,我的机器人不明白,页面已经改变,但这也可以由正则表达式完成。我只是忘了确保,建立一个“htmlstructure has not changed”机制以避免检测到蜂。 我的应用程序的早期版本在内部使用了正则表达式。这是一场噩梦(与 Xpath 相比)。以上是关于解析 html -> xml 并使用 Xpath 进行查询的主要内容,如果未能解决你的问题,请参考以下文章