当前位置: 首页 > news >正文

【智能协同云图库】第六期:基于 百度API 和 Jsoup 爬虫实现以图搜图

摘要:本节扩展图片功能,实现以图搜图功能。采用数据抓取方案,以图搜图支持用户通过图片精准搜索相似图片,经后端开发(含数据模型、API 及接口开发,运用门面模式简化调用)从全网获取结果。

以图搜图效果展示:

思维导图:

图片搜索 - 以图搜图

需求分析

用户可通过图片搜索相似图片,相比关键词搜索更精准,需从全网搜索以获取更多结果。


方案设计

  • 有第三方 API 和数据抓取两种方案。第三方 API 中,百度 AI 图片搜索 API 适用于自建图库,Bing 以图搜图 API 可全网搜索且免费。
  • 为便于学习,选择数据抓取方案,即利用现有以图搜图网站,实时抓取其返回结果。

数据抓取

百度搜图流程与接口分析示例

  •  进入百度图片搜索,通过 URL 上传图片,涉及接口https://graph.baidu.com/upload?uptime=其返回以图搜图的页面地址。

  • 访问该页面地址,可在返回值中找到 firstUrl。

使用以图搜图功能:

后端开发

新建 api 包,由于项目可能会用到多个 api,可以将每个 api 都放在 api 目录下的一个包中。比如图片搜索 api 的相关代码,全部放在 api.imagesearch 包下。

1、数据模型开发

imagesearch.model 包中,新建一个图片搜索结果类,用于接受 API 的返回值:

@Data
public class ImageSearchResult {/*** 缩略图地址*/private String thumbUrl;/*** 来源地址*/private String fromUrl;
}
2、API 开发

根据方案,我们要调用多个 API,每个子 API 可以作为一个静态类来实现,统一放在 imagesearch.sub 包中,并且每个类都包含一个 main 方法,用于进行本地测试。

1)获取以图搜图的页面地址

通过向百度؜发送 POST 请⁠求,获取给定图片 ‏URL 的相似图片页‌面地址。

@Slf4j
public class GetImagePageUrlApi {/*** 获取图片页面地址** @param imageUrl* @return*/public static String getImagePageUrl(String imageUrl) {// 1. 准备请求参数Map<String, Object> formData = new HashMap<>();formData.put("image", imageUrl);formData.put("tn", "pc");formData.put("from", "pc");formData.put("image_source", "PC_UPLOAD_URL");// 获取当前时间戳long uptime = System.currentTimeMillis();// 请求地址String url = "https://graph.baidu.com/upload?uptime=" + uptime;try {// 2. 发送 POST 请求到百度接口HttpResponse response = HttpRequest.post(url).form(formData).timeout(5000).execute();// 判断响应状态if (HttpStatus.HTTP_OK != response.getStatus()) {throw new BusinessException(ErrorCode.OPERATION_ERROR, "接口调用失败");}// 解析响应String responseBody = response.body();Map<String, Object> result = JSONUtil.toBean(responseBody, Map.class);// 3. 处理响应结果if (result == null || !Integer.valueOf(0).equals(result.get("status"))) {throw new BusinessException(ErrorCode.OPERATION_ERROR, "接口调用失败");}Map<String, Object> data = (Map<String, Object>) result.get("data");String rawUrl = (String) data.get("url");// 对 URL 进行解码String searchResultUrl = URLUtil.decode(rawUrl, StandardCharsets.UTF_8);// 如果 URL 为空if (searchResultUrl == null) {throw new BusinessException(ErrorCode.OPERATION_ERROR, "未返回有效结果");}return searchResultUrl;} catch (Exception e) {log.error("搜索失败", e);throw new BusinessException(ErrorCode.OPERATION_ERROR, "搜索失败");}}public static void main(String[] args) {// 测试以图搜图功能String imageUrl = "https://www.codefather.cn/logo.png";String result = getImagePageUrl(imageUrl);System.out.println("搜索成功,结果 URL:" + result);}
}

2)获取图片列表页面地址

