我的多线程爬虫项目实战

1 篇文章 0 订阅
1 篇文章 0 订阅

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

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

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

使用框架

Jsoup:jsoup是一款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框架,创建定时任务的文件

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

入库结果

入库

爬虫流程图

我的设计思路大致如下:

爬虫项目

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

往期推荐

扫码二维码,获取更多精彩。或微信搜Lvshen_9,可后台回复获取资料

1.回复"java" 获取java电子书;

2.回复"python"获取python电子书;

3.回复"算法"获取算法电子书;

4.回复"大数据"获取大数据电子书;

5.回复"spring"获取SpringBoot的学习视频。

6.回复"面试"获取一线大厂面试资料

7.回复"进阶之路"获取Java进阶之路的思维导图

8.回复"手册"获取阿里巴巴Java开发手册(嵩山终极版)

9.回复"总结"获取Java后端面试经验总结PDF版

10.回复"Redis"获取Redis命令手册,和Redis专项面试习题(PDF)

11.回复"并发导图"获取Java并发编程思维导图(xmind终极版)

另:点击【我的福利】有更多惊喜哦。

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值