用.NET Core寫爬蟲爬取電影天堂
自從上一個(gè)項(xiàng)目從.NET遷移到.NET core之后,磕磕碰碰磨蹭了一個(gè)月才正式上線到新版本。
然后最近又開了個(gè)新坑,搞了個(gè)爬蟲用來爬dy2018電影天堂上面的電影資源。這里也借機(jī)簡單介紹一下如何基于.NET Core寫一個(gè)爬蟲。
PS:如有偏錯(cuò),敬請指明…
PPS:該去電影院還是多去電影院,畢竟美人良時(shí)可無價(jià)。
準(zhǔn)備工作(.NET Core準(zhǔn)備)
首先,肯定是先安裝.NET Core咯。下載及安裝教程在這里: http://www.dbjr.com.cn/article/87907.htm http://www.dbjr.com.cn/article/88735.htm。無論你是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程序,至于用什么工具寫代碼都無關(guān)緊要了。
安裝好以上工具之后,在VS2015的新建項(xiàng)目就可以看到.NET Core的模板了。如下圖:
為了簡單起見,我們創(chuàng)建的時(shí)候,直接選擇VS .NET Core tools自帶的模板。
一個(gè)爬蟲的自我修養(yǎng) 分析網(wǎng)頁
寫爬蟲之前,我們首先要先去了解一下即將要爬取的網(wǎng)頁數(shù)據(jù)組成。
具體到網(wǎng)頁的話,便是分析我們要抓取的數(shù)據(jù)在HTML里面是用什么標(biāo)簽抑或有什么樣的標(biāo)記,然后使用這個(gè)標(biāo)記把數(shù)據(jù)從HTML中提取出來。在我這里的話,用的更多的是HTML標(biāo)簽的ID和CSS屬性。
以本文章想要爬取的dy2018.com為例,簡單描述一下這個(gè)過程。dy2018.com主頁如下圖:
在chrome里面,按F12進(jìn)入開發(fā)者模式,接著如下圖使用鼠標(biāo)選擇對應(yīng)頁面數(shù)據(jù),然后去分析頁面HTML組成。
接著我們開始分析頁面數(shù)據(jù):
經(jīng)過簡單分析HTML,我們得到以下結(jié)論:
www.dy2018.com首頁的電影數(shù)據(jù)存儲(chǔ)在一個(gè)class為co_content222的div標(biāo)簽里面
電影詳情鏈接為a標(biāo)簽,標(biāo)簽顯示文本就是電影名稱,URL即詳情URL
那么總結(jié)下來,我們的工作就是:找到class='co_content222' 的div標(biāo)簽,從里面提取所有的a標(biāo)簽數(shù)據(jù)。
開始寫代碼…
之前在寫做項(xiàng)目的時(shí)候用到過AngleSharp庫,一個(gè)基于.NET(C#)開發(fā)的專門為解析xHTML源碼的DLL組件。
AngleSharp主頁在這里: https://anglesharp.github.io/ ,
詳細(xì)介紹:http://www.dbjr.com.cn/article/99082.htm
Nuget地址: Nuget AngleSharp 安裝命令:Install-Package AngleSharp
獲取電影列表數(shù)據(jù)
private static HtmlParser htmlParser = new HtmlParser(); private ConcurrentDictionary<string, MovieInfo> _cdMovieInfo = new ConcurrentDictionary<string, MovieInfo>(); privatevoidAddToHotMovieList() { //此操作不阻塞當(dāng)前其他操作,所以使用Task // _cdMovieInfo 為線程安全字典,存儲(chǔ)了當(dāng)期所有的電影數(shù)據(jù) Task.Factory.StartNew(()=> { try { //通過URL獲取HTML var htmlDoc = HTTPHelper.GetHTMLByURL("http://www.dy2018.com/"); //HTML 解析成 IDocument var dom = htmlParser.Parse(htmlDoc); //從dom中提取所有class='co_content222'的div標(biāo)簽 //QuerySelectorAll方法接受 選擇器語法 var lstDivInfo = dom.QuerySelectorAll("div.co_content222"); if (lstDivInfo != null) { //前三個(gè)DIV為新電影 foreach (var divInfo in lstDivInfo.Take(3)) { //獲取div中所有的a標(biāo)簽且a標(biāo)簽中含有"/i/"的 //Contains("/i/") 條件的過濾是因?yàn)樵跍y試中發(fā)現(xiàn)這一塊div中的a標(biāo)簽有可能是廣告鏈接 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)) { //獲取電影的詳細(xì)信息 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) { } }); }
獲取電影詳細(xì)信息
privateMovieInfoFillMovieInfoFormWeb(AngleSharp.Dom.IElement a, string onlineURL) { var movieHTML = HTTPHelper.GetHTMLByURL(onlineURL); var movieDoc = htmlParser.Parse(movieHTML); //http://www.dy2018.com/i/97462.html 分析過程見上,不再贅述 //電影的詳細(xì)介紹 在id為Zoom的標(biāo)簽中 var zoom = movieDoc.GetElementById("Zoom"); //下載鏈接在 bgcolor='#fdfddf'的td中,有可能有多個(gè)鏈接 var lstDownLoadURL = movieDoc.QuerySelectorAll("[bgcolor='#fdfddf']"); //發(fā)布時(shí)間 在class='updatetime'的span標(biāo)簽中 var updatetime = movieDoc.QuerySelector("span.updatetime"); var pubDate = DateTime.Now; if(updatetime!=null && !string.IsNullOrEmpty(updatetime.InnerHtml)) { //內(nèi)容帶有“發(fā)布時(shí)間:”字樣, //replace成""之后再去轉(zhuǎn)換,轉(zhuǎn)換失敗不影響流程 DateTime.TryParse(updatetime.InnerHtml.Replace("發(fā)布時(shí)間:", ""), out pubDate); } var movieInfo = new MovieInfo() { //InnerHtml中可能還包含font標(biāo)簽,做多一個(gè)Replace MovieName = 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
這邊有個(gè)小坑,dy2018網(wǎng)頁編碼格式是GB2312,.NET Core默認(rèn)不支持GB2312,使用Encoding.GetEncoding(“GB2312”)的時(shí)候會(huì)拋出異常。
解決方案是手動(dòng)安裝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(); publicstaticstringGetHTMLByURL(stringurl) { 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這個(gè)網(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; } } } }
定時(shí)任務(wù)的實(shí)現(xiàn)
定時(shí)任務(wù)我這里使用的是 Pomelo.AspNetCore.TimedJob 。
Pomelo.AspNetCore.TimedJob是一個(gè).NET Core實(shí)現(xiàn)的定時(shí)任務(wù)job庫,支持毫秒級定時(shí)任務(wù)、從數(shù)據(jù)庫讀取定時(shí)配置、同步異步定時(shí)任務(wù)等功能。
由.NET Core社區(qū)大神兼前微軟MVP AmamiyaYuuko (入職微軟之后就卸任MVP…)開發(fā)維護(hù),不過好像沒有開源,回頭問下看看能不能開源掉。
nuget上有各種版本,按需自取。地址: https://www.nuget.org/packages/Pomelo.AspNetCore.TimedJob/1.1.0-rtm-10026
作者自己的介紹文章: Timed Job - Pomelo擴(kuò)展包系列
Startup.cs相關(guān)代碼
我這邊使用的話,首先肯定是先安裝對應(yīng)的包: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. publicvoidConfigureServices(IServiceCollection services) { // Add framework services. services.AddMvc(); //Add TimedJob services services.AddTimedJob(); } publicvoidConfigure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { //使用TimedJob app.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相關(guān)代碼
接著新建一個(gè)類,明明為XXXJob.cs,引用命名空間using Pomelo.AspNetCore.TimedJob,XXXJob繼承于Job,添加以下代碼。
public class AutoGetMovieListJob:Job { // Begin 起始時(shí)間;Interval執(zhí)行時(shí)間間隔,單位是毫秒,建議使用以下格式,此處為3小時(shí); //SkipWhileExecuting是否等待上一個(gè)執(zhí)行完成,true為等待; [Invoke(Begin = "2016-11-29 22:10", Interval = 1000 * 3600*3, SkipWhileExecuting =true)] publicvoidRun() { //Job要執(zhí)行的邏輯代碼 //LogHelper.Info("Start crawling"); //AddToLatestMovieList(100); //AddToHotMovieList(); //LogHelper.Info("Finish crawling"); } }
項(xiàng)目發(fā)布相關(guān) 新增runtimes節(jié)點(diǎn)
使用VS2015新建的模板工程,project.json配置默認(rèn)是沒有runtimes節(jié)點(diǎn)的.
我們想要發(fā)布到非Windows平臺(tái)的時(shí)候,需要手動(dòng)配置一下此節(jié)點(diǎn)以便生成。
"runtimes": { "win7-x64": {}, "win7-x86": {}, "osx.10.10-x64": {}, "osx.10.11-x64": {}, "ubuntu.14.04-x64": {} }
刪除/注釋scripts節(jié)點(diǎn)
生成時(shí)會(huì)調(diào)用node.js腳本構(gòu)建前端代碼,這個(gè)不能確保每個(gè)環(huán)境都有bower存在…注釋完事。
//"scripts": { // "prepublish": [ "bower install", "dotnet bundle" ], // "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] //},
刪除/注釋dependencies節(jié)點(diǎn)里面的type
"dependencies": { "Microsoft.NETCore.App": { "version": "1.1.0" //"type": "platform" },
project.json的相關(guān)配置說明可以看下這個(gè)官方文檔: 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
PS:回頭寫個(gè)爬片大家滋持不啊…
相關(guān)文章
.NET Core 3.0之創(chuàng)建基于Consul的Configuration擴(kuò)展組件
在本文里小編給大家分享了關(guān)于.NET Core 3.0之創(chuàng)建基于Consul的Configuration擴(kuò)展組件相關(guān)知識點(diǎn),需要的朋友們學(xué)習(xí)下。2019-05-05如何在ASP.NET Core中使用ViewComponent
這篇文章主要介紹了如何在ASP.NET Core中使用ViewComponent,幫助大家更好的理解和學(xué)習(xí)使用.net技術(shù),感興趣的朋友可以了解下2021-04-04asp.net中EXCEL數(shù)據(jù)導(dǎo)入到數(shù)據(jù)庫的方法
這篇文章主要介紹了asp.net中EXCEL數(shù)據(jù)導(dǎo)入到數(shù)據(jù)庫的方法,實(shí)現(xiàn)讀取excel數(shù)據(jù)并導(dǎo)入到SQL Server數(shù)據(jù)庫的功能,是非常實(shí)用的技巧,需要的朋友可以參考下2015-01-01asp.net MVC實(shí)現(xiàn)無組件上傳圖片實(shí)例介紹
無組件實(shí)現(xiàn)上傳圖片使用input的file作為上傳選擇文件,具體實(shí)現(xiàn)如下:前后臺(tái)代碼很詳細(xì),感興趣的朋友們可不要錯(cuò)過了哈2013-05-05asp.net 刪除MFC單文檔默認(rèn)菜單欄的兩種方法
新建一個(gè)MFC單文檔程序,默認(rèn)都有四個(gè)菜單欄:文件、編輯、視圖和幫助。怎么把這四個(gè)菜單欄刪除掉呢?2010-03-03MVC4制作網(wǎng)站教程第三章 刪除用戶組操作3.4
這篇文章主要為大家詳細(xì)介紹了MVC4制作網(wǎng)站教程,刪除用戶組功能的實(shí)現(xiàn)代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-08-08