通过 jsoup 爬取 HTML,提取其中包含 firstUrl 的 JavaScript 脚本,并返回图片列表的页面地址。

@Slf4j
public class GetImageFirstUrlApi {/*** 获取图片列表页面地址** @param url* @return*/public static String getImageFirstUrl(String url) {try {// 使用 Jsoup 获取 HTML 内容Document document = Jsoup.connect(url).timeout(5000).get();// 获取所有 <script> 标签Elements scriptElements = document.getElementsByTag("script");// 遍历找到包含 `firstUrl` 的脚本内容for (Element script : scriptElements) {String scriptContent = script.html();if (scriptContent.contains("\"firstUrl\"")) {// 正则表达式提取 firstUrl 的值Pattern pattern = Pattern.compile("\"firstUrl\"\\s*:\\s*\"(.*?)\"");Matcher matcher = pattern.matcher(scriptContent);if (matcher.find()) {String firstUrl = matcher.group(1);// 处理转义字符firstUrl = firstUrl.replace("\\/", "/");return firstUrl;}}}throw new BusinessException(ErrorCode.OPERATION_ERROR, "未找到 url");} catch (Exception e) {log.error("搜索失败", e);throw new BusinessException(ErrorCode.OPERATION_ERROR, "搜索失败");}}public static void main(String[] args) {// 请求目标 URLString url = "https://graph.baidu.com/s?card_key=&entrance=GENERAL&extUiData[isLogoShow]=1&f=all&isLogoShow=1&session_id=16250747570487381669&sign=1265ce97cd54acd88139901733452612&tpl_from=pc";String imageFirstUrl = getImageFirstUrl(url);System.out.println("搜索成功,结果 URL:" + imageFirstUrl);}
}

3)获取图片列表

通过调用百؜度接口返回的 JS⁠ON 数据,提取出‏其中的图片列表‌并返回。

@Slf4j
public class GetImageListApi {/*** 获取图片列表** @param url* @return*/public static List<ImageSearchResult> getImageList(String url) {try {// 发起GET请求HttpResponse response = HttpUtil.createGet(url).execute();// 获取响应内容int statusCode = response.getStatus();String body = response.body();// 处理响应if (statusCode == 200) {// 解析 JSON 数据并处理return processResponse(body);} else {throw new BusinessException(ErrorCode.OPERATION_ERROR, "接口调用失败");}} catch (Exception e) {log.error("获取图片列表失败", e);throw new BusinessException(ErrorCode.OPERATION_ERROR, "获取图片列表失败");}}/*** 处理接口响应内容** @param responseBody 接口返回的JSON字符串*/private static List<ImageSearchResult> processResponse(String responseBody) {// 解析响应对象JSONObject jsonObject = new JSONObject(responseBody);if (!jsonObject.containsKey("data")) {throw new BusinessException(ErrorCode.OPERATION_ERROR, "未获取到图片列表");}JSONObject data = jsonObject.getJSONObject("data");if (!data.containsKey("list")) {throw new BusinessException(ErrorCode.OPERATION_ERROR, "未获取到图片列表");}JSONArray list = data.getJSONArray("list");return JSONUtil.toList(list, ImageSearchResult.class);}public static void main(String[] args) {String url = "https://graph.baidu.com/ajax/pcsimi?carousel=503&entrance=GENERAL&extUiData%5BisLogoShow%5D=1&inspire=general_pc&limit=30&next=2&render_type=card&session_id=16250747570487381669&sign=1265ce97cd54acd88139901733452612&tk=4caaa&tpl_from=pc";List<ImageSearchResult> imageList = getImageList(url);System.out.println("搜索成功" + imageList);}
}

3、门面模式改造

这里我们运用؜一种设计模式来提供图片搜索服⁠务。门面模式通过提供‏一个统一的接口来简化多个‌接口的调用,使得客户端不‏需要关注内部的具体实现。

我们可以将多个 API 整合到一个门面类中,简化调用过程。在 imagesearch 包下新建门面类,整合几个接口的调用:

@Slf4j
public class ImageSearchApiFacade {/*** 搜索图片** @param imageUrl* @return*/public static List<ImageSearchResult> searchImage(String imageUrl) {String imagePageUrl = GetImagePageUrlApi.getImagePageUrl(imageUrl);String imageFirstUrl = GetImageFirstUrlApi.getImageFirstUrl(imagePageUrl);List<ImageSearchResult> imageList = GetImageListApi.getImageList(imageFirstUrl);return imageList;}public static void main(String[] args) {// 测试以图搜图功能String imageUrl = "https://www.codefather.cn/logo.png";List<ImageSearchResult> resultList = searchImage(imageUrl);System.out.println("结果列表" + resultList);}
}
4、接口开发

开发请求类:

@Data
public class SearchPictureByPictureRequest implements Serializable {/*** 图片 id*/private Long pictureId;private static final long serialVersionUID = 1L;
}

开发接口:

/*** 以图搜图*/
@PostMapping("/search/picture")
public BaseResponse<List<ImageSearchResult>> searchPictureByPicture(@RequestBody SearchPictureByPictureRequest searchPictureByPictureRequest) {ThrowUtils.throwIf(searchPictureByPictureRequest == null, ErrorCode.PARAMS_ERROR);Long pictureId = searchPictureByPictureRequest.getPictureId();ThrowUtils.throwIf(pictureId == null || pictureId <= 0, ErrorCode.PARAMS_ERROR);Picture oldPicture = pictureService.getById(pictureId);ThrowUtils.throwIf(oldPicture == null, ErrorCode.NOT_FOUND_ERROR);List<ImageSearchResult> resultList = ImageSearchApiFacade.searchImage(oldPicture.getUrl());return ResultUtils.success(resultList);
}

至此,相关的后端接口开发⁠完毕,大功告成!🎉🎉🎉

http://www.lryc.cn/news/613160.html

相关文章:

