使用HtmlAgilityPack解析HTML表格并将行数据赋值给自定义模型
问题原因分析
- 第一个原因:浏览器渲染生成的
tbody标签不存在于原始HTML源码中。你在浏览器开发者工具里复制的XPath包含tbody,但HtmlAgilityPack加载的是原始页面源码,多数维基百科的表格源码没有显式写tbody标签,导致你的行定位XPath本身就拿不到有效行数据,后续取值自然会出现异常。 - 第二个原因:表格的第一行是表头行,子节点是
th标签而非td标签,遍历到表头行时调用row.SelectNodes("td")自然返回null。 - 第三个隐藏风险:单元格文本包含大量空白符、特殊符号(比如薪资里的$、逗号),直接调用
DateTime.Parse、int.Parse大概率会抛出格式异常。
修正方案
1. 调整XPath定位
去掉XPath中的tbody节点,同时可以用更鲁棒的相对路径避免页面结构微调导致定位失效:
// 原错误XPath "/html/body/div[3]/div[3]/div[5]/div[1]/table[1]/tbody/tr" // 修正后XPath "/html/body/div[3]/div[3]/div[5]/div[1]/table[1]/tr" // 更稳定的相对定位(推荐) "//table[@class='wikitable sortable jquery-tablesorter'][1]//tr"
2. 修正ParseTable方法逻辑
增加空值判断、表头过滤、文本清洗逻辑,修正后代码如下:
private static List<SuspensionModel> ParseTable(HtmlDocument htmlDocument, string xPath) { var returnData = new List<SuspensionModel>(); var rows = htmlDocument.DocumentNode.SelectNodes(xPath); // 先判断行集合是否为空 if (rows == null || rows.Count == 0) return returnData; foreach (HtmlNode row in rows) { var cells = row.SelectNodes("td"); // 过滤表头行、空行 if (cells == null || cells.Count < 7) continue; // 清洗单元格文本:去除换行、多余空格、特殊符号 var cleanedTexts = cells.Select(c => c.InnerText.Trim().Replace("\n", "").Replace("\t", "")).ToList(); try { var susp = new SuspensionModel { IncidentDate = DateTime.Parse(cleanedTexts[0]), OffenderName = cleanedTexts[1], OffenderTeam = cleanedTexts[2], OffenseDesc = cleanedTexts[3], ActionDate = DateTime.Parse(cleanedTexts[4]), OffenseLength = cleanedTexts[5], // 清洗薪资字段:移除$、逗号后再转数字 SalaryLoss = int.Parse(cleanedTexts[6].Replace("$", "").Replace(",", "")) }; returnData.Add(susp); } catch (FormatException) { // 可在此处加日志,跳过格式异常的行 continue; } } return returnData; }
额外优化建议
- 可以先调用
HtmlNode.Descendants("table")拿到所有表格,再通过表格的特征(比如表头文本、类名)定位目标表格,比写死绝对XPath兼容性更好。 - 日期解析可以指定格式,避免因为页面日期格式变化出现解析异常,比如
DateTime.ParseExact(cleanedTexts[0], "MMMM d, yyyy", CultureInfo.InvariantCulture)。
内容的提问来源于stack exchange,提问作者dotnetdev
相关产品推荐
相关产品推荐

