我在玩写一个简单的多线程网络爬虫。我看到很多消息来源都说网络爬虫很明显是并行的,因为你可以从不同的URL开始爬行,但我从未见过他们讨论web爬虫是如何处理他们之前见过的URL的。似乎某种类型的全球地图对于避免一遍又一遍地重新爬行同样的页面是必不可少的,但是关键部分将如何构建呢?锁能有多细粒度才能最大限度地提高性能?我只想看到一个很好的例子,它既不太密集,也不太简单化。
发布于 2017-07-31 05:31:04
如果您坚持只使用java并发框架来完成它,那么ConcurrentHashMap可能是最好的选择。其中有趣的方法是ConcurrentHashMap.putIfAbsent方法,它将给您很好的效率,如何使用它的想法是:
您将从爬行页面中获得一些“进入url地址的多线程源”--您可以使用一些并发队列来存储它们,或者只创建一个ExecutorService (无界?)队列中,您将放置将爬行urls的Runnable。
在爬行Runnable中,您应该引用这个已经爬行页面的普通ConcurrentHashMap,并且在run方法的开始时,请执行以下操作:
private final ConcurrentHashMap<String, Long> crawledPages = new ConcurrentHashMap<String, Long>();
...
private class Crawler implements Runnable {
private String urlToBeCrawled;
public void Crawler(String urlToBeCrawled) {
this.urlToBeCrawled = urlToBeCrawled;
}
public void run() {
if (crawledPages.putIfAbsent(urlToBeCrawled, System.currentTimeMillis())==null) {
doCrawlPage(urlToBeCrawled);
}
}
}如果crawledPages.putIfAbsent(urlToBeCrawled)将向您返回null,那么您就知道这个页面不是由任何人爬行的,因为这个方法原子地将您可以在这个页面上爬行的值-您是幸运的线程,如果它将返回一个非空值,那么您知道有人已经处理了这个url,所以您的可运行程序应该完成,线程返回池,以便在下一个运行时使用。
发布于 2017-07-31 07:29:27
特定域用例:在内存中使用
如果是特定域(如abc.com ),则最好在内存中设置vistedURL集或并发散列映射,在内存中检查访问状态将更快,内存消耗将相对较少。DB将有IO开销,而且代价很高,访问状态检查将非常频繁。它会严重影响你的表演。根据您的用例,可以在内存或DB中使用。我的用例是特定于域的访问URL将不会再次被访问,所以我使用并发散列映射。
发布于 2017-07-31 05:25:39
您可以使用ConcurrentHashMap存储以查找重复的url。ConcurrentHashMap也使用拆分锁机制,而不是使用全局锁。
也可以使用自己的实现,在其中可以将所有数据拆分到不同的键中。
一个关于番石榴API的例子
Striped<ReadWriteLock> rwLockStripes = Striped.readWriteLock(10);
String key = "taskA";
ReadWriteLock rwLock = rwLockStripes.get(key);
try{
rwLock.lock();
.....
}finally{
rwLock.unLock();
}ConcurrentHashMap实例
private Set<String> urls = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());https://stackoverflow.com/questions/45405321
复制相似问题