前文链接 : java爬取捧腹网段子

上一篇文章讲述了如何使用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<Future<StringBuilder>> futureList = new ArrayList<>();
    Splider splider = new Splider();
    for (int i = 1; i <= 10; i++) {
        futureList.add(esPool.submit(splider));
    }

    List<StringBuilder> finishCount = new ArrayList<>();
    for (Future<StringBuilder> 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();
    }
}

执行结果:

clipboard.png

查看本地文件,顺序和内容也都没有问题:

clipboard.png

总结:
多个线程共享变量,只new一个实例,传给多个线程使用;
可以使用atomit类、synchronized、volitaile、lock保证多线程共享变量的安全性;
future.get()顺序和executorService.submit()顺序一致,和谁先执行完毕无关
使用callable + future可以获取线程返回值、捕获;


瓦力
575 声望15 粉丝

一只coder