Java多线程爬虫案例
由于单线程下性能问题,无法满足快速爬取网页数据,于是采用了多线程爬虫的方式来浅试一下,只尝试了几千条数据,没有尝试大数据,还有很多需要改进的地方。
主要逻辑如下:
- 线程池
- 数据爬取
- 数据保存
- 多线程操作
线程池配置类
@Configuration
public class ThreadPoolConfig {
@Bean(destroyMethod = "shutdown")
public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 线程池中的线程名前缀
executor.setThreadNamePrefix("common-asyn-thread-");
// 设置线程池关闭的时候需要等待所有任务都完成 才能销毁其他的Bean
executor.setWaitForTasksToCompleteOnShutdown(true);
// 设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住
executor.setAwaitTerminationSeconds(60);
//核心线程
executor.setCorePoolSize(10);
//最大线程
executor.setMaxPoolSize(300);
// 任务阻塞队列的大小
executor.setQueueCapacity(300);
return executor;
}
}
使用okhttp可能会有坑,需加入这个到pom文件中,即可解决
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>1.3.72</version>
</dependency>
核心代码
@Slf4j
@ActiveProfiles("prod")
@RunWith(SpringRunner.class)
@SpringBootTest(classes = DdjdApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class Test01_sys {
@Resource
private ThreadPoolConfig theaThreadPoolConfig;
@Resource
private ISmStudentService smStudentService;
private List<SmStudent> dataList = new Vector<>();
@Test
public void mutiTheadGetAndSaveDataDemo() {
// 原子类 线程安全
AtomicInteger page = new AtomicInteger(0);
// 闭锁计数器
CountDownLatch countDownLatch = new CountDownLatch(136);
for (int i = 1; i <= 136; i++) {
theaThreadPoolConfig.threadPoolTaskExecutor().execute(() -> {
try {
dealData(page);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 减一操作
countDownLatch.countDown();
}
});
}
try {
// 阻塞主线程 增加兜底10s,自动释放
countDownLatch.await(10, TimeUnit.SECONDS);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(dataList);
// 数据分组处理
List<List<SmStudent>> studentListAll = Lists.partition(dataList, 200);
// 闭锁计数器
CountDownLatch countDownLatchNew = new CountDownLatch(studentListAll.size());
for (List<SmStudent> students : studentListAll) {
theaThreadPoolConfig.threadPoolTaskExecutor().execute(() -> {
try {
saveData(students);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 减一操作
countDownLatchNew.countDown();
}
});
}
try {
// 阻塞主线程 增加兜底10s,自动释放
countDownLatchNew.await(10, TimeUnit.SECONDS);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭线程池
theaThreadPoolConfig.threadPoolTaskExecutor().shutdown();
}
log.warn("数据保存成功:{}", dataList.size());
}
/**
* 多线程数据爬取
*
* @param page 页面索引
*/
private void dealData(AtomicInteger page) {
// 索引自增,使用原子类,考虑到线程安全问题
int index = page.incrementAndGet();
// 通过okhttp的方式爬取数据
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
// 网页链接,这里因为隐私隐掉了地址
.url("url" + index + "&pageSize=20")
.get()
// 令牌 例如token
.addHeader("LoginUser", "")
.addHeader("cache-control", "no-cache")
.build();
try {
Response response = client.newCall(request).execute();
// 获取返回数据,并进行处理解析
String rsp = response.body().string();
JSONObject jsonRsp = JSONObject.parseObject(rsp);
JSONObject data = (JSONObject) jsonRsp.get("data");
JSONArray array = data.getJSONArray("list");
/*for (Object o : array) {
// 解析每一条数据
StudentInfo studentInfo = JSON.toJavaObject((JSON) o, StudentInfo.class);
System.err.println(studentInfo.getRange());
}*/
// 转成java list
List<SmStudent> list = array.toJavaList(SmStudent.class);
dataList.addAll(list);
} catch (IOException e) {
log.error("数据爬取失败!");
}
}
/**
* 数据保存
*
* @param students
*/
private void saveData(List<SmStudent> students) {
smStudentService.saveBatch(students);
}
}