写在前面

自从上一个项目58HouseSearch从.NET迁移到.NET core之后,磕磕碰碰磨蹭了一个月才正式上线到新版本。

然后最近又开了个新坑,搞了个Dy2018Crawler用来爬dy2018电影天堂上面的电影资源。这里也借机简单介绍一下如何基于.NET Core写一个爬虫。

PS:如有偏错,敬请指明...

PPS:该去电影院还是多去电影院,毕竟美人良时可无价。

准备工作(.NET Core准备)

首先,肯定是先安装.NET Core咯。下载及安装教程在这里:.NET - Powerful Open Source Development。无论你是Windows、linux还是mac,统统可以玩。

我这里的环境是:Windows10 + VS2015 community updata3 + .NET Core 1.1.0 SDK + .NET Core 1.0.1 tools Preview 2.

理论上,只需要安装一下 .NET Core 1.1.0 SDK 即可开发.NET Core程序,至于用什么工具写代码都无关紧要了。

安装好以上工具之后,在VS2015的新建项目就可以看到.NET Core的模板了。如下图:

为了简单起见,我们创建的时候,直接选择VS .NET Core tools自带的模板。

一个爬虫的自我修养

分析网页

写爬虫之前,我们首先要先去了解一下即将要爬取的网页数据组成。

具体到网页的话,便是分析我们要抓取的数据在HTML里面是用什么标签抑或有什么样的标记,然后使用这个标记把数据从HTML中提取出来。在我这里的话,用的更多的是HTML标签的ID和CSS属性。

以本文章想要爬取的dy2018.com为例,简单描述一下这个过程。dy2018.com主页如下图:

在chrome里面,按F12进入开发者模式,接着如下图使用鼠标选择对应页面数据,然后去分析页面HTML组成。

接着我们开始分析页面数据:

经过简单分析HTML,我们得到以下结论:

  1. www.dy2018.com首页的电影数据存储在一个class为co_content222的div标签里面

  2. 电影详情链接为a标签,标签显示文本就是电影名称,URL即详情URL

那么总结下来,我们的工作就是:找到class='co_content222' 的div标签,从里面提取所有的a标签数据。

开始写代码...

