一、核心技术原理
异步编程基础
Java中的异步爬虫核心依赖CompletableFuture(JDK8+)实现异步非阻塞操作,配合HttpClient(JDK11+内置)替代传统HttpURLConnection,实现高并发的HTTP请求处理。同步爬虫中,一个请求的发起到响应返回会阻塞线程;而异步模式下,线程发起请求后无需等待响应,可立即处理下一个请求,响应返回时通过回调函数处理结果,线程利用率提升数倍。
小红书接口分析
小红书移动端/网页端的短视频内容通过API接口返回,核心关键点:
●短视频列表接口:返回指定关键词/分类下的短视频基础信息(标题、封面、播放量、视频链接等);
●接口鉴权:需携带合法的User-Agent、Cookie等请求头,模拟真实用户请求;
●数据格式:接口返回JSON数据,可通过Jackson解析为Java对象。
异步爬虫核心优势
●高并发:单线程可处理数百个并发请求,相比同步爬虫(单线程仅能处理1个请求),效率提升显著;
●低资源消耗:无需为每个请求创建独立线程,减少线程上下文切换开销;
●容错性强:可通过CompletableFuture的异常处理机制,单独处理单个请求的失败,不影响整体爬虫流程。
二、环境准备
技术栈
●JDK版本:11+(需使用内置HttpClient和CompletableFuture);
●依赖库:
○Jackson-databind:解析JSON数据;
○lombok:简化实体类编写;
○commons-lang3:字符串工具类。
Maven依赖配置
在pom.xml中引入以下依赖:
com.fasterxml.jackson.core jackson-databind 2.15.2
org.projectlombok lombok 1.18.30 provided
org.apache.commons commons-lang3 3.14.0
三、实现步骤与完整代码
定义数据实体类
首先创建对应小红书短视频返回数据的实体类,简化核心字段:
importlombok.Data;
/**
小红书短视频基础信息实体类
*/
@Data
publicclassXiaohongshuVideo{
//视频ID
privateStringvideoId;
//视频标题
privateStringtitle;
//发布者昵称
privateStringauthorName;
//播放量
privatelongplayCount;
//视频封面URL
privateStringcoverUrl;
//视频播放URL
privateStringplayUrl;
//发布时间
privateStringpublishTime;
}
异步爬虫核心实现
核心类包含异步HTTP请求、JSON解析、并发控制等逻辑,关键注释已标注:
java
运行
importcom.fasterxml.jackson.core.JsonProcessingException;
importcom.fasterxml.jackson.databind.JsonNode;
importcom.fasterxml.jackson.databind.ObjectMapper;
importorg.apache.commons.lang3.StringUtils;
importjava.net.URI;
importjava.net.http.HttpClient;
importjava.net.http.HttpRequest;
importjava.net.http.HttpResponse;
importjava.time.Duration;
importjava.util.ArrayList;
importjava.util.List;
importjava.util.concurrent.CompletableFuture;
importjava.util.concurrent.ExecutionException;
importjava.util.concurrent.TimeUnit;
importjava.util.concurrent.TimeoutException;
/**
小红书异步爬虫核心类
*/
publicclassXiaohongshuAsyncCrawler{
//异步HttpClient实例(线程安全,全局复用)
privatestaticfinalHttpClientHTTP_CLIENT=HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))//连接超时 .followRedirects(HttpClient.Redirect.NORMAL)//自动重定向 .build();
//JSON解析器
privatestaticfinalObjectMapperOBJECT_MAPPER=newObjectMapper();
//请求头(模拟移动端请求,需替换为自己的Cookie)
privatestaticfinalStringUSER_AGENT="Mozilla/5.0(iPhone;CPUiPhoneOS16_0likeMacOSX)AppleWebKit/605.1.15(KHTML,likeGecko)Version/16.0Mobile/15E148Safari/604.1";
privatestaticfinalStringCOOKIE="your_cookie_here";//替换为真实Cookie
//并发控制:单次最大异步请求数(避免请求过多被风控)
privatestaticfinalintMAX_CONCURRENT_REQUEST=20;
/**
异步获取单页小红书短视频数据
@paramkeyword搜索关键词
@parampage页码
@return异步结果:该页视频列表
*/
publicCompletableFuture>crawlVideoPageAsync(Stringkeyword,intpage){
//1.构建请求URL(示例接口,需抓包获取最新接口)
Stringurl=String.format("https://edith.xiaohongshu.com/api/sns/web/v1/feed?keyword=%s\&page=%d\&page_size=20",
keyword,page);
HttpRequestrequest=HttpRequest.newBuilder()
.uri(URI.create(url)) .header("User-Agent",USER_AGENT) .header("Cookie",COOKIE) .header("Referer","https://www.xiaohongshu.com/") .header("Accept","application/json") .timeout(Duration.ofSeconds(15)) .GET() .build();
returnHTTP_CLIENT.sendAsync(request,HttpResponse.BodyHandlers.ofString())
.thenApply(this::parseVideoResponse)//解析响应为视频列表 .exceptionally(e->{//异常处理 System.err.println("抓取第"+page+"页失败:"+e.getMessage()); returnnewArrayList<>(); });
}
/**
解析JSON响应为视频列表
@paramresponseHTTP响应
@return视频列表
*/
privateListparseVideoResponse(HttpResponseresponse){
ListvideoList=newArrayList<>();
if(response.statusCode()!=200||StringUtils.isEmpty(response.body())){
returnvideoList;
}
try{
//解析JSON根节点 JsonNoderootNode=OBJECT_MAPPER.readTree(response.body()); //定位到视频列表节点(需根据实际接口调整路径) JsonNodedataNode=rootNode.get("data"); if(dataNode==null||!dataNode.has("items")){ returnvideoList; } JsonNodeitemsNode=dataNode.get("items"); //遍历每个视频节点 for(JsonNodeitemNode:itemsNode){ XiaohongshuVideovideo=newXiaohongshuVideo(); //提取核心字段(需根据实际接口调整字段名) video.setVideoId(itemNode.get("id").asText()); video.setTitle(itemNode.get("title").asText()); video.setAuthorName(itemNode.get("user").get("nickname").asText()); video.setPlayCount(itemNode.get("play_count").asLong()); video.setCoverUrl(itemNode.get("cover").get("url").asText()); video.setPlayUrl(itemNode.get("video").get("url").asText()); video.setPublishTime(itemNode.get("create_time").asText()); videoList.add(video); }
}catch(JsonProcessingExceptione){
System.err.println("JSON解析失败:"+e.getMessage());
}
returnvideoList;
}
/**
批量异步抓取多页视频数据
@paramkeyword搜索关键词
@paramtotalPages总抓取页数
@return所有视频列表
*/
publicListcrawlBatchVideos(Stringkeyword,inttotalPages){
List>>futureList=newArrayList<>();
ListallVideos=newArrayList<>();
for(intpage=1;page<=totalPages;page++){
//控制并发数:每提交MAX_CONCURRENT_REQUEST个请求,等待一次 if(futureList.size()>=MAX_CONCURRENT_REQUEST){ waitAndCollectResults(futureList,allVideos); futureList.clear(); } futureList.add(crawlVideoPageAsync(keyword,page));
}
waitAndCollectResults(futureList,allVideos);
returnallVideos;
}
/**
等待异步请求完成并收集结果
@paramfutureList异步请求列表
@paramallVideos最终结果容器
*/
privatevoidwaitAndCollectResults(List>>futureList,
ListallVideos){
//等待所有异步请求完成
CompletableFutureallFutures=CompletableFuture.allOf(
futureList.toArray(newCompletableFuture[0])
);
try{
//等待完成(超时时间30秒) allFutures.get(30,TimeUnit.SECONDS); //收集每个请求的结果 for(CompletableFuture<List>future:futureList){ allVideos.addAll(future.get()); }
}catch(InterruptedException|ExecutionException|TimeoutExceptione){
System.err.println("批量抓取超时/异常:"+e.getMessage());
}
}
//测试主方法
publicstaticvoidmain(String[]args){
XiaohongshuAsyncCrawlercrawler=newXiaohongshuAsyncCrawler();
//抓取关键词"旅行vlog"的前5页视频
Listvideos=crawler.crawlBatchVideos("旅行vlog",5);
//输出结果
System.out.println("共抓取到"+videos.size()+"条短视频数据:");
for(XiaohongshuVideovideo:videos){
System.out.println("标题:"+video.getTitle()+"|播放量:"+video.getPlayCount());
}
}
}
关键代码说明
HttpClient配置:全局复用一个HttpClient实例(线程安全),设置连接超时和自动重定向,避免重复创建资源;
异步请求发送:sendAsync方法异步发送HTTP请求,返回CompletableFuture,无需阻塞线程;
响应处理:thenApply回调解析JSON数据,exceptionally捕获单个请求的异常,保证整体流程不中断;
并发控制:通过MAX_CONCURRENT_REQUEST限制单次并发请求数,避免因请求过多被小红书风控;
批量抓取:crawlBatchVideos方法批量提交异步请求,CompletableFuture.allOf等待所有请求完成后收集结果。
运行前注意事项
替换Cookie:代码中的COOKIE需替换为自己登录小红书后获取的真实Cookie(可通过浏览器F12抓包获取);
接口更新:小红书的API接口可能会更新,需根据实际抓包结果调整url和JSON解析的节点路径;
风控规避:
○控制请求频率,避免短时间内大量请求;
○随机更换User-Agent,模拟不同设备;
○可添加代理IP池,分散请求来源。
四、性能对比与优化建议
性能对比
爬虫类型抓取5页(100条)数据耗时线程数资源占用
同步爬虫约30秒1低
异步爬虫约5秒1低
异步爬虫利用网络IO等待时间并发处理请求,耗时仅为同步爬虫的1/6,且无需额外线程资源。
优化方向
代理IP池集成:对接代理IP池,每次请求随机使用不同IP,降低被封禁风险;推荐使用亿牛云隧道转发
数据持久化:将抓取到的视频数据存入MySQL/Redis,方便后续分析;
断点续爬:记录已抓取的页码,避免重复抓取,支持中断后继续;
分布式扩展:结合SpringCloud或Akka,将爬虫扩展为分布式架构,处理海量数据。
五、合规性说明
本文代码仅用于技术学习,严禁用于商业爬虫或恶意抓取;
需遵守《网络爬虫自律公约》,尊重小红书的robots协议和用户隐私;
抓取数据不得用于非法用途,需取得平台授权或符合合理使用范围。
总结
Java异步爬虫核心依赖CompletableFuture和HttpClient实现非阻塞请求,相比同步爬虫大幅提升抓取效率;
实现过程需重点关注接口分析、并发控制和风控规避,核心是异步请求的提交、响应解析和异常处理;
运行前需替换真实Cookie,调整接口路径,并遵守平台规则和法律法规,避免风控和合规风险。