C#爬蟲工具 如何使用HtmlAgilityPack解析Html
HtmlAgilityPack 是一個開源的快速解析Html的C#類庫。簡單理解,它可以像解析Xml一樣,將Html根據XPATH轉化為一個個Node節點,並支援調整節點以及節點的各種屬性。
多種方式載入Html
主要載入方式有3類:從網路連結載入、從字串文字中載入、從檔案載入
var doc = new HtmlDocument(); //直接通過url載入 doc = new HtmlWeb().Load("https://www.baidu.com/"); //通過字串載入 doc.LoadHtml(result); //通過html檔案載入 可指定編碼方式 doc.Load(@"c://index.html",Encoding.UTF8)
HtmlNode常用方法
使用SelectNodes()和SelectSingleNode()方法(類似解析XML格式資料的XmlDocument)來獲取的目標節點,分別對應HtmlNodeCollection和HtmlNode兩個類。
"//"表示從根節點開始查詢,兩個斜槓"//"表示查詢所有childnodes;一個斜槓"/"表示只查詢第一層的childnodes(即不查詢grandchild);點斜槓"./"表示從當前結點而不是根結點開始查詢(只在xpath最開始出現)
注意:
id class 屬性匹配大小寫敏感
xpath匹配下標從1開始
1. 通過屬性和路徑匹配來選擇對應的節點
var node = doc.DocumentNode; //選擇不包含class屬性的div節點 var result = node.SelectNodes(".//div[not(@class)]"); //選擇不包含class和id屬性的div節點 var result = node.SelectNodes(".//div[not(@class) and not(@id)]"); //選擇class中包含"expire"的span節點 var result = node.SelectNodes(".//span[contains(@class,'expire')]"); //選擇class中不包含"expire"的span節點 var result = node.SelectNodes(".//span[not(contains(@class,'expire'))]"); //選擇class="expire"的span節點 var result = node.SelectNodes(".//span[@class='expire']"); //選擇id="expire"的div節點下第一個div節點 var result = node.SelectSingleNode(".//div[@id='expire']/div[1]");
2. 獲取節點文字內容
根據需求不同,通過不同的方式來獲取相應的文字內容。
OuterHtml:返回包含當前節點在內的所有Html
InnerHtml:返回當前節點內所有子節點Html
InnerText:返回當前節點內去除所有Html後的文字內容
<div id="title">
<p>
<a class="MainTitle" href="https://www.cnblogs.com/cplemom/">傅小灰</a>
</p>
</div>
以上面的Html為例
var node= doc.DocumentNode.SelectSingleNode("//div[@id='title'/p]");
node.OuterHtml; //返回結果:<p><a class="MainTitle"href="https://www.cnblogs.com/cplemom/">傅小灰</a></p>
node.InnerHtml; //返回結果:<a class="MainTitle"href="https://www.cnblogs.com/cplemom/">傅小灰</a>
node.InnerText; //返回結果:傅小灰
3. 獲取/修改節點屬性值
以上面的Html舉例,我們獲取到了a標籤為node節點。我們想要獲取a標籤指向的連結地址,並修改為我們設定的地址。這裡以href屬性舉例,同樣可以用在class/src/id等屬性上。
var node= doc.DocumentNode.SelectSingleNode("//div[@id='title'/p/a]");
//第二個引數為找不到對應屬性時返回的預設值
var url = node.GetAttributeValue("href", "");//返回結果:https://www.cnblogs.com/cplemom/
//設定屬性值
node.SetAttributeValue("href", "http://www.cplemom.com/");
//獲取所有屬性值
var list = node.Attributes.ToList();
4. 刪除/替換節點
繼續以上面的Html舉例,我們獲取到了a標籤為node節點。
對於我們不需要的內容,我們只需要呼叫節點Remove方法即可。
var node= doc.DocumentNode.SelectSingleNode("//div[@id='title'/p/a]");
node.Remove();//刪除節點
一個很常見的場景就是我們需要移除a標籤,但是要在html上下文中保留a標籤的文字。
PS : a標籤內的文字在HtmlDocument中其實是一個text型別的node節點。所以我們可以通過刪除a標籤,保留text標籤的方式來完成目標。
node.ParentNode.RemoveChild(node,true);
true表示留下a標籤的子節點只刪除a標籤,在這裡就表示保留下“傅小灰”text節點; false表示將此結點連同所有子節點一起刪除。
換個角度思考下,當前node節點代表的是單個a標籤,那麼如果p標籤下存在多個a標籤需要處理,或者node節點指向的就是p標籤呢?當然,我們可以通過獲取所有a標籤然後迴圈處理的方式來實現,但是還有沒有別的更好的處理方式呢?
這裡提供一個思路,獲取所有的文字內容,新建為一個text節點,然後替換掉當前節點。
node.ParentNode.ReplaceChild(HtmlNode.CreateNode(item.InnerText), node);
幾個常見使用場景和解決方案
1. 獲取所有的img標籤
//通過Descendants獲取所有的子後代節點中的img標籤
var list = node.Descendants("img");
//通過Xpath匹配獲取所有img標籤
var list = node.SelectNodes("//img");
2. 通過url訪問時需要攜帶cookie等驗證資訊
有些頁面需要攜帶驗證資訊才能訪問,比如使用者中心,訂單列表等,這時候直接通過HtmlWeb類獲取html會被拒絕。有個簡單的方式就是通過HttpClient請求到對應的html內容,再使用HtmlDocument載入。其實HtmlWeb說白了也是封裝的HttpWebRequest進行網路請求的,所以暴露一個委託給外部用以修改請求上下文。
var web = new HtmlWeb();
web.PreRequest = new HtmlWeb.PreRequestHandler(GetRequest);
var node = web.Load("https://www.cplemom.com/");
public static bool GetRequest(HttpWebRequest req)
{
req.Headers.Add("Host", "www.cplemom.com");
req.Headers.Add("Cookie", "xxxxxxxxxxxxx");
return true;
}
總結
用到現在,個人感覺上面的方法已經可以實現90%以上的的Html解析相關需求了,更多方便快捷的方法還是到官網的API文件進行了解吧。