爬虫,Crawler,最早被用于搜索引擎收录页面,例如百度蜘蛛等等。说简单点,原理就是根据一些规则,获取url和页面,再从获取到的页面中继续提取url,一直进行下去。
现在爬虫不仅仅用于搜索引擎抓取页面,也大量用于数据分析、数据挖掘等方面,在大数据的今天,爬虫的作用越来越重要。WEB爬虫的具体作用可以参考以下知乎上的一篇文章:
有哪些网站用爬虫爬取能得到很有价值的数据?
当然只是获取到数据往往是不够的,还要对数据进行分析,提取出有用的、有价值的信息,这才是爬虫的正真目的。
爬虫工具有很多,比如nutch等等,就不再这里列举了,可以参考以下一篇文章:
GitHub 上有哪些优秀的 Java 爬虫项目?
Crawler4j是一个Java版的多线程爬虫工具,简单易用。以下是Crawler4j的github:
yasserg/crawler4j · GitHub
通过官方的示例就能很快写出一个简单的爬虫。而且它的配置也很简单和实用。
现在我们来用Crawler4j实现一个简单的爬虫(以下代码摘自官方github,并做了部分解释)。
首先要引入依赖,我比较喜欢用maven,所以依赖如下:
edu.uci.ics
crawler4j
4.4.0
然后定义一个我们自己的爬虫,只需要继承WebCrawler即可:
public class MyCrawler extends WebCrawler {
//定义抓取规则,这里过滤了css、js等等非html的后缀
private final static Pattern FILTERS = Pattern.compile(".*(\\.(css|js|gif|jpg"
+ "|png|mp3|mp4|zip|gz))$");
//shouldVisit,应当被获取的url
@Override
public boolean shouldVisit(Page referringPage, WebURL url) {
String href = url.getURL().toLowerCase();
return !FILTERS.matcher(href).matches()
&& href.startsWith("http://www.ics.uci.edu/");
}
//当获取到匹配的URL时,进行处理,我们可以在这里写我们的处理逻辑
@Override
public void visit(Page page) {
String url = page.getWebURL().getURL();
System.out.println("URL: " + url);
if (page.getParseData() instanceof HtmlParseData) {
HtmlParseData htmlParseData = (HtmlParseData) page.getParseData();
String text = htmlParseData.getText();
String html = htmlParseData.getHtml();
Set links = htmlParseData.getOutgoingUrls();
System.out.println("Text length: " + text.length());
System.out.println("Html length: " + html.length());
System.out.println("Number of outgoing links: " + links.size());
}
}
}
这样我们就建立好了一个可以只获取html链接的爬虫,但是它还是死的,需要一个类来启动和配置其他的抓取规则。
public class Controller {
public static void main(String[] args) throws Exception {
//爬虫状态存储文件夹,可以从这里边读取数据,以边恢复之前的爬取状态
String crawlStorageFolder = "/data/crawl/root";
//爬虫数量,也就是线程数,一般不超过CPU线程数
int numberOfCrawlers = 7;
//爬虫配置
CrawlConfig config = new CrawlConfig();
config.setCrawlStorageFolder(crawlStorageFolder);
/*
* Instantiate the controller for this crawl.
*/
PageFetcher pageFetcher = new PageFetcher(config);
RobotstxtConfig robotstxtConfig = new RobotstxtConfig();
RobotstxtServer robotstxtServer = new RobotstxtServer(robotstxtConfig, pageFetcher);
CrawlController controller = new CrawlController(config, pageFetcher, robotstxtServer);
//要爬取的起始地址
controller.addSeed("http://www.ics.uci.edu/~lopes/");
controller.addSeed("http://www.ics.uci.edu/~welling/");
controller.addSeed("http://www.ics.uci.edu/");
//启动
controller.start(MyCrawler.class, numberOfCrawlers);
}
}
Crawler4J是多线程的,因此就设计到多线程下的如何收集数据的问题。
好在Crawler4j为我们提供了一个方法,可以返回一个线程结束的时候收集到的数据:
/**
* The CrawlController instance that has created this crawler instance will
* call this function just before terminating this crawler thread. Classes
* that extend WebCrawler can override this function to pass their local
* data to their controller. The controller then puts these local data in a
* List that can then be used for processing the local data of crawlers (if needed).
*
* @return currently NULL
*/
public Object getMyLocalData() {
return null;
}
大意如下:
创建此爬行器实例的爬行控制器实例将在终止此爬行器线程之前调用此函数。扩展WebRe爬行器的类可以重写该函数,以将它们的本地数据传递给它们的控制器。然后控制器将这些本地数据放在一个列表中,然后该列表可以用来处理爬虫的本地数据(如果需要的话)。
因此我们可以传入一个list或者map来存放这些数据:
public class HtmlCrawler extends WebCrawler {
private Map map;
public HtmlCrawler() {
this.map = new HashMap<>();
}
@Override
public void visit(Page page) {
if(page.getParseData() instanceof HtmlParseData) {
String url = page.getWebURL().getURL();
log.debug("URL: {}", url);
page.setContentData(null);
this.map.put(url, page);
}
}
@Override
public Object getMyLocalData() {
return map;
}
}
我们以当前url为key,将获取的到的数据page放入map,然后我们就可以在爬取结束的时候调用CarwlerController的getLocalData()来获取这些数据。
基于Crawler4j,我设计了一个简单的带界面的爬虫系统,整合到我的个人博客中,地址如下:
老吴 - 爬虫
其中UserKey暂时为随机生成的字符串,也可以用唯一的固定值作为UserKey,这样即使在关闭浏览器的情况下,也会在后台自动抓取,并且当你在此输入相同的UserKey时,将获取到上一次的抓取结果。
因为服务器资源有限,所以限制抓取页面数量,并且会每半小时自动清理已经抓取完成但是用户并没有请求获取的数据。
另外,当你选中生成报表时,抓取的数据将会生成一个excel表格,可以下载到本地查看。