文章目錄
- 前言
- 一、安裝HtmlAgilityPack
- 1、從NuGet下載HtmlAgilityPack包
- 2、獲取HtmlAgilityPack.dll
- 二、HtmlAgilityPack常用操作
- 1、加載 HTML
- 2、查詢方式
- 2.1 使用 XPath 查詢(推薦)
- 2.2 使用 LINQ 查詢
- 3、常用查詢操作
- 3.1 選擇節點
- 3.2 獲取屬性值
- 3.3 遍歷節點
- 3.4 獲取節點內容
- 三、XPath 語法
- 1、節點類型
- 2、路徑表達式
- 3、節點選擇
- 4、謂語(Predicates)
- 5、通配符
- 6、多條件篩選
- 7、文本內容匹配
- 8、軸(Axes)選擇
- 9、函數
- 四、快捷從瀏覽器獲取XPath
- 五、XPath使用示例
- 1、案例
- 2、常見用法
- 六、HAP 中的實際應用示例
- 1. 獲取所有鏈接
- 2. 獲取特定 class 的內容
- 3. 處理表格數據
- 七、性能優化
- 八、實戰
- 1、爬取靜態網頁文本數據
- 2、爬取并保持圖片數據
- 專欄推薦
- 完結
前言
Html Agility Pack (HAP) 是一個強大的 .NET HTML 解析庫,特別適合在 C# 和 Unity 中實現爬蟲功能。它支持有缺陷的 HTML 解析、XPath 查詢和 LINQ 操作。
核心組件
-
HtmlWeb
:網頁下載器 -
HtmlDocument
:HTML 文檔容器 -
HtmlNode
:HTML 節點對象 -
github:https://github.com/zzzprojects/html-agility-pack?tab=readme-ov-file
一、安裝HtmlAgilityPack
1、從NuGet下載HtmlAgilityPack包
https://www.nuget.org/packages/HtmlAgilityPack/#versions-body-tab
選擇版本點擊下載
2、獲取HtmlAgilityPack.dll
下載的是nupkg文件,解壓后在lib文件夾下, 有不同版本的不同.NET版本對應的包
各版本核心區別
特性/版本 | .NET 8.0版 | .NET Standard 2.0版 | .NET Framework 3.5版 |
---|---|---|---|
兼容性 | 僅支持.NET 6.0+ | 跨平臺(兼容.NET Core/.NET 5+/Unity等) | 僅限傳統.NET Framework |
性能 | 最優(AOT優化) | 中等 | 較低 |
API完整性 | 最新API(如CSS選擇器) | 大部分核心功能 | 基礎功能 |
Unity支持情況 | 需Unity 2021.2+ | 最佳支持(推薦) | 舊版Unity(2018-2020) |
NuGet包名 | HtmlAgilityPack | HtmlAgilityPack.NetCore 或標準版 | HtmlAgilityPack |
建議大多數Unity項目選擇**.NET Standard 2.0**版本,平衡兼容性和功能性。僅當明確需要新特性時再考慮.NET 8.0版本。
將HtmlAgilityPack.DLL文件放入Unity項目的Assets/Plugins文件夾中即可
二、HtmlAgilityPack常用操作
1、加載 HTML
// 從 URL 加載
var web = new HtmlWeb();
HtmlDocument doc = web.Load("https://example.com");// 從字符串加載
var doc = new HtmlDocument();
doc.LoadHtml(htmlContent);// 從文件加載(Unity 使用 Application.dataPath)
doc.Load("path/to/file.html");
2、查詢方式
2.1 使用 XPath 查詢(推薦)
// 獲取所有鏈接
HtmlNodeCollection links = doc.DocumentNode.SelectNodes("//a[@href]");if (links != null)
{foreach (HtmlNode link in links){string href = link.GetAttributeValue("href", "");string text = link.InnerText.Trim();Debug.Log($"鏈接: {text} -> {href}");}
}// 獲取特定 class 的元素
var products = doc.DocumentNode.SelectNodes("//div[@class='product-item']");
2.2 使用 LINQ 查詢
using System.Linq;var titles = doc.DocumentNode.Descendants("h2").Where(node => node.GetAttributeValue("class", "") == "title").Select(node => node.InnerText.Trim()).ToList();
3、常用查詢操作
3.1 選擇節點
// 通過XPath選擇節點
HtmlNodeCollection nodes = doc.DocumentNode.SelectNodes("//div[@class='content']");// 選擇單個節點
HtmlNode node = doc.DocumentNode.SelectSingleNode("//h1");
3.2 獲取屬性值
string href = node.GetAttributeValue("href", ""); // 第二個參數是默認值
string id = node.Attributes["id"]?.Value;
3.3 遍歷節點
foreach (HtmlNode link in doc.DocumentNode.SelectNodes("//a[@href]"))
{string hrefValue = link.GetAttributeValue("href", string.Empty);Debug.Log(hrefValue);
}
3.4 獲取節點內容
string innerText = node.InnerText; // 不含HTML標簽的文本
string innerHtml = node.InnerHtml; // 包含HTML標簽
string outerHtml = node.OuterHtml; // 包含節點自身及其內容
三、XPath 語法
XPath (XML Path Language) 是一種用于在 XML 和 HTML 文檔中定位節點的查詢語言,Html Agility Pack (HAP) 完全支持 XPath 查詢。掌握 XPath 是高效使用 HAP 的關鍵。
1、節點類型
- 元素節點:HTML 標簽(如
<div>
、<a>
) - 屬性節點:元素的屬性(如
href
、class
) - 文本節點:元素內的文本內容
- 文檔節點:整個文檔
2、路徑表達式
XPath 使用路徑表達式來選取節點,類似于文件系統路徑:
/
從根節點開始//
從當前節點選擇匹配節點,不考慮位置.
當前節點..
父節點@
選取屬性
3、節點選擇
表達式 | 說明 | 示例 |
---|---|---|
nodename | 選取所有該名稱的節點 | div 選擇所有 <div> |
/ | 從根節點開始 | /html/body/div |
// | 從任意位置選擇 | //div 選擇所有 <div> |
. | 當前節點 | ./span 當前節點的子 <span> |
.. | 父節點 | ../div 父節點下的 <div> |
@ | 選擇屬性 | @href 選擇 href 屬性 |
4、謂語(Predicates)
用于查找特定節點,放在方括號中:
//div[1] // 第一個<div>
//div[last()] // 最后一個<div>
//div[position()<3] // 前兩個<div>
//a[@href] // 帶有href屬性的<a>
//div[@class='main'] // class為"main"的<div>
5、通配符
通配符 | 說明 | 示例 |
---|---|---|
* | 匹配任何元素節點 | //* 所有元素 |
@* | 匹配任何屬性節點 | //div[@*] 帶任意屬性的 |
node() | 匹配任何類型節點 | //div/node() div的所有子節點 |
6、多條件篩選
//div[@class='article' and @data-id='123']
//a[contains(@class,'btn') or @type='submit']
7、文本內容匹配
//h1[text()='Welcome'] // 精確匹配
//p[contains(text(),'Hello')] // 包含文本
//span[starts-with(text(),'Copyright')]
8、軸(Axes)選擇
軸 | 說明 | 示例 |
---|---|---|
child:: | 子節點(默認軸,可省略) | //div/child::span 或 //div/span |
parent:: | 父節點 | //span/parent::div |
ancestor:: | 所有祖先節點 | //span/ancestor::div |
descendant:: | 所有后代節點 | //div/descendant::span |
following:: | 文檔中當前節點之后的所有節點 | //div/following::span |
preceding:: | 文檔中當前節點之前的所有節點 | //div/preceding::span |
following-sibling:: | 同一層級之后的兄弟節點 | //li/following-sibling::li |
preceding-sibling:: | 同一層級之前的兄弟節點 | //li/preceding-sibling::li |
9、函數
//div[contains(@class, 'header')] // 屬性包含特定字符串
//a[starts-with(@href, 'https')] // 屬性以特定字符串開頭
//p[string-length(text()) > 100] // 文本長度大于100
count(//div) // 統計div數量
四、快捷從瀏覽器獲取XPath
實際情況我們其實可以直接從瀏覽器復制獲取XPath,而不需要自己寫
五、XPath使用示例
1、案例
比如我想要查找 id="liveroom__sidebar"
的 div 元素內部所有 class="status"
的子元素(無論嵌套層級),可以使用以下 XPath 表達式:
//div[@id='liveroom__sidebar']//*[contains(@class, 'status')]
或精確匹配(如果 class 只有 ‘status’):
//div[@id='liveroom__sidebar']//*[@class='status']
關鍵點說明
//
雙斜杠:表示搜索所有后代節點(不限層級)
*
星號:匹配任何標簽名(div/span/p等)
contains(@class, 'status')
:
比
@class='status'
更靈活能匹配復合
class
如<div class="status active">
精確匹配場景:
- 如果確定 class 只有
"status"
(沒有其他class
),可以用@class='status'
2、常見用法
/ 從根節點選取。
// 從匹配選擇的當前節點選擇文檔中的節點,而不考慮它們的位置。
. 選取當前節點。
.. 選取當前節點的父節點。
@ 選取屬性
/bookstore/* 選取 bookstore 元素的所有子元素
//* 選取文檔中的所有元素
//title[@*] 選取所有帶有屬性的 title 元素//div/a/@href 獲取a標簽的href的值
//div/a/text() 獲取a標簽的文本內容
/div/book[1] 選取屬于div子元素的第一個 book 元素。
/div/book[last()] 選取屬于 div 子元素的最后一個 book 元素。
/div/book[last()-1] 選取屬于 div子元素的倒數第二個 book 元素。
//title[@lang] 選取所有擁有名為 lang 的屬性的 title 元素。
//title[@name='a'] 選取所有 title 元素,且這些元素擁有值為 a的 name 屬性。
/div/book[price>35.00] 選取 div元素的所有 book 元素,且其中的 price 元素的值須大于 35.00
//book/title | //book/price 選取 book 元素的所有 title 和 price 元素。
六、HAP 中的實際應用示例
1. 獲取所有鏈接
var links = doc.DocumentNode.SelectNodes("//a[@href]");
foreach (HtmlNode link in links)
{string href = link.GetAttributeValue("href", "");Console.WriteLine(href);
}
2. 獲取特定 class 的內容
var nodes = doc.DocumentNode.SelectNodes("//div[contains(@class,'product')]");
foreach (HtmlNode node in nodes)
{string title = node.SelectSingleNode(".//h3").InnerText;string price = node.SelectSingleNode(".//span[@class='price']").InnerText;
}
3. 處理表格數據
var rows = doc.DocumentNode.SelectNodes("//table[@id='data']/tr");
foreach (HtmlNode row in rows)
{var cells = row.SelectNodes("./td");if (cells != null && cells.Count >= 2){string name = cells[0].InnerText.Trim();string value = cells[1].InnerText.Trim();}
}
七、性能優化
- 盡量使用具體路徑而非
//
開頭 - 優先使用屬性而非文本內容定位
- 緩存常用 XPath 查詢結果
八、實戰
1、爬取靜態網頁文本數據
比如我實現爬取自己的博客的簡介數據,按f12
查看html代碼結構
代碼如下
using System.Collections;
using HtmlAgilityPack;
using UnityEngine;public class WebCrawler : MonoBehaviour
{// 目標網站URL(請替換為實際網址)public string targetUrl = "https://xiangyu.blog.csdn.net";void Start(){StartCoroutine(FetchWebData());}IEnumerator FetchWebData(){// 從URL加載HTML文檔HtmlWeb web = new HtmlWeb();HtmlDocument doc = web.Load(targetUrl);//個人簡介(這里故意分兩段獲取只是為了演示效果)HtmlNode nodeIntro = doc.DocumentNode.SelectSingleNode("//div[@class='user-profile-head-info-b']");string strIntro = nodeIntro.SelectSingleNode("//p[contains(@class, 'introduction-fold')]").InnerText;Debug.Log(strIntro);// 博客簡介和博客描述HtmlNodeCollection nodes = doc.DocumentNode.SelectNodes("//div[@class='user-profile-wrapper-box']");if (nodes != null){foreach (HtmlNode node in nodes){Debug.Log(node.InnerText);}}yield return null;}
}
結果
2、爬取并保持圖片數據
using UnityEngine;
using UnityEngine.Networking;
using System.IO;
using HtmlAgilityPack;
using System.Collections;
using System.Web;public class SimpleImageDownloader : MonoBehaviour
{public string url = "https://cn.bing.com/images/search?q=%E5%8F%AF%E7%88%B1%E5%9B%BE%E7%89%87&form=HDRSC2&first=1&cw=1177&ch=917"; // 目標網頁public string saveFolder = "DownloadedImages"; // 保存文件夾void Start(){StartCoroutine(DownloadImages());}IEnumerator DownloadImages(){// 1. 下載網頁UnityWebRequest webRequest = UnityWebRequest.Get(url);yield return webRequest.SendWebRequest();if (webRequest.result != UnityWebRequest.Result.Success){Debug.LogError("網頁加載失敗: " + webRequest.error);yield break;}// 2. 解析圖片var htmlDoc = new HtmlDocument();htmlDoc.LoadHtml(webRequest.downloadHandler.text);var imgNodes = htmlDoc.DocumentNode.SelectNodes("//img[@src]");if (imgNodes == null) yield break;// 3. 創建保存目錄string savePath = Path.Combine(Application.dataPath, "../", saveFolder);Directory.CreateDirectory(savePath);// 4. 下載圖片foreach (var imgNode in imgNodes){string imgUrl = imgNode.GetAttributeValue("src", "");if (!imgUrl.StartsWith("http")) {// 處理相對路徑imgUrl = new System.Uri(new System.Uri(url), imgUrl).AbsoluteUri;}UnityWebRequest imgRequest = UnityWebRequestTexture.GetTexture(imgUrl);yield return imgRequest.SendWebRequest();if (imgRequest.result == UnityWebRequest.Result.Success){Texture2D texture = DownloadHandlerTexture.GetContent(imgRequest);byte[] bytes = texture.EncodeToPNG();string fileName = Path.GetFileName(imgUrl.Split('?')[0])+".png";if (string.IsNullOrEmpty(fileName)) fileName = "image.png";File.WriteAllBytes(Path.Combine(savePath, fileName), bytes);Debug.Log("已保存: " + fileName);}else{Debug.LogWarning("下載失敗: " + imgUrl);}}Debug.Log("圖片下載完成!保存在" + savePath);}
}
結果
專欄推薦
地址 |
---|
【unity游戲開發入門到精通——C#篇】 |
【unity游戲開發入門到精通——unity通用篇】 |
【unity游戲開發入門到精通——unity3D篇】 |
【unity游戲開發入門到精通——unity2D篇】 |
【unity實戰】 |
【制作100個Unity游戲】 |
【推薦100個unity插件】 |
【實現100個unity特效】 |
【unity框架/工具集開發】 |
【unity游戲開發——模型篇】 |
【unity游戲開發——InputSystem】 |
【unity游戲開發——Animator動畫】 |
【unity游戲開發——UGUI】 |
【unity游戲開發——聯網篇】 |
【unity游戲開發——優化篇】 |
【unity游戲開發——shader篇】 |
【unity游戲開發——編輯器擴展】 |
【unity游戲開發——熱更新】 |
【unity游戲開發——網絡】 |
完結
好了,我是向宇
,博客地址:https://xiangyu.blog.csdn.net,如果學習過程中遇到任何問題,也歡迎你評論私信找我。
贈人玫瑰,手有余香!如果文章內容對你有所幫助,請不要吝嗇你的點贊評論和關注
,你的每一次支持
都是我不斷創作的最大動力。當然如果你發現了文章中存在錯誤
或者有更好的解決方法
,也歡迎評論私信告訴我哦!