Java 网络爬虫的多线程 DFS
Multithreaded DFS for web crawler in Java
我正在 Java 使用 Jsoup 编写网络爬虫。
目前我有一个使用深度优先搜索的单线程实现(它只需要抓取一个域,所以我可以选择 DFS 或 BFS,并选择 DFS,因为这意味着我可以使用队列代替的堆栈,因此在我做多线程版本时使用 LinkedBlockingQueue
)
我有一个 Queue
的 link 要访问和一个 HashSet
的已经访问过的 link,我的主循环弹出一个 link,访问页面,并将页面中任何未访问的 link 添加到队列中。
这是我的 class 实现我的单线程实现的内容(如果任何 throws
声明是虚假的,请告诉我原因,因为我需要掌握它)
private static LinkedBlockingQueue<String> URLSToCrawl = new LinkedBlockingQueue<String>();
private static String baseURL;
private static String HTTPSBaseURL;
private static HashSet<String> alreadyCrawledSet = new HashSet<String>();
private static List<String> deadLinks = new LinkedList<String>();
public static void main(String[] args) throws IOException, InterruptedException {
// should output a site map, showing the static assets for each page.
Validate.isTrue(args.length == 1, "usage: supply url to fetch");
baseURL = args[0];
HTTPSBaseURL = baseURL.replace("http://", "https://");
alreadyCrawledSet.add(baseURL);
URLSToCrawl.add(baseURL);
while (!URLSToCrawl.isEmpty() ) {
String url = URLSToCrawl.take();
crawlURL(url);
}
}
private static void crawlURL(String url) throws IOException, InterruptedException {
print("%s", url);
try {
Document doc = Jsoup.connect(url).get();
Elements links = doc.select("a[href]");
for (Element link : links) {
String linkURL = link.attr("abs:href");
if (sameDomain(linkURL) && !alreadyCrawled(linkURL)) {
alreadyCrawledSet.add(linkURL);
URLSToCrawl.put(linkURL);
}
}
} catch (HttpStatusException e) {
deadLinks.add(url);
}
}
private static boolean alreadyCrawled(String url) {
if (alreadyCrawledSet.contains(url)) {
return true;
} else {
return false;
}
}
我想让这个多线程,以利用单线程实现必须在 Jsoup.connect(url).get()
调用 return 之前等待 HTTP 请求,然后再继续处理这一事实.我希望通过允许多个线程同时执行,一些工作将在此 I/O-bound 延迟期间完成,从而加快程序速度。
我对并发不是很有经验 - 我的第一个想法是简单地创建一个 Executor
并提交对 crawlURL
的每个调用。但我很困惑——我不知道如何确保以线程安全的方式访问我的 HashSet
和 Queue
,尤其是考虑到每个线程不仅 消耗 来自 Queue
的 URL,但也会将新 URL 推送到 Queue
.
我了解原子性概念的基础知识,以及线程可以 "lock" 共享资源的想法,但我不知道如何在这种情况下将它们付诸实践。
有人对制作这个多线程有什么建议吗?
我的解决方案是一次处理一层图。因此,对于每个级别,将每个 link 提交给 ExecutorService
以进行爬网(多线程),然后等待该级别完成(使用 CountDownLatch
),然后再进入下一个级别。
我使用 FixedThreadPool
作为速率限制的一种形式。
(最初我尝试只异步调度每个 url,这肯定更有效,但我不知道如何终止整个事情。)
我正在 Java 使用 Jsoup 编写网络爬虫。
目前我有一个使用深度优先搜索的单线程实现(它只需要抓取一个域,所以我可以选择 DFS 或 BFS,并选择 DFS,因为这意味着我可以使用队列代替的堆栈,因此在我做多线程版本时使用 LinkedBlockingQueue
)
我有一个 Queue
的 link 要访问和一个 HashSet
的已经访问过的 link,我的主循环弹出一个 link,访问页面,并将页面中任何未访问的 link 添加到队列中。
这是我的 class 实现我的单线程实现的内容(如果任何 throws
声明是虚假的,请告诉我原因,因为我需要掌握它)
private static LinkedBlockingQueue<String> URLSToCrawl = new LinkedBlockingQueue<String>();
private static String baseURL;
private static String HTTPSBaseURL;
private static HashSet<String> alreadyCrawledSet = new HashSet<String>();
private static List<String> deadLinks = new LinkedList<String>();
public static void main(String[] args) throws IOException, InterruptedException {
// should output a site map, showing the static assets for each page.
Validate.isTrue(args.length == 1, "usage: supply url to fetch");
baseURL = args[0];
HTTPSBaseURL = baseURL.replace("http://", "https://");
alreadyCrawledSet.add(baseURL);
URLSToCrawl.add(baseURL);
while (!URLSToCrawl.isEmpty() ) {
String url = URLSToCrawl.take();
crawlURL(url);
}
}
private static void crawlURL(String url) throws IOException, InterruptedException {
print("%s", url);
try {
Document doc = Jsoup.connect(url).get();
Elements links = doc.select("a[href]");
for (Element link : links) {
String linkURL = link.attr("abs:href");
if (sameDomain(linkURL) && !alreadyCrawled(linkURL)) {
alreadyCrawledSet.add(linkURL);
URLSToCrawl.put(linkURL);
}
}
} catch (HttpStatusException e) {
deadLinks.add(url);
}
}
private static boolean alreadyCrawled(String url) {
if (alreadyCrawledSet.contains(url)) {
return true;
} else {
return false;
}
}
我想让这个多线程,以利用单线程实现必须在 Jsoup.connect(url).get()
调用 return 之前等待 HTTP 请求,然后再继续处理这一事实.我希望通过允许多个线程同时执行,一些工作将在此 I/O-bound 延迟期间完成,从而加快程序速度。
我对并发不是很有经验 - 我的第一个想法是简单地创建一个 Executor
并提交对 crawlURL
的每个调用。但我很困惑——我不知道如何确保以线程安全的方式访问我的 HashSet
和 Queue
,尤其是考虑到每个线程不仅 消耗 来自 Queue
的 URL,但也会将新 URL 推送到 Queue
.
我了解原子性概念的基础知识,以及线程可以 "lock" 共享资源的想法,但我不知道如何在这种情况下将它们付诸实践。
有人对制作这个多线程有什么建议吗?
我的解决方案是一次处理一层图。因此,对于每个级别,将每个 link 提交给 ExecutorService
以进行爬网(多线程),然后等待该级别完成(使用 CountDownLatch
),然后再进入下一个级别。
我使用 FixedThreadPool
作为速率限制的一种形式。
(最初我尝试只异步调度每个 url,这肯定更有效,但我不知道如何终止整个事情。)