HtmlAgilityPack数据混乱:如何提取单条广告节点内的信息?
解决广告卡片内信息提取的XPath路径问题
你的推测完全正确://*[@class='...']这种以双斜杠开头的XPath表达式,会从整个HTML文档的根节点开始搜索匹配元素,而不是限定在当前的mainCard[i]节点范围内。这就是为什么每次都返回同一个标题的原因。
修正方案:使用相对路径XPath
要在当前节点的范围内查找子元素,需要使用相对路径:
- 用
.//开头:表示从当前节点开始,搜索所有后代节点(包括子节点、孙节点等) - 或者直接写子节点路径(如果目标元素是直接子节点)
修正后的单卡片标题提取代码
var url = url_textbox.Text; var web = new HtmlWeb(); var doc = web.Load(url); // 先获取所有广告卡片节点(请根据实际页面结构调整XPath) var mainCard = doc.DocumentNode.SelectNodes("//div[@id='AdCardId']"); if (mainCard != null) { for (int i = 0; i < mainCard.Count; i++) { // 使用.//限定在当前卡片节点内查找标题 var titleNode = mainCard[i].SelectSingleNode(".//*[@class='" + adTitleClassName + "']"); if (titleNode != null) { var title = titleNode.InnerText; Print(title); // 现在会输出每个卡片对应的标题 } } }
完整的广告信息提取优化代码
把所有字段的提取都改成相对路径,彻底解决信息错乱问题:
var url = url_textbox.Text; var web = new HtmlWeb(); var doc = web.Load(url); // 选中所有广告卡片节点(请根据实际页面结构调整XPath) var adCards = doc.DocumentNode.SelectNodes("//div[@class='css-1sw7q4x']"); // 示例OLX卡片类名,替换为你的实际选择器 if (adCards != null) { foreach (var card in adCards) { // 从当前卡片内提取所有信息 var titleNode = card.SelectSingleNode(".//*[@class='" + adTitleClassName + "']"); var priceNode = card.SelectSingleNode(".//*[@class='" + adPriceClassName + "']"); var linkNode = card.SelectSingleNode(".//*[@class='" + adLinkClassName + "']"); var damagedNode = card.SelectSingleNode(".//*[@class='" + adDamagedClassName + "']"); var locationNode = card.SelectSingleNode(".//*[@class='" + adLocationClassName + "']"); var imageContainer = card.SelectSingleNode(".//div[@class='css-gl6djm']"); var imageNode = imageContainer?.SelectSingleNode(".//img"); // 空值判断,避免空引用异常 if (titleNode == null || priceNode == null || linkNode == null) continue; var title = titleNode.InnerText.Trim(); var priceText = priceNode.InnerText.Trim(); var link = linkNode.GetAttributeValue("href", ""); var damagedText = damagedNode?.InnerText.Trim() ?? ""; var timeAdded = locationNode?.InnerText.Trim() ?? ""; var imageUrl = imageNode?.GetAttributeValue("src", "") ?? ""; // 解析价格和损坏状态 var damagedParsed = damagedText == "Uszkodzone"; var negotiate = priceText.Contains("do negocjacji"); double.TryParse(priceText.Replace("do negocjacji", "").Replace(" zł", "").Replace(" ", ""), out var price); var ad = new Ad { Title = title, Price = price, URL = string.IsNullOrEmpty(link) ? "" : "https://www.olx.pl" + link, Damaged = damagedParsed, TimeAdded = timeAdded, ImageURL = imageUrl, Negotiate = negotiate }; // 过滤iPhone广告(如果需要) if (only_iphone_checkBox.Checked) { var lowerTitle = title.ToLower(); if (lowerTitle.Contains("iphone") || lowerTitle.Contains("apple")) { ads.Add(ad); } } else { ads.Add(ad); } } } // 渲染广告列表 foreach (Ad ad in ads) { AdItem adItem = new AdItem(ad); ads_list.Controls.Add(adItem); }
关键注意点
- XPath路径规则:
//xxx:绝对路径,从文档根开始搜索.//xxx:相对路径,从当前节点开始搜索后代./xxx:仅搜索当前节点的直接子元素
- 空值判断:添加节点空值检查,避免页面结构变化导致的异常
- 卡片选择器准确性:确保
adCards的XPath能正确选中每个独立广告卡片,这是后续提取的基础
内容的提问来源于stack exchange,提问作者thewolf1119
相关产品推荐
相关产品推荐

