上一篇文章讲述了如何使用Java爬取内容并写入文件,但是速度堪忧,今天将代码搞成了多线程版本,
具体方式如下:
新建一个splider类,继承callable接口,用于存放我们需要多线程执行的逻辑:
将上一篇文章中爬取网页内容的代码搬过来
public class Splider implements Callable {
// 使用atomicInteger保证共享变量的安全自增
private AtomicInteger pageNum = new AtomicInteger(0);
@Override
public StringBuilder call() throws Exception {
// 当前页码
Integer privateNum = this.pageNum.addAndGet(1);
// 存储当前页的文本
StringBuilder currentPageText = new StringBuilder();
System.out.println("正在爬取第" + privateNum + "页内容。。。");
String html = ConnectionUtil.Connect("https://www.pengfu.com/xiaohua_" + privateNum + ".html");
Document doc = Jsoup.parse(html);
Elements titles = doc.select("h1.dp-b");
for (Element titleEle : titles) {
Element parent = titleEle.parent();
String title = titleEle.getElementsByTag("a").text();
String author = parent.select("p.user_name_list > a").text();
String content = parent.select("div.content-img").text();
// 将内容格式化
currentPageText.append(title)
.append("\r\n作者:").append(author)
.append("\r\n").append(content)
.append("\r\n").append("\r\n");
}
currentPageText.append("-------------第").append(privateNum).append("页-------------").append("\r\n");
System.out.println("第" + privateNum + "页内容爬取完毕。。。");
// 将当前页内容返回给future对象
return currentPageText;
}
}
主函数:
public static void main(String[] args) throws ExecutionException, InterruptedException {
long startTime = System.currentTimeMillis();
// 创建大小为5的线程池
ExecutorService esPool = Executors.newFixedThreadPool(5);
List> futureList = new ArrayList<>();
Splider splider = new Splider();
for (int i = 1; i <= 10; i++) {
futureList.add(esPool.submit(splider));
}
List finishCount = new ArrayList<>();
for (Future future : futureList) {
// 线程结束,将线程返回的内容添加到list
finishCount.add(future.get());
}
/*
* 所有内容爬取完毕,将内容统一写入磁盘
*/
if (finishCount.size() == 10) {
StringBuilder allText = new StringBuilder();
/*
* finishCount中future.get()的顺序 和 futureList中的future顺序一致
* 所以内容是从第1页...第N页顺序写入
*/
for (StringBuilder pageNum : finishCount) {
allText.append(pageNum);
}
// 写入磁盘
Test.writeToFile(allText.toString());
long endTime = System.currentTimeMillis();
System.out.println("耗时 : " + (endTime - startTime));
// 关闭线程池
esPool.shutdownNow();
}
}
执行结果:
查看本地文件,顺序和内容也都没有问题:
总结:
多个线程共享变量,只new一个实例,传给多个线程使用;
可以使用atomit类、synchronized、volitaile、lock保证多线程共享变量的安全性;
future.get()顺序和executorService.submit()顺序一致,和谁先执行完毕无关
使用callable + future可以获取线程返回值、捕获;