news 2026/10/6 19:22:45

Python爬虫实战:详解京东商品价格与详情页抓取流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python爬虫实战:详解京东商品价格与详情页抓取流程

聊到Python爬虫,绕不开的一个经典练手项目就是电商商品信息的抓取。我这次写的是京东商品价格及详情页抓取,属于一个偏入门的实战场景,目标是输入一个商品链接或SKU,自动拿到标题、价格、店铺、评价等基础信息,再整理成结构化结果。这类项目非常适合正在学Python、刚接触爬虫但不知道从哪开头的人,也适合做比价工具、价格监控、竞品分析前,先练一练数据采集基本功。

为什么选京东而不是淘宝或拼多多?因为京东的商品页URL规则非常简单清晰,价格信息又有相对稳定的接口,对新手友好,能让你把注意力放在“请求-解析-存储”这条主线上,而不是一开始就陷进各种加密参数里。本文会以我的实际抓取过程为线索,带上环境准备、请求头伪装、HTML解析、价格接口调用、批量采集以及遇到302、滑块验证码、编码乱码时的排查经验。每段都按“我当时是怎么想的,后来怎么做的”来写,尽量不说废话。

1. 项目整体设计与思路拆解

1.1 先搞清楚数据到底在哪

很多新手写爬虫,拿过URL就requests.get然后把HTML打印出来,发现页面里怎么找不到价格?这是因为现在的电商页面早就不是一次HTTP请求能拿全所有数据的结构。京东商品页里的标题、图片、店铺信息有一部分在服务端渲染的HTML里,但价格、促销、配送、库存这类实时性很强的字段,通常是通过额外的XHR接口异步加载,再由前端JavaScript填充到页面上的。

我刚开始做这个项目的时候,第一件事不是写代码,而是打开浏览器的开发者工具(F12),切到“Network”面板,清空请求记录,再刷新一次商品页。你会看到页面发起了几十个请求,按类型过滤一下,只留XHR或Fetch,就能看到一堆JSON接口。其中带price、stock、promotion这类关键字的请求,就是我们要重点关注的。

这个思路对整个爬虫生涯都通用:不管目标网站长什么样,先搞清楚数据源是渲染在HTML里,还是通过接口返回。搞错这一步,后面全是白干。

1.2 HTML解析和接口调用怎么选

理论上,数据从HTML里能拿,从接口里也能拿,但两者的稳定性和成本差别很大。

HTML解析的优点是“所见即所得”,浏览器里看到什么,HTML里一般就有对应节点。缺点是页面结构一旦改版,选择器全要重写,而且服务器返回的HTML里包含大量无关内容,正经解析起来繁琐。接口方案则是直接拿到结构化JSON,字段清晰、性能好、更新相对稳定,但前提是你得先找到接口、搞清楚参数怎么带,有的接口还会校验签名和Cookie。

我的建议很直接:详情页的基础信息,比如标题、店铺名,用HTML解析来做,练基本功;价格这类实时数据,优先找接口拿数据,因为价格接口通常返回的就是干净的数字,用正则或JSON解析都很方便。这两种能力以后换个平台也能复用,不算白学。

1.3 合规意识先讲清楚

写爬虫之前,一定要明白边界。本文的代码和思路只用于个人学习、技术研究和少量数据验证,不要用来大规模采集商业数据,更不要拿去卖钱。京东的robots协议对于目录的爬取有限制,实际生产环境中的数据采集还需要评估法律风险,并严格遵守目标网站的访问协议。

实际操作中,我会主动控制请求频率,比如每抓一个商品休息1到5秒,一天总量控制在几十条以内。这不是“胆小”,而是对自己和对目标服务器的基本尊重。爬虫技术本身是中性的,用得好是效率工具,用不好就是给自己找麻烦。

2. 环境准备与核心依赖安装

2.1 Python环境怎么搭最省事

做Python爬虫,我推荐直接用Python 3.8以上版本,太老的版本在处理一些第三方依赖时会有兼容性问题。如果你还没装Python,去官网下载安装包,安装时记得勾选“Add Python to PATH”,这一步虽然基础,但踩坑的人特别多。

装好之后,我习惯先建一个独立的虚拟环境,避免把系统Python搞乱。在项目目录下执行:

python -m venv venv

然后激活环境。Windows下是:

venv\Scripts\activate

Mac或Linux下是:

source venv/bin/activate

激活后提示符前面会出现(venv),说明已经进入虚拟环境了。接下来安装依赖就不会污染全局。

2.2 用到的库和国内镜像源

本次项目会用到的Python库不多,核心就四个:

  • requests:发HTTP请求,爬虫入门必装
  • lxml:高性能HTML/XML解析库,配XPath用
  • beautifulsoup4:另一个解析HTML的选择,适合习惯CSS选择器的人
  • pandas:最后把抓到的数据整理成表格,导出CSV或Excel时好用

安装命令是:

pip install requests lxml beautifulsoup4 pandas

国内用户如果下载慢,建议临时指定清华镜像源:

pip install -i https://pypi.tuna.tsinghua.edu.cn/simple requests lxml beautifulsoup4 pandas

这个镜像源地址是公开的,速度比默认源快很多,我至今还在用。如果你在的公司或学校有内部源,那就更好了。

2.3 先封装一个带点“伪装”的请求模板

在正式写抓取逻辑前,我建议先把请求和响应的基础封装好。不要每次请求都临时拼headers,你会在调试时被自己乱写的代码搞疯。

下面这段是我常用的小模板,把超时、UA、重试都提前考虑好:

import requests from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry def make_session(): session = requests.Session() retry = Retry( total=2, backoff_factor=0.5, status_forcelist=[500, 502, 503, 504] ) adapter = HTTPAdapter(max_retries=retry) session.mount("http://", adapter) session.mount("https://", adapter) session.headers.update({ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", }) return session session = make_session()

注意这里的Retry只对连接问题和5xx错误生效,4xx不会自动重试。原因很简单,4xx是请求本身有问题,重试一万次也没用。

3. 详情页抓取实操:以标题、店铺、评价数为例

3.1 拆解商品URL和SKU的关系

京东的商品URL非常有规律,长这样:

https://item.jd.com/100012043978.html

最后那串数字就是商品的SKU ID,是商品的唯一标识。只要你能拿到SKU列表,就能批量构造URL,这也是京东比较好抓的原因之一。反观淘宝的链接里带着一堆spm参数,一个页面一个样,新手看着就头大。

我习惯在代码里把SKU作为入口参数,而不是直接传整个URL,这样后面批量抓取时只需要传ID列表就能跑通。

3.2 请求商品页并确认响应状态

用前面封装好的session去请求详情页:

sku_id = "100012043978" url = f"https://item.jd.com/{sku_id}.html" resp = session.get(url, timeout=(3, 6)) print(resp.status_code) print(resp.encoding) print(resp.apparent_encoding)

这里有个小细节:timeout我习惯传元组(3, 6),意思是连接超时3秒,读取超时6秒。只传一个数字的话,两个阶段用同一个值,不够灵活。请求返回后,先看状态码是不是200,再看响应编码。

京东页面返回的charset通常是utf-8,但保险起见,我会用resp.apparent_encoding来动态判断编码,或者直接手动设置为utf-8,然后再打印前500个字符确认一下HTML内容正常显示。这一步看着笨,但能省掉后面一堆玄学乱码问题。

3.3 用XPath提取标题和店铺名称

HTML拿到手后,解析方案我推荐lxml,性能好,XPath写起来也直观。先导入库:

from lxml import etree html = resp.text doc = etree.HTML(html)

接下来是关键:怎么找到标题节点?我不会把选择器写得死板,而是给你一套定位方法。旧版京东页面的标题在class="sku-name"的h1标签里,店铺名称在class="name"的a标签里,类似这样:

title_nodes = doc.xpath("//h1[contains(@class, 'sku-name')]/text()") shop_nodes = doc.xpath("//div[contains(@class, 'name')]/a/@title")

但页面结构是可能变的。所以我更推荐的做法是:先在浏览器里右键目标元素,选“检查”,看它所在的DOM层级,再回头写XPath。比如标题节点如果带class,就优先用class定位;如果层级很深,可以稍微放宽一点条件,用contains匹配部分类名,降低改版带来的影响。

还有一个很实际的问题:提取出来的文本可能带换行和大量空格。京东页面尤其如此,一个标题字符串里能塞进好多\n和空格。我写了段简单的清理逻辑:

def clean_text(text): return " ".join(text.split()) if text else ""

text.split()会按空白字符切开再重新拼接,这样能一次性清掉所有多余换行和空格,很实用。

3.4 评价数和详情图,能拿多少拿多少

评价数通常在页面上表现为“100万+”这种格式,在源码里经常出现在带comment相关class的节点里。你可以先搜索“评论”或“评价”这几个字,再找附近的数字节点。这个字段不一定稳定,所以我一般把它定位为“可选字段”,拿不到就跳过,不影响主流程。

至于详情图,京东的详情页主图在经过懒加载处理后,很多时候img标签的src是一个占位符,真正的图片地址在>[ { "id": "J_100012043978", "p": "4999.00", "m": "5999.00", "op": "4999.00" } ]

不同字段含义不一样:p是促销价,也就是用户实际看到的价格;m是市场价,经常被划掉的那个;op是普通售价。以哪个为准,取决于你业务怎么定义“价格”。如果只是监控成交价,看p就对了。

4.2 构造请求并解析价格JSON

接口找到了,代码就简单了:

def fetch_price(sku_id): price_url = "https://p.3.cn/prices/mgets" params = { "skuIds": f"J_{sku_id}" } resp = session.get(price_url, params=params, timeout=(3, 6)) resp.raise_for_status() data = resp.json() if data: return data[0] return None

用params传参,requests会自动把参数拼到URL后面,比自己手动拼字符串安全,能正确转义特殊字符。返回后直接调resp.json()解析,不用手动处理字符串。

这个接口胜在返回稳定、速度极快,而且支持一个请求传多个SKU,这就是后面并发和批量采集的基础。实测一下,单个SKU的响应基本在几十毫秒量级,一次请求传10个SKU也是几十毫秒,效率比逐个解析HTML高一个数量级。

4.3 把详情页和价格拼成一条完整记录

有了详情页信息和价格接口,就可以把两者合并成一条结构化数据。我用一个字典来组织:

def crawl_one_sku(sku_id): detail = {} # 详情页解析出的标题、店铺等 price = fetch_price(sku_id) record = { "sku_id": sku_id, "url": f"https://item.jd.com/{sku_id}.html", "title": detail.get("title", ""), "shop": detail.get("shop", ""), "price": price.get("p") if price else "", "market_price": price.get("m") if price else "", } return record

字段里有英文有中文,但这不是给用户看的,是给后续数据处理程序看的,所以保持稳定和可预测才是最重要的。后面如果存数据库,这个字典的key直接当列名即可。

5. 批量抓取、限速与反爬应对

5.1 多商品批量抓取怎么写才靠谱

批量抓取前,先准备好一批SKU ID。你可以手工维护一个CSV,也可以用selenium之类从列表页自动提取,但那是另一个话题。我这次用一个简单的SKU列表演示:

sku_ids = ["100012043978", "100008348542", "100012129956"]

批量抓的循环没什么花哨的,关键是节奏控制。我写了一个带随机延时的版本:

import time import random for sku_id in sku_ids: try: record = crawl_one_sku(sku_id) print(record) except Exception as e: print(f"抓取 {sku_id} 失败: {e}") time.sleep(random.uniform(1, 3))

随机延时是为了避免固定间隔的机械感,因为固定间隔的请求在服务端看来更像机器行为。1到3秒的间隔对学习场景足够了。

5.2 并发能不能上?我建议先别急

很多人在掌握了单请求之后就急着上并发,觉得并发越高越快。理论上,Python网络IO密集型的爬虫多线程确实能提速,用concurrent.futures的ThreadPoolExecutor就能做:

from concurrent.futures import ThreadPoolExecutor, as_completed with ThreadPoolExecutor(max_workers=5) as pool: futures = {pool.submit(crawl_one_sku, sku_id): sku_id for sku_id in sku_ids} for future in as_completed(futures): record = future.result() print(record)

但我的经验是:新手阶段,并发带来的收益远没有你想象的那么大,反而会增加被封风险。京东对单IP的请求频率有一定限制,一旦触发风控,轻则请求变慢,重则直接弹滑块验证码。先老老实实把单线程流程跑通,观察一段时间确认没问题,再一点点增加并发数。爬虫不是越快越好,是越稳越好。

如果后续真要大规模抓取,要考虑的不只是线程池,而是更完整的调度方案,比如用Redis做队列、多节点分布式采集,但那已经属于分布式爬虫的范畴了,等把单机版跑利索了再去了解也不迟。

5.3 遇到302跳转、验证码和IP限制怎么办

批量抓的时候,最大的拦路虎是各种风控。最典型的情况是:请求发出去,返回的不是200,而是302跳转到一个验证页面,或者直接弹一个滑块。

我建议抓之前先在浏览器里打开一次商品页,手动过一遍验证,让浏览器留下正常访问的Cookie,再把这些Cookie抓出来,放到请求头里。requests会话第一次请求时带上这个Cookie,很多时候就能顺利通过验证。但要注意,Cookie是有时效的,过期之后需要重新获取。

如果连续请求被限制,最直接的应对是退一步:降低频率、增加延时、减少批次。至于IP代理,确实是一种常见方案,但学习阶段真不建议碰,一是花费高,二是代理池质量参差不齐,三是容易依赖这些手段而忽略了更本质的反爬规避思路。

再补充一点:一旦触发了滑块验证码,我个人建议直接停手,不要傻傻地去破解验证码。模拟点击、轨迹识别这些方案从技术上讲可以做,但涉及的安全合规边界非常模糊,而且成本极高,对学习项目来说完全不值当。把频率降下来,或者过段时间再跑,往往更实际。

5.4 数据存储:CSV是默认选项

抓到的数据如果只打印出来,那就白抓了。我会先用pandas整理成DataFrame,然后导出CSV:

import pandas as pd records = [] for sku_id in sku_ids: records.append(crawl_one_sku(sku_id)) df = pd.DataFrame(records) df.to_csv("jd_products.csv", index=False, encoding="utf-8-sig")

注意编码用utf-8-sig而不是utf-8,这个细节一定要记住。utf-8-sig会在文件开头写入BOM标记,Excel打开时才不会乱码。我早期用utf-8导出CSV,发给朋友用Excel打开全乱,后来才知道是编码问题。这个小坑很典型,新手基本都遇到过。

6. 常见问题与排查技巧实录

6.1 问题速查表

我把这个项目里容易踩的坑整理成了一个速查表,遇到问题可以先对着查一遍。

现象可能原因解决思路
返回403 Forbidden请求头太少,被识别为爬虫补全UA、Referer、Accept等请求头,使用Session维持会话
返回302跳转触发了风控或登录校验更换Cookie,降低请求频率,避免短时间高频访问
返回HTML但找不到价格节点价格走异步接口返回按4.1节的方法去Network里找价格接口
中文字符乱码编码判断错误设置resp.encoding = "utf-8"或使用apparent_encoding
取到的标题带一堆换行和空格页面源码包含大量空白字符用" ".join(text.split())清理空白
图片URL取不到图片在懒加载属性里尝试@data-src、@data-lazy-img等属性
请求偶尔超时网络波动或服务器稍慢设置timeout=(3,6),增加重试逻辑
批量抓取时被封IP频率太高,触发反爬策略降低并发、增加随机延时,必要时换IP池
页面改版后选择器失效页面结构变化重新打开开发者工具定位节点,更新XPath

6.2 一个典型的排查过程实录

有一次我抓详情页,所有商品都正常,只有某一个SKU返回的标题是空的。我以为是选择器写错了,但其他商品都能取到,说明代码本身没问题。后来我拿这个SKU在浏览器里打开,发现商品已经是“已下架”状态,页面结构和其他正常商品完全不同,标题位置变成了一个下架提示。

这就引出一个重要经验:爬虫代码要始终对异常情况保持宽容。下架的、缺货的、秒杀未开始的、错误页面的,各种场景都可能在抓取过程中遇到。我的做法是给缺失值一个默认结果,比如标题为空字符串,而不是直接让程序崩溃。同时,每抓一个商品就打印一条日志,能第一时间定位是哪些SKU出了问题。

6.3 关于页面改版的一点心态

做爬虫,一定要接受一个事实:选择器不是一劳永逸的。网站前端改了class,你的代码就废了。这不是你写得不对,而是技术环境的常态。我之前维护过一个类似的采集脚本,平均每两三个月就要更新一轮页面选择器。

应对方式就是模块化。把页面解析的代码单独封装成一个函数,不要和请求逻辑、存储逻辑混在一起。这样页面改版后,只需要改解析那一小块逻辑,其他部分不用动。这种代码组织思维,比多会几个库重要得多。

7. 我个人实操中的一个小习惯

最后分享一个我自己一直在用的习惯:拿到一个目标页面后,先用curl或requests把页面源码完整下载下来存成一个HTML文件,然后用浏览器打开这个本地文件,配合开发者工具慢慢研究节点结构。这样做的好处是,后面怎么改代码都不会因为页面原始内容丢失而抓瞎,也方便对比网站前后改版时的结构差异。

这个项目本身还有很多可以扩展的方向,比如加上定时任务做价格监控、把数据落到MySQL里做后续分析、接入更友好的通知机制,每一个方向都能延展成一篇文章。不过对于刚起步的人来说,先把单页抓明白、把接口找清楚、把反爬应对形成肌肉记忆,就已经是很大的进步了。爬虫这条路没有太多捷径,多写、多调试、多看页面源码,慢慢就有感觉了。

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

Agent-Reach 实战:用 CLI 统一 AI Agent 开发与部署

1. 项目缘起与核心定位Agent-Reach 这个名字第一次出现在我视野里的时候,我正被一堆零散的 AI Agent 工具链折腾得够呛。那段时间我在同时维护三个不同技术栈的智能体项目,一个基于 Python 的 LangChain 做知识问答,一个用 Rust 写的高频任务…

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

OpenShell:让Windows终端体验媲美Linux和macOS

在使用Windows终端时,我猜很多人跟我一样,多少有点羡慕Linux和macOS上那种开箱即用的终端体验:漂亮的提示符、方便的自动补全、一眼就能看懂的Git状态。过去想在Windows上达到这个效果,得手动装一堆第三方工具,挨个配置…

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

Tomcat 7绿色版zip部署指南:JDK8环境变量与启动避坑

简介:Apache Tomcat 7.0.103的Windows 64位免安装发行包,是面向Java Web应用开发与部署的成熟服务器环境,适合需要快速启动本地开发调试或搭建测试环境的开发者。整个压缩包含648个文件,总体积约10.38MB,内部既有HTML静…

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

魔术公式轮胎模型Matlab实现与参数辨识实战

1. 为什么我最终选定了魔术公式轮胎模型干车辆动力学仿真这行,轮胎模型是绕不过去的一道坎。纵向力、侧向力、回正力矩全都靠轮胎与地面的接触产生,模型选得不对,后面整车操纵稳定性、制动性能全是空中楼阁。跑了几年仿真,我个人的…

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

Rocfall落石分析软件安装全攻略:从下载到License配置一篇搞定

岩土圈做边坡设计的同行,对Rocfall这个名字应该不陌生。它是Rocscience旗下专门做二维落石运动分析的软件,用来模拟岩石从边坡滚落的路径、弹跳高度、动能变化和落点分布,直接为防护网、挡石墙、拦石栅的设计提供计算依据。很多公路、铁路、矿…

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

ActiveMovie控件播放器:老系统视频播放的注册、调用与迁移指南

简介:这份资源面向需要在Windows应用程序中集成视频播放功能的开发者,尤其是使用VC6与MFC进行多媒体编程的初学者和中级程序员。它基于微软早期的ActiveMovie控件(DirectShow前身,属ActiveX组件)构建,实现了…

作者头像 李华