手把手教你用.NET Core写爬虫
寫在前面
自從上一個項目58HouseSearch從.NET遷移到.NET core之后,磕磕碰碰磨蹭了一個月才正式上線到新版本。
然后最近又開了個新坑,搞了個Dy2018Crawler用來爬dy2018電影天堂上面的電影資源。這里也借機簡單介紹一下如何基于.NET Core寫一個爬蟲。
PS:如有偏錯,敬請指明...
PPS:該去電影院還是多去電影院,畢竟美人良時可無價。
準備工作(.NET Core準備)
首先,肯定是先安裝.NET Core咯。下載及安裝教程在這里:.NET - Powerful Open Source Development。無論你是Windows、linux還是mac,統(tǒng)統(tǒng)可以玩。
我這里的環(huán)境是:Windows10 + VS2015 community updata3 + .NET Core 1.1.0 SDK + .NET Core 1.0.1 tools Preview 2.
理論上,只需要安裝一下 .NET Core 1.1.0 SDK 即可開發(fā).NET Core程序,至于用什么工具寫代碼都無關緊要了。
安裝好以上工具之后,在VS2015的新建項目就可以看到.NET Core的模板了。如下圖:
為了簡單起見,我們創(chuàng)建的時候,直接選擇VS .NET Core tools自帶的模板。
一個爬蟲的自我修養(yǎng)
分析網(wǎng)頁
寫爬蟲之前,我們首先要先去了解一下即將要爬取的網(wǎng)頁數(shù)據(jù)組成。
具體到網(wǎng)頁的話,便是分析我們要抓取的數(shù)據(jù)在HTML里面是用什么標簽抑或有什么樣的標記,然后使用這個標記把數(shù)據(jù)從HTML中提取出來。在我這里的話,用的更多的是HTML標簽的ID和CSS屬性。
以本文章想要爬取的dy2018.com為例,簡單描述一下這個過程。dy2018.com主頁如下圖:
在chrome里面,按F12進入開發(fā)者模式,接著如下圖使用鼠標選擇對應頁面數(shù)據(jù),然后去分析頁面HTML組成。
接著我們開始分析頁面數(shù)據(jù):
經(jīng)過簡單分析HTML,我們得到以下結(jié)論:
www.dy2018.com首頁的電影數(shù)據(jù)存儲在一個class為co_content222的div標簽里面
電影詳情鏈接為a標簽,標簽顯示文本就是電影名稱,URL即詳情URL
那么總結(jié)下來,我們的工作就是:找到class='co_content222' 的div標簽,從里面提取所有的a標簽數(shù)據(jù)。
開始寫代碼...
之前在寫58HouseSearch項目遷移到asp.net core簡單提過AngleSharp庫,一個基于.NET(C#)開發(fā)的專門為解析xHTML源碼的DLL組件。
AngleSharp主頁在這里:https://anglesharp.github.io/,
博客園文章:解析HTML利器AngleSharp介紹,
Nuget地址:Nuget AngleSharp 安裝命令:Install-Package AngleSharp
獲取電影列表數(shù)據(jù)
private static HtmlParser htmlParser = new HtmlParser();private ConcurrentDictionary<string, MovieInfo> _cdMovieInfo = new ConcurrentDictionary<string, MovieInfo>();private void AddToHotMovieList(){//此操作不阻塞當前其他操作,所以使用Task// _cdMovieInfo 為線程安全字典,存儲了當期所有的電影數(shù)據(jù)Task.Factory.StartNew(()=> {try{//通過URL獲取HTMLvar htmlDoc = HTTPHelper.GetHTMLByURL("http://www.dy2018.com/");//HTML 解析成 IDocumentvar dom = htmlParser.Parse(htmlDoc);//從dom中提取所有class='co_content222'的div標簽//QuerySelectorAll方法接受 選擇器語法 var lstDivInfo = dom.QuerySelectorAll("div.co_content222");if (lstDivInfo != null){//前三個DIV為新電影foreach (var divInfo in lstDivInfo.Take(3)){//獲取div中所有的a標簽且a標簽中含有"/i/"的//Contains("/i/") 條件的過濾是因為在測試中發(fā)現(xiàn)這一塊div中的a標簽有可能是廣告鏈接divInfo.QuerySelectorAll("a").Where(a => a.GetAttribute("href").Contains("/i/")).ToList().ForEach(a =>{//拼接成完整鏈接var onlineURL = "http://www.dy2018.com" + a.GetAttribute("href");//看一下是否已經(jīng)存在于現(xiàn)有數(shù)據(jù)中if (!_cdMovieInfo.ContainsKey(onlineURL)){//獲取電影的詳細信息MovieInfo movieInfo = FillMovieInfoFormWeb(a, onlineURL);//下載鏈接不為空才添加到現(xiàn)有數(shù)據(jù)if (movieInfo.XunLeiDownLoadURLList != null && movieInfo.XunLeiDownLoadURLList.Count != 0){_cdMovieInfo.TryAdd(movieInfo.Dy2018OnlineUrl, movieInfo);}}});}}}catch(Exception ex){}});}獲取電影詳細信息
private MovieInfo FillMovieInfoFormWeb(AngleSharp.Dom.IElement a, string onlineURL){var movieHTML = HTTPHelper.GetHTMLByURL(onlineURL);var movieDoc = htmlParser.Parse(movieHTML);//http://www.dy2018.com/i/97462.html 分析過程見上,不再贅述//電影的詳細介紹 在id為Zoom的標簽中var zoom = movieDoc.GetElementById("Zoom");//下載鏈接在 bgcolor='#fdfddf'的td中,有可能有多個鏈接var lstDownLoadURL = movieDoc.QuerySelectorAll("[bgcolor='#fdfddf']");//發(fā)布時間 在class='updatetime'的span標簽中var updatetime = movieDoc.QuerySelector("span.updatetime"); var pubDate = DateTime.Now;if(updatetime!=null && !string.IsNullOrEmpty(updatetime.InnerHtml)){//內(nèi)容帶有“發(fā)布時間:”字樣,replace成""之后再去轉(zhuǎn)換,轉(zhuǎn)換失敗不影響流程DateTime.TryParse(updatetime.InnerHtml.Replace("發(fā)布時間:", ""), out pubDate);}var movieInfo = new MovieInfo(){//InnerHtml中可能還包含font標簽,做多一個ReplaceMovieName = a.InnerHtml.Replace("<font color=\"#0c9000\">","").Replace("<font color=\" #0c9000\">","").Replace("</font>", ""),Dy2018OnlineUrl = onlineURL,MovieIntro = zoom != null ? WebUtility.HtmlEncode(zoom.InnerHtml) : "暫無介紹...",//可能沒有簡介,雖然好像不怎么可能XunLeiDownLoadURLList = lstDownLoadURL != null ?lstDownLoadURL.Select(d => d.FirstElementChild.InnerHtml).ToList() : null,//可能沒有下載鏈接PubDate = pubDate,};return movieInfo;}HTTPHelper
這邊有個小坑,dy2018網(wǎng)頁編碼格式是GB2312,.NET Core默認不支持GB2312,使用Encoding.GetEncoding("GB2312")的時候會拋出異常。
解決方案是手動安裝System.Text.Encoding.CodePages包(Install-Package System.Text.Encoding.CodePages),
然后在Starup.cs的Configure方法中加入Encoding.RegisterProvider(CodePagesEncodingProvider.Instance),接著就可以正常使用Encoding.GetEncoding("GB2312")了。
using System; using System.Net.Http; using System.Net.Http.Headers; using System.Text;namespace Dy2018Crawler {public class HTTPHelper{public static HttpClient Client { get; } = new HttpClient();public static string GetHTMLByURL(string url){try{System.Net.WebRequest wRequest = System.Net.WebRequest.Create(url);wRequest.ContentType = "text/html; charset=gb2312";wRequest.Method = "get";wRequest.UseDefaultCredentials = true;// Get the response instance.var task = wRequest.GetResponseAsync();System.Net.WebResponse wResp = task.Result;System.IO.Stream respStream = wResp.GetResponseStream();//dy2018這個網(wǎng)站編碼方式是GB2312,using (System.IO.StreamReader reader = new System.IO.StreamReader(respStream, Encoding.GetEncoding("GB2312"))){return reader.ReadToEnd();}}catch (Exception ex){Console.WriteLine(ex.ToString());return string.Empty;}}}}定時任務的實現(xiàn)
定時任務我這里使用的是Pomelo.AspNetCore.TimedJob。
Pomelo.AspNetCore.TimedJob是一個.NET Core實現(xiàn)的定時任務job庫,支持毫秒級定時任務、從數(shù)據(jù)庫讀取定時配置、同步異步定時任務等功能。
由.NET Core社區(qū)大神兼前微軟MVPAmamiyaYuuko(入職微軟之后就卸任MVP...)開發(fā)維護,不過好像沒有開源,回頭問下看看能不能開源掉。
nuget上有各種版本,按需自取。地址:https://www.nuget.org/package...
作者自己的介紹文章:Timed Job - Pomelo擴展包系列
Startup.cs相關代碼
我這邊使用的話,首先肯定是先安裝對應的包:Install-Package Pomelo.AspNetCore.TimedJob -Pre
然后在Startup.cs的ConfigureServices函數(shù)里面添加Service,在Configure函數(shù)里面Use一下。
// This method gets called by the runtime. Use this method to add services to the container.public void ConfigureServices(IServiceCollection services){// Add framework services.services.AddMvc();//Add TimedJob servicesservices.AddTimedJob();}public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory){//使用TimedJobapp.UseTimedJob();if (env.IsDevelopment()){app.UseDeveloperExceptionPage();app.UseBrowserLink();}else{app.UseExceptionHandler("/Home/Error");}app.UseStaticFiles();app.UseMvc(routes =>{routes.MapRoute(name: "default",template: "{controller=Home}/{action=Index}/{id?}");});Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);}Job相關代碼
接著新建一個類,明明為XXXJob.cs,引用命名空間using Pomelo.AspNetCore.TimedJob,XXXJob繼承于Job,添加以下代碼。
public class AutoGetMovieListJob:Job{// Begin 起始時間;Interval執(zhí)行時間間隔,單位是毫秒,建議使用以下格式,此處為3小時;SkipWhileExecuting是否等待上一個執(zhí)行完成,true為等待;[Invoke(Begin = "2016-11-29 22:10", Interval = 1000 * 3600*3, SkipWhileExecuting =true)]public void Run(){//Job要執(zhí)行的邏輯代碼//LogHelper.Info("Start crawling");//AddToLatestMovieList(100);//AddToHotMovieList();//LogHelper.Info("Finish crawling");}}項目發(fā)布相關
新增runtimes節(jié)點
使用VS2015新建的模板工程,project.json配置默認是沒有runtimes節(jié)點的.
我們想要發(fā)布到非Windows平臺的時候,需要手動配置一下此節(jié)點以便生成。
"runtimes": {"win7-x64": {},"win7-x86": {},"osx.10.10-x64": {},"osx.10.11-x64": {},"ubuntu.14.04-x64": {}}刪除/注釋scripts節(jié)點
生成時會調(diào)用node.js腳本構建前端代碼,這個不能確保每個環(huán)境都有bower存在...注釋完事。
//"scripts": {// "prepublish": [ "bower install", "dotnet bundle" ],// "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]//},刪除/注釋dependencies節(jié)點里面的type
"dependencies": {"Microsoft.NETCore.App": {"version": "1.1.0"//"type": "platform"},project.json的相關配置說明可以看下這個官方文檔:Project.json-file,
或者張善友老師的文章.NET Core系列 : 2 、project.json 這葫蘆里賣的什么藥
開發(fā)編譯發(fā)布
//還原各種包文件 dotnet restore;//發(fā)布到C:\code\website\Dy2018Crawler文件夾 dotnet publish -r ubuntu.14.04-x64 -c Release -o "C:\code\website\Dy2018Crawler";最后,照舊開源......以上代碼都在下面找到:
Gayhub地址:https://github.com/liguobao/Dy2018Crawler
在線地址:http://codelover.win/
PS:回頭寫個爬片大家滋持不啊...
本文首發(fā)于:http://codelover.link/2016/12...
新人創(chuàng)作打卡挑戰(zhàn)賽發(fā)博客就能抽獎!定制產(chǎn)品紅包拿不停!總結(jié)
以上是生活随笔為你收集整理的手把手教你用.NET Core写爬虫的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: stackoverflow.com
- 下一篇: Qt 文档编辑设置