[开源 .NET 跨平台 数据采集 爬虫框架: DotnetSpider] [二] 最基本,最自由的使用方式
上一篇大至 介绍了一下爬虫的框架设计,从这一篇开始着重介绍如何使用这个爬虫。
数据抽取定义
之前也有人反应说用Attribute+模型来定义抽取规则太花哨,实用性不强。实际上可能他没有仔细看到我的设计,我的核心抽取不是Attrbiute+模型,而是采用类似JSON的定义格式,可以实现各种嵌套,各种能想像到的复杂情况。参考最早一版定义(最新版有修改,设计思路没有变化)
"entities": [ { "targeturls": [ { "sourceregion": "", "values": [ "http://shu.taobao.com/top/[\\d]+/search" ] } ], "expression": "//div[contains(@class,'mod')]", "multi": true, "selector": "xpath", "schema": { "databasename": "alibaba", "tablename": "industry_rank" }, "identity": "industry_rank", "fields": [ { "datatype": "string(100)", "expression": "./h3[1]", "name": "category", "selector": "xpath", "multi": false }, { "datatype": { "fields": [ { "datatype": "string(100)", "expression": ".//a[1]", "name": "keyword", "selector": "xpath", "multi": false }, { "datatype": "string(100)", "expression": "./@data-rise", "name": "rise", "selector": "xpath", "multi": false } ] }, "expression": ".//ol/li", "multi": true, "name": "results", "selector": "xpath" } ] } ]
- Entities 是数组,表示一个页面可以抽取出多个数据对象
- 第一个Entity的第一个Field是string(100)的数据类型(常用数据类型)
- 第一个Entity的第二个Field是一个数据对象
因此,爬虫的解析是非常自由化的。而Attrbiute+模型的抽取是先转换成了以上定义再传给解析类的,我设计这个解析类的原因也是考虑到跨语言的可能性的,只要你能传正确的JSON过来,我就能解析成一个正确的爬虫。所以只要有兴趣的人写上他们自己语言的Provider, 其实就是写几个Class序列化成JSON传过来就好了。
是否够灵活?
也有人反应说Attrbiute+模型的抽取不够灵活,不能满足大部分情况。其实最灵活的就是使用核心库即Core这个DLL,在这个项目里,实现了爬虫的基本逻辑,URL调度、去重,Html的Selector,基本的下载器,多线程控制等等。就是说,你要自由、灵活我也给你的呀。
如何使用核心库
我们在上一篇也说过,实现一个完整的业务爬虫需要4大模块:下载器(已有实现),URL调度(已有实现),数据抽取(需要自己实现),数据存储(需要自己实现),因此,你只需要实现2个模块就可以完成一个爬虫了
定义数据对象
public class YoukuVideo { public string Name { get; set; } public string Volume { get; set; } }
数据抽取的实现
只需要实现 IPageProcessor 这个接口就可以了
public class MyPageProcessor : IPageProcessor { public Site Site { get; set; } public void Process(Page page) { var totalVideoElements = page.Selectable.SelectList(Selectors.XPath("//li[@class='yk-col4 mr1']")).Nodes(); List<YoukuVideo> results = new List<YoukuVideo>(); foreach (var videoElement in totalVideoElements) { var video = new YoukuVideo(); video.Name = videoElement.Select(Selectors.XPath(".//li[@class='title']/a[1]")).GetValue(); video.Volume = videoElement.Select(Selectors.XPath(".//ul[@class='info-list']/li[3]")).GetValue(); video.Volume = video.Volume.Replace("\r", ""); results.Add(video); } page.AddResultItem("VideoResult", results); } }
这里就需要注意4点
- public Site Site { get; set; } 是必需的,并且不需要市值,会在Spider类的初始化时设值
- Page 对象传过来的时候,已经是加载好下载的HTML到Selectable属性中了,所以你只需要调用Seletable的接口并传入合适的查询XPATH,CSS, JSONPATH,REGEX就可以查询到你想到值,并且Selectable是可以循环调用
- Selectable的GetValue传入true时会把结果去HTML标签
- 把你在此处组装好的对象,如上面的 YoukuVideo这个List, 存到page的ResultITem中,并指定一个KEY
数据存取
只需要实现 IPipeline,在这里,我们需要用到在 PageProcessor存入数据的KEY,通过这个KEY把数据对象取出来,之后你想把这个数据存文件或是MYSQL,MSSQL,MONGODB就由你自己实现了
public class MyPipeline : IPipeline { private string _path; public MyPipeline(string path) { if (string.IsNullOrEmpty(path)) { throw new Exception("XXXX"); } _path = path; if (!File.Exists(_path)) { File.Create(_path); } } public void Process(ResultItems resultItems, ISpider spider) { foreach (YoukuVideo entry in resultItems.Results["VideoResult"]) { File.AppendAllText(_path, JsonConvert.SerializeObject(entry)); } } public void Dispose() { } }
运行爬虫
写好上面两个必需的模块之后,我们就可以运行这个爬虫了。首先需要下载最新的DotnetSpider的代码并编译,编译成功后会把DLL移动到solution文件夹下的output文件夹。我们建立一个空的Console程序,引用必要的DLL如下图
再把上面3个类添加到Project中
运行代码如下,需要注意必须要添加StartUrl, 这是爬虫的第一个起始URL,如果你可以初始计算出所有的翻页URL,也可以在这里一定完全初始化。
public static void Main() { HttpClientDownloader downloader = new HttpClientDownloader(); var site = new Site() { EncodingName = "UTF-8" }; site.AddStartUrl("http://www.youku.com/v_olist/c_97_g__a__sg__mt__lg__q__s_1_r_0_u_0_pt_0_av_0_ag_0_sg__pr__h__d_1_p_1.html"); Spider spider = Spider.Create(site, new MyPageProcessor(), new QueueDuplicateRemovedScheduler()).AddPipeline(new MyPipeline("test.json")).SetThreadNum(1); spider.Run(); }
F5运行项目,结果如下
如何翻页、抽取目标页
上面的例子只爬取了一个页面,那么如何从页面中抽取翻页的URL或者其它的目标页呢?我们只需要在PageProccessor中解析你的目标页,并加入到Page对象的TargetRequests这个List中即可。我们做如下改动:
public class MyPageProcessor : IPageProcessor { public Site Site { get; set; } public void Process(Page page) { var totalVideoElements = page.Selectable.SelectList(Selectors.XPath("//li[@class='yk-col4 mr1']")).Nodes(); List<YoukuVideo> results = new List<YoukuVideo>(); foreach (var videoElement in totalVideoElements) { var video = new YoukuVideo(); video.Name = videoElement.Select(Selectors.XPath(".//li[@class='title']/a[1]")).GetValue(); video.Volume = videoElement.Select(Selectors.XPath(".//ul[@class='info-list']/li[3]")).GetValue(); video.Volume = video.Volume.Replace("\r", ""); results.Add(video); } page.AddResultItem("VideoResult", results); foreach (var url in page.Selectable.SelectList(Selectors.XPath("//ul[@class='yk-pages']")).Links().Nodes()) { page.AddTargetRequest(new Request(url.GetValue(), 0, null)); } } }
重新运行程序,我们可以看到,爬虫不停的开始翻页往下爬取了
总结
到这里最基本,最灵活的使用方法就结束了。是不是很简单?至于有朋友提到的要用到大量的if else, replace什么的你都可以在PageProcessor里做这个组装,抽取的工作。然后我个人感觉世上没有十全十美的东西 ,灵活就可能要更多的代码,而Attrbibute+模型的死板也不是一无是处,至少我用下来70%-80%都可以应付,更何况在Attribute上还可以配置各种各样的Formatter, 当然跟我的抓取对象大多结构化比较规范有关吧。大至缕一下后面要写的章节吧
- HTTP Header、Cookie的设置,POST的使用
- JSON 数据的解析
- 基于配置的使用(Extension项目)
- WebDriverDownloader的使用,包含基本登录,手动登录
- 分布式的使用