本期推荐的 Gecco 是一款用Java语言开发的轻量化的易用的网络爬虫,整合了jsoup、httpclient、fastjson、spring、htmlunit、redission等优秀框架。
Gecco 介绍
Gecco 是一款用 java 语言开发的轻量化的易用的网络爬虫。Gecco 整合了 jsoup、httpclient、fastjson、spring、htmlunit、redission 等优秀框架,让您只需要配置一些 jquery 风格的选择器就能很快地写出一个爬虫。Gecco 框架有优秀的可扩展性,框架基于开闭原则进行设计,对修改关闭、对扩展开放。
主要特征
- 简单易用,使用 jquery 风格的选择器抽取元素
- 支持爬取规则的动态配置和加载
- 支持页面中的异步 ajax 请求
- 支持页面中的 javascript 变量抽取
- 利用 Redis 实现分布式抓取,参考gecco-redis
- 支持结合 Spring 开发业务逻辑,参考gecco-spring
- 支持 htmlunit 扩展,参考gecco-htmlunit
- 支持插件扩展机制
- 支持下载时 UserAgent 随机选取
- 支持下载代理服务器随机选取
框架概述
GeccoEngine
GeccoEngine 是爬虫引擎,每个爬虫引擎最好是一个独立进程,在分布式爬虫场景下,建议每台爬虫服务器(物理机或者虚机)运行一个 GeccoEngine。爬虫引擎包括 Scheduler、Downloader、Spider、SpiderBeanFactory、PipelineFactory5 个主要模块。
Downloader
Downloader 负责从 Scheduler 中获取需要下载的请求,gecco 默认采用 httpclient4.x 作为下载引擎。通过实现 Downloader 接口可以自定义自己的下载引擎。你也可以对每个请求定义 BeforeDownload 和 AfterDownload,实现不同的请求下载的个性需求。
SpiderBeanFactory
Gecco 将下载下来的内容渲染为 SpiderBean,所有爬虫渲染的 JavaBean 都统一继承 SpiderBean,SpiderBean 又分为 HtmlBean 和 JsonBean 分别对应 html 页面的渲染和 json 数据的渲染。SpiderBeanFactroy 会根据请求的 url 地址,匹配相应的 SpiderBean,同时生成该 SpiderBean 的上下文 SpiderBeanContext。上下文 SpiderBeanContext 会告知这个 SpiderBean 采用什么渲染器,采用哪个下载器,渲染完成后采用哪些 pipeline 处理等相关上下文信息。
Spider
Gecco 框架最核心的类应该是 Spider 线程,一个爬虫引擎可以同时运行多个 Spider 线程。Spider 描绘了这个框架运行的基本骨架,先从 Scheduler 获取请求,再通过 SpiderBeanFactory 匹配 SpiderBeanClass,再通过 SpiderBeanClass 找到 SpiderBean 的上下文,下载网页并对比 SpiderBean 渲染,将渲染后的 SpiderBean 交个 pipeline 处理。
使用
Maven
<dependency>
<groupId>com.geccocrawler</groupId>
<artifactId>gecco</artifactId>
<version>x.x.x</version>
</dependency>
快速开始
@Gecco(matchUrl="https://github.com/{user}/{project}", pipelines="consolePipeline")
public class MyGithub implements HtmlBean {
private static final long serialVersionUID = -7127412585200687225L;
@RequestParameter("user")
private String user;//url中的{user}值
@RequestParameter("project")
private String project;//url中的{project}值
@Text
@HtmlField(cssPath=".pagehead-actions li:nth-child(2) .social-count")
private String star;//抽取页面中的star
@Text
@HtmlField(cssPath=".pagehead-actions li:nth-child(3) .social-count")
private String fork;//抽取页面中的fork
@Html
@HtmlField(cssPath=".entry-content")
private String readme;//抽取页面中的readme
public String getReadme() {
return readme;
}
public void setReadme(String readme) {
this.readme = readme;
}
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public String getProject() {
return project;
}
public void setProject(String project) {
this.project = project;
}
public String getStar() {
return star;
}
public void setStar(String star) {
this.star = star;
}
public String getFork() {
return fork;
}
public void setFork(String fork) {
this.fork = fork;
}
public static void main(String[] args) {
GeccoEngine.create()
//工程的包路径
.classpath("com.geccocrawler.gecco.demo")
//开始抓取的页面地址
.start("https://github.com/xtuhcy/gecco")
//开启几个爬虫线程
.thread(1)
//单个爬虫每次抓取完一个请求后的间隔时间
.interval(2000)
//循环抓取
.loop(true)
//使用pc端userAgent
.mobile(false)
//非阻塞方式运行
.start();
}
}
示例-使用java爬虫gecco抓取JD全部商品信息
@Gecco(matchUrl="http://www.jd.com/allSort.aspx", pipelines={"consolePipeline", "allSortPipeline"})
public class AllSort implements HtmlBean {
private static final long serialVersionUID = 665662335318691818L;
@Request
private HttpRequest request;
//手机
@HtmlField(cssPath=".category-items > div:nth-child(1) > div:nth-child(2) > div.mc > div.items > dl")
private List<Category> mobile;
//家用电器
@HtmlField(cssPath=".category-items > div:nth-child(1) > div:nth-child(3) > div.mc > div.items > dl")
private List<Category> domestic;
public List<Category> getMobile() {
return mobile;
}
public void setMobile(List<Category> mobile) {
this.mobile = mobile;
}
public List<Category> getDomestic() {
return domestic;
}
public void setDomestic(List<Category> domestic) {
this.domestic = domestic;
}
public HttpRequest getRequest() {
return request;
}
public void setRequest(HttpRequest request) {
this.request = request;
}
}
可以看到,这里以抓取手机和家用电器两个大类的商品信息为例,可以看到每个大类都包含若干个子分类,用List<Category>表示。gecco支持Bean的嵌套,可以很好地表达html页面结构。Category表示子分类信息内容,HrefBean是共用的链接Bean。
public class Category implements HtmlBean {
private static final long serialVersionUID = 3018760488621382659L;
@Text
@HtmlField(cssPath="dt a")
private String parentName;
@HtmlField(cssPath="dd a")
private List<HrefBean> categorys;
public String getParentName() {
return parentName;
}
public void setParentName(String parentName) {
this.parentName = parentName;
}
public List<HrefBean> getCategorys() {
return categorys;
}
public void setCategorys(List<HrefBean> categorys) {
this.categorys = categorys;
}
}
##获取页面元素cssPath的小技巧 上面两个类难点就在cssPath的获取上,这里介绍一些cssPath获取的小技巧。用Chrome浏览器打开需要抓取的网页,按F12进入发者模式。选择你要获取的元素,如图:
元素,鼠标右键选择Copy–Copy selector,即可获得该元素的cssPath
body > div:nth-child(5) > div.main-classify > div.list > div.category-items.clearfix > div:nth-child(1) > div:nth-child(2) > div.mc > div.items
如果你对jquery的selector有了解,另外我们只希望获得dl元素,因此即可简化为:
.category-items > div:nth-child(1) > div:nth-child(2) > div.mc > div.items > dl
编写AllSort的业务处理类 完成对AllSort的注入后,我们需要对AllSort进行业务处理,这里我们不做分类信息持久化等处理,只对分类链接进行提取,进一步抓取商品列表信息。看代码:
@PipelineName("allSortPipeline")
public class AllSortPipeline implements Pipeline<AllSort> {
@Override
public void process(AllSort allSort) {
List<Category> categorys = allSort.getMobile();
for(Category category : categorys) {
List<HrefBean> hrefs = category.getCategorys();
for(HrefBean href : hrefs) {
String url = href.getUrl()+"&delivery=1&page=1&JL=4_10_0&go=0";
HttpRequest currRequest = allSort.getRequest();
SchedulerContext.into(currRequest.subRequest(url));
}
}
}
}
@PipelinName定义该pipeline的名称,在AllSort的@Gecco注解里进行关联,这样,gecco在抓取完并注入Bean后就会逐个调用@Gecco定义的pipeline了。为每个子链接增加”&delivery=1&page=1&JL=4_10_0&go=0″的目的是只抓取京东自营并且有货的商品。SchedulerContext.into()方法是将待抓取的链接放入队列中等待进一步抓取。