.net core之ACG小站爬虫(一)

想到好久没写过.net的代码了,因此就尝试来写一写.net的代码。此外,也想要熟悉一下Phantomjs。


环境配置

  • .net core下载。可选的可以下载宇宙大IDEVisual Studio,当然更加推荐使用Visual Studio Code进行代码的书写。

  • Phantomjs。这个不用说了,今天的主角。采用无头浏览器爬取ACG小站的很大原因是它的页面很难分析,此外也有熟悉一下Phantomjs的意思。

  • HttpCode.Core,是一个.net core的HTTP请求库,可以使用Nuget安装。库本质上是基于HttpWebRequest实现的,但是为了舍去我们自己封装的麻烦,就采用此库来减少代码量。

  • AngleSharp,一个帮助解析HTML的.net库,可以直接使用LINQ来查询,很方便。

页面分析


详情页

就以其中随便的一个动漫链接下载为例子,发现了坑爹的玩意,下载链接的href属性是javascript:;。介于浅薄的javascript知识,百度了才知道这个是伪协议,实际运行了一段js代码回调,从而打开了新的页面。但是这个网站是用React写的,更可怕的在下面。


JavaScript

原本采用直接分析,并借助Debug调试来分析它点击后的回调函数,但是这个JavaScript文件竟然有2万行代码,Chrome打开调试定点,根本就没法format,不然直接卡死,于是放弃了这个念头,转而采用无头浏览器进行爬取。

实现

一般采用Phantomjs的手段都为Selenium+Phantomjs,但是性能不是很好,而且暂时也没怎么找.netSelenium接口,所以就选用了最近看到的直接让Phantomjs作为服务端,然后去请求它,让它把要爬取的结果反馈给.net。注意,这里的结果可以是网页页面,也可以是Phantomjs进行HTML解析完的真实数据。

.Net Core代码

        public async Task<string> GetDownloadPageAsync(string url)
        {
            string result = string.Empty;
            //请求phantomjs 获取下载页面
            string dom = "Tappable-inactive animated fadeIn";
            KeyValuePair<string, string> url2dom = new KeyValuePair<string, string>(url, dom);
            var postData = JsonConvert.SerializeObject(url2dom);
            CookieContainer cc = new CookieContainer();  
            HttpHelpers helper = new HttpHelpers();  
            HttpItems items = new HttpItems();
            HttpResults hr = new HttpResults();
            items.Url = "http://localhost:8088/";
            items.Method = "POST";
            items.Container = cc;
            items.Postdata = postData;
            items.Timeout = 100000;
            hr = await helper.GetHtmlAsync(items);
            var downloadPageUrl = hr.Html;
            Console.WriteLine($"first => { downloadPageUrl }");
            if(downloadPageUrl.Contains("http"))
            {
                # TODO
            }
            else
            {
                result = downloadPageUrl; //输出错误信息
            }
            return result;
        }

这里展示的是第一部分的代码,即请求详情页的代码,还未涉及到点了下载按钮之后页面的分析,实际上差不多。

JavaScript代码

"use strict";
var port = 8088;
var server = require('webserver').create();

