3

我正在用 Java 实现一个网络爬虫。在对要抓取的网站进行了一些操作之后,我想使用 Java 中并发 HTTP 连接的最佳实践。我目前正在使用Jsoup 的连接方法。我想知道是否可以创建线程并在这些线程中建立连接,类似于HttpAsyncClient

4

1 回答 1

5

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 个 URL,这个简单应用程序的目标是<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();
    }
});
于 2018-01-17T10:33:46.423 回答