最近刚刚开始研究爬虫,身为小白的我不知道应该从何处下手,网上查了查,发现主要的开源java爬虫有nutch apache/nutch · GitHub,Heritrix internetarchive/heritrix3 · GitHub和Crawler4j yasserg/crawler4j · GitHub,还有WebCollectorCrawlScript/WebCollector · GitHub和 WebMagic code4craft/webmagic · GitHub
由于刚刚开始接触爬虫,因此决定先接触个小型的项目Crawler4j 。先从git上clone下来,结果发现不会导入eclipse(实在是小白啊,见谅)。一点点了解发现这是个maven项目,直接导入maven项目即可。最后终于运行了给的例子。
在初步了解的过程中,发现了一个robots协议,百度了一下,居然是个爬虫协议,有点吃惊。robots.txt是一个文本文件,放在网站的根目录,因此我就去尝试了读取大众点评的robots.txt ,发现还真有这么个文件。不过这是个道德规范的文件,因为它无法阻止“强盗”进入。具体的文件写法可以百度。
可以看到crawler4j也是支持robots.txt协议的,总共有以下这几个类:
1.RobotstxtConfig
这个类十分简单,里面就3个变量,分别是是否开启robots协议,user-agent 那么以及缓存(这个缓存是指最多能缓存的robots.txt的数量,如果超过这个数量,会将最久不用的一个替换)。
2.HostDirectives
这个类就是存放robots.txt的类,里面主要存放了disallows和allows (这2个是作者写的RuleSet,稍后说),还有个终止期限,超过这个期限要重新获取对应的robots.txt。
3. RuleSet
这个类是存放具体的robots规则的,继承了TreeSet,因为TreeSet是按自然排序(这里字符串比较升序排)的,而又要将前缀路径覆盖所有后续的路径(作者思虑真周密啊),比如a/b覆盖a/b/c。但其实这样的话a/b/c1会覆盖a/b/c12,因此其实也有点小缺陷。附上源码:
public boolean add(String str) { SortedSet<String> sub = headSet(str); if (!sub.isEmpty() && str.startsWith(sub.last())) { // no need to add; prefix is already present return false; } boolean retVal = super.add(str); sub = tailSet(str + "\0"); while (!sub.isEmpty() && sub.first().startsWith(str)) { // remove redundant entries sub.remove(sub.first()); } return retVal; }
4. RobotstxtParser
顾名思义,就是将Robots.txt解析成HostDirectives,这里只有一个静态方法parse。这里对每一行进行解析,首先对于协议指定的user-agent,如果包括我们自己的user-agent,则下面的disallow或者allow才加入规则中。具体是如何解析的有兴趣自己看源码吧。
不过这一块代码不是很清楚。
int commentIndex = line.indexOf('#'); if (commentIndex > -1) { line = line.substring(0, commentIndex); } // remove any html markup line = line.replaceAll("<[^>]+>", "");
希望有小伙伴指教下。
5.RobotstxtServer
这是Robots的主类,有个对外的方法。
public boolean allows(WebURL webURL) { if (config.isEnabled()) { try { URL url = new URL(webURL.getURL()); String host = getHost(url); String path = url.getPath(); HostDirectives directives = host2directivesCache.get(host); if ((directives != null) && directives.needsRefetch()) { synchronized (host2directivesCache) { host2directivesCache.remove(host); //这里用双重锁更合适,不然可能会remove异常 directives = null; } } if (directives == null) { directives = fetchDirectives(url); } return directives.allows(path); } catch (MalformedURLException e) { logger.error("Bad URL in Robots.txt: " + webURL.getURL(), e); } } return true; }
代码很清晰,就是首先获取HostDirectives,(如果必要的话解析robots.txt),然后判断是否允许。
public boolean allows(String path) { timeLastAccessed = System.currentTimeMillis(); return !disallows.containsPrefixOf(path) || allows.containsPrefixOf(path); }
只要allows包含或者disallows不包含即可。
最后这个类有一个map存放各个host的robots.txt解析过来的HostDirectives,由于涉及多线程,因此当把HostDirectives加入这个map的时候需要加锁,不然remove可能会出异常。
synchronized (host2directivesCache) { if (host2directivesCache.size() == config.getCacheSize()) { String minHost = null; long minAccessTime = Long.MAX_VALUE; for (Map.Entry<String, HostDirectives> entry : host2directivesCache.entrySet()) { if (entry.getValue().getLastAccessTime() < minAccessTime) { minAccessTime = entry.getValue().getLastAccessTime(); minHost = entry.getKey(); } } host2directivesCache.remove(minHost); } host2directivesCache.put(host, directives); }