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