用.NET Core写爬虫爬取电影天堂
自从上一个项目从.net迁移到.net core之后,磕磕碰碰磨蹭了一个月才正式上线到新版本。
然后最近又开了个新坑,搞了个爬虫用来爬dy2018电影天堂上面的电影资源。这里也借机简单介绍一下如何基于.net core写一个爬虫。
ps:如有偏错,敬请指明…
pps:该去电影院还是多去电影院,毕竟美人良时可无价。
准备工作(.net core准备)
首先,肯定是先安装.net core咯。下载及安装教程在这里: 。无论你是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,我们得到以下结论:
www.dy2018.com首页的电影数据存储在一个class为co_content222的div标签里面
电影详情链接为a标签,标签显示文本就是电影名称,url即详情url
那么总结下来,我们的工作就是:找到class='co_content222' 的div标签,从里面提取所有的a标签数据。
开始写代码…
之前在写做项目的时候用到过anglesharp库,一个基于.net(c#)开发的专门为解析xhtml源码的dll组件。
anglesharp主页在这里: ,
详细介绍:
nuget地址: nuget anglesharp 安装命令:install-package anglesharp
获取电影列表数据
private static htmlparser htmlparser = new htmlparser(); private concurrentdictionary<string, movieinfo> _cdmovieinfo = new concurrentdictionary<string, movieinfo>(); privatevoidaddtohotmovielist() { //此操作不阻塞当前其他操作,所以使用task // _cdmovieinfo 为线程安全字典,存储了当期所有的电影数据 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标签 //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/") 条件的过滤是因为在测试中发现这一块div中的a标签有可能是广告链接 divinfo.queryselectorall("a").where(a => a.getattribute("href").contains("/i/")) .tolist().foreach( a => { //拼接成完整链接 var onlineurl = "http://www.dy2018.com" + a.getattribute("href"); //看一下是否已经存在于现有数据中 if (!_cdmovieinfo.containskey(onlineurl)) { //获取电影的详细信息 movieinfo movieinfo = fillmovieinfoformweb(a, onlineurl); //下载链接不为空才添加到现有数据 if (movieinfo.xunleidownloadurllist != null && movieinfo.xunleidownloadurllist.count != 0) { _cdmovieinfo.tryadd (movieinfo.dy2018onlineurl,movieinfo); } } }); } } } catch(exception ex) { } }); }
获取电影详细信息
privatemovieinfofillmovieinfoformweb(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']"); //发布时间 在class='updatetime'的span标签中 var updatetime = moviedoc.queryselector("span.updatetime"); var pubdate = datetime.now; if(updatetime!=null && !string.isnullorempty(updatetime.innerhtml)) { //内容带有“发布时间:”字样, //replace成""之后再去转换,转换失败不影响流程 datetime.tryparse(updatetime.innerhtml.replace("发布时间:", ""), out pubdate); } var movieinfo = new movieinfo() { //innerhtml中可能还包含font标签,做多一个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
这边有个小坑,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”)了。
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这个网站编码方式是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; } } } }
定时任务的实现
定时任务我这里使用的是 pomelo.aspnetcore.timedjob 。
pomelo.aspnetcore.timedjob是一个.net core实现的定时任务job库,支持毫秒级定时任务、从数据库读取定时配置、同步异步定时任务等功能。
由.net core社区大神兼前微软mvp amamiyayuuko (入职微软之后就卸任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一下。
// 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相关代码
接着新建一个类,明明为xxxjob.cs,引用命名空间using pomelo.aspnetcore.timedjob,xxxjob继承于job,添加以下代码。
public class autogetmovielistjob:job { // begin 起始时间;interval执行时间间隔,单位是毫秒,建议使用以下格式,此处为3小时; //skipwhileexecuting是否等待上一个执行完成,true为等待; [invoke(begin = "2016-11-29 22:10", interval = 1000 * 3600*3, skipwhileexecuting =true)] publicvoidrun() { //job要执行的逻辑代码 //loghelper.info("start crawling"); //addtolatestmovielist(100); //addtohotmovielist(); //loghelper.info("finish crawling"); } }
项目发布相关 新增runtimes节点
使用vs2015新建的模板工程,project.json配置默认是没有runtimes节点的.
我们想要发布到非windows平台的时候,需要手动配置一下此节点以便生成。
"runtimes": { "win7-x64": {}, "win7-x86": {}, "osx.10.10-x64": {}, "osx.10.11-x64": {}, "ubuntu.14.04-x64": {} }
删除/注释scripts节点
生成时会调用node.js脚本构建前端代码,这个不能确保每个环境都有bower存在…注释完事。
//"scripts": { // "prepublish": [ "bower install", "dotnet bundle" ], // "postpublish": [ "dotnet publish-iis --publish-folder %publish:outputpath% --framework %publish:fulltargetframework%" ] //},
删除/注释dependencies节点里面的type
"dependencies": { "microsoft.netcore.app": { "version": "1.1.0" //"type": "platform" },
project.json的相关配置说明可以看下这个官方文档: project.json-file ,
或者张善友老师的文章 .net core系列 : 2 、project.json 这葫芦里卖的什么药
开发编译发布
//还原各种包文件 dotnet restore; //发布到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:回头写个爬片大家滋持不啊…
下一篇: PHP fclose函数用法总结