最近想写个爬虫下点视频,于是乎网络上找找开源软件。找来找去都没找到自己满意的轻量级爬虫软件。诸如:Nutch、Heritrix、webmagic、solr都感觉太庞大太复杂。
网络上也有很多人利用httpclient和httpparser写的爬虫实例,但是又感觉太随意。
然后,就发现了vidageek crawer,我如获至宝,这不正是我想要的超级超级轻量级爬虫框架嘛,该框架是利用开源工具httpclient、httpparser、log4j、icu4j等写得一个多线程爬虫框架,代码量不多,如果遇到问题也可以自己调测代码:
官网:http://projetos.vidageek.net/crawler/crawler/
下载地址:https://github.com/vidageek/crawler/downloads
官方还有javadoc可以参考。他们最后发布时间是2011年,虽然其利用的开源工具都有比较大的更新,但是也不影响使用,如果有时间,也可以对其进行修改,将vidageek crawer更新到httpclient、log4j等的同步版本。
于是乎,利用该开源爬虫小试了一把。官方是这么说的:
“The main goal is to abstract that boring and error-prone code from your codebase and let you focus on crawling the site. Its quite easy to use it:
CrawlerConfiguration cfg = new CrawlerConfiguration("http://www.yoursite.com");
PageCrawler crawler = new PageCrawler(cfg);
You don't even need to know that there is code to make parallel requests, handle content encoding, find links on the pages, normalize those links and so on.
You just focus on implementing your net.vidageek.crawler.PageVisitor .”
也的确是这样。我是在windows下利用jdk进行的测试,测试代码目录结构:
test
|--first
| |--Spider.java /* 测试程序 */
|--firstMakefile.bat
|--fistRun.bat
|--lib /* 依赖的jar包 */
| |--crawler-1.0.jar
| |--httpcomponents-client-4.4.1
| | |--commons-codec-1.9.jar
| | |--commons-logging-1.2.jar
| | |--httpclient-4.4.1.jar
| | |--httpcore-4.4.1.jar
| |--icu4j
| | |--icu4j-charsetdetector-4_4_2.jar
| |--log4j
| | |--log4j-1.2-api-2.3.jar
| | |--log4j-api-2.3.jar
| | |--log4j-core-2.3.jar
|--log4j2.xml
2.下载依赖的jar包:crawler、httpclient、icu4j、log4j
3. Spider测试代码:
Spider.class
package first;
import net.vidageek.crawler.config.CrawlerConfiguration;
import net.vidageek.crawler.PageCrawler;
import net.vidageek.crawler.PageVisitor;
import net.vidageek.crawler.Url;
import net.vidageek.crawler.Page;
import net.vidageek.crawler.Status;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Spider
{
public static void main(String[] args)
{
final Logger logger = LogManager.getLogger(Spider.class);
PageVisitor visitor = new PageVisitor() {
public boolean followUrl(Url url) {
return false;
}
public void visit(Page page) {
System.out.println("pageContent: " + page.getContent());
logger.info("pageContent:\n " + page.getContent());
}
public void onError(Url errorUrl, Status statusError) {
System.err.println("onError: " + errorUrl.toString() + "statusError: " + statusError);
}
};
CrawlerConfiguration cfg = new CrawlerConfiguration("http://www.baidu.com/");
PageCrawler crawler = new PageCrawler(cfg);
crawler.crawl(visitor);
}
}
4.cmd下敲命令感觉别扭,没有linux shell那么嗨皮,所以利用bat脚本来编译和运行程序。
firstMakefile.bat
javac -Xlint:depreciation -Djava.ext.dirs=.\lib;.\lib\log4j; .\first\*.java
firstRun.bat
java -Djava.ext.dirs=.\lib;.\lib\log4j;.\lib\httpcomponents-client-4.4.1;.\lib\icu4j first.Spider
5.最好,log4j2.xml主要是为调测代码和配置的日志文件,如果不关注日志,这个文件即使不要也能正常运行。
log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<!--
log4j2的配置文件只能采用.xml, .json或者.jsn。
在默认情况下,系统选择configuration文件的优先级如下(rootdir为项目当前执行目录):
1.rootdir目录下log4j2-test.json或者log4j2-test.jsn文件
2.rootdir目录下log4j2-test.xml
3.rootdir目录下log4j2.json或者log4j2.jsn文件
4.rootdir目录下log4j2.xml
如果配置文件不在rootdir目录下,则需要通过下列两种方式指定配置文件路径,两种方法:
1.System.setProperty("log4j.configurationFile", "配置文件路径")
2.-Dlog4j.configurationFile=配置文件路径
-->
<Configuration status="WARN">
<properties>
<property name="LogHome">./logs</property>
<property name="CrawlerLogName">crawler</property>
<property name="SpiderLogName">spider</property>
</properties>
<Appenders>
<!-- for console -->
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} [%thread] [%file:%line] - %msg%n"/>
</Console>
<!-- for debug -->
<!-- filePattern: 表示当日志到达指定的大小或者时间而要产生新日志时,旧日志的命名规则 -->
<RollingRandomAccessFile name="CrawlerLogFile" fileName="${LogHome}/${CrawlerLogName}.recent.log" filePattern="${LogHome}/${CrawlerLogName}.%d{yyyy-MM-dd-HH}.log">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} [%thread] [%file:%line] - %msg%n"/>
<Policies>
<!--
modulate: 说明是否对封存时间进行调制
若modulate=true,则封存时间将以0点为边界进行偏移计算
比如,modulate=true,interval=4,那么假设上次封存日志的时间为3:00,
则下次封存日志的时间为4:00,之后的封存时间依次为8:00,12:00,16:00...
-->
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
</Policies>
</RollingRandomAccessFile>
<RollingRandomAccessFile name="SpiderLogFile" fileName="${LogHome}/${SpiderLogName}.recent.log" filePattern="${LogHome}/${SpiderLogName}.%d{yyyy-MM-dd-HH}.log">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} [%thread] [%file:%line] - %msg%n"/>
<Policies>
<!--
modulate: 说明是否对封存时间进行调制
若modulate=true,则封存时间将以0点为边界进行偏移计算
比如,modulate=true,interval=4,那么假设上次封存日志的时间为3:00,
则下次封存日志的时间为4:00,之后的封存时间依次为8:00,12:00,16:00...
-->
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
</Policies>
</RollingRandomAccessFile>
<RollingFile name="RollingFile" fileName="${LogHome}/log.log" filePattern="${LogHome}/log.%d{yyyy-MM-dd-HH}.log">
<SizeBasedTriggeringPolicy size="50MB"/>
</RollingFile>
</Appenders>
<Loggers>
<!-- additivity开启的话,若这个logger也满足root的话,将会在两个地方输出。 -->
<logger name="net.vidageek.crawler" level="trace" additivity="false">
<AppenderRef ref="CrawlerLogFile"/>
</logger>
<logger name="first.Spider" level="info" additivity="false">
<AppenderRef ref="SpiderLogFile"/>
</logger>
<Root level="info"> <!-- trace < debug < info < warn < error < fatal -->
<AppenderRef ref="RollingFile"/>
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
当然,这款开源框架也还是有许多不足之处,首先用于实际工程项目是不行的,必须对其进行扩展,但是用于学习和折腾是最好不过了,比如:你就没法配置网络代理,没有post方式,没有所谓的bloom filter等,该框架仅仅是利用了List方式保存待爬取的链接。但是值得利用该爬虫框架进行修修补补,自己可以添加可以修改,想怎么折腾就怎么折腾去吧。
实际上,该框架给用户更大的自由,即在考虑继承PageVistor的时候,通过followUrl方法自行实现对待爬取链接的过滤,此时你可以采取bloom filter对已爬取的链接进行过滤,或者队列、hashmap方式等。框架中DoesNotFollowVisitedUrlVisitor类就是采用ConcurrentHashMap结构保存已爬取过的链接的:
final public class DoesNotFollowVisitedUrlVisitor implements PageVisitor {
private final PageVisitor visitor;
// Using map since jdk 1.5 does not provide a good concurrent set
// implementation
private final Map<Url, String> visitedUrls = new ConcurrentHashMap<Url, String>();
public DoesNotFollowVisitedUrlVisitor(final String beginUrl, final PageVisitor visitor) {
this.visitor = visitor;
visitedUrls.put(new Url(beginUrl, 0), "");
}
public boolean followUrl(final Url url) {
if (visitedUrls.get(url) != null) {
return false;
}
visitedUrls.put(url, "");
return visitor.followUrl(url);
}
public void onError(final Url url, final Status statusError) {
visitor.onError(url, statusError);
}
public void visit(final Page page) {
visitor.visit(page);
}
}