使用HTML Agility Pack爬取时DataTable重复列名报错如何解决
问题修复与实现方案
核心错误原因
- XPath查询语法错误:
table.SelectNodes("//tr")中的//会从整个HTML文档根节点查找所有tr元素,而非当前遍历到的table节点下的tr,导致所有表格的行被重复塞入同一个DataTable,多次读取不同表格的同名列表头,触发重复列名报错。 - 缺少重名列兼容逻辑:就算修复XPath问题,单个表格内部如果出现重名列,依然会触发报错,直接跳过重名列会导致表头列数和数据行单元格数不匹配,后续赋值时触发“列不存在”报错。
修复后完整代码
using System.Data; using System.IO; using System.Text; using System.Windows.Forms; using HtmlAgilityPack; public void WebDataScrap() { try { const string url = "https://www.wsj.com/market-data/quotes/MY/XKLS/0146/financials/annual/cash-flow"; var web = new HtmlWeb(); var doc = web.Load(url); const string classValue = "cr_dataTable"; var resultDataset = new DataSet(); foreach (HtmlNode table in doc.DocumentNode.SelectNodes($"//table[@class='{classValue}']") ?? Enumerable.Empty<HtmlNode>()) { // 给无Id的表格设置默认名称 var tableName = string.IsNullOrEmpty(table.Id) ? $"Table_{resultDataset.Tables.Count + 1}" : table.Id; var resultTable = new DataTable(tableName); // 修复XPath,只查当前table下的tr foreach (HtmlNode row in table.SelectNodes(".//tr") ?? Enumerable.Empty<HtmlNode>()) { var headerCells = row.SelectNodes("th"); if (headerCells != null) { foreach (HtmlNode cell in headerCells) { string colName = cell.InnerText.Trim(); // 处理重名列,自动加后缀 int suffix = 1; string tempColName = colName; while (resultTable.Columns.Contains(tempColName)) { tempColName = $"{colName}_{suffix}"; suffix++; } resultTable.Columns.Add(tempColName); } continue; // 表头行不需要处理数据逻辑 } var dataCells = row.SelectNodes("td") ?? Enumerable.Empty<HtmlNode>(); if (dataCells.Any()) { var dataRow = resultTable.NewRow(); // 兼容列数和单元格数不一致的情况 int cellCount = Math.Min(dataCells.Count, resultTable.Columns.Count); for (int i = 0; i < cellCount; i++) { dataRow[i] = dataCells[i].InnerText.Trim(); } resultTable.Rows.Add(dataRow); } } resultDataset.Tables.Add(resultTable); } // 导出到CSV,每个表格对应一个CSV文件,也可以合并导出到Excel的不同Sheet ExportDatasetToCsv(resultDataset, @"C:\你的导出路径\WSJ财务数据"); MessageBox.Show("数据爬取导出完成"); } catch (Exception ex) { MessageBox.Show(ex.ToString()); } } // 无需第三方依赖的CSV导出方法 private void ExportDatasetToCsv(DataSet ds, string exportPathPrefix) { if (!Directory.Exists(Path.GetDirectoryName(exportPathPrefix))) { Directory.CreateDirectory(Path.GetDirectoryName(exportPathPrefix)); } foreach (DataTable table in ds.Tables) { var filePath = $"{exportPathPrefix}_{table.TableName}.csv"; var sb = new StringBuilder(); // 写入表头 IEnumerable<string> columnNames = table.Columns.Cast<DataColumn>().Select(c => c.ColumnName); sb.AppendLine(string.Join(",", columnNames)); // 写入数据行 foreach (DataRow row in table.Rows) { IEnumerable<string> fields = row.ItemArray.Select(field => $"\"{field.ToString().Replace("\"", "\"\"")}\""); // 处理内容里的逗号和引号 sb.AppendLine(string.Join(",", fields)); } File.WriteAllText(filePath, sb.ToString(), Encoding.UTF8); } }
可选优化
如果需要导出为单个Excel文件,每个表格对应一个Sheet,可以引入EPPlus或NPOI库实现,逻辑和导出CSV类似,遍历DataSet的Table对应生成Excel的Sheet即可。
内容的提问来源于stack exchange,提问作者Marcus Tan
相关产品推荐
相关产品推荐