//服务端监听
server.listen(8088, function (request, response) {
    //传入的参数有待更改,目前为
    //{"Key":"https://acg12.com/200340/", "Value":"Tappable-inactive animated fadeIn"}的json字符窜
    //第一个参数为详情页,第二个为下载按钮的Dom
    var data = JSON.parse(request.postRaw);
    var url = data.Key.toString();
    var dom = data.Value.toString();
    var code = 0;
    var page = require('webpage').create();
    //初始化headers
    page.onInitialized = function() {
      page.customHeaders = {};
    };
    page.settings.loadImages = false;
    page.customHeaders = {
        "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36",
        "Referer": url
    };
    response.headers = {
        'Cache': 'no-cache',
        'Content-Type': 'text/plain',
        'Connection': 'Keep-Alive',
        'Keep-Alive': 'timeout=20, max=100'
    };
    //根据Phantomjs的官网,这个回调在打开新标签页会触发
    page.onPageCreated = function(newPage) {
        //console.log('A new child page was created! Its requested URL is not yet available, though.');
        newPage.onLoadFinished = function(status) {
            console.log('A child page is Loaded: ' + newPage.url);
            //newPage.render('newPage.png');
            response.write(newPage.url);
            response.statusCode = code;
            response.close(); //写入返回给.net端的响应内容。
        };
    };
    //让Phantomjs帮助我们去请求页面
    page.open(url, function (status) {
        console.log("----" + status);
        if (status !== 'success') {
            code = 400;
            response.write('4XX');
            response.statusCode = code;
            response.close();
        } else {
            code = 200;
            window.setTimeout(function (){
                //执行JavaScript代码,类似于在浏览器Console中执行JavaScript
                page.evaluate(function(dom) {
                    console.log(dom);
                    var btnList = document.getElementsByClassName(dom);
                    if(btnList.length > 0){
                        var btn = document.getElementsByClassName(dom)[1]; // 获取下载按钮
                        btn.click(); //点击下载按钮,打开新标签页,触发page.onPageCreated回调函数。
                    }
                }, dom);        
            }, 7000);
        }
    });
    //根据Phantomjs的官网,这个回调主要应对执行evaluate函数内部的console.log输出,因为两个环境是隔离的。
    page.onConsoleMessage = function(msg, lineNum, sourceId) {
      console.log("$$$$$" + msg);
    };
    page.onError = function(msg, trace) {
       var msgStack = ['PHANTOM ERROR: ' + msg];
       if (trace && trace.length) {
         msgStack.push('TRACE:');
         trace.forEach(function(t) {
           msgStack.push(' -> ' + (t.file || t.sourceURL) + ': ' + t.line + (t.function ? ' (in function ' + t.function +')' : ''));
         });
       }
       console.log(msgStack.join('\n'));
       phantom.exit(1);
     };
});
phantom.onError = function(msg, trace) {
   var msgStack = ['PHANTOM ERROR: ' + msg];
   if (trace && trace.length) {
     msgStack.push('TRACE:');
     trace.forEach(function(t) {
       msgStack.push(' -> ' + (t.file || t.sourceURL) + ': ' + t.line + (t.function ? ' (in function ' + t.function +')' : ''));
     });
   }
   console.log(msgStack.join('\n'));
   phantom.exit(1);
 };

这里的注释比较详细,就不细说了。

启动上述Phantomjs服务端的脚本

phantomjs  --ssl-protocol=any --debug=true .\server_get_detail_page.js 

第一个参数是为了保持ssl的链接,第二个参数开启debug,第三个参数为上面的JavaScript代码。

还有之后的页面类似于以上的代码,但是有一些细节需要注意,为了防止文章过长(明明是再水一篇(๑ ̄ _   ̄๑))大家下期再见。等等,完整的源代码就先全放在Github上了


原文地址:http://www.jianshu.com/p/9b738a25b585


.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注

