Java异步爬虫高效抓取小红书短视频技术解析

一、核心技术原理

异步编程基础

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);

//2.构建HTTP请求

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();

//3.异步发送请求并处理响应

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<>();

//1.提交所有异步请求

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));

}

//2.处理剩余的异步请求

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,调整接口路径,并遵守平台规则和法律法规,避免风控和合规风险。

参考:https://www.chiniurou.com/bingweitongzhishu/

相关推荐
2501_9418072612 小时前
在迪拜智能机场场景中构建行李实时调度与高并发航班数据分析平台的工程设计实践经验分享
java·前端·数据库
一 乐12 小时前
餐厅点餐|基于springboot + vue餐厅点餐系统(源码+数据库+文档)
java·前端·数据库·vue.js·spring boot·后端
ss27312 小时前
volatile的可见性、安全发布的秘密与ThreadLocal原理
java·开发语言
小猪配偶儿_oaken12 小时前
SpringBoot实现单号生成功能(Java&若依)
java·spring boot·okhttp
宋情写12 小时前
JavaAI04-RAG
java·人工智能
毕设源码-钟学长12 小时前
【开题答辩全过程】以 中医健康管理系统为例,包含答辩的问题和答案
java
susu108301891112 小时前
docker部署 Java 项目jar
java·docker·jar
Haooog12 小时前
LangChain4j 学习
java·学习·大模型·langchain4j
爬山算法12 小时前
Hibernate(25)Hibernate的批量操作是什么?
java·后端·hibernate
2501_9418752812 小时前
从日志语义到可观测性的互联网工程表达升级与多语言实践分享随笔
java·前端·python