DotnetSpider (一) 架构的理解、应用、搭建
本文连接:http://www.cnblogs.com/grom/p/8931650.html
受业务影响,决定将Downloader单独分层,做出修改。
最近在做爬虫,之前一直在使用 HttpWebRequest 和 WebClient ,很方便快捷,也很适合新手,但随着抓取任务的增多,多任务,多库等情况的出现,使用一个优秀的爬虫框架是十分必要的。于是开始接触dotnetspider。
借鉴一下框架的设计图,在引入dotnetspider的NuGet包后,我基本也是按照这个进行了分层
Data.Spider - 存放前台页面(Winform、控制台)和实体爬虫(EntitySpider)等,相当于发起请求的起点。
Spider.Downloader - 封装请求等信息,可实现自定义cookie等,非必须。
Spider.Processor - 处理器,继承 IPageProcessor 实现对抓取内容的处理
Spider.Pipe - 管道,我将它理解为经过了 Processor 处理后的一个回调,将处理好的数据存储(文件、数据库等)
Spider.Entity - 数据实体类,继承 SpiderEntity
Spider.Command - 一些常用的公用命令,我这目前存放着转数据格式类,后台执行JS类,SqlHelper(因架构自带数据库管道,暂时没用)等
这样的分层也是参考了源码的示例
随着这几天的尝试,真的发现这个框架真的非常灵活,以凹凸租车的爬虫为例,上代码
实体类:
[EntityTable("CarWinsSpider", "AtzucheCar", EntityTable.Today)]
[EntitySelector(Expression = "$.data.content[*]", Type = SelectorType.JsonPath)]
public class AtzucheModel : SpiderEntity
{
/// <summary>
/// 车辆编号
/// </summary>
[PropertyDefine(Expression = "$.carNo", Type = SelectorType.JsonPath)]
public int carNo { get; set; }
/// <summary>
/// 品牌
/// </summary>
//[ReplaceFormatter(NewValue = "", OldValue = "\r")]
//[ReplaceFormatter(NewValue = "", OldValue = "\t")]
//[ReplaceFormatter(NewValue = "", OldValue = " ")]
//[ReplaceFormatter(NewValue = "", OldValue = "\n")]
//[ReplaceFormatter(NewValue = "", OldValue = "\"")]
//[ReplaceFormatter(NewValue = "", OldValue = " ")]
[PropertyDefine(Expression = "$.brand", Type = SelectorType.JsonPath)]
public string brand { get; set; }
/// <summary>
/// 地址
/// </summary>
[PropertyDefine(Expression = "$.carAddr", Type = SelectorType.JsonPath)]
public string carAddr { get; set; }
/// <summary>
/// 车系
/// </summary>
[PropertyDefine(Expression = "$.type", Type = SelectorType.JsonPath)]
public string type { get; set; }
/// <summary>
/// 排量
/// </summary>
[PropertyDefine(Expression = "$.sweptVolum", Type = SelectorType.JsonPath)]
public string sweptVolum { get; set; }
/// <summary>
/// 图片
/// </summary>
[PropertyDefine(Expression = "$.coverPic", Type = SelectorType.JsonPath)]
public string coverPic { get; set; }
/// <summary>
/// 日租金
/// </summary>
[PropertyDefine(Expression = "$.dayPrice", Type = SelectorType.JsonPath)]
public int dayPrice { get; set; }
/// <summary>
/// 公里数
/// </summary>
[PropertyDefine(Expression = "$.distance", Type = SelectorType.JsonPath)]
public string distance { get; set; }
/// <summary>
/// 评分
/// </summary>
[PropertyDefine(Expression = "$.evalScore", Type = SelectorType.JsonPath)]
public string evalScore { get; set; }
[PropertyDefine(Expression = "$.gbType", Type = SelectorType.JsonPath)]
public string gbType { get; set; }
/// <summary>
/// 车牌
/// </summary>
[PropertyDefine(Expression = "$.plateNum", Type = SelectorType.JsonPath)]
public string plateNum { get; set; }
[PropertyDefine(Expression = "$.replyTag", Type = SelectorType.JsonPath)]
public string replyTag { get; set; }
[PropertyDefine(Expression = "$.transCount", Type = SelectorType.JsonPath)]
public string transCount { get; set; }
/// <summary>
/// 年款
/// </summary>
[PropertyDefine(Expression = "$.year", Type = SelectorType.JsonPath)]
public int year { get; set; }
[PropertyDefine(Expression = "$.isPrivilege", Type = SelectorType.JsonPath)]
public int isPrivilege { get; set; }
[PropertyDefine(Expression = "$.isRecommend", Type = SelectorType.JsonPath)]
public int isRecommend { get; set; }
[PropertyDefine(Expression = "$.isUpgrade", Type = SelectorType.JsonPath)]
public int isUpgrade { get; set; }
[PropertyDefine(Expression = "$.lat", Type = SelectorType.JsonPath)]
public string lat { get; set; }
[PropertyDefine(Expression = "$.lon", Type = SelectorType.JsonPath)]
public string lon { get; set; }
[PropertyDefine(Expression = "$.queryId", Type = SelectorType.JsonPath)]
public string queryId { get; set; }
[PropertyDefine(Expression = "$.supplyCarService", Type = SelectorType.JsonPath)]
public int supplyCarService { get; set; }
[PropertyDefine(Expression = "$.freeCarService", Type = SelectorType.JsonPath)]
public int freeCarService { get; set; }
[PropertyDefine(Expression = "$.isShenMaCar", Type = SelectorType.JsonPath)]
public int isShenMaCar { get; set; }
[PropertyDefine(Expression = "$.supportGetReturn", Type = SelectorType.JsonPath)]
public int supportGetReturn { get; set; }
[PropertyDefine(Expression = "$.confirmation", Type = SelectorType.JsonPath)]
public int confirmation { get; set; }
}
起始:
/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
static void Main()
{
var site = new Site
{
CycleRetryTimes = 1,
SleepTime = 200,
Headers = new Dictionary<string, string>()
{
{"Accept","application/json, text/javascript, */*; q=0.01" },
{"Accept-Encoding","gzip, deflate" },
{"gzip, deflate","zh-CN,zh;q=0.9" },
{"X-Requested-With","XMLHttpRequest" },
{ "Referer", "http://www.atzuche.com/hz/car/search"},
{ "Connection","keep-alive" },
{ "Content-Type","application/json;charset=UTF-8" },
{ "Host","www.atzuche.com"},
{ "User-Agent","Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36"}
}
};
List<Request> resList = new List<Request>();
Request res = new Request();
//res.PostBody = $"id=7&j=%7B%22createMan%22%3A%2218273159100%22%2C%22createTime%22%3A1518433690000%2C%22row%22%3A5%2C%22siteUserActivityListId%22%3A8553%2C%22siteUserPageRowModuleId%22%3A84959%2C%22topids%22%3A%22%22%2C%22wherePhase%22%3A%221%22%2C%22wherePreferential%22%3A%220%22%2C%22whereUsertype%22%3A%220%22%7D&page={i}&shopid=83106681";//据说是post请求需要
res.Url = "http://www.atzuche.com/car/searchListMap/2?cityCode=330100&sceneCode=U002&filterCondition%5Blon%5D=120.219294&filterCondition%5Blat%5D=30.259258&filterCondition%5Bseq%5D=4&pageNum=1&pageSize=0";
res.Method = System.Net.Http.HttpMethod.Get;
resList.Add(res);
var spider = DotnetSpider.Core.Spider.Create(site, new QueueDuplicateRemovedScheduler(), new AtzucheProcessor())
.AddStartRequests(resList.ToArray())//页面抓取整理
.AddPipeline(new AtzuchePipe());//数据回调
//----------------------------------
spider.Monitor = new DotnetSpider.Core.Monitor.NLogMonitor();
spider.Downloader = new AtzucheDownloader(); //new DotnetSpider.Core.Downloader.HttpClientDownloader();
spider.ClearSchedulerAfterComplete = false;//爬虫结束后不取消调度器
//----------------------------------
spider.ThreadNum = 1;
spider.Run();
Console.WriteLine("Press any key to continue...");
Console.Read();
}
这里也可将整个抓取方法当做一个Spider实例单独放置 -> EntitySpider
/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
static void Main()
{
AtzucheEntitySpider dDengEntitySpider = new AtzucheEntitySpider();
dDengEntitySpider.AddPageProcessor(new AtzucheProcessor());//控制器
dDengEntitySpider.AddPipeline(new AtzuchePipe());//回调
dDengEntitySpider.ThreadNum = 1;
dDengEntitySpider.Run();
Console.WriteLine("Press any key to continue...");
Console.Read();
}
Downloader
对目标的请求全部包含着这里,可以根据需要自行设置,下篇将进行自定义Request的应用
public class AtzucheDownloader : BaseDownloader
{
protected override Page DowloadContent(Request request, ISpider spider)
{
return new HttpClientDownloader().Download(request, spider);
}
}
新建爬虫实体类
public class AtzucheEntitySpider : EntitySpider
{
protected override void MyInit(params string[] arguments)
{
AddPipeline(new SqlServerEntityPipeline("Server=.;Database=AuzucheSpider;uid=sa;pwd=123;MultipleActiveResultSets=true"));//注意连接字符串中数据库不能带 . 亲测报错。。。
AddStartUrl("http://www.atzuche.com/car/searchListMap/2?cityCode=330100&sceneCode=U002&filterCondition%5Blon%5D=120.219294&filterCondition%5Blat%5D=30.259258&filterCondition%5Bseq%5D=4&pageNum=1&pageSize=0");
AddEntityType<AtzucheModel>();//如添加此实体类,框架将会根据此实体类上面的特性选择进行匹配,匹配成功后插入数据库,固可以省略Processor和Pipe,或者不使用此句,通过控制器和回调自定义存储方法
}
public AtzucheEntitySpider() : base("AuzucheSpider", new Site
{
CycleRetryTimes = 1,
SleepTime = 200,
Headers = new Dictionary<string, string>()
{
{"Accept","application/json, text/javascript, */*; q=0.01" },
{"Accept-Encoding","gzip, deflate" },
{"gzip, deflate","zh-CN,zh;q=0.9" },
{"X-Requested-With","XMLHttpRequest" },
{ "Referer", "http://www.atzuche.com/hz/car/search"},
{ "Connection","keep-alive" },
{ "Content-Type","application/json;charset=UTF-8" },
{ "Host","www.atzuche.com"},
{ "User-Agent","Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36"}
}
})
{
}
}
接下来是处理器:
解析抓取的数据封装到"AtzucheList"内,可Pipe内通过此名称获取处理好的数据。
public class AtzucheProcessor : IPageProcessor
{
public void Process(Page page, ISpider spider)
{
List<AtzucheModel> list = new List<AtzucheModel>();
var html = page.Selectable.JsonPath("$.data.content").GetValue();
list = JsonConvert.DeserializeObject<List<AtzucheModel>>(html);
page.AddResultItem("AtzucheList", list);
}
}
最后是回调,可在此加入保存数据的代码,至此结束。
public class AtzuchePipe : BasePipeline
{
public override void Process(IEnumerable<ResultItems> resultItems, ISpider spider)
{
var result = new List<AtzucheModel>();
foreach (var resultItem in resultItems)
{
Console.WriteLine((resultItem.Results["AtzucheList"] as List<AtzucheModel>).Count);
foreach (var item in (resultItem.Results["AtzucheList"] as List<AtzucheModel>))
{
result.Add(new AtzucheModel()
{
carNo = item.carNo
});
Console.WriteLine($"{item.carNo}:{item.type} ");
}
}
}
}
结果图:
总体来说,此框架对新手还是很友好的,灵活写法可以让我们有较多的方式去实现爬虫,因为这个爬虫比较简单,就先写到这里,未来如果可能,会再尝试使用框架内的多线程、代理等功能,如有心得将继续分享,希望能对跟我一样的新手有所帮助,十分感谢。
作者:Grom
DotnetSpider (一) 架构的理解、应用、搭建相关推荐
- 云技术-SaaS架构初步理解
最近公司准备整一个SaaS的东西.有幸参入这一块东西的搭建,借着這个机会也重新好好梳理了一下对SaaS的认识.今天整理一下! 一.云计算与SaaS 说起SaaS,就得先说说云计算了.关于云计算分为三层 ...
- 从WebService到面向服务架构SOA理解【二】
概要: (1)通过上一篇项目的WebService搭建学习,能够有了对WebService一定的认识. (2)接下来记录自己通过对WebService的学习对面向服务架构的理解. WebService ...
- 【我对软件平台架构的理解】第一部分:软件平台架构有什么用
一.先说说软件工程 在提"软件平台架构"这个概念之前,先说一下我对软件工程的理解,借鉴一下网上对软件工程的定义:[注1] (1).将系统化的.规范的.可量化的方法应用于软件的开发. ...
- 对前端架构的理解 - 分层与抽象
大厂技术 高级前端 Node进阶 点击上方 程序员成长指北,关注公众号 回复1,加入高级Node交流群 可能一些同学会认为前端比较简单而不需要架构,或者因为前端交互细节杂而乱难以统一抽象,所以没办 ...
- Android基础-系统架构分析,环境搭建,下载Android Studio,AndroidDevTools,Git使用教程,Github入门,界面设计介绍
系统架构分析 Android体系结构 安卓结构有四大层,五个部分,Android分四层为: 应用层(Applications),应用框架层(Application Framework),系统运行层(L ...
- BlockChain:《Blockchain Gate》听课笔记——区块链的1.0架构 VS 区块链3.0架构+个人理解
BlockChain:<Blockchain Gate>听课笔记--区块链的1.0架构 VS 区块链3.0架构+个人理解 相关文章 BlockChain:<Blockchain G ...
- gitee如何搭建mysql_MySQL高可用架构集群环境搭建手册.md
# MySQL高可用架构集群环境搭建手册 ## 环境准备 ### 机器规划 | 节点 | IP | 配置 | 角色 | | -------- | -------------- | ---- | --- ...
- vue click事件_Vue.js---实现前后端分离架构中前端页面搭建(二)
[Vue.js实现前后端分离架构中前端页面搭建] 九.Vue的事件处理 Vue的事件都是使用 v-on:事件类型 进行绑定.也可以使用@事件类型进行操作.其中事件类型和之前学习jQuery中事件名称是 ...
- 阿里道延:我对技术架构的理解与架构师角色的思考
我叫道延, 2014 年加入阿里,在阿里通信工作了近两年.2016 年年底加入业务平台团队,当时 Leader 找我的第一件事就是要解决大促的问题,第二件事就是解决安全生产的问题. 我带着这个命题进入 ...
最新文章
- Java怎么定义图片公共路径_【Java】springboot配置图片访问路径
- 使用Git分布式版本控制系统
- 基于Google排名因素对Drupal进行SEO优化
- 大数据架构师基础:hadoop家族,Cloudera产品系列等各种技术
- .Net与Oracle地数据库连接池(Connection Pool)
- Composer 本地路径加载 laravel-admin 扩展包
- 优迈系统服务器初始化,优迈系统手机操作器服务器操作使用说明.pptx
- 继 Swin Transformer 之后,MSRA 开源 Video Swin Transformer,在视频数据集上SOTA
- 把本地的jar包打包到maven本地仓库里
- 从零开始学_JavaScript_系列(22)——dojo(9)(表单、JsonRest的post方法,widget的使用思路)...
- 深度学习自学(十五):人脸识别数据预处理方法
- Openlayer:学习笔记之图标与提示信息
- 数据分析之Pandas(一) 学习资料汇总
- Unofficial Windows Binaries for Python Extensi...
- 无盘服务器 免费,免费无广告的网咖专用云无盘安装图文教程
- Linux下MinDoc安装使用
- Java - 两个对象值相同(x.equals(y) == true),但却可以有不同的hash code,这句话对不对?
- (附源码)ssm人力资源管理系统 毕业设计 271621
- 省计算机软件评审活动网站,2012年广东省计算机教育软件评审活动.doc
- 《绝地求生》玩家排名预测(2万5千字~大型综合实战)
热门文章
- OpenCV特征描述Feature Description
- C++line segment intersection线段求交(交点)(附完整源码)
- const关键字的作用?
- C++ deque方法
- QML绘制不同类型的图表
- C++STL常用集合算法
- ecs要按两次才有效_猫咪想要增肥有什么办法?吃是最简单有效的了,但要吃对了才行...
- Nacos 快速开始、版本选择、预备环境准备、下载源码或者安装包、从 Github 上下载源码方式、下载编译后压缩包方式、配置nacos、配置集群、启动服务器、服务注册发现和配置管理、关闭服务器
- listener.ora--sqlnet.ora--tnsnames.ora的关系以及手工配置举例(转载:http://blog.chinaunix.net/uid-83572-id-5510.ht)
- 【C语言】逗号运算符 ,