之前在写58HouseSearch项目迁移到asp.net core简单提过AngleSharp库,一个基于.NET(C#)开发的专门为解析xHTML源码的DLL组件。

  1. AngleSharp主页在这里:https://anglesharp.github.io/

  2. 博客园文章:解析HTML利器AngleSharp介绍

  3. Nuget地址:Nuget AngleSharp 安装命令:Install-Package AngleSharp

获取电影列表数据
  1. private static HtmlParser htmlParser = new HtmlParser();
  2. private ConcurrentDictionary<string, MovieInfo> _cdMovieInfo = new ConcurrentDictionary<string, MovieInfo>();
  3. private void AddToHotMovieList()
  4. {
  5. //此操作不阻塞当前其他操作,所以使用Task
  6. // _cdMovieInfo 为线程安全字典,存储了当期所有的电影数据
  7. Task.Factory.StartNew(()=>
  8. {
  9. try
  10. {
  11. //通过URL获取HTML
  12. var htmlDoc = HTTPHelper.GetHTMLByURL("http://www.dy2018.com/");
  13. //HTML 解析成 IDocument
  14. var dom = htmlParser.Parse(htmlDoc);
  15. //从dom中提取所有class='co_content222'的div标签
  16. //QuerySelectorAll方法接受 选择器语法
  17. var lstDivInfo = dom.QuerySelectorAll("div.co_content222");
  18. if (lstDivInfo != null)
  19. {
  20. //前三个DIV为新电影
  21. foreach (var divInfo in lstDivInfo.Take(3))
  22. {
  23. //获取div中所有的a标签且a标签中含有"/i/"的
  24. //Contains("/i/") 条件的过滤是因为在测试中发现这一块div中的a标签有可能是广告链接
  25. divInfo.QuerySelectorAll("a").Where(a => a.GetAttribute("href").Contains("/i/")).ToList().ForEach(
  26. a =>
  27. {
  28. //拼接成完整链接
  29. var onlineURL = "http://www.dy2018.com" + a.GetAttribute("href");
  30. //看一下是否已经存在于现有数据中
  31. if (!_cdMovieInfo.ContainsKey(onlineURL))
  32. {
  33. //获取电影的详细信息
  34. MovieInfo movieInfo = FillMovieInfoFormWeb(a, onlineURL);
  35. //下载链接不为空才添加到现有数据
  36. if (movieInfo.XunLeiDownLoadURLList != null && movieInfo.XunLeiDownLoadURLList.Count != 0)
  37. {
  38. _cdMovieInfo.TryAdd(movieInfo.Dy2018OnlineUrl, movieInfo);
  39. }
  40. }
  41. });
  42. }
  43. }
  44. }
  45. catch(Exception ex)
  46. {
  47. }
  48. });
  49. }

获取电影详细信息

  1. private MovieInfo FillMovieInfoFormWeb(AngleSharp.Dom.IElement a, string onlineURL)
  2. {
  3. var movieHTML = HTTPHelper.GetHTMLByURL(onlineURL);
  4. var movieDoc = htmlParser.Parse(movieHTML);
  5. //http://www.dy2018.com/i/97462.html 分析过程见上,不再赘述
  6. //电影的详细介绍 在id为Zoom的标签中
  7. var zoom = movieDoc.GetElementById("Zoom");
  8. //下载链接在 bgcolor='#fdfddf'的td中,有可能有多个链接
  9. var lstDownLoadURL = movieDoc.QuerySelectorAll("[bgcolor='#fdfddf']");
  10. //发布时间 在class='updatetime'的span标签中
  11. var updatetime = movieDoc.QuerySelector("span.updatetime"); var pubDate = DateTime.Now;
  12. if(updatetime!=null && !string.IsNullOrEmpty(updatetime.InnerHtml))
  13. {
  14. //内容带有“发布时间:”字样,replace成""之后再去转换,转换失败不影响流程
  15. DateTime.TryParse(updatetime.InnerHtml.Replace("发布时间:", ""), out pubDate);
  16. }
  17. var movieInfo = new MovieInfo()
  18. {
  19. //InnerHtml中可能还包含font标签,做多一个Replace
  20. MovieName = a.InnerHtml.Replace("<font color=\"#0c9000\">","").Replace("<font color=\" #0c9000\">","").Replace("</font>", ""),
  21. Dy2018OnlineUrl = onlineURL,
  22. MovieIntro = zoom != null ? WebUtility.HtmlEncode(zoom.InnerHtml) : "暂无介绍...",//可能没有简介,虽然好像不怎么可能
  23. XunLeiDownLoadURLList = lstDownLoadURL != null ?
  24. lstDownLoadURL.Select(d => d.FirstElementChild.InnerHtml).ToList() : null,//可能没有下载链接
  25. PubDate = pubDate,
  26. };
  27. return movieInfo;
  28. }

HTTPHelper

这边有个小坑,dy2018网页编码格式是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")了。

  1. using System;
  2. using System.Net.Http;
  3. using System.Net.Http.Headers;
  4. using System.Text;
  5. namespace Dy2018Crawler
  6. {
  7. public class HTTPHelper
  8. {
  9. public static HttpClient Client { get; } = new HttpClient();
  10. public static string GetHTMLByURL(string url)
  11. {
  12. try
  13. {
  14. System.Net.WebRequest wRequest = System.Net.WebRequest.Create(url);
  15. wRequest.ContentType = "text/html; charset=gb2312";
  16. wRequest.Method = "get";
  17. wRequest.UseDefaultCredentials = true;
  18. // Get the response instance.
  19. var task = wRequest.GetResponseAsync();
  20. System.Net.WebResponse wResp = task.Result;
  21. System.IO.Stream respStream = wResp.GetResponseStream();
  22. //dy2018这个网站编码方式是GB2312,
  23. using (System.IO.StreamReader reader = new System.IO.StreamReader(respStream, Encoding.GetEncoding("GB2312")))
  24. {
  25. return reader.ReadToEnd();
  26. }
  27. }
  28. catch (Exception ex)
  29. {
  30. Console.WriteLine(ex.ToString());
  31. return string.Empty;
  32. }
  33. }
  34. }
  35. }

定时任务的实现

定时任务我这里使用的是Pomelo.AspNetCore.TimedJob

Pomelo.AspNetCore.TimedJob是一个.NET Core实现的定时任务job库,支持毫秒级定时任务、从数据库读取定时配置、同步异步定时任务等功能。

由.NET Core社区大神兼前微软MVPAmamiyaYuuko(入职微软之后就卸任MVP...)开发维护,不过好像没有开源,回头问下看看能不能开源掉。

nuget上有各种版本,按需自取。地址:https://www.nuget.org/packages/Pomelo.AspNetCore.TimedJob/1.1.0-rtm-10026

作者自己的介绍文章:Timed Job - Pomelo扩展包系列

Startup.cs相关代码

我这边使用的话,首先肯定是先安装对应的包:Install-Package Pomelo.AspNetCore.TimedJob -Pre

然后在Startup.cs的ConfigureServices函数里面添加Service,在Configure函数里面Use一下。

  1. // This method gets called by the runtime. Use this method to add services to the container.
  2. public void ConfigureServices(IServiceCollection services)
  3. {
  4. // Add framework services.
  5. services.AddMvc();
  6. //Add TimedJob services
  7. services.AddTimedJob();
  8. }
  9. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
  10. {
  11. //使用TimedJob
  12. app.UseTimedJob();
  13. if (env.IsDevelopment())
  14. {
  15. app.UseDeveloperExceptionPage();
  16. app.UseBrowserLink();
  17. }
  18. else
  19. {
  20. app.UseExceptionHandler("/Home/Error");
  21. }
  22. app.UseStaticFiles();
  23. app.UseMvc(routes =>
  24. {
  25. routes.MapRoute(
  26. name: "default",
  27. template: "{controller=Home}/{action=Index}/{id?}");
  28. });
  29. Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
  30. }

Job相关代码

接着新建一个类,明明为XXXJob.cs,引用命名空间using Pomelo.AspNetCore.TimedJob,XXXJob继承于Job,添加以下代码。

  1. public class AutoGetMovieListJob:Job
  2. {
  3. // Begin 起始时间;Interval执行时间间隔,单位是毫秒,建议使用以下格式,此处为3小时;SkipWhileExecuting是否等待上一个执行完成,true为等待;
  4. [Invoke(Begin = "2016-11-29 22:10", Interval = 1000 * 3600*3, SkipWhileExecuting =true)]
  5. public void Run()
  6. {
  7. //Job要执行的逻辑代码
  8. //LogHelper.Info("Start crawling");
  9. //AddToLatestMovieList(100);
  10. //AddToHotMovieList();
  11. //LogHelper.Info("Finish crawling");
  12. }
  13. }

项目发布相关

新增runtimes节点

使用VS2015新建的模板工程,project.json配置默认是没有runtimes节点的.

我们想要发布到非Windows平台的时候,需要手动配置一下此节点以便生成。


  1. "runtimes": {
  2. "win7-x64": {},
  3. "win7-x86": {},
  4. "osx.10.10-x64": {},
  5. "osx.10.11-x64": {},
  6. "ubuntu.14.04-x64": {}
  7. }

删除/注释scripts节点

生成时会调用node.js脚本构建前端代码,这个不能确保每个环境都有bower存在...注释完事。


  1. //"scripts": {
  2. // "prepublish": [ "bower install", "dotnet bundle" ],
  3. // "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
  4. //},

删除/注释dependencies节点里面的type

  1. "dependencies": {
  2. "Microsoft.NETCore.App": {
  3. "version": "1.1.0"
  4. //"type": "platform"
  5. },

project.json的相关配置说明可以看下这个官方文档:Project.json-file,

或者张善友老师的文章.NET Core系列 : 2 、project.json 这葫芦里卖的什么药

开发编译发布

  1. //还原各种包文件
  2. dotnet restore;
  3. //发布到C:\code\website\Dy2018Crawler文件夹
  4. dotnet publish -r ubuntu.14.04-x64 -c Release -o "C:\code\website\Dy2018Crawler";

最后,照旧开源......以上代码都在下面找到:

Gayhub地址:https://github.com/liguobao/Dy2018Crawler

在线地址:http://codelover.win/

PS:回头写个爬片大家滋持不啊...

手把手教你用.NET Core写爬虫的更多相关文章

  1. 手把手教你ASP.NET Core:使用Entity Framework Core进行增删改查

    新建表Todo,如图 添加模型类 在"解决方案资源管理器"中,右键单击项目. 选择"添加" > "新建文件夹". 将文件夹命名为 Mo ...

  2. [原创]手把手教你写网络爬虫(4):Scrapy入门

    手把手教你写网络爬虫(4) 作者:拓海 摘要:从零开始写爬虫,初学者的速成指南! 封面: 上期我们理性的分析了为什么要学习Scrapy,理由只有一个,那就是免费,一分钱都不用花! 咦?怎么有人扔西红柿 ...

  3. 手把手教你写电商爬虫-第三课 实战尚妆网AJAX请求处理和内容提取

    版权声明:本文为博主原创文章,未经博主允许不得转载. 系列教程: 手把手教你写电商爬虫-第一课 找个软柿子捏捏 手把手教你写电商爬虫-第二课 实战尚妆网分页商品采集爬虫 看完两篇,相信大家已经从开始的 ...

  4. 手把手教你写电商爬虫-第四课 淘宝网商品爬虫自动JS渲染

    版权声明:本文为博主原创文章,未经博主允许不得转载. 系列教程: 手把手教你写电商爬虫-第一课 找个软柿子捏捏 手把手教你写电商爬虫-第二课 实战尚妆网分页商品采集爬虫 手把手教你写电商爬虫-第三课 ...

  5. [原创]手把手教你写网络爬虫(5):PhantomJS实战

    手把手教你写网络爬虫(5) 作者:拓海 摘要:从零开始写爬虫,初学者的速成指南! 封面: 大家好!从今天开始,我要与大家一起打造一个属于我们自己的分布式爬虫平台,同时也会对涉及到的技术进行详细介绍.大 ...

  6. [原创]手把手教你写网络爬虫(7):URL去重

    手把手教你写网络爬虫(7) 作者:拓海 摘要:从零开始写爬虫,初学者的速成指南! 封面: 本期我们来聊聊URL去重那些事儿.以前我们曾使用Python的字典来保存抓取过的URL,目的是将重复抓取的UR ...

  7. 手把手教你写基于C++ Winsock的图片下载的网络爬虫

    手把手教你写基于C++ Winsock的图片下载的网络爬虫 先来说一下主要的技术点: 1. 输入起始网址,使用ssacnf函数解析出主机号和路径(仅处理http协议网址) 2. 使用socket套接字 ...

  8. win10 uwp 手把手教你使用 asp dotnet core 做 cs 程序

    本文是一个非常简单的博客,让大家知道如何使用 asp dot net core 做后台,使用 UWP 或 WPF 等做前台. 本文因为没有什么业务,也不想做管理系统,所以看到起来是很简单. Visua ...

  9. 知识全聚集 .Net Core 技术突破 | 我用C#手把手教你玩微信自动化一

    知识全聚集 .Net Core 技术突破 | 我用C#手把手教你玩微信自动化一 教程 01 | 模块化方案一 02 | 模块化方案二 03 | 简单说说工作单元 其他教程预览 分库分表项目实战教程 G ...

随机推荐

  1. MYSQL ORDER BY Optimization

    ORDER BY Optimization 某些情况下,MYSQL可以使用index排序而避免额外的sorting. 即使order by语句列不能准确的匹配index,只要没有index中(不在or ...

  2. DS18B20温度传感器知识点总结

    2018-01-1818:20:48 感觉自己最近有点凌乱,一个很简单的问题都能困扰自己很久.以前能很好使用和调试的DS18B20温度传感器,今天愣是搞了很久,妈卖批. 仅仅一个上拉电阻就困扰了我很久 ...

  3. Windows ftp脚本和RSCD agent自动安装脚本

    Windows ftp脚本 和bladelogic RSCD Agent自动安装脚本 比较简单的命令是msiexec /I "C:\RSCD85-SP1-WIN64.msi" /Q ...

  4. android 弹起键盘把ui顶上去的解决办法

    键盘输入框上面的ui布局必须为Relative相对布局.然后设置 <activityandroid:name=".activity.HomeActivity"Android: ...

  5. Bilibili/DanmakuFlameMaster: Android开源弹幕引擎·烈焰弹幕使 ~ JNI source:Bilibili/NativeBitmapFactory

    https://github.com/Bilibili/DanmakuFlameMaster

  6. redis数据类型-字符串类型

    Redis数据类型 字符串类型 字符串类型是Redis中最基本的数据类型,它能存储任何形式的字符串,包括二进制数据.你可以用其存储用户的邮箱.JSON化的对象甚至是一张图片.一个字符串类型键允许存储的 ...

  7. fopen fclose feof fgets fetl

    fopen :Open file, or obtain information about open files 例如 fid = fopen(filename, permission)%许可包括: ...

  8. 揭开Java内存管理的面纱

    前言 相对于C.C++这些高性能语言,Java有着让此类程序员羡慕的功能:内存自动管理.似乎这样,Java程序员不用再关心内存,也不用去了解相关知识.但结果真的是这样吗?特别对于我们这种Android ...

  9. Cisco配置aaa验证

    当您的网络中部署了一台集中的radius校验服务器(比如我司的SAM,cisco的ACS等),希望对登陆设备的用户身份进行合法性校验,而账号都统一由该radius服务器集中产生与维护,您希望所有的登入 ...

  10. [HTTP] PHP 实现 HTTP Server 原理

    单进程服务器简陋版: <?php /** * Single http server. * * Access http://127.0.0.1:8081 * * @license Apache-2 ...