HTML敏捷包 - 解析表格

63

我想使用HTML Agility Pack来解析复杂网页中的表格,但是在对象模型中迷失了方向。

我查看了链接示例,但没有找到任何表格数据。我是否可以使用XPath获取表格?加载数据后,我基本上不知道如何获取表格。我以前用Perl做过这个,有点笨重,但可行(HTML::TableParser)。

如果您可以为解析指出正确的对象顺序,我也会很高兴。

5个回答

128

可以尝试使用HTML Agility Pack

HtmlDocument doc = new HtmlDocument();
doc.LoadHtml(@"<html><body><p><table id=""foo""><tr><th>hello</th></tr><tr><td>world</td></tr></table></body></html>");
foreach (HtmlNode table in doc.DocumentNode.SelectNodes("//table")) {
    Console.WriteLine("Found: " + table.Id);
    foreach (HtmlNode row in table.SelectNodes("tr")) {
        Console.WriteLine("row");
        foreach (HtmlNode cell in row.SelectNodes("th|td")) {
            Console.WriteLine("cell: " + cell.InnerText);
        }
    }
}

请注意,如果您愿意,您可以使用LINQ-to-Objects使其更加美观:

var query = from table in doc.DocumentNode.SelectNodes("//table").Cast<HtmlNode>()
            from row in table.SelectNodes("tr").Cast<HtmlNode>()
            from cell in row.SelectNodes("th|td").Cast<HtmlNode>()
            select new {Table = table.Id, CellText = cell.InnerText};

foreach(var cell in query) {
    Console.WriteLine("{0}: {1}", cell.Table, cell.CellText);
}

1
嗨 Mark,你能否就解析大文件提供建议吗?对于超过50MB的文件,我无法在较大的文件中获取子tr节点。 - Johnny_D
@Marc - 如果表格是分页的,那么如何通过爬取进入下一页? - Dark_Knight
@Dark_Knight,您需要访问原始页面使用的任何Ajax路由。 - Marc Gravell
@MarcGravell,我发现了这个代码 paging_init('sites', 'sites_tbl','/ipID/23.227.38.0/ipIDii/23.227.38.255/sort/6/asc/1', true, '1', '536', {sortCol: '6', sortAsc: '1'}),你知道如何调用这个函数吗? - Dark_Knight

30

我发现获取特定元素的XPath最简单的方法是安装Firefox的FireBug扩展程序,打开网页后按F12打开Firebug; 鼠标右键单击您想要查询的页面元素并选择"检查元素",Firebug将在其IDE中选择该元素,然后在Firebug中单击该元素并选择"复制XPath",此功能将为您提供确切的XPath查询,以使用HTML Agility Library获取您想要的元素。


4
请注意,有时浏览器会稍微更改HTML的DOM - 例如,如果缺少<tbody>,则会将其添加到<table>中。 Html Agility Pack默认情况下在解析HTML时也不包括<form>和<option>标签。请记住这些差异,这样您就可以更成功地使用XPath在浏览器和Html Agility Pack之间兼容。 - Anders
即使承认Anders提到的缺陷,这仍然是一个很好的时间节省工具。 - Phill Healey
似乎Firefox不再支持它了 :( - Noctis

3

我知道这是一个比较老的问题,但是我的解决方案可以帮助您可视化表格并创建一个类结构。这也使用了HTML Agility Pack。

HtmlDocument doc = new HtmlDocument();
doc.LoadHtml(@"<html><body><p><table id=""foo""><tr><th>hello</th></tr><tr><td>world</td></tr></table></body></html>");
var table = doc.DocumentNode.SelectSingleNode("//table");
var tableRows = table.SelectNodes("tr");
var columns = tableRows[0].SelectNodes("th/text()");
for (int i = 1; i < tableRows.Count; i++)
{
    for (int e = 0; e < columns.Count; e++)
    {
        var value = tableRows[i].SelectSingleNode($"td[{e + 1}]");
        Console.Write(columns[e].InnerText + ":" + value.InnerText);
    }
Console.WriteLine();
}

1
在我的情况下,有一个单独的表格,恰好是来自路由器的设备列表。如果你希望使用TR/TH/TD(行、表头、数据)而不是上面提到的矩阵来读取表格,你可以像下面这样做:
    List<TableRow> deviceTable = (from table in document.DocumentNode.SelectNodes(XPathQueries.SELECT_TABLE)
                                       from row in table?.SelectNodes(HtmlBody.TR)
                                       let rows = row.SelectSingleNode(HtmlBody.TR)
                                       where row.FirstChild.OriginalName != null && row.FirstChild.OriginalName.Equals(HtmlBody.T_HEADER)
                                       select new TableRow
                                       {
                                           Header = row.SelectSingleNode(HtmlBody.T_HEADER)?.InnerText,
                                           Data = row.SelectSingleNode(HtmlBody.T_DATA)?.InnerText}).ToList();
                                       }  

TableRow是一个简单的对象,具有Header和Data属性。这种方法考虑了空值和这种情况:

<tr>
    <td width="28%">&nbsp;</td>
</tr>

这是一个没有标题的表格行。HtmlBody 对象中常量的含义可能很容易推断,但我仍然感到抱歉。我来自一个世界,在那里,如果你的代码中有双引号,它应该是常量或可本地化的。

-1

来自以上答案的行:

HtmlDocument doc = new HtmlDocument();

在VS 2015 C#中,这个不起作用。你不能再构造一个HtmlDocument了。

另一个让使用变得更加困难的微软“特性”。尝试使用HtmlAgilityPack.HtmlWeb并查看此链接以获取一些示例代码。


1
对我来说是可行的,不确定你在说什么。 - Peroxy

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