并发网络爬虫通常将访问的 URL 存储在并发映射中,还是使用同步来避免对同一页面进行两次爬网?



我正在编写一个简单的多线程网络爬虫。我看到很多消息来源都谈论网络爬虫显然是并行的,因为您可以从不同的 URL 开始抓取,但我从未看到他们讨论网络爬虫如何处理他们以前已经看到的 URL。似乎某种全局地图对于避免一遍又一遍地重新抓取相同的页面至关重要,但是关键部分将如何构建?锁的粒度可以有多细才能最大限度地提高性能?我只是想看到一个不太密集也不太简单的好例子。

特定域用例:在内存中使用

如果是特定的域说 abc.com 那么最好在内存中设置vistedURL或并发哈希映射,在内存中检查访问状态会更快,内存消耗会相对较少。数据库将有 IO 开销,并且成本高昂,并且访问状态检查将非常频繁。它会极大地打击你的表现。根据您的使用案例,您可以在内存或数据库中使用。我的用例特定于不再访问访问 URL 的域,因此我使用了并发哈希映射。

如果你坚持只使用 java 并发框架来做到这一点,那么ConcurrentHashMap可能是要走的路。其中有趣的方法是ConcurrentHashMap.putIfAbsent方法,它会给你非常好的效率,如何使用它的想法是:

您将从已爬网页面中获得一些"传入 url 地址的多线程源" - 您可以使用一些并发队列来存储它们,或者只是创建一个带有(无限?(队列的 ExecutorService,您将在其中放置将爬网 url 的 Runnables。

在爬行的 Runnables 中,您应该引用这个已爬网页面的通用 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,那么你就知道这个页面没有被任何人抓取,因为这个方法原子地放置了你可以继续抓取这个页面的值 - 你是幸运的线程,如果它会返回一个非 null 值,那么你知道有人已经关心过这个 URL,所以你的可运行对象应该完成, 线程返回到池以供下一个 Runnable 使用。

您可以使用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>());

对于爬虫,不要使用 ConcurrentHashMap,而是使用 Databse

可见 URL 的数量会增长得非常快,因此将它们存储在内存中并不是一件好事,最好使用 databese,存储 URL 和上次抓取的日期,然后只需检查 URL 是否已存在于数据库中或有资格刷新。例如,我在嵌入式模式下使用 Derby DB,它非常适合我的网络爬虫。我不建议在内存数据库中使用像H2这样的数据库,因为随着爬网页面的数量,你最终会得到OutOfMemoryException。

您很少会遇到在同一时间多次抓取同一页面的情况,因此检查数据库中最近是否已经爬网足以避免浪费大量资源"一遍又一遍地重新爬网相同的页面"。我相信这是"一个不太密集且不太简单的好解决方案">

此外,使用带有 url 的"上次访问日期"的 Databse,您可以在需要时停止并继续工作,使用 ConcurrentHashMap,您将在应用程序退出时丢失所有结果。您可以使用网址的"上次访问日期"来确定它是否需要重新抓取。

最新更新