HttpAsyncClient和多线程Jsoup连接类之间有区别吗?

时间:2018-01-17 09:57:40

标签: java multithreading web-scraping jsoup apache-httpasyncclient

我在Java中实现了一个Web scraper。在与我将要抓取的网站进行一些游戏之后,我想在Java中使用最佳实践进行并发HTTP连接。我目前正在使用Jsoup's连接方法。我想知道是否有可能创建线程并在这些线程内建立与HttpAsyncClient类似的连接。

1 个答案:

答案 0 :(得分:2)

Jsoup不使用HttpAsyncClient。 Jsoup的Jsoup.connect(String url)方法使用了阻止URL.openConnection()方法。

如果要异步使用Jsoup,可以并行执行所有Jsoup.connect()。在Java 8中,您可以使用并行流来执行此操作。我们假设您有一个要并行扫描的URL列表。看一下下面的例子:

import org.jsoup.Jsoup;
import org.jsoup.select.Elements;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;

public class ConcurrentJsoupExample {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        final List<String> urls = Arrays.asList(
                "https://google.com",
                "https://stackoverflow.com/questions/48298219/is-there-a-difference-between-httpasyncclient-and-multithreaded-jsoup-connection",
                "https://mvnrepository.com/artifact/org.jsoup/jsoup",
                "https://docs.oracle.com/javase/7/docs/api/java/net/URL.html#openConnection()",
                "https://docs.oracle.com/javase/7/docs/api/java/net/URLConnection.html"
        );

        final List<String> titles = urls.parallelStream()
                .map(url -> {
                    try {
                        return Jsoup.connect(url).get();
                    } catch (IOException e) {
                        return null;
                    }
                })
                .filter(Objects::nonNull)
                .map(doc -> doc.select("title"))
                .map(Elements::text)
                .peek(it -> System.out.println(Thread.currentThread().getName() + ": " + it))
                .collect(Collectors.toList());
    }
}

此处我们定义了5个网址,这个简单应用程序的目标是从这些网站获取<title> HTML标记的文本值。会发生什么是我们使用URL列表创建并行流,并将每个URL映射到Jsoup的Document对象 - .get()方法抛出已检查的异常,因此我们必须尝试捕获它并且如果发生异常我们返回null值。所有null值都会被.filter(Objects::nonNull)过滤掉,之后我们可以提取我们需要的元素 - 在这种情况下,<title>标记的文本值。我还添加了.peek()来打印提取的值以及它运行的线程名称。示例性输出可能如下所示:

ForkJoinPool.commonPool-worker-1: java - Is there a difference between HttpAsyncClient and multithreaded Jsoup connection class? - Stack Overflow
main: Maven Repository: org.jsoup » jsoup
ForkJoinPool.commonPool-worker-4: URL (Java Platform SE 7 )
ForkJoinPool.commonPool-worker-2: URLConnection (Java Platform SE 7 )
ForkJoinPool.commonPool-worker-3: Google

最后,我们调用.collect(Collectors.toList())来终止流,执行所有转换并返回标题列表。

这只是一个简单的例子,但它应该给你一个如何并行使用Jsoup的提示。

或者,如果类似功能的方法无法说服您,则可以使用url.parallelStream().forEach()

urls.parallelStream().forEach(url -> {
    try {
        final Document doc = Jsoup.connect(url).get();
        final String title = doc.select("title").text();

        System.out.println(Thread.currentThread().getName() + ": " + title);

        // do something with extracted title...

    } catch (IOException e) {
        e.printStackTrace();
    }
});