1. 程式人生 > >C# NetCore使用AngleSharp爬取周公解夢資料

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 string
CateName { 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