## 前言: 在當今信息化社會,網路數據分析越來越受到重視。而作為開發人員,掌握一門能夠抓取網頁內容的語言顯得尤為重要。在此篇文章中,將分享如何使用 .NET構建網路抓取工具。詳細瞭解如何執行 HTTP 請求來下載要抓取的網頁,然後從其 DOM 樹中選擇 HTML 元素,進行匹配需要的欄位信息,從中 ...
前言:
在當今信息化社會,網路數據分析越來越受到重視。而作為開發人員,掌握一門能夠抓取網頁內容的語言顯得尤為重要。在此篇文章中,將分享如何使用 .NET構建網路抓取工具。詳細瞭解如何執行 HTTP 請求來下載要抓取的網頁,然後從其 DOM 樹中選擇 HTML 元素,進行匹配需要的欄位信息,從中提取數據。
一、準備工作:
創建項目:
創建一個簡單的Winfrom客戶端程式,我使用的是.NET 5.0框架。為使項目顯得條理清晰,此處進行了項目分層搭建項目,也就是多建立幾個幾個類庫罷了,然後進行引用。
項目結構:
客戶端界面設計:
NuGet添加引用類庫HtmlAgilityPack:
HtmlAgilityPack是一個開源的C#庫,它允許你解析HTML文檔,從公DOM中選擇元素並提取數據。該工具基本上提供了抓取靜態內容網站所需要的一切。這是一個敏捷的HTML解析器,它構建了一個讀和寫DOM,並支持普通的XPATH或XSLT,你實際上不必瞭解XPATH也不必瞭解XSLT就可以使用它。它是一個.NET代碼庫,允許你解析“Web外”的HTML文件。解析器對“真實世界”中格式錯誤的HTML非常寬容。對象模型與System.Xml非常相似,但適用於HTML文檔或流。
NuGet安裝引用:
dotnet add package HtmlAgilityPack --version 1.11.51
二、實現核心代碼:
設計定義實體:
網站爬取信息:
爬取信息實體定義:根據美圖的首頁展示的信息分析,進行定義爬取欄位的信息,定義如下:
#region << 版 本 註 釋 >>
/*----------------------------------------------------------------
* 創建者:碼農阿亮
* 創建時間:2023/8/4 9:58:03
* 版本:V1.0.0
* 描述:
*
* ----------------------------------------------------------------
* 修改人:
* 時間:
* 修改說明:
*
* 版本:V1.0.1
*----------------------------------------------------------------*/
#endregion << 版 本 註 釋 >>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CrawlerModel.Meitu
{
/// <summary>
/// 爬取的專區欄位實體
/// </summary>
public class BeautyZone
{
/// <summary>
/// 專區標題
/// </summary>
public string Tittle { get; set; }
/// <summary>
/// 專區每種類型美女排行榜
/// </summary>
public List<EveryCategoryBeautyTop> categoryBeauties { get; set; }
}
/// <summary>
/// 每分類美女排行榜
/// </summary>
public class EveryCategoryBeautyTop
{
/// <summary>
/// 類別
/// </summary>
public string Category { get; set; }
/// <summary>
/// 每種類型排行榜
/// </summary>
public List<Beauty> beauties { get; set; }
}
/// <summary>
/// 美女排行信息
/// </summary>
public class Beauty
{
/// <summary>
/// 排行
/// </summary>
public string No { get; set; }
/// <summary>
/// 姓名
/// </summary>
public string Name { get; set; }
/// <summary>
/// 熱度
/// </summary>
public string Popularity { get; set; }
/// <summary>
/// 圖片地址
/// </summary>
public string ImageUrl { get; set; }
}
}
更新UI界面實體定義:根據客戶端需要展示的界面,定義如下:
#region << 版 本 註 釋 >>
/*----------------------------------------------------------------
* 創建者:碼農阿亮
* 創建時間:2023/8/04 16:42:12
* 版本:V1.0.0
* 描述:
*
* ----------------------------------------------------------------
* 修改人:
* 時間:
* 修改說明:
*
* 版本:V1.0.1
*----------------------------------------------------------------*/
#endregion << 版 本 註 釋 >>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CrawlerModel.Meitu
{
/// <summary>
/// UpdateUIModel 更新UI界面需要的欄位
/// </summary>
public class UpdateUIModel
{
/// <summary>
/// 下載的數量
/// </summary>
public int DownloadNumber { get; set; }
/// <summary>
/// 分類
/// </summary>
public string Category { get; set; }
/// <summary>
/// 美女寫真實體
/// </summary>
public Beauty beauty =new Beauty();
}
}
匹配DOM標簽常量實體: 定義如下:
#region << 版 本 註 釋 >>
/*----------------------------------------------------------------
* 創建者:碼農阿亮
* 創建時間:2023/8/4 10:08:06
* 版本:V1.0.0
* 描述:
*
* ----------------------------------------------------------------
* 修改人:
* 時間:
* 修改說明:
*
* 版本:V1.0.1
*----------------------------------------------------------------*/
#endregion << 版 本 註 釋 >>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CrawlerModel.Meitu
{
/// <summary>
/// MeituConfig DOM標簽常量
/// </summary>
public class MeituConfig
{
/// <summary>
/// 存放數據地址
/// </summary>
public const string JsonDataPath = "meitu/data";
/// <summary>
/// 爬取美圖首頁地址
/// </summary>
public const string Url = "https://www.meitu131.com";
/// <summary>
/// 專區XPath
/// </summary>
public const string ZoneXPath = @"/html/body/div[10]/div[2]/div";
/// <summary>
/// 專區名稱XPath
/// </summary>
public const string ZoneNameXPath = @"/html/body/div[10]/div[1]/ul/li";
/// <summary>
/// 排行榜
/// </summary>
public const string TopXPath = @"/html/body/div[10]/div[2]/div[{0}]/dl";
/// <summary>
/// 人員隸屬種類
/// </summary>
public const string PersonCategoryXPath = @"/html/body/div[10]/div[2]/div[{0}]/dl/dt";
/// <summary>
/// 人員
/// </summary>
public const string PersonXPath = @"/html/body/div[10]/div[2]/div[{0}]/dl/dd";
/// <summary>
/// 排行
/// </summary>
public const string NoXPath = @"/html/body/div[3]/div[1]/ul/li";
/// <summary>
/// 姓名
/// </summary>
public const string NameXPath = @"/html/body/div[3]/div[1]/ul/li";
/// <summary>
/// 熱度
/// </summary>
public const string PopularityXPath = @"/html/body/div[3]/div[1]/ul/li";
/// <summary>
/// 圖片地址
/// </summary>
public const string ImageUrlXPath = @"/html/body/div[3]/div[1]/ul/li";
}
}
業務實現代碼:
幫助類:Web請求和下載資源幫助方法,定義義如下:
#region << 版 本 註 釋 >>
/*----------------------------------------------------------------
* 創建者:碼農阿亮
* 創建時間:2023/8/4 10:04:16
* 版本:V1.0.0
* 描述:
*
* ----------------------------------------------------------------
* 修改人:
* 時間:
* 修改說明:
*
* 版本:V1.0.1
*----------------------------------------------------------------*/
#endregion << 版 本 註 釋 >>
using HtmlAgilityPack;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace CrawlerService.Helper
{
/// <summary>
/// 創建一個Web請求
/// </summary>
public class MyWebClient : WebClient
{
protected override WebRequest GetWebRequest(Uri address)
{
HttpWebRequest request = base.GetWebRequest(address) as HttpWebRequest;
request.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;
return request;
}
}
/// <summary>
/// 下載HTML幫助類
/// </summary>
public static class LoadHtmlHelper
{
/// <summary>
/// 從Url地址下載頁面
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
public async static ValueTask<HtmlDocument> LoadHtmlFromUrlAsync(string url)
{
var data = new MyWebClient()?.DownloadString(url);
var doc = new HtmlDocument();
doc.LoadHtml(data);
return doc;
}
/// <summary>
/// 獲取單個節點擴展方法
/// </summary>
/// <param name="htmlDocument">文檔對象</param>
/// <param name="xPath">xPath路徑</param>
/// <returns></returns>
public static HtmlNode GetSingleNode(this HtmlDocument htmlDocument, string xPath)
{
return htmlDocument?.DocumentNode?.SelectSingleNode(xPath);
}
/// <summary>
/// 獲取多個節點擴展方法
/// </summary>
/// <param name="htmlDocument">文檔對象</param>
/// <param name="xPath">xPath路徑</param>
/// <returns></returns>
public static HtmlNodeCollection GetNodes(this HtmlDocument htmlDocument, string xPath)
{
return htmlDocument?.DocumentNode?.SelectNodes(xPath);
}
/// <summary>
/// 獲取多個節點擴展方法
/// </summary>
/// <param name="htmlDocument">文檔對象</param>
/// <param name="xPath">xPath路徑</param>
/// <returns></returns>
public static HtmlNodeCollection GetNodes(this HtmlNode htmlNode, string xPath)
{
return htmlNode?.SelectNodes(xPath);
}
/// <summary>
/// 獲取單個節點擴展方法
/// </summary>
/// <param name="htmlDocument">文檔對象</param>
/// <param name="xPath">xPath路徑</param>
/// <returns></returns>
public static HtmlNode GetSingleNode(this HtmlNode htmlNode, string xPath)
{
return htmlNode?.SelectSingleNode(xPath);
}
/// <summary>
/// 下載圖片
/// </summary>
/// <param name="url">地址</param>
/// <param name="filpath">文件路徑</param>
/// <returns></returns>
public async static ValueTask<bool> DownloadImg(string url ,string filpath)
{
HttpClient httpClient = new HttpClient();
try
{
var bytes = await httpClient.GetByteArrayAsync(url);
using (FileStream fs = File.Create(filpath))
{
fs.Write(bytes, 0, bytes.Length);
}
return File.Exists(filpath);
}
catch (Exception ex)
{
throw new Exception("下載圖片異常", ex);
}
}
}
}
主要業務實現方法:定義如下:
#region << 版 本 註 釋 >>
/*----------------------------------------------------------------
* 創建者:碼農阿亮
* 創建時間:2023/8/4 10:07:17
* 版本:V1.0.0
* 描述:
*
* ----------------------------------------------------------------
* 修改人:
* 時間:
* 修改說明:
*
* 版本:V1.0.1
*----------------------------------------------------------------*/
#endregion << 版 本 註 釋 >>
using CrawlerComm.Handler;
using CrawlerModel.Meitu;
using CrawlerService.Helper;
using HtmlAgilityPack;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CrawlerService.Meitu
{
/// <summary>
/// MeituParseHtml 的摘要說明
/// </summary>
public class MeituParseHtmService
{
/// <summary>
/// json數據文件夾存放文件夾位置
/// </summary>
private static string _dataDirectoryPath = Path.Combine(Directory.GetCurrentDirectory(), MeituConfig.JsonDataPath);
/// <summary>
/// 爬取json數據文件
/// </summary>
private static string _CrawlerData = Path.Combine(_dataDirectoryPath, "categories.json");
/// <summary>
/// 開始爬取
/// </summary>
/// <returns></returns>
public async Task StartAsync()
{
//專區集合
List<BeautyZone> beautyZones = new List<BeautyZone>();
//獲取首頁Html文檔
HtmlDocument htmlDocument = await LoadHtmlHelper.LoadHtmlFromUrlAsync(MeituConfig.Url);
//創建存放數據的文件
FileInfo fileInfo = new FileInfo(_CrawlerData);
//獲取到專區標簽
HtmlNodeCollection zoneHtmlNodes = htmlDocument.GetNodes(MeituConfig.ZoneXPath);
//專區名稱
HtmlNodeCollection zoneNameHtmlNodes = htmlDocument.GetNodes(MeituConfig.ZoneNameXPath);
if (zoneHtmlNodes != null && zoneHtmlNodes.Count> 0)
{
//專區個數
var zoneCount = zoneHtmlNodes.Count;
for (int i = 0; i < zoneCount; i++)
{
//每個專區
BeautyZone beautyZone = new BeautyZone()
{
Tittle = zoneNameHtmlNodes[i].InnerText,
categoryBeauties = new List<EveryCategoryBeautyTop>()
};
HtmlNodeCollection topHtmlNodes = htmlDocument.GetNodes(string.Format( MeituConfig.TopXPath,i+1));
if (topHtmlNodes != null && topHtmlNodes.Count > 0)
{
//每個專區下所有分類
HtmlNodeCollection personCategoryHtmlNodes = htmlDocument.GetNodes(string.Format(MeituConfig.PersonCategoryXPath, i + 1));
//爬取所有人員的標簽內容
HtmlNodeCollection personHtmlNodes = htmlDocument.GetNodes(string.Format(MeituConfig.PersonXPath, i + 1));
if (personCategoryHtmlNodes !=null && personHtmlNodes!=null && personCategoryHtmlNodes.Count() > 0)
{
for (int j = 0; j < personCategoryHtmlNodes.Count(); j++)
{
//根據每個專區-分類下,進行遍歷人氣值人員排名
EveryCategoryBeautyTop everyCategoryBeautyTop = new EveryCategoryBeautyTop();
everyCategoryBeautyTop.Category = personCategoryHtmlNodes[j].InnerText;
everyCategoryBeautyTop.beauties = new List<Beauty>();
for (int k = 8*j; k < personHtmlNodes.Count(); k++)
{
var child = personHtmlNodes[k];//每個美女對應的節點信息
var i1 = child.GetSingleNode(child.XPath + "/i");//排名節點
var img = child.GetSingleNode(child.XPath + "/a[1]/div[1]/img[1]");//姓名和圖片地址
var span2 = child.GetSingleNode(child.XPath + "/a[1]/div[2]/span[2]");//熱度值
//同一類別添加美女到集合
everyCategoryBeautyTop.beauties.Add(new Beauty
{
No = i1.InnerText,
Name = img.GetAttributeValue("alt", "未找到"),
Popularity = span2.InnerText,
ImageUrl = img.GetAttributeValue("data-echo", "未找到")
}
);
}
//將在同一分區內Top分類添加到集合
beautyZone.categoryBeauties.Add(everyCategoryBeautyTop);
}
}
}
beautyZones.Add(beautyZone);
}
if (beautyZones.Count()> 0)
{
//爬取數據轉Json
string beautiesJsonData = JsonConvert.SerializeObject(beautyZones); ;
//寫入爬取數據數據
string jsonFile = "beauties.json";
WriteData(jsonFile, beautiesJsonData);
//下載圖片
DownloadImage(beautyZones);
}
}
}
/// <summary>
/// 寫入文件數據
/// </summary>
/// <param name="fileName"></param>
/// <param name="data"></param>
private void WriteData(string fileName, string data)
{
FileStream fs = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.Write);
StreamWriter sw = new StreamWriter(fs);
try
{
sw.Write(data);
}
finally
{
if (sw != null)
{
sw.Close();
}
}
}
/// <summary>
/// 下載圖片
/// </summary>
/// <param name="beautyZones"></param>
private async void DownloadImage(List<BeautyZone> beautyZones)
{
int count = 0;
foreach (var beautyZone in beautyZones)
{
string rootPath = System.IO.Directory.GetCurrentDirectory() +"\\DownloadImg\\"+ beautyZone.Tittle;
foreach (var category in beautyZone.categoryBeauties)
{
string downloadPath = rootPath + "\\" + category.Category;
foreach (var beauty in category.beauties)
{
count += 1;//下載數量累加
string filePath = downloadPath + "\\" + beauty.Name+".jpg";
if (!Directory.Exists(downloadPath))
{
Directory.CreateDirectory(downloadPath);
}
UpdateUIModel updateUIModel = new UpdateUIModel()
{
DownloadNumber =count,
Category = category.Category,
beauty = beauty
};
//更新UI
UpdateFrmHandler.OnUpdateUI(updateUIModel);
//非同步下載
await LoadHtmlHelper.DownloadImg(beauty.ImageUrl,filePath);
}
}
}
}
}
}
定義委托代碼:
更新UI界面的委托事件:定義如下:
#region << 版 本 註 釋 >>
/*----------------------------------------------------------------
* 創建者:碼農阿亮
* 創建時間:2023/8/04 11:28:24
* 版本:V1.0.0
* 描述:
*
* ----------------------------------------------------------------
* 修改人:
* 時間:
* 修改說明:
*
* 版本:V1.0.1
*----------------------------------------------------------------*/
#endregion << 版 本 註 釋 >>
using CrawlerModel.Meitu;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CrawlerComm.Handler
{
/// <summary>
/// UpdateFrmHandler 更新UI事件委托
/// </summary>
public class UpdateFrmHandler
{
/// <summary>
/// 更新下載界面事件
/// </summary>
public static event EventHandler<UpdateUIModel> UpdateUI;
public static void OnUpdateUI(UpdateUIModel updateUIModel)
{
UpdateUI?.Invoke(null, updateUIModel);
}
}
}
客戶端執行代碼:
點擊觸發事件:定義如下:
#region << 版 本 註 釋 >>
/*----------------------------------------------------------------
* 創建者:碼農阿亮
* 創建時間:2023/8/4 08:07:17
* 版本:V1.0.0
* 描述:
*
* ----------------------------------------------------------------
* 修改人:
* 時間:
* 修改說明:
*
* 版本:V1.0.1
*----------------------------------------------------------------*/
#endregion << 版 本 註 釋 >>
using CrawlerComm.Handler;
using CrawlerModel.Meitu;
using CrawlerService.Meitu;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace CrawlerData
{
public partial class FrmMain : Form
{
public FrmMain()
{
InitializeComponent();
//訂閱委托
UpdateFrmHandler.UpdateUI += UpdateUIFuc;
}
/// <summary>
/// 更新界面委托方法
/// </summary>
/// <param name="sender"></param>
/// <param name="updateUIModel"></param>
private void UpdateUIFuc(object sender, UpdateUIModel updateUIModel )
{
try
{
//改變界面UI中的值
this.Invoke((Action)delegate
{
UpdateUIBiz(updateUIModel);
});
}
catch (Exception ex)
{
}
}
/// <summary>
/// 更新UI界面業務
/// </summary>
/// <param name="updateUIModel"></param>
private void UpdateUIBiz(UpdateUIModel updateUIModel)
{
try
{
//分類
tbCategory.Text = updateUIModel.Category;
//排名
tbNo.Text = updateUIModel.beauty.No;
//姓名
tbName.Text = updateUIModel.beauty.Name;
//人氣值
tbPopularity.Text = updateUIModel.beauty.Popularity;
//圖片地址
tbUrl.Text = updateUIModel.beauty.ImageUrl;
//圖片
WebClient client = new WebClient();
string imageUrl = updateUIModel.beauty.ImageUrl;// 要顯示的網路圖片的URL
byte[] imageBytes = client.DownloadData(imageUrl);
Image img = Image.FromStream(new MemoryStream(imageBytes));
picBox.Image = img;
//爬取數量
lbNumber.Text = updateUIModel.DownloadNumber.ToString();
}
catch (Exception ex)
{
}
}
/// <summary>
/// 開始執行按鈕點擊事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btStart_Click(object sender, EventArgs e)
{
StartCrawler();
}
/// <summary>
/// 開始爬取
/// </summary>
public async void StartCrawler()
{
try
{
MeituParseHtmService meituParseHtml = new MeituParseHtmService();
await meituParseHtml.StartAsync();
}
catch (Exception ex )
{
}
}
}
}
三、爬取驗證:
啟動客戶端:
文章上傳圖片大小有限制,只截取了部分gif:
爬取的Json數據結果:
下載的圖片資源:
分目錄層級存放:
源碼鏈接地址:
Gitee完整實例地址:
建群聲明:本著技術在於分享,方便大家交流學習的初心,特此建立【編程內功修煉交流群】,熱烈歡迎各位愛交流學習的程式員進群,也希望進群的大佬能不吝分享自己遇到的技術問題和學習心得
本文來自博客園,作者:碼農阿亮,轉載請註明原文鏈接:https://www.cnblogs.com/wml-it/p/17628041.html
技術的發展日新月異,隨著時間推移,無法保證本博客所有內容的正確性。如有誤導,請大家見諒,歡迎評論區指正!
開源庫鏈接,歡迎點亮:
GitHub:https://github.com/ITMingliang
Gitee:https://gitee.com/mingliang_it
GitLab:https://gitlab.com/ITMingliang
【編程內功修煉交流群】: 【個人公眾號】: