news 2026/10/14 6:27:51

Java爬取京东商品信息与价格实战:从数据分布到反爬风控全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java爬取京东商品信息与价格实战:从数据分布到反爬风控全解析

做电商数据抓取的人,多半绕不开京东。国内比价工具、市场分析、竞品监控,十来有八都要跟京东商品信息打交道。这几年陆陆续续接了很多类似需求,我发现多数人第一次上手 Java 爬取京东商品详细信息、价格时,都会卡在同一个问题上:页面上明明什么都看得见,代码里却什么都抓不出来。这篇文章就是把我的实操思路完整梳理一遍,从数据分布、技术选型、代码实现到反爬风控、入库更新,把链路讲透,希望能给你省掉几个晚上的摸索时间。

1. 先摸清京东的数据分布:哪些藏在HTML里,哪些藏在接口里

很多刚接触爬虫的人有个思维惯性:用 HTTP 请求拿到 HTML,然后用正则或者解析库把字段抠出来。这套思路在静态页面上有效,但放到京东这种大型电商平台上,就是踩坑的开始。我实测下来的结论是:京东商品详情页只有一部分数据渲染在初始 HTML 里,价格、促销、库存这类关键信息全部依赖异步接口。搞清楚数据分布,比急着写代码重要得多。

1.1 你能看到的,不等于爬虫能拿到的

京东的商品详情页 URL 长这样:https://item.jd.com/100012043954.html,后边那串数字就是 SKU ID。页面真正在浏览器里加载后,所有信息都凑齐了,有标题、价格、评价数、店铺名。但这里有个关键区别——浏览器执行了 JavaScript,把页面补全了;而爬虫直接发 GET 请求拿到的 HTML 只是骨架。

我用 HttpClient 拉过一次某个 SKU 的详情页,HTML 里能找到的是:

  • 商品标题(在<div class="sku-name">里)
  • 商品编号、品牌、分类路径
  • 部分商品属性(比如屏幕尺寸、颜色)
  • 图片地址、规格参数

找不到的是:

  • 成交价 / 促销价(要调价格接口)
  • 库存状态(要调库存接口)
  • 实时评价数(部分版本走异步接口)

这不是京东故意为难爬虫,而是电商系统的常规做法。页面骨架适合缓存,高频变化的价格、库存单独走接口,能显著降低服务端渲染压力。对爬虫来说,看清这一点就知道力气该往哪里使。

1.2 目标字段与对应来源

我整理了一张常用字段表,按照“哪个字段从哪个渠道拿”做了划分,如果你打算自己实现,直接照着这张表规划解析模块就行。

字段是否在初始HTML真实来源备注
商品标题是详情页sku-name标签直接解析
SKU 编号是URL 或页面 JS 变量URL 里就有
商品主图是#spec-list img的><dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.14</version> </dependency> <dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.16.1</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.15.2</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>1.7.36</version> </dependency>

这套组合各司其职:HttpClient 负责 HTTP 通信,Jsoup 负责 HTML 解析,Jackson 负责 JSON 处理,SLF4J 记日志。没有多余的中间层,出了问题也好排查。

2.2 为什么不上 Selenium 或 Playwright

这可能是很多人会杠的点。既然页面需要 JS 渲染,我直接用无头浏览器不就行了吗?我也试过。实测结论是:Selenium 方案在京东场景下有四个绕不开的问题。

第一,慢。每次启动浏览器、加载完整页面,一次请求至少要两三秒,跑一万个商品就是七八个小时,而 HttpClient 请求只要几百毫秒。

第二,重。多线程跑 Selenium 需要管理一堆浏览器实例,内存消耗轻松上几个 G。轻量组合单机就能跑几百线程。

第三,容易被识别。无头浏览器在 TLS 指纹、浏览器特征上和真实浏览器还是有差异,电商风控对这类访问很敏感。轻量组合只要 Header 伪装做得好,反而没那么扎眼。

第四,维护成本高。京东页面改版,Selenium 方案里一堆 CSS 选择器要跟着改,而轻量方案只需要改极少代码。

2.3 理清思路再动手:抓取链路设计

动手写代码前,我习惯先把流程画清楚。抓取一个京东 SKU 的完整链路是:

  1. 根据搜索关键词,访问京东搜索页https://search.jd.com/Search?keyword=xxx
  2. 从搜索结果页提取商品列表,拿到各个 SKU ID
  3. 逐个访问https://item.jd.com/{skuId}.html
  4. 解析详情页 HTML,提取标题、图片、属性等静态字段
  5. 调价格接口,拿到实时价格和优惠信息
  6. 数据组装、清洗、入库

下面几个小节我会按这个顺序逐步展开,每一段代码都是我在真实项目中跑过的版本,可以直接复制改改就用。

3. 从搜索关键词到 SKU 提取:进入京东商品体系的第一步

要抓京东商品,首先得有 SKU ID。SKU ID 是京东商品体系里的唯一标识,所有后续操作都围着它转。获取 SKU ID 的方式有很多种,最简单直接的就是搜索页。

3.1 搜索页的请求参数解析

京东搜索页的 URL 结构是:

https://search.jd.com/Search?keyword=机械键盘&enc=utf-8&psort=3

参数说明:

  • keyword:搜索关键词,需要 URL 编码
  • enc:编码方式,固定utf-8
  • psort:排序方式。3 是按销量排序,代码里可以按需调整。

先写一个最基础的搜索请求:

import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import java.net.URLEncoder; public class JDSearchDemo { public static void main(String[] args) throws Exception { String keyword = URLEncoder.encode("机械键盘", "UTF-8"); String url = "https://search.jd.com/Search?keyword=" + keyword + "&enc=utf-8&psort=3"; CloseableHttpClient client = HttpClients.createDefault(); HttpGet request = new HttpGet(url); // 这一步很关键,没有合适的UA会被重定向到验证页 request.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"); request.setHeader("Accept-Language", "zh-CN,zh;q=0.9"); CloseableHttpResponse response = client.execute(request); String html = EntityUtils.toString(response.getEntity(), "UTF-8"); System.out.println(html.length()); client.close(); } }

第一次跑你可能发现返回的 HTML 里没有商品列表。别慌,这是京东的反爬策略在起作用,后面第 6 章专门讲怎么处理。这里你先确认一件事:请求能返回内容,没有直接 403,链路就通了一半。

3.2 解析搜索结果以提取 SKU 列表

能看到正常搜索结果 HTML 之后,解析就简单了。京东搜索页的商品列表项有一个比较稳定的 class 属性,我用 Jsoup 做了解析:

public static List<String> parseSkuIds(String html) { List<String> skuIds = new ArrayList<>(); Document doc = Jsoup.parse(html); // 搜索结果中的商品项都带有>String title = doc.select("div.sku-name").text();

.title 提取出来之后建议做一次去空格处理,因为京东有些标题里带了非断行空格(&nbsp;),直接存库会导致数据不干净。

4.2 隐藏在页面里的 JSON 数据:事半功倍的提取入口

很多人在详情页只盯着 HTML 节点,但其实京东详情页的<script>标签里藏着一坨一坨的 JSON 数据。这些 JSON 里往往包含了比 HTML 节点更完整的信息——商品编号、品牌、分类、属性列表全都齐了。

京东的页面变量格式大致是:

window.pageConfig = {...}; window._itemInfo = {...};

我的解析思路是用正则先圈出 JSON 片段,再用 Jackson 解析:

import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.regex.Matcher; import java.util.regex.Pattern; public static JsonNode extractPageConfig(String html) throws Exception { // 匹配 window.pageConfig = {...}; 中的 JSON 内容 Pattern pattern = Pattern.compile("window\\.pageConfig\\s*=\\s*(\\{.*?\\});", Pattern.DOTALL); Matcher matcher = pattern.matcher(html); if (matcher.find()) { String json = matcher.group(1); ObjectMapper mapper = new ObjectMapper(); return mapper.readTree(json); } return null; }

JSON 里能提取到的信息密度很高。我常用的字段路径参考:

信息JSON 路径(举例)说明
商品编号wareInfo或itemInfo.skuId与 URL 一致
品牌itemInfo.brand注意部分类目为空
分类路径itemInfo.categoryPath可用于商品归类
商品属性itemInfo.attributes数组形式,需要遍历

这里提醒一个细节:正则匹配 JSON 时,必须用DOTALL模式,否则换行处会断掉。我见过不少人在这一步卡了半小时,最后发现是正则的.*?没匹配到跨行内容。

4.3 图片与附属信息的采集技巧

商品主图在详情页里的结构通常是一组缩略图,>Elements imgs = doc.select("#spec-list img"); for (Element img : imgs) { String realImg = img.attr("data-src"); if (realImg.startsWith("//")) { realImg = "https:" + realImg; // 协议相对地址要补全 } // 这里可以继续做去重、过滤小图 }

另外图片 URL 里经常带有!q70这样的后缀,这是京东的图片压缩参数,如果追求原图,可以把后缀去掉。做比价工具时图片清晰度无所谓,但做商品素材采集时这个细节就很重要。

5. 价格接口:异步加载的核心难点,也是本文最值的部分

如果说详情页解析是开胃菜,那价格接口就是主菜。京东商品价格数据是独立的异步接口,不走详情页的初始 HTML。这也是整个爬虫项目里最容易出错、也最值得写明白的地方。

5.1 京东为什么要把价格拆出去

从技术角度说,价格是电商系统里变动最频繁的数据。秒杀、优惠券、会员价、地区价,同一个 SKU 在不同时间、不同用户、不同地址下看到的价格可能都不一样。如果每次请求都从服务端渲染 HTML 返回价格,缓存基本就废了。

所以京东的页面架构选择了把价格逻辑单独做成一个接口,页面加载后用 JS 异步去拉。这个设计对电商系统是合理的,对爬虫来说就多了一道工序——你得去发现这个接口、模拟这个接口、解析这个接口。

5.2 价格接口的调用方式与 Java 实现

京东价格接口的常见形态是:

https://p.3.cn/prices/mgets?skuIds=J_100012043954

这个接口会返回一个 JSON 数组,字段大致是:

[ { "p": "499.00", "op": "599.00", "m": "459.00", "id": "J_100012043954", "txt": "满299减30" } ]

字段解读:

  • p:当前售价
  • op:原始标价(划线价)
  • m:会员价或其他优惠价
  • txt:促销文案

它返回的是字符串而不是数字,这点要特别注意,解析时建议直接保留字符串并自己转 BigDecimal,浮点数精度问题在金额计算里踩过很多次坑。

Java 调用实现非常简单:

import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import com.fasterxml.jackson.databind.ObjectMapper; public static String fetchPrice(String skuId) throws Exception { String url = "https://p.3.cn/prices/mgets?skuIds=J_" + skuId; CloseableHttpClient client = HttpClients.createDefault(); HttpGet request = new HttpGet(url); request.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"); request.setHeader("Referer", "https://item.jd.com/" + skuId + ".html"); CloseableHttpResponse response = client.execute(request); String json = EntityUtils.toString(response.getEntity(), "UTF-8"); response.close(); client.close(); // 解析JSON ObjectMapper mapper = new ObjectMapper(); var arr = mapper.readTree(json); if (arr.size() > 0) { String price = arr.get(0).path("p").asText(); return price; } return null; }

这段代码我在多台机器上跑过,成功率取决于频率,单价抓取非常稳。注意Referer一定要带上,否则部分接口会返回异常数据。

5.3 价格接口的字段校验与边界情况

价格接口字段有一个隐藏的大坑:op才是商品划价,p才是实际成交价。

有些商品参与促销时,p会低于op;没有促销时两者相等。做数据统计时如果用错字段,算出来的客单价会偏差非常大。

还有一个边界情况:部分商品(尤其是参与秒杀、预售的)在非活动时段调用接口,返回的p可能会是-1或者null。遇到这种情况,我的处理是保留op作为兜底价格,同时把这条记录标记为“价格待确认”,等下一个采集周期再来对比。

6. 反爬对抗与风控规则:代码之外,决定生死的是你的访问节奏

写爬虫的人都知道一句话:爬虫写得好不好,一半看代码,一半看调度。京东的风控体系在电商里属于非常敏感的一档,跑重了直接滑块验证码,跑猛了 IP 拉黑。这一节聊聊我用下来的体会。

6.1 UA、Cookie、Referer 三件套

请求头里最基础的三样:

  • User-Agent:浏览器的 UA 解析字符串,别用 Python-requests 或者 Java HttpURLConnection 的默认 UA
  • Referer:详情页请求的 Referer 可以是搜索页,价格接口的 Referer 必须是详情页
  • Cookie:带上匿名 Cookie 可以显著降低接口返回异常的几率

Cookie 的获取方式:不用登录京东,直接访问一次首页,把Set-Cookie头里的值存下来,后续请求复用。部分接口对 Cookie 有校验,但这个匿名方案对商品详情和价格接口是够用的。

6.2 请求频率:宁慢一分,不抢一秒

具体频率没有统一的建议值,我给出的启动配置是:

  • 单线程顺序采集,单次请求之间间隔 1.5 到 2.5 秒,随机浮动
  • 每个 IP 每分钟不超过 30 个请求
  • 单次任务建议控制在 2000 个 SKU 以内,跑完休息半小时再继续

如果你需要高频抓取价格做监控,更好的方案是同一个 SKU 每次间隔至少 5 分钟,用多 IP 轮换。而不是单 IP 高速梭哈,那样基本活不过 10 分钟。

另外,不管什么时候,都建议给代码加一个随机延时器。固定间隔比高频更危险,因为模式太规律更容易被风控判定为脚本。

private static void randomSleep() throws InterruptedException { int delay = 1500 + new Random().nextInt(1000); Thread.sleep(delay); }

6.3 验证码与滑块:遇见了要懂怎么收手

京东的反爬升级路径大致是:告警日志 → 限流 → 验证码 → 封禁。当你看到返回页面里出现滑块验证码时,说明当前 IP 已经进入观察名单了。

我最开始也尝试过接入打码平台绕滑块,但后来想通了:爬虫项目里最值钱的不是对抗能力,而是调度策略。遇到验证码的正确姿势是立即停止任务、切换代理、降低频率、过一段时间再跑。强顶着验证码刷,只会让 IP 被更快地封掉。

7. 数据落地:从抓取到可用的最后一步

跑通抓取只是第一步,数据的价值在于能持续积累、稳定查询。我见过太多人辛辛苦苦把数据抓下来了,存成 CSV 完事,等到要分析时发现结构混乱无法使用。这里分享一下存 MySQL 时比较顺手的表结构设计。

7.1 商品主表设计

核心字段表设计参考:

CREATE TABLE `jd_product` ( `id` BIGINT AUTO_INCREMENT PRIMARY KEY, `sku_id` VARCHAR(20) NOT NULL COMMENT '京东SKU编号', `title` VARCHAR(500) COMMENT '商品标题', `brand` VARCHAR(100) COMMENT '品牌', `category_path` VARCHAR(200) COMMENT '分类路径', `shop_name` VARCHAR(200) COMMENT '店铺名', `detail_url` VARCHAR(300) COMMENT '详情页URL', `main_image` VARCHAR(500) COMMENT '主图地址', `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, `updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY `uk_sku` (`sku_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='京东商品主表';

sku_id唯一键很重要,重复采集时可以用INSERT ... ON DUPLICATE KEY UPDATE做幂等写入,避免每次抓取都产生重复行。

7.2 价格历史表设计

价格是需要单独建表做历史记录的,这样后面才能分析价格波动、预测降价趋势。价格表和主表分开的原因很简单:主表一行代表一个商品的最新状态,价格表一行代表一个商品的一次快照,两者是一对多的关系。

CREATE TABLE `jd_product_price` ( `id` BIGINT AUTO_INCREMENT PRIMARY KEY, `sku_id` VARCHAR(20) NOT NULL COMMENT 'SKU编号', `price` DECIMAL(10,2) COMMENT '成交价', `origin_price` DECIMAL(10,2) COMMENT '划线价', `promotion_txt` VARCHAR(300) COMMENT '促销文案', `snapshot_time` DATETIME COMMENT '采集时间', KEY `idx_sku_time` (`sku_id`, `snapshot_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='京东价格历史表';

时间字段建议用采集端时间,而不是数据库默认时间。为什么要这样?因为如果你做的是历史回补或数据迁移,数据库时间代表的是写入时间,而不是数据真实产生时间,分析降价趋势时会乱套。

7.3 价格快照的正确落库方式

落库时最容易被忽视的一个细节是:写价格历史表时,要先查询主表里该 SKU 的当前价格,如果一样就不写,避免重复快照。比如你每天采 3 次价,价格不变时没必要存 3 条一模一样的记录,只存价格变化的那次快照,历史表会轻盈很多,分析时也更精准。

8. 合规边界与做这事的正确姿势

聊了这么多技术细节,最后必须说几句关于边界的话。爬虫项目做得越深,越要清楚哪些事能做、哪些事不能碰。

8.1 安全的落地场景

我自己接触到比较合理的京东数据采集需求有几类:一是个人比价工具,抓自己关注的商品,用于购物决策;二是小范围市场研究,比如采集某个品类下几十个 SKU 的价格走势,输出分析报告;三是内部竞品监控,频率很低,总量很小。

这类场景的共同点是:数据量不大、请求频率低、对私域用户数据无涉及。风控基本不会触发,合规风险也相对可控。

8.2 需要规避的东西

有几种行为明确不建议碰:高频抓取导致对方服务器压力骤增;采集用户个人维度的信息;把抓取到的平台数据二次打包出售;刻意绕过反爬机制做大规模商业采集。这些行为无论是从技术风险还是道德风险上,都容易把自己搭进去。

我在实际项目里给自己定过几条规矩:第一,单次任务规模控制在可解释的范围内,不做全站数据搬运;第二,采集到的数据只用于自用分析,不公开传播、不转售;第三,遇到验证码或者封禁提示,立即停止,而不是人机对抗;第四,robots 协议里明确禁止的目录,不去碰。守住这四条,项目基本能在安全边界内长期运转。

最后再分享一个小经验。很多人把爬虫项目的成败押在反爬对抗上,天天研究怎么绕验证码、怎么伪装指纹。我做了这些年爬虫,反而觉得最核心的能力是数据建模和任务调度。你需要想清楚抓什么字段、怎么组织存储、怎么控制节奏、怎么处理异常。技术上绕得了一时,绕不了一世;但调度策略和数据管理能力,才是项目能持续跑的根基。希望这篇 Java 爬取京东商品信息与价格的完整梳理,能让你少走一些弯路。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 6:27:23

27B大模型部署实战:量化档位与显存配置全指南

把27B这个档位的模型从零开始部署起来&#xff0c;关键其实就两个问题&#xff1a;量化档位选哪个&#xff0c;手上这台机器到底能不能跑。这两件事不搞清楚&#xff0c;后面全是折腾。比如你兴冲冲下了一个FP16的27B权重&#xff0c;一加载就OOM&#xff1b;或者你贪心选了Q2量…

作者头像 李华
网站建设 2026/10/14 6:27:19

新能源汽车双向充放电系统架构与V2G技术解析

1. 新能源汽车双向充放电系统概述在新能源汽车快速普及的今天&#xff0c;车载充电系统(OBC)已经不再是简单的单向AC/DC转换器。现代电动汽车的能量管理系统正在向"车网互动"(V2G)方向发展&#xff0c;这使得双向充放电技术成为行业热点。这套系统主要由以下几个核心…

作者头像 李华
网站建设 2026/10/14 6:27:01

Agent-Reach:多智能体协作中的可达性治理与调度实践

Agent-Reach这个名字我第一次看到的时候&#xff0c;第一反应是“又一个智能体通信框架”&#xff0c;但真正把资料翻完、在本地把Demo跑起来之后&#xff0c;我意识到它解决的是一个比通信更底层、也更让人头疼的问题——在多智能体协作场景下&#xff0c;服务的“可达性”到底…

作者头像 李华
网站建设 2026/10/14 6:26:45

游戏逆向与反作弊:攻防原理、技术栈与可落地的检测方案

1. 这块领域到底在玩什么如果你对"游戏逆向"四个字的第一反应是外挂、破解、灰色地带&#xff0c;那这篇文章可能和你想的不太一样。我做了几年反作弊方向的研发&#xff0c;日常工作中打交道最多的恰好就是游戏逆向——只不过我的立场在防守端&#xff1a;别人想办法…

作者头像 李华
网站建设 2026/10/14 6:26:45

Ubuntu 22.04下RoboCup 3D仿真平台源码编译与物理仿真调优

1. 项目概述&#xff1a;为什么在Ubuntu上装RoboCup 3D仿真平台不是“配环境”&#xff0c;而是搭一座桥RoboCup 3D仿真平台——这个名字听起来像某个高校实验室角落里积灰的旧项目&#xff0c;但实际它是一套高度工程化的多智能体协同验证系统。我第一次接触它&#xff0c;是在…

作者头像 李华
网站建设 2026/10/14 6:26:40

短信投诉号过滤与平台普通黑名单:企业短信风控的关键分水岭

在企业短信运营体系中&#xff0c;很多团队常常把短信投诉号过滤和普通短信黑名单混为一谈&#xff0c;认为只要做了黑名单拦截就万事大吉。事实上&#xff0c;这两者在数据来源、判定逻辑和风控目标上存在根本性差异。作为国内较早深耕号码风险检测领域的技术服务商&#xff0…

作者头像 李华

关于博客

这是一个专注于编程技术分享的极简博客,旨在为开发者提供高质量的技术文章和教程。

订阅更新

输入您的邮箱,获取最新文章更新。

© 2025 极简编程博客. 保留所有权利.