在之前的博客中也分析了WebMagic的基本所有的主要代码,在我们的项目中也遇到了很多由于WebMagic的问题而导致正个服务器性能大范围的下降,那么今天说说这些bug
我们的应用需要在每天额固定时间启动爬虫,然后去爬,很明显我们需要从昨天的爬取结果中增量爬虫,这个增量是相对于昨天,而不是一次爬虫中的过程中那么怎么存储昨天爬取的url呢。
很明显我们能够在pipeline中存储url,然后通过Mysql的unique来解决,那么我们直接insert就好了。那我们看看数据库中的url都长什么样子
http://club.xdnice.com/forum.php?mod=viewthread&tid=1410423&extra=page%3D1%26filter%3Dsortid%26sortid%3D252
http://club.xdnice.com/forum.php?mod=viewthread&tid=1410561&extra=page%3D1%26filter%3Dsortid%26sortid%3D252
http://club.xdnice.com/forum.php?mod=viewthread&tid=1410558&extra=page%3D1%26filter%3Dsortid%26sortid%3D252
http://club.xdnice.com/forum.php?mod=viewthread&tid=1410496&extra=page%3D1%26filter%3Dsortid%26sortid%3D252
http://club.xdnice.com/forum.php?mod=viewthread&tid=1410287&extra=page%3D1%26filter%3Dsortid%26sortid%3D252
http://club.xdnice.com/forum.php?mod=viewthread&tid=1410546&extra=page%3D1%26filter%3Dsortid%26sortid%3D252
http://club.xdnice.com/forum.php?mod=viewthread&tid=1410430&extra=page%3D1%26filter%3Dsortid%26sortid%3D252
http://club.xdnice.com/forum.php?mod=viewthread&tid=1410535&extra=page%3D1%26filter%3Dsortid%26sortid%3D252
http://club.xdnice.com/forum.php?mod=viewthread&tid=1410488&extra=page%3D1%26filter%3Dsortid%26sortid%3D252
http://club.xdnice.com/forum.php?mod=viewthread&tid=1410542&extra=page%3D1%26filter%3Dsortid%26sortid%3D252
http://club.xdnice.com/forum.php?mod=viewthread&tid=1410005&extra=page%3D1%26filter%3Dsortid%26sortid%3D252
http://club.xdnice.com/forum.php?mod=viewthread&tid=1410541&extra=page%3D1%26filter%3Dsortid%26sortid%3D252
http://club.xdnice.com/forum.php?mod=viewthread&tid=1410394&extra=page%3D1%26filter%3Dsortid%26sortid%3D252
http://club.xdnice.com/forum.php?mod=viewthread&tid=1405225&extra=page%3D1%26filter%3Dsortid%26sortid%3D252
http://club.xdnice.com/forum.php?mod=viewthread&tid=1409449&extra=page%3D1%26filter%3Dsortid%26sortid%3D252
http://club.xdnice.com/forum.php?mod=viewthread&tid=1409833&extra=page%3D1%26filter%3Dsortid%26sortid%3D252
http://club.xdnice.com/forum.php?mod=viewthread&tid=1409545&extra=page%3D1%26filter%3Dsortid%26sortid%3D252
http://club.xdnice.com/forum.php?mod=viewthread&tid=1410528&extra=page%3D1%26filter%3Dsortid%26sortid%3D252
http://club.xdnice.com/forum.php?mod=viewthread&tid=1410492&extra=page%3D1%26filter%3Dsortid%26sortid%3D252
http://club.xdnice.com/forum.php?mod=viewthread&tid=1410503&extra=page%3D1%26filter%3Dsortid%26sortid%3D252
http://club.xdnice.com/forum.php?mod=viewthread&tid=1410524&extra=page%3D1%26filter%3Dsortid%26sortid%3D252
http://club.xdnice.com/forum.php?mod=viewthread&tid=1409577&extra=page%3D1%26filter%3Dsortid%26sortid%3D252
http://club.xdnice.com/forum-113-1.html
http://club.xdnice.com/forum-113-2.html
http://club.xdnice.com/forum-113-3.html
http://club.xdnice.com/forum-113-4.html
http://club.xdnice.com/forum-113-5.html
http://club.xdnice.com/forum-113-6.html
http://club.xdnice.com/forum-113-7.html
http://club.xdnice.com/forum-113-8.html
http://club.xdnice.com/forum-113-9.html
http://club.xdnice.com/forum-113-10.html
http://club.xdnice.com/forum-113-261.html
http://club.xdnice.com/thread-1391424-1-1.html
http://club.xdnice.com/thread-1384098-1-1.html
http://club.xdnice.com/thread-1410600-1-1.html
http://club.xdnice.com/thread-1410577-1-1.html
http://club.xdnice.com/thread-1410562-1-1.html
http://club.xdnice.com/thread-1410485-1-1.html
http://club.xdnice.com/thread-1410402-1-1.html
看一下基本就这么3-4种网站,然后每一条url真正有区别的是最后的一点点。这样的索引需要很长,虽然满足索引的使用场景(字段中的数据尽可能的不一致),但是这里面有太多的数据是冗余的,也就是每条url的公共部分。那么有没有可能将一条url分为base+special
,例如http://club.xdnice.com/forum-113-261.html
就是由 http://club.xdnice.com
+ forum-113-261.html
组成。如果我们分为两个字段进行存储,那么如何查询呢?因为base
不适合建索引(区分度太小),所以基本就是全表扫,及时special
存在索引,sql的速度也不会快。那么在思考,其实我们可以做一个调查,其实special
的唯一性已经不需要再加base
来确定了,这时我们只按照special
来进行查询就ok!当然,这可能会有问题!但是首先可能性很小,然后也是能够接受的。
我们之前的博客介绍了Scheduler
是用来对url进行去重工作的,上一个解决方案其实接住了Pipeline
来解决,这样很不WebMagic!
能否使用Scheduler
来解决呢,我们看到之前存在一个FileCacheQueueScheduler
,我们也分析了其代码,我们只需要做小小改动就行了
@Override
protected void pushWhenNoDuplicate(Request request, Task task) {
if (!inited.get()) {
init(task);
}
//就增加这一行就够了!
if (urls.contains(request.getUrl()))
return;
queue.add(request);
fileUrlWriter.println(request.getUrl());
}
为什么呢?因为urls
是一个LinkedHashSet
,并且在初始化的时候,会把url.txt全部读入进去。
private void readUrlFile() throws IOException {
String line;
BufferedReader fileUrlReader = null;
try {
fileUrlReader = new BufferedReader(new FileReader(getFileName(fileUrlAllName)));
int lineReaded = 0;
while ((line = fileUrlReader.readLine()) != null) {
urls.add(line.trim());
lineReaded++;
if (lineReaded > cursor.get()) {
queue.add(new Request(line));
}
}
} finally {
if (fileUrlReader != null) {
IOUtils.closeQuietly(fileUrlReader);
}
}
}
当然这样也就违反了这个原有的设计思想,不要弄混哈。原有的是为了能够在下一次启动的时候仍然能够取上次没有下载完的,然后继续下载~
我们看到了爬虫是使用文件进行存储的,对于文件的操作需要注意线程之间的同步,所以我们看到了这样的代码,注意synchronized
@Override
public synchronized Request poll(Task task) {
if (!inited.get()) {
init(task);
}
fileCursorWriter.println(cursor.incrementAndGet());
return queue.poll();
}
但是!这样只能保证一个Spider(OOSpider)是能够线程安全的,千万不要同时启动两个Spider,还爬取同一个域名下的不同子类别,例如:
http://blog.csdn.net/a
http://blog.csdn.net/b
因为这样是会使用blog.csdn.net.urls.txt和blog.csdn.net.cursor.txt这个文件,这样会在进程级别造成不同步
什么意思呢,就是我们希望爬虫在爬了一定内容之后或者在没有新的内容(基于增量)之后就停止爬虫。否则我们的爬虫的线程就永远的卡在Condition.await
Request request = scheduler.poll(this);
if (request == null) {
if (threadPool.getThreadAlive() == 0 && exitWhenComplete) {
break;
}
// wait until new url added
waitNewUrl();
}
导致的结果是什么呢,就是这个爬虫已经没有内容可爬,但是有卡在这里使得资源没有释放。一个线程可能也就10M左右的资源,但是如果放在服务器中定时爬虫,那么每天都会浪费很多的资源,过了一段时间看一下服务器内存已经被占满,jstack中全部是“死循环”的线程。
但是WebMagic却没有给我们提供设置爬虫停止的策略,只提供了stop方法。我们就需要自己设置如何停止了,我们仍然在Scheduler做文章
@Override
public synchronized Request poll(Task task) {
if (!inited.get()) {
init(task);
}
Request result = queue.poll();
if(result == null ){
if (waitTimes > 0){
waitTimes--;
} else {
spider.stop();
}
} else {
fileCursorWriter.println(cursor.incrementAndGet());
}
return result;
}
我们在Scheduler的poll方法中进行判断,如果超过10次取出的都是null,就认为已经没有新的内容可以爬了,那么爬虫就stop。
同时提一个FileCacheQueueScheduler
的poll方法bug
@Override
public synchronized Request poll(Task task) {
if (!inited.get()) {
init(task);
}
fileCursorWriter.println(cursor.incrementAndGet());
return queue.poll();
}
在poll时无论是否为null,都会添加cursor,而这个cursor会作为读取url文件的指针,所以在线上的机器中经常看到cursor比url文件中总数多很多,就是因为这里无论是否为空都增加cursor。
其实在Scheduler中不应该参与爬虫是否停止的判断,但是由于WebMagic目前没有很好地机制停止,所以最近也会想办法跟作者沟通一下
使用过爬虫都应该知道反爬是什么意思,但是我们之前遇到的情况是,服务器执行lsof -i:80
发现大量的cloase_wait
状态,这种状态在TCP中是因为对方网站已经发送fin
表示不能再从对方网站爬任何东西,但是我们的爬虫有没有发送fin
所以连接处于一种单工状态,并无卵用,但是耗费资源!
看过线上的log日志,这种Exception只会被Jetty服务器拦截,不会透传至应用,所以应用根本不知道发生了什么,还在傻等。。。
出现这种问题我们项目的解决方法是不要爬太多的内容,使用了上面的Scheduler就会每天增量的爬取,数量就不会很大,这样就不会出现这种问题。同时也可以修改线上机器的配置避免浪费过多资源:
echo 1200 > /proc/sys/net/ipv4/tcp_keepalive_time
echo 20 > /proc/sys/net/ipv4/tcp_keepalive_intvl
echo 3 > /proc/sys/net/ipv4/tcp_keepalive_probes
第一项修改tcp保持时间为20分钟,第二项为探测时间为20s,探测次数为3
参考文献:http://blog.csdn.net/unix21/article/details/8743537