我的多线程爬虫项目实战

我的多线程爬虫项目实战

点击上方蓝字关注我!




爬虫相信很多小伙伴都做过,大部分都是用的Python。我之前也用Python爬取过12306的数据,有兴趣的可以看看我的这篇文章:

我在github上面的一个项目———用Python爬取12306火车票

但是这次我想用Java试试如何爬取网站数据。

使用框架

Jsoupjsoup是一款Java的HTML解析器,可直接解析某个URL地址、HTML文本内容。它提供了一套非常省力的API,可通过DOM,CSS以及类似于jQuery的操作方法来取出和操作数据。

官方文档地址如下:

https://www.open-open.com/jsoup/

下面我来介绍爬取过程。

导入Maven

<dependency>
  <!-- jsoup HTML parser library @ https://jsoup.org/ -->
  <groupId>org.jsoup</groupId>
  <artifactId>jsoup</artifactId>
  <version>1.13.1</version>
</dependency>

返回结果是Json格式的,可以使用

jsonBody = jsoupService.parseBodyJson(url);

解析jsonBody,获取指定参数值

我的多线程爬虫项目实战

如果是Document格式的

 document = jsoupService.parseDocument(detailUrl);

爬取测试

爬取的部分数据如下

我的多线程爬虫项目实战

线程池

爬取数据是一条一条的爬取,如果是单线程爬,速度肯定很慢,这里使用多线程。我们使用SpringBoot的方式创建线程池。

注意:因为是多线程成爬取,如果爬取的数据需要存入集合,需要采用并发安全的List。比如:CopyOnWriterArrayList,否则在list.add()的时候很有可能出现并发操作异常

@Component
@EnableAsync
public class TreadPoolConfigurer implements AsyncConfigurer {

    @Override
 @Bean(name = "taskExecutor")
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //核心线程池数量,方法: 返回可用处理器的Java虚拟机的数量。
        executor.setCorePoolSize(5);
        //最大线程数量
        executor.setMaxPoolSize(10);
        //线程池的队列容量
        executor.setQueueCapacity(20);
        // 设置线程活跃时间(秒)
        executor.setKeepAliveSeconds(60);
        //线程名称的前缀
        executor.setThreadNamePrefix("这里是线程名");
        // setRejectedExecutionHandler:当pool已经达到max size的时候,如何处理新任务
        // CallerRunsPolicy:不在新线程中执行任务,而是由调用者所在的线程来执行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 等待所有任务结束后再关闭线程池
        executor.setWaitForTasksToCompleteOnShutdown(true);
        return executor;
        }

        @Override
        public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
            return new SimpleAsyncUncaughtExceptionHandler();
        }
}

如上代码,在设置最大线程数量的时候,数量最好不要超过当前系统的CPU核数。

采用异步注解创建任务,并指定线程池。

@Async("taskExecutor")
public void startNewThreadTaskProductInfoMachineryByUrl(List<ProductInfo> infoList, String url) {
     handleProductInfoMachinery(infoList, url, null);
}

Redis队列

因为在爬取的时候可能因为网络等原因,爬取的那一条数据会失败。这时我会记录失败的urlcode,并将爬取异常的urlcode存入Redis队列。

log.error("异常信息:", e.getMessage());
log.info("将异常的url存入阻塞队列...");
//存入阻塞队列
redisUtils.leftPush(MACHINERY_DETAIL_URL, code);

我在后台重新启动一个线程,自旋的形式将Redis的队列中的数据阻塞式取出。然后再一次爬取。

String code = (String)redisUtils.rightPop(MACHINERY_DETAIL_URL);

因为爬取的数据存到List中,需要持久化到数据库。考虑到数据量比较大,采用分组的方式存入数据库。

 List<List<ProductInfo>> groups = Lists.partition(productInfos, 500);
        groups.forEach(infos -> this.saveList(infos));

ApplicationRunner

系统启动时,开启定时任务,定时爬取。并开启后台重试线程。

@Component
@Slf4j
public class ProductInfoCrawlInitService implements ApplicationRunner {

    @Autowired
    private ProductInfoRetryCrawlMachineryTask productInfoRetryCrawlMachineryTask;

    @Override
    public void run(ApplicationArguments args) {
        log.info("系统已加载定时任务....");
        //启动定时任务
        CronUtil.start();
        
        new Thread(productInfoRetryCrawlMachineryTask).start();
    }
}

定时任务

定时任务配置,采用Hutool框架,创建定时任务的文件

srcmainresourcesconfigcron.setting
# 配置定时任务
[com.xxxxxx.domain.service.impl]
#每天晚上0点执行
ProductInfoServiceImpl.saveProductInfoMachinery = 0 0 0 * * ?

入库结果

我的多线程爬虫项目实战
入库

爬虫流程图

我的设计思路大致如下:

我的多线程爬虫项目实战
爬虫项目

当然我的爬虫项目还在逐渐完善中,期待完工的时候是个什么样的😀



往期推荐


我的多线程爬虫项目实战
我的多线程爬虫项目实战

扫码二维码

获取更多精彩

Lvshen_9

我的多线程爬虫项目实战
我的多线程爬虫项目实战


原文始发于微信公众号(Lvshen的技术小屋):我的多线程爬虫项目实战

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/262470.html

(0)
Java朝阳的头像Java朝阳

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!