
很多刚开始学 Python 的朋友都会问同一个问题想抓点电商网站的商品数据练手从哪里起步最合适我通常建议直接拿京东练手。理由很简单京东的商品列表页结构相对规整移动端接口虽然加了签名校验但 PC 端的商品列表页面至今保留着比较干净的 HTML 结构适合做分析和解析而且京东的商品数多、字段全做出来的数据表也像模像样。本篇文章就基于“Python 爬取京东商品数据”这个项目从环境准备、请求分析、源码实现到排错技巧一条线讲完代码直接贴出来就能跑。这是让我从“只会跟着写 requests.get”到“能独立完成一个采集小项目”的典型练习很适合 Python 入门已经掌握基础语法、想接触真实网页解析的读者。下面进入正题。1. 项目整体设计与思路拆解在写代码之前先把项目拆清楚。很多初学者一上来就搜关键词、直接写爬虫代码结果连要抓什么、数据从哪个接口来都搞不清后面全是坑。这个项目虽然叫“爬取京东商品数据”但实际上可以拆成三条数据链路列表页的商品主体信息、价格异步接口、数据落盘。我分别说明为什么这样设计。1.1 京东网页端的产物与数据来源京东搜索页和大多数电商站点一样商品列表由两部分组成第一部分是页面 HTML 里已经渲染出来的商品卡片包含商品标题、链接、店铺名称、商品编号skuId、评价数量等第二部分是价格。价格没有直接渲染在 HTML 里而是在页面加载完成后由浏览器异步请求一个独立的 JavaScript 接口这个接口的核心参数就是商品列表里的 skuId。所以整个爬虫采用“先拿列表页 HTML再批量请求价格接口最后合并数据并保存”的流程。这个思路比直接解析整个 JSON 或者用浏览器渲染引擎要轻量得多既不用安装笨重的 Selenium也不容易触发复杂的反爬校验。1.2 技术选型requests parsel 足够这个项目不需要用到 Scrapy 这种重型框架也不需要 Selenium 模拟浏览器。我的技术选型如下组件用途理由requests发送 HTTP 请求获取 HTML 和 JSON 数据简单直接社区资料多parsel解析 HTML 中的商品列表信息底层的 CSS 选择器与 scrapy 一脉相承以后上手 Scrapy 无缝切换python-dotenv可选管理请求头中的环境变量避免把 cookie 等个人信息硬编码进源码方便分享代码pandas / csv数据清洗与落盘生成 Excel 或 CSV 都很方便初学者对表格更熟悉有些人会用 lxml 或者 BeautifulSoup这两种也没问题。我选择 parsel 是因为它本身是从 Scrapy 中抽离出来的解析模块选择器语法和 Scrapy 完全一致练完这一个项目之后以后学 Scrapy 会轻松很多。1.3 合规预期与数据边界任何爬虫项目都应该先说清楚合规问题。这个教程只抓取商品公开列表页中的展示信息包括商品标题、价格、链接、店铺名和评价数量不采集用户个人信息不模拟登录不对网站造成压力所有程序的并发请求数量都维持在非常低的水平。数据仅用于个人学习、数据分析练习和非商业用途。如果你是拿来做商用项目请直接使用京东开放平台的正式接口并遵守相关协议。这是底线也是做任何爬虫项目都必须有的自觉。2. 环境准备从零搭好 Python 运行环境很多代码跑不起来八成是环境问题。这里我按 Windows 和 macOS/Linux 两种情况说明尽量把所有细节都覆盖到避免你在第一步就卡住。2.1 Python 安装与版本验证如果电脑上还没有 Python请到 Python 官网下载 3.9 以上版本的安装包。安装时务必勾选“Add Python to PATH”否则后面命令行里敲 python 会提示找不到命令。安装完成后在终端Windows 下是 cmd 或 PowerShellmacOS/Linux 下是 Terminal输入python --version如果输出类似Python 3.11.5说明安装成功。如果提示python 不是内部或外部命令多半是 PATH 没有配置好需要到系统环境变量里手动把 Python 安装目录和 Scripts 子目录加入 PATH。2.2 创建虚拟环境虚拟环境是隔离项目依赖的工具强烈建议每个 Python 项目都单独建一个避免多个项目的包互相冲突。在项目文件夹下执行python -m venv venv然后激活虚拟环境。Windows 下激活命令是venv\Scripts\activatemacOS/Linux 下激活命令是source venv/bin/activate激活成功后终端前面会出现(venv)标记说明你现在已经在虚拟环境内局部分装的包不会污染全局环境。2.3 安装项目依赖本项目的第三方库只有两个是必需的requests 和 parsel。pandas 只是最后保存 Excel 时用如果你更愿意用标准库 csv那连它都可以不装。安装命令如下pip install requests parsel pandas这里我统一用 pip 安装。如果你所在网络环境下载速度慢可以临时切换为国内镜像源例如清华镜像pip install requests parsel pandas -i https://pypi.tuna.tsinghua.edu.cn/simple安装完成后可以用下面的命令快速验证是否成功python -c import requests, parsel; print(ok)只要输出 ok环境就准备好了。3. 抓包分析搞清楚数据到底从哪来拿到任何网站第一步永远是分析请求而不是盲写代码。这一部分非常关键我把整个分析过程写清楚你照着操作一遍就能掌握通用的网页分析思路。3.1 分析商品列表页请求打开 Chrome 浏览器访问https://search.jd.com/Search?keyword笔记本电脑进入商品搜索列表页。按 F12 打开开发者工具切到“网络”Network标签然后刷新页面。你会看到很多请求但我们只需要找到那个返回 HTML 的主文档请求。筛选请求类型为 Document找到与搜索 URL 对应的那个请求。点开预览Preview标签你会看到整个页面的 HTML 结构。这个请求的请求头Headers里有个非常重要的信息User-Agent。你复制这个 UA待会写代码时放到请求头里能大幅降低被拒绝的概率。列表页中的商品数据嵌在ul classgl-warp clearfix这个容器中里面每个li classgl-item对应一件商品。这就是我们解析的入口。3.2 从 HTML 中定位商品字段我把一件商品的 HTML 结构简化后放出来方便你对照理解li classgl-item>https://p.3.cn/prices/mgets?skuIdsJ_100017516672返回结果是一段 JSON格式如下[ { p: 4499.00, op: 4699.00, m: 4899.00, id: J_100017516672 } ]其中p是促销价op是原价m是市场价。这个接口一次可以传多个 skuId用逗号分隔例如https://p.3.cn/prices/mgets?skuIdsJ_100017516672,J_100017516673这正好方便我们批量获取价格减少请求次数。注意参数中skuIds前面带了J_前缀这是京东接口对普通商品 SKU 的一种拼接规则。搞清楚了这三个核心信息——HTML 中的商品列表结构、翻页参数规律、价格接口返回格式这个爬虫项目的 80% 已经完成了。剩下的就是代码工程化的问题。4. 源码实现手把手写出可复用的采集脚本这部分是整篇教程的核心。我先把完整源码贴出来然后逐段解释关键逻辑并说一下为什么这么写。你不需要完全理解每一行但至少把主流程弄明白。4.1 完整源码import csv import random import time import requests from parsel import Selector class JDSpider: def __init__(self): self.headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36, Referer: https://www.jd.com/, Accept-Language: zh-CN,zh;q0.9, } self.price_url https://p.3.cn/prices/mgets?skuIds{} self.data [] def fetch_html(self, keyword, page): 获取搜索列表页的 HTML # PC 端关键词页面使用 keyword 参数翻页时 page 遵循奇偶规律 url https://search.jd.com/Search params { keyword: keyword, page: page * 2 - 1, } try: resp requests.get(url, paramsparams, headersself.headers, timeout10) resp.encoding utf-8 if resp.status_code 200: return resp.text else: print(f[错误] 请求失败状态码: {resp.status_code}) return None except Exception as e: print(f[异常] 请求页面出错: {e}) return None def parse_list_html(self, html): 从页面 HTML 中解析商品基础信息 sel Selector(texthtml) items sel.css(li.gl-item) products [] for item in items: sku_id item.css(::attr(data-sku)).get() title item.css(.p-name a::attr(title)).get() link item.css(.p-name a::attr(href)).get() shop item.css(.p-shop a::text).get() comment item.css(.p-commit a::text).get() if sku_id: products.append({ sku_id: sku_id.strip(), title: title.strip() if title else , link: https: link if link.startswith(//) else link, shop: shop.strip() if shop else , comment: comment.strip() if comment else , }) return products def fetch_price(self, sku_ids): 根据 skuIds 批量获取价格信息 sku_str ,.join([fJ_{sid} for sid in sku_ids]) try: resp requests.get(self.price_url.format(sku_str), headersself.headers, timeout10) resp.encoding utf-8 if resp.status_code 200: return {item[id].replace(J_, ): item for item in resp.json()} else: print(f[错误] 价格接口请求失败: {resp.status_code}) return {} except Exception as e: print(f[异常] 价格接口出错: {e}) return {} def crawl(self, keyword, pages5): 主入口抓取多页并合并价格 for page in range(1, pages 1): print(f正在抓取第 {page} 页关键词{keyword}) html self.fetch_html(keyword, page) if not html: continue products self.parse_list_html(html) if not products: print(f[提示] 第 {page} 页没有解析到商品可能触发了校验稍后重试) time.sleep(random.uniform(3, 6)) continue sku_ids [p[sku_id] for p in products] price_map self.fetch_price(sku_ids) for p in products: price_info price_map.get(p[sku_id], {}) p[price] price_info.get(p, ) p[origin_price] price_info.get(op, ) self.data.append(p) print(f第 {page} 页完成累计已采集 {len(self.data)} 条数据) time.sleep(random.uniform(1.5, 3.5)) def save_to_csv(self, filenamejd_products.csv): 保存到 CSV 文件 fieldnames [sku_id, title, price, origin_price, link, shop, comment] with open(filename, w, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnamesfieldnames) writer.writeheader() writer.writerows(self.data) print(f数据已保存至 {filename}共 {len(self.data)} 条) if __name__ __main__: spider JDSpider() spider.crawl(笔记本电脑, pages3) spider.save_to_csv()这段代码在设计上做了三件重要的事我将请求头单独拆出来方便扩充将解析逻辑和请求逻辑分离方便后续加字段将翻页转换为奇偶规律避免重复采集。最终的输出结果用 utf-8-sig 编码保存这是避免 Excel 打开 CSV 出现中文乱码的关键。4.2 代码关键点说明第一请求头里为什么必须带Referer京东部分接口会检查来源页面如果 Referer 不是京东自身的域名可能会返回异常或者空数据。带上之后模拟从站内跳转的访问行为请求成功率会明显提升。User-Agent务必要用浏览器真实的 UA 字符串不要用 requests 默认的python-requests/x.x后者很容易被服务端识别并拒绝。第二价格接口的批量参数拼接这是提升效率的核心。一次请求带 30 个 skuId 通常没有问题但建议单次不要超过 50 个否则有可能返回数据不完整。我的代码里以每页商品数来拼接一页 30 件商品就发 1 次价格请求非常轻量。第三限速的意义。time.sleep(random.uniform(1.5, 3.5))这段代码不是没事找事而是让每次请求之间有一个随机的间隔保证采集频率看起来更像人工操作。这个爬虫设计的目标是低负载、不打扰目标站点的正常服务所以宁愿慢一点也不要一次性发大量请求。4.3 数据保存与结果查看运行脚本后当前目录下会生成jd_products.csv文件。用 Excel 打开后你会看到类似下面的表头和数据sku_idtitlepriceorigin_pricelinkshopcomment100017516672某品牌笔记本电脑 16英寸 i7 16G 512G4499.004699.00https://item.jd.com/100017516672.html某品牌京东自营旗舰店5000条评价100018275188某品牌游戏本 15.6英寸 RTX40606299.006599.00https://item.jd.com/100018275188.html某品牌官方旗舰店2000条评价如果你更习惯 Excel 格式可以在save_to_csv方法后面追加一个 pandas 保存逻辑import pandas as pd def save_to_excel(self, filenamejd_products.xlsx): df pd.DataFrame(self.data) df.to_excel(filename, indexFalse) print(f数据已保存至 {filename}共 {len(df)} 条)注意如果使用to_excel需要额外安装openpyxl执行pip install openpyxl即可。4.4 自定义关键词和页码在脚本末尾的main区域你可以自由修改关键词和页码spider.crawl(手机, pages10)这里我建议以pages3起步测试跑通后再加大页码。直接一次性抓 50 页容易出现两个问题一是中途可能遇到验证码导致数据中断二是采集时间太长调试不方便。多次小规模运行比一次性大规模运行更稳。5. 常见问题与排查技巧实录这部分的每一个问题都是我实际运行过程中踩过的坑可以说是整个教程里最值钱的部分。我按照“现象 → 原因 → 解决方案”的方式挨个整理方便你遇到问题时直接对号入座。5.1 列表页返回空数据很多人在跑代码的时候发现打印出来“第 1 页没有解析到商品”于是怀疑解析逻辑写错了。通常有两种原因。第一种是你请求到的数据不是理想的商品列表 HTML而是京东的空搜索结果页或者验证页。这种情况在html里搜li classgl-item如果搜不到基本可以判定是这种问题。解决方案是先检查是否能手动访问同样的 URL。如果能访问多半是请求头不够完善补全User-Agent、Referer、Accept-Language三个字段后再试。第二种原因是翻页参数写错了。page1和page2在第一页可能都能返回结果但从第二页开始必须按奇偶规律转换。如果你直接用page2去请求第二页其实返回的还是第一页的数据这在代码里不容易直接察觉只有比对商品 skuId 时才会发现重复。解决方法是严格使用page * 2 - 1的规则。5.2 访问后被重定向到验证页面有时候你可能遇到页面提示需要做验证操作这是网站的正常风控机制目的就是识别异常访问。这时候不要硬顶也不要尝试绕过验证功能那已经超出了爬虫学习的正常范围。正确的做法是停止程序等待几分钟再继续降低请求频率把每次请求间隔调大到 5 秒以上更换关键词采集时的初始页面。这种验证机制的本质是提醒我们保持低骚扰的访问节奏采集本质上应该是对公开数据的礼貌访问。5.3 CSV 文件中文乱码用 Excel 打开 CSV 文件出现中文乱码几乎都是编码问题。解决办法是在写入文件时使用utf-8-sig编码而不是普通的utf-8。utf-8-sig会在文件开头加入 BOM字节序标记Excel 识别到 BOM 后就能正确处理中文。我在源码中已经使用了这个编码如果你是从别处复制的代码检查open函数中的encoding参数即可。5.4 价格接口返回空字典如果你发现price字段多半是空字符串先检查价格接口手动访问是否正常。在浏览器中直接打开https://p.3.cn/prices/mgets?skuIdsJ_100017516672这里有几个易错点。第一个是skuIds参数的前缀J_漏掉了就获取不到对应的价格信息。第二个是请求头中的Referer部分接口会校验来源确保请求头里有Referer: https://www.jd.com/。第三个是接口的反爬策略如果短时间内请求太多可能会出现部分返回为空加大请求间隔能缓解。但如果你已经手动在浏览器访问正常、代码却返回空请打开浏览器开发者工具对比代码发出去的请求和浏览器发出的请求重点检查请求头和参数名是否完全一致。5.5 多页采集时出现重复数据排查重复问题最好的办法是把 skuId 作为唯一标识。我一般会在代码里加一个集合用来去重seen set() for p in products: if p[sku_id] in seen: continue seen.add(p[sku_id]) price_info price_map.get(p[sku_id], {}) ...这套去重逻辑可以单独抽成函数。以我的经验来看翻页规则错误造成重复的案例占绝大多数先把page参数打印出来手动拼接 URL 看看返回结果问题基本就能定位。5.6 采集过程中断怎么办爬虫运行到一半中断非常常见网络波动、内存占用、临时封禁都有可能。我的建议是控制单次采集规模每跑完一页就立即保存一次而不是等全部跑完才落盘。这样即使中断已采集的数据也不会丢。可以简单改造一下crawl方法for page in range(1, pages 1): ... self.save_to_csv(filenamefjd_products_page_{page}.csv)分页保存之后再手动合并多个 CSV 文件或者之后写个简单的合并脚本都可以。数据安全在爬虫项目里同样值得重视宁可文件碎一点也别让自己辛苦采集的数据白费。6. 扩展方向从单页采集到数据分析实战等到上面的代码跑通了你可以进一步拓展这个项目让它和数据分析技能结合起来形成完整的数据分析链路。第一个方向是采集字段的扩展。当前代码只抓了商品标题、价格、店铺和评价数。如果你注意到商品列表页的 HTML 中还有.p-icons里的标签信息比如“自营”“京尊达”“放心购”等也可以一并解析出来作为后续做筛选分析时的标签特征。这样采集出来的数据就更接近真实业务中需要的商品全貌。第二个方向是把采集结果和可视化结合起来。用 pandas 读取 CSV算不同店铺的价格区间、评价数的分布再用 matplotlib 画柱状图或散点图。比如“笔记本电脑价格与评价数量分布图”就可以很直观地反映市场情况。我曾在一次分析中发现某个品牌的价格中位数明显高于同类但评价数却偏低这类信息结合业务来看是很有价值的。第三个方向是引入定时增量采集。比如每天早上自动跑一次把前一天的新品价格变化记录下来形成一份价格走势数据文件。这对监控商品价格波动、做采购决策会有直接帮助。实现方式很简单在原有代码外套一个循环前一次采集完成后等待 24 小时再继续即可。第四个方向是脱离网页版研究一下京东的移动端页面结构或者使用官方开放平台接口。不过这已经超出“保姆级入门”的范畴属于进阶进阶的路线。我的建议是先把当前这个项目扎实地做完把 requests、parsel、CSV 这三个基础能力练到位再考虑框架化和工程化。7. 写在最后的几个实操心得根据我个人经验这类爬虫项目最容易让人产生挫败感的不是代码不会写而是网络环境和网站反爬机制带来的不确定性。同一个脚本上午运行正常下午可能就被验证码挡住。这不代表代码有问题只说明你的请求频率触碰到了对方的风控阈值。遇到这种情况最有效的处理办法就是降速而不是继续硬试。另外我强烈建议你在真正动手爬取任何网站之前先写一小段“礼貌礼仪检查”这个网站有没有公开 APIrobots.txt 是否明确禁止了某些路径我的采集频率会不会影响其他用户正常访问如果这三个问题里有任何一个答案让你不安那就停下来换个方式获取数据。爬虫技术最好的实践场景是那些提供了公开接口、允许程序访问的服务以及用于学习和研究的实验性项目。最后再分享一个小技巧调试爬虫时不要把全部逻辑一次性写完再运行。我会先把fetch_html单独跑起来打印前 500 个字符看看结构确认拿到正常页面后再写解析逻辑解析出 skuId 后再用真实 skuId 手测价格接口。这样每个环节都得到验证整个项目一次跑通的概率会高很多。代码跑了三次、改了三次才算跑通这个过程中积累下来的调试经验才是比源码本身宝贵得多的东西。