.Net中有不少开源的爬虫工具,abot就是其中之一。Abot是一个开源的.net爬虫,速度快,易于使用和扩展。项目的地址是 https://github.com/sjdirect/abot 对于爬取的Html,使用的分析工具是CsQuery, CsQuery可以算是.net中实现的Jquery, 可以使用类似Jquery中的方法来处理html页面。CsQuery的项目地址是https://github.com/afeiship/CsQuery一. 对Abot爬虫配置1. 通过属性设置先创建config对象,然后设置config中的各项属性:CrawlConfiguration crawlConfig = new CrawlConfiguration();  crawlConfig.CrawlTimeoutSeconds = 100;  crawlConfig.MaxConcurrentThreads = 10;  crawlConfig.MaxPagesToCrawl = 1000;  crawlConfig.UserAgentString = "abot v1.0 http://code.google.com/p/abot";  crawlConfig.ConfigurationExtensions.Add("SomeCustomConfigValue1", "1111");  crawlConfig.ConfigurationExtensions.Add("SomeCustomConfigValue2", "2222");2. 通过App.config配置直接从配置文件中读取,但是也任然可以在修改各项属性:CrawlConfiguration crawlConfig = AbotConfigurationSectionHandler.LoadFromXml().Convert(); crawlConfig.CrawlTimeoutSeconds = 100;  crawlConfig.MaxConcurrentThreads = 10;3. 应用配置到爬虫对象PoliteWebCrawler crawler = new PoliteWebCrawler(); PoliteWebCrawler crawler = new PoliteWebCrawler(crawlConfig, null, null, null, null, null, null, null);二,使用爬虫,注册各种事件爬虫中主要是4个事件, 页面爬取开始、页面爬取失败、页面不允许爬取事件、页面中的链接不允许爬取事件。下面是示例代码:crawlergeCrawlStartingAsync  = crawler_ProcessPageCrawlStarting;//单个页面爬取开始  crawler.PageCrawlCompletedAsync  = crawler_ProcessPageCrawlCompleted;//单个页面爬取结束  crawler.PageCrawlDisallowedAsync  = crawler_PageCrawlDisallowed;//页面不允许爬取事件  crawler.PageLinksCrawlDisallowedAsync  = crawler_PageLinksCrawlDisallowed;//页面链接不允许爬取事件 void crawler_ProcessPageCrawlStarting(object sender, PageCrawlStartingArgs e) {   PageToCrawl pageToCrawl = e.PageToCrawl;   Console.WriteLine("About to crawl link {0} which was found on page {1}", pageToCrawl.Uri.AbsoluteUri, pageToCrawl.ParentUri.AbsoluteUri); } void crawler_ProcessPageCrawlCompleted(object sender, PageCrawlCompletedArgs e) {   CrawledPage crawledPage = e.CrawledPage;   if (crawledPage.WebException != null || crawledPage.HttpWebResponse.StatusCode != HttpStatusCode.OK)     Console.WriteLine("Crawl of page failed {0}", crawledPage.Uri.AbsoluteUri);   else     Console.WriteLine("Crawl of page succeeded {0}", crawledPage.Uri.AbsoluteUri);   if (string.IsNullOrEmpty(crawledPage.Content.Text))     Console.WriteLine("Page had no content {0}", crawledPage.Uri.AbsoluteUri); } void crawler_PageLinksCrawlDisallowed(object sender, PageLinksCrawlDisallowedArgs e) {   CrawledPage crawledPage = e.CrawledPage;   Console.WriteLine("Did not crawl the links on page {0} due to {1}", crawledPage.Uri.AbsoluteUri, e.DisallowedReason); } void crawler_PageCrawlDisallowed(object sender, PageCrawlDisallowedArgs e) {   PageToCrawl pageToCrawl = e.PageToCrawl;   Console.WriteLine("Did not crawl page {0} due to {1}", pageToCrawl.Uri.AbsoluteUri, e.DisallowedReason); }三, 为爬虫添加多个附加对象Abot应该是借鉴了Asp.net MVC中的ViewBag, 也为爬虫对象设置了对象级别的CrwalBag和Page级别的ViewBag.PoliteWebCrawler crawler = new PoliteWebCrawler(); crawler.CrawlBag.MyFoo1 = new Foo();//对象级别的 CrwalBagcrawler.CrawlBag.MyFoo2 = new Foo(); crawler.PageCrawlStartingAsync  = crawler_ProcessPageCrawlStarting; ...void crawler_ProcessPageCrawlStarting(object sender, PageCrawlStartingArgs e) {   //获取CrwalBag中的对象   CrawlContext context = e.CrawlContext;    context.CrawlBag.MyFoo1.Bar();  //使用CrwalBag    context.CrawlBag.MyFoo2.Bar();      //使用页面级别的    PageBag  e.PageToCrawl.PageBag.Bar = new Bar(); }四,启动爬虫启动爬虫非常简单,调用Crawl方法,指定好开始页面,就可以了。CrawlResult result = crawler.Crawl(new Uri("  if (result.ErrorOccurred)         Console.WriteLine("Crawl of {0} completed with error: {1}",          result.RootUri.AbsoluteUri, result.ErrorException.Message         );          else         Console.WriteLine("Crawl of {0} completed without error.", result.RootUri.AbsoluteUri);五,介绍CsQuery在PageCrawlCompletedAsync事件中, e.CrawledPage.CsQueryDocument就是一个CsQuery对象。这里介绍一下CsQuery在分析Html上的优势:cqDocument.Select(".bigtitle > h1")这里的选择器的用法和Jquery完全相同,这里是取class为.bittitle下的h1标签。如果你能熟练的使用Jquery,那么上手CsQuery会非常快和容易。 标签:网络爬虫  网络蜘蛛
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值