C# NetCore使用AngleSharp爬取周公解夢資料
這一章詳細講解編碼過程
那麼接下來就是碼程式碼了,GO
新建NetCore WebApi專案 空的就可以
NuGet安裝
Install-Package AngleSharp
或者介面安裝
using。。
預設本地裝有mysql或者有遠端開放的mysql資料庫,如何安裝mysql,園區有很多文章都詳細說明。
配置檔案新增mysql連線 appsettings.json
{ "Logging": { "LogLevel": { "Default": "Warning" } }, "AllowedHosts": "*", "ConnectionStrings": { "MySql": "server=localhost;user id=root;pwd=root;database=dreaminfo;" } }
新建實體類,這裡由於比較簡單,所以建立到一起,實際工作中最好不要這樣,可讀性較差
using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using System.Threading.Tasks;namespace WebAPI.Models { public class Dream { [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; } public string Name { get; set; } public string Url { get; set; } public string Summary { get; set; } public stringCateName { get; set; } public DateTime? CreateTime { get; set; } } public class DreamInfo { [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; } public int FkDreamId { get; set; } public string DreamName { get; set; } public string Name { get; set; } public string Content { get; set; } public DateTime? CreateTime { get; set; } } }
安裝,mysql的ef支援
Install-Package Pomelo.EntityFrameworkCore.MySql
建立DBContext
using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace WebAPI.Models { public class MainDBContext : DbContext { public MainDBContext(DbContextOptions<MainDBContext> options) : base(options) { } private string connection; public MainDBContext(string connection) => this.connection = connection; protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { if (!string.IsNullOrWhiteSpace(connection)) optionsBuilder.UseMySql(connection); } public DbSet<Dream> Dream { get; set; } public DbSet<DreamInfo> DreamInfo { get; set; } } }
配置服務
public void ConfigureServices(IServiceCollection services) { var mysqlCon = Configuration.GetSection("ConnectionStrings:MySql").Value; services.AddDbContext<MainDBContext>(l => l.UseMySql(mysqlCon, b => b.MigrationsAssembly("Dream"))); //跳轉檢視 services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); }
然後是資料庫遷移 ,這裡使用的是codefirst,所以建立好實體類之後在實體類及EF存在的程式集執行Add-Migration命令,我這裡做的比較簡單,有的如果是按照框架來設計,可能實體類被設計在單獨的類庫裡
這裡是直接在webapi裡面
執行之後會多出來遷移的的檔案,然後執行Update-Database就可以生成資料庫拉。
後期如果資料庫有變更還是同樣的操作
工具》開啟Nuget包管理器》程式包管理控制檯 選中EF所在類庫
Add-Migration Update-20181123 //然後等待變更檔案生成之後執行 Update-Database
然後我們看下資料庫
我們重新整理下,資料庫和對應的表都有了。
獲取頁面內容
新建一個空的API控制器DreamController
然後建立獲取類別資料的方法
/// <summary> /// 定義一個獲取列表資料的方法,需要傳遞一個列表頁面的url地址 /// </summary> /// <param name="rul"></param> public void GetData(string url) { //這個跟我之前使用的瀏覽器驅動類似,也是通過選擇器和xpath來爬取資料 //區別在於那個是模擬真人操作,這個是通過通過HttpWebRequest直接請求 var html = GetHtml(url); //建立一個(可重用)解析器前端 var parser = new HtmlParser(); var document = parser.Parse(html); //找到一個頁面有多少個dream資訊 var mengList = document.QuerySelectorAll("#list > div.main > div.l-item > ul > li"); //迴圈獲取夢的資訊 for (var i = 0; i < mengList.Length; i++) { var meng = new Dream(); meng.CateName = "人物"; //名稱 meng.Name = mengList[i].QuerySelector("h3 > a").TextContent; //簡介 meng.Summary = mengList[i].QuerySelector("p").TextContent; //連線 meng.Url = mengList[i].QuerySelector("h3 > a").GetAttribute("href"); meng.CreateTime = DateTime.Now; _context.Dream.Add(meng); _context.SaveChanges();//可以單個儲存,也可以獲取當前頁資料之後儲存一次,可優化的點有很多,這裡不再詳細描述 } }
但是這裡獲取的都是單頁的資料,我們繼續上一章說的,使用選擇器裡面的.next來尋找翻頁按鈕附帶的連線
然後看看最後一頁是什麼樣式
發現最後一頁就沒有下一頁樣式了,就獲取不到值了,好的 那麼我們開始翻頁
至於翻頁 還有第二種思路,就是直接獲取這種類別下的最後一頁的頁碼,然後迴圈就行了,似乎比較簡單,我們就用這一種
完善後的方法
/// <summary> /// 定義一個獲取列表資料的方法,需要傳遞一個列表頁面的url地址 /// </summary> /// <param name="rul"></param> public void GetData(string url, int pageIndex) { var thisUrl = url + pageIndex + ".html"; var html = GetHtml(thisUrl); //建立一個(可重用)解析器前端 var parser = new HtmlParser(); var document = parser.Parse(html); var mengList = document.QuerySelectorAll("#list > div.main > div.l-item > ul > li"); //#list > div.main > div.pagelist > a.end //獲取最末頁資料 if (!PageEnd.HasValue) { var pageEnd = document.QuerySelector("#list > div.main > div.pagelist > a.end")?.TextContent; PageEnd = Convert.ToInt32(pageEnd); } var list = new List<Dream>(); for (var i = 0; i < mengList.Length; i++) { var meng = new Dream(); meng.CateName = "其他"; //名稱 meng.Name = mengList[i].QuerySelector("h3 > a").TextContent; //簡介 meng.Summary = mengList[i].QuerySelector("p").TextContent; //連線 meng.Url = mengList[i].QuerySelector("h3 > a").GetAttribute("href"); meng.CreateTime = DateTime.Now; _context.Dream.Add(meng); _context.SaveChanges(); } //翻頁記錄頁碼 pageIndex++; if (pageIndex <= Convert.ToInt32(PageEnd)) { Console.WriteLine(pageIndex + "/" + PageEnd); //翻頁完之前一直抓取 GetData(url, pageIndex); } }
定義介面,輸出一下看看獲取了多少頁的資料
PageEnd是在控制器裡面定義的
public class DreamController : ControllerBase { static int? PageEnd = null; //other code
[HttpGet] public async Task<IActionResult> GetMengXinfo() { GetData("http://www.xzw.com/jiemeng/lib/renwu/", 1); return Ok(PageEnd); }
除錯走一波
可以看到這裡資料其實已經拿到了,那我們就開始往資料庫儲存
由於是演示,我們找一個數據量較少的分類來獲取 使用“其他”分類獲取,呼叫介面,看到返回值是45
這個跟我們找到對應的資料是一樣的
說明資料是ok的,我們看下資料庫,dream表已經有資料了
到這裡 分類資料就獲取到了,其他幾個分類 可以使用一個數組,迴圈陣列拼連接獲取,也可以放到後臺任務慢慢執行,比如Hangfire
詳細資訊獲取
詳細資訊的頁面我們其實是有的,就是dream表裡面的url欄位,拼接上domain之後就成了詳細頁面的連線,我們又可以使用AngleSharp來獲取資料拉。。
其實AngleSharp是對獲取到的文件進行解析,裡面構建了很多C#和js習慣的語法,比如
document.QuerySelector(選擇器)//選擇器查詢單個符合條件的資料
document.QuerySelectorAll("#list > div.main > div.l-item > ul > li");獲取複合條件的元素集合
獲取詳情頁面內容,並給實體物件賦值,這裡有很多可以試探的方法,大家可以嘗試一下,我這個只是簡單的為了完成我想要的功能。
/// <summary> /// 獲取詳情頁的頁面解析 /// </summary> /// <param name="dreamId"></param> /// <param name="url"></param> public void GetDetailData(int dreamId, string url) { var html = GetHtml(url); var parser = new HtmlParser(); var document = parser.Parse(html); //#wraper > div.main-wrap > div.pleft.fl > div.viewbox.box > div.sbody var sbody = document.QuerySelector("#wraper > div.main-wrap > div.pleft.fl > div.viewbox.box > div.sbody"); var dllist = sbody.QuerySelectorAll("dl"); var title = sbody.QuerySelector("h2").TextContent;//標題 if (dllist.Length > 0) { foreach (var detail in dllist) { //#wraper > div.main-wrap > div.pleft.fl > div.viewbox.box > div.sbody > dl:nth-child(4) > dt > strong var info = new DreamInfo(); info.FkDreamId = dreamId; info.DreamName = title; info.Name = detail.QuerySelector("dt > strong").TextContent; info.Content = detail.QuerySelector("dd").TextContent; ; info.CreateTime = DateTime.Now; _context.DreamInfo.Add(info); _context.SaveChanges(); } } }
定義介面
[Route("detail")] [HttpGet] public async Task<IActionResult> GetDreamInfo() { var domain = "http://www.xzw.com"; //查詢出其他分類的夢資料來解析詳細內容 var dreamList = _context.Dream.Where(l => l.CateName == "其他").ToList(); foreach (var dream in dreamList) { GetDetailData(dream.Id, domain + dream.Url); } return Ok(PageEnd); }
執行介面https://localhost:44329/api/dream/detail
這裡不展示除錯資訊了,怕被說水內容
然後看資料庫dreaminfo也有了資料
找到對應頁面
到這裡,資料基本都可以獲取到了,其他分類可以做計劃任務來獲取資料
總結
對自己感興趣的東西,可能下決心投入的時間會更長一點,共勉。
排版較亂可能影響閱讀,不過內容還是能看到的。。
GitHub:https://github.com/ermpark/dream.git