  • RabbitMQ面试精讲 Day 15:RabbitMQ故障转移与数据恢复
  • 【数据结构】排序(sort) -- 交换排序(冒泡快排)
  • 大数据杀熟:技术阴影下的消费陷阱与破局之道
  • Dokcer创建中间件环境
  • RabbitMQ面试精讲 Day 13:HAProxy与负载均衡配置
  • 【Day 18】Linux-DNS解析
  • 香港网站服务器被占用的资源怎么释放?
  • 股指期货合约是个啥?怎么玩?
  • JVM 终止机制详解:用户线程与守护线程
  • WD6208资料和引脚图
  • MCU中的晶振(Crystal Oscillator)
  • 时间戳表示
  • 汽车娱乐信息系统域控制器的网络安全开发方案
  • 基于Ruby的IP池系统构建分布式爬虫架构
  • 基于 MATLAB 的 QPSK 调制、解调、通过高斯信道的误码率计算,并绘制误码率图和眼图、星座图
  • SurgRIPE 挑战赛:手术机器人器械位姿估计基准测试|文献速递-医学影像算法文献分享
  • 【源码】AndroidPlayer
  • 智能升级新纪元:基于Deepoc具身模型外拓开发板的除草机器人认知进化
  • 【图文教程】三步用Cpolar+JuiceSSH实现手机远程连接内网Linux虚拟机
  • Web开发模式 前端渲染 后端渲染 身份认证
  • 网页前端CSS实现表格3行平均分配高度,或者用div Flexbox布局
  • 网络安全等级保护(等保)2.0 概述
  • 深入理解Apache Camel:原理剖析与实践指南
  • 安全合规2--网络安全等级保护2.0介绍
  • 【Apache Olingo】全面深入分析报告-OData
  • 首个!3D空间推理框架3D-R1:融合强化学习、推理链、动态视角,实现7大任务SOTA!
  • ubuntu22.04安装docker
  • 基于 HT 引擎实现 3D 智慧物流转运中心一体化管控系统
  • 手写数字识别实战 - 从传统机器学习到深度学习
  • Spring AOP动态代理核心原理深度解析 - 图解+实战揭秘Java代理设计模式