ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

京东关键词搜索API调用实战:item_search_pro接口解析与Python示例

京东关键词搜索API调用实战:item_search_pro接口解析与Python示例 做电商数据分析和竞品监控的时候我经常需要拿到京东平台上指定关键词下的商品列表。用过一段时间手工复制后来发现效率实在太低商品标题、价格、销量这些信息光靠肉眼整理一天也处理不了几个关键词。后来接入了京东关键词搜索 item_search_pro API 接口用 Python 写了一段调用逻辑才把整套流程稳定下来。这篇文章就把我实际在用的代码拿出来逐行拆解讲清楚每个参数、每行代码到底在干什么顺便把我在调试过程中踩过的坑也一并整理出来。如果你打算做价格监控、选品分析、比价工具或者只是想快速把“京东搜索结果”变成结构化数据这篇文章应该能帮你节省不少试错时间。先说清楚一个前提无论你用的是京东开放平台官方接口还是第三方电商数据服务商提供的 item_search_pro 接口都需要先在对应平台注册应用、获取自己的 app_key 和 app_secret并严格遵守平台调用规范和频率限制。下面所有代码均基于“合法的 API 调用”这个前提来写后面我还会再单独强调合规问题。1. 为什么我建议用 item_search_pro 做京东关键词搜索1.1 从业务场景看接口价值假设你负责的电商项目需要监控“蓝牙耳机”、“智能手表”、“机械键盘”这三个关键词下的京东 TOP50 商品价格和销量变化。如果依赖人工每天打开京东网页搜索、复制、粘贴到 Excel一个人一天最多维护几个关键词而且容易出错。用页面爬虫虽然能自动化但京东网页版有大量动态加载、反爬校验、登录墙写爬虫的成本和维护成本都很高。item_search_pro 接口解决的核心问题就是把“搜索 结果提取”这个动作标准化。你只需要把关键词和其他筛选条件传给接口它就把商品列表结构化地返回给你。返回的字段里通常包含商品ID、标题、主图、价格、销量、店铺名称、链接等这些信息直接对应我们做数据分析和价格监控时最关心的内容。1.2 接口选型官方接口和第三方聚合接口怎么权衡很多朋友会问京东开放平台不是也有自己的商品搜索接口吗为什么还要用 item_search_pro我的理解是京东官方确实有成体系的开放接口比如京东联盟的jd.union.open.goods.query但这类接口主要面向推广返佣场景权限申请相对严格部分数据项不一定能满足所有分析需求。而名为 item_search_pro 的接口在不少第三方电商数据服务平台上都能找到属于“关键词搜索商品”这一类聚合型 API它一般把多个平台的搜索能力封装好调用方式统一返回字段也相对规整。选型时我的建议是如果能直接申请到官方接口并且你的业务场景完全匹配优先用官方接口稳定性和权限边界都有保障。如果官方接口覆盖不到你的需求再考虑第三方服务商。此时一定要确认服务商的数据来源合规、接口稳定、文档完善并且你有正式的使用授权。无论如何都不要试图绕过平台限制去抓取非公开数据这在技术、法律和商业道德上都存在风险。我在下面的代码示例里调用方法名直接写item_search_pro这是为了让标题和代码保持对齐。实际使用时请你替换成你的目标服务商文档里提供的确切 method 名称。不同服务商虽然接口命名有差异但签名方式、请求参数、返回解析的思路是共通的。2. 调用前的准备工作凭证、公共参数和签名机制2.1 申请 app_key 和 app_secret不管用哪家平台第一步都是拿到应用凭证也就是 app_key应用标识和 app_secret应用密钥。这两个字符串相当于你的 API 身份证明。申请流程一般是在开放平台注册开发者账号完成企业或个人信息认证。创建应用选择需要的 API 权限。等待审核通过后在应用详情页查看 app_key 和 app_secret。这里有两个必须注意的细节第一app_secret 是敏感信息。我不建议你把它硬编码在代码里更不要提交到公开的代码仓库中。我的做法是放在环境变量里或者使用本地独立的配置文件并且在 .gitignore 中忽略它。第二测试环境和生产环境的请求地址、参数可能不一致。一定要仔细阅读你的服务商文档区分好沙箱环境和正式环境的 endpoint。2.2 公共参数和业务参数的区别一次完整的 API 请求由公共参数和业务参数两部分组成。公共参数是每次调用都要带的比如method要调用的接口方法名app_key应用标识timestamp当前请求的时间戳通常精确到秒format返回格式一般用 jsonv接口版本号sign签名值业务参数是具体某个接口特有的比如 item_search_pro 需要keyword搜索关键词page当前页码pageSize每页返回条数sort排序方式销量、价格、综合可能还有cid类目、brand品牌、price_min/price_max价格区间等理解这两类的区别非常重要。签名通常是对所有参数包括公共参数和业务参数一起参与计算的所以你不能只把业务参数传给签名函数而把公共参数漏掉。2.3 签名规则最容易出错的一环不同平台的签名算法大同小异常见步骤是将所有请求参数除 sign 本身按参数名的字典序升序排列。按照“参数名参数值”的顺序拼接成一个原始字符串。在原始字符串的首尾分别拼接上 app_secret。对整体做 MD5 计算然后转成大写字符串。举个例子。假设你有三个参数app_keyabc123 methoditem_search_pro timestamp2025-04-01 12:00:00排序后字符串就是app_keyabc123methoditem_search_protimestamp2025-04-01 12:00:00再在两边加上 secret哈希转大写就是最终的 sign。这段逻辑看着简单但出错率极高。我后面会在常见问题里详细说几个我实际踩过的坑比如时间格式不一致、参数值为空、排序时大小写不同等。3. 代码逐行解析从请求到解析 JSON3.1 环境准备和依赖导入我的开发环境是 Python 3.9只需要用到requests、hashlib、time、json、os。其中requests不是标准库需要先安装pip install requests在代码里这样导入import requests import hashlib import time import json import os说明一下各模块的用途requests发送 HTTP 请求接收响应。hashlib计算 MD5 签名。time生成当前时间戳。json解析返回的 JSON 数据。os读取环境变量避免把密钥写在代码里。3.2 核心代码及逐行注释下面这段代码是我的一个简化可运行版本。为了便于讲解我把函数拆成三部分读取配置、生成签名、发送请求并解析结果。import requests import hashlib import time import json import os # 从环境变量读取应用凭证避免密钥硬编码 APP_KEY os.getenv(JD_APP_KEY, your_app_key) APP_SECRET os.getenv(JD_APP_SECRET, your_app_secret) # API 请求地址请替换为服务商文档提供的真实地址 API_URL https://api.example.com/routerjson def make_sign(params, secret): 根据参数生成 MD5 签名。 规则参数名按字典序排序拼接成字符串首尾加 secretMD5 后转大写。 # 1. 过滤掉 sign 本身和值为空的参数减少出错概率 items [(k, v) for k, v in params.items() if k ! sign and v is not None] # 2. 按参数名排序 items.sort(keylambda item: item[0]) # 3. 将参数名和参数值拼接成原始字符串 raw .join(f{k}{v} for k, v in items) # 4. 首尾加上 secret raw secret raw secret # 5. 计算 MD5 并转大写 sign hashlib.md5(raw.encode(utf-8)).hexdigest().upper() return sign def search_items(keyword, page1, page_size20, sortdefault): 京东关键词搜索 item_search_pro 接口调用。 :param keyword: 搜索关键词如 蓝牙耳机 :param page: 页码 :param page_size: 每页数量通常 10-40 :param sort: 排序方式default/price_asc/price_desc/sale :return: 解析后的 JSON 数据字典 # 组装业务参数和公共参数 params { method: item_search_pro, # 接口方法名替换为你的服务商对应的值 app_key: APP_KEY, # 应用标识 timestamp: time.strftime(%Y-%m-%d %H:%M:%S), # 请求时间戳 format: json, # 返回格式 v: 1.0, # 接口版本 keyword: keyword, # 搜索关键词 page: page, # 页码 pageSize: page_size, # 每页条数 sort: sort, # 排序 } # 生成签名并放入参数 params[sign] make_sign(params, APP_SECRET) # 发送 GET 请求requests 会自动对参数做 URL 编码 try: resp requests.get(API_URL, paramsparams, timeout10) resp.raise_for_status() # 如果状态码不是 200抛出异常 except requests.exceptions.RequestException as e: print(f请求失败: {e}) return None # 解析 JSON 响应 try: result resp.json() except json.JSONDecodeError: print(响应内容不是合法 JSON) return None return result if __name__ __main__: data search_items(蓝牙耳机, page1, page_size10) print(json.dumps(data, ensure_asciiFalse, indent2))下面我逐段解释这段代码里的关键点。读取环境变量APP_KEY os.getenv(JD_APP_KEY, your_app_key) APP_SECRET os.getenv(JD_APP_SECRET, your_app_secret)这里把 key 和 secret 放在环境变量中。运行前在终端设置export JD_APP_KEY你的app_key export JD_APP_SECRET你的app_secret也可以写到项目根目录的.env文件配合python-dotenv加载。总而言之不要在代码里出现明文密钥这是最基本的工程素养。生成签名的函数make_sign里有一个容易被忽略的步骤过滤v is not None。我在实际开发中遇到过因为某个参数值为None导致拼接后的字符串变成param1None加进签名字符串后服务端死活验证不通过的情况。所以稳妥起见先把空参数排除掉。另外有些平台的签名规则要求排除sign本身这里也做了处理。排序用items.sort(keylambda item: item[0])然后直接join拼接。注意参数名和参数值之间不加任何连接符参数之间也不加这是很多平台签名规则的写法但也有的平台要求加特定分隔符。强烈建议以你的服务商文档为准。组装请求参数在search_items函数中公共参数和业务参数放在同一个字典里。timestamp我使用的是strftime(%Y-%m-%d %H:%M:%S)。这个格式是很多平台的默认时间格式但也不是绝对的。有的平台要求yyyyMMddHHmmss有的要求秒级时间戳。如果签名一直报错先检查是不是时间格式不匹配。requests 自动编码中文requests.get(API_URL, paramsparams)这一句库内部会帮我们把keyword里的中文进行 URL 编码。比如“蓝牙耳机”会被编码成%E8%93%9D%E7%89%99%E8%80%B3%E6%9C%BA。如果你习惯自己手动拼接 URL一定要用urllib.parse.quote处理否则中文参数会传输异常。超时和异常处理timeout10表示 10 秒内没有响应就放弃。网络环境不稳定时可以加一个重试机制我这里先只做最简单的异常捕获。返回的内容如果不是合法 JSON就打印提示并返回None避免后续处理和 None 值相关的问题。3.3 返回数据解析拿到需要的关键字段调用成功后不同服务商返回的 JSON 结构会有差异。通常外层会有状态码和消息数据部分在一个data或items字段里。我这里用一个典型结构演示{ code: 0, msg: success, data: { total: 1234, page: 1, pageSize: 10, items: [ { skuId: 100012345, title: 蓝牙耳机 真无线入耳式 运动降噪 超长续航, price: 99.0, originalPrice: 199.0, image: https://img.example.com/blue_tooth.jpg, shopName: 某某官方旗舰店, sales: 5000, url: https://item.jd.com/100012345.html } ] } }解析的核心逻辑是这样的def parse_items(result): if not result: return [] # 判断接口是否成功 if result.get(code) ! 0: print(接口错误, result.get(msg)) return [] items result[data][items] parsed [] for item in items: parsed.append({ sku_id: item[skuId], title: item[title].strip(), price: float(item[price]), shop: item.get(shopName, ), sales: int(item.get(sales, 0)), url: item.get(url, ) }) return parsed这里有几个细节值得注意先判断code。很多刚上手的同学不看状态码直接去取data一旦接口报错就会报KeyError。先判断返回码是好习惯。items里的字段不一定都存在。有些商品没有销量有些没有店铺名。用get(key, default)来取避免报错。数值类型转换。很多接口把价格、销量返回成字符串比如99.0、5000如果不转类型直接做运算会得到意外结果。转成float和int是必要的。标题清洗。商品标题里经常有空格、换行、特殊符号strip()是最基本的处理。3.4 一个完整的小例子把结果打印成表格我们可以把上面两个函数连起来做一个简单的调用if __name__ __main__: raw search_items(机械键盘, page1, page_size5) items parse_items(raw) for i, item in enumerate(items, 1): print(f{i}. {item[title]}) print(f 价格{item[price]} | 销量{item[sales]} | 店铺{item[shop]})输出效果1. 机械键盘 87键 有线键盘 电竞游戏办公 外设 价格99.0 | 销量1280 | 店铺某某键盘专营店 2. 机械键盘 合金面板 无线机械手感键盘 电脑台式笔记本通用 价格49.9 | 销量3200 | 店铺某某数码旗舰店到这一步你已经完成了“搜索关键词 - 拿到商品列表 - 结构化展示”的核心链路。4. 从单页调用到批量采集翻页、去重和频率控制4.1 翻页需要考虑的边界条件搜索引擎类的接口单页返回条数有限制。你要拿到某个关键词下的前 N 个商品就必须循环调用。翻页代码看起来很简单def search_all_pages(keyword, max_pages5, page_size20): all_items [] for page in range(1, max_pages 1): data search_items(keyword, pagepage, page_sizepage_size) items parse_items(data) if not items: break all_items.extend(items) # 如果当前页返回的数量小于 page_size说明已经到最后一页 if len(items) page_size: break return all_items这里有个隐藏问题当前页返回数量小于 page_size 就退出这个概念其实是依赖返回结构的。如果接口返回了total更严谨的做法是total int(data[data][total]) already_fetched page * page_size if already_fetched total: break另外接口单页默认最大值可能是 40 或 100不是越大越好。page_size 设置太大会导致响应变慢还会提高超时风险。我试过 page_size100经常有请求超时后来改成 20稳定性明显提升。4.2 商品去重用 skuId 建立唯一索引同一个商品可能出现在多个关键词下也可能在一次翻页中重复出现。为了避免数据重复最稳妥的方案是在数据库里给skuId建唯一索引。如果你只是保存在本地列表里可以用一个集合来记录seen set() unique_items [] for item in all_items: sku_id item[sku_id] if sku_id not in seen: seen.add(sku_id) unique_items.append(item)这比直接按照标题去重要可靠。商品标题会因为促销信息变化但skuId一般不会变。4.3 频率控制防超频的实用策略大部分 API 都有 QPS 限制比如每秒最多 1 次或 5 次。如果你写一个 for 循环猛刷很容易被限流甚至封禁。我的做法是在循环里加一个延时import time for keyword in keyword_list: items search_all_pages(keyword, max_pages3, page_size20) # 处理 items time.sleep(1) # 每轮请求之间至少间隔 1 秒如果需要更高阶的并发也应该使用带有令牌桶限流的异步框架。但我的经验是对于个人数据分析和监控场景同步加延时完全够用而且最不容易出问题。4.4 缓存减少重复请求的好办法同一关键词在几分钟内搜索出来的结果不会剧烈变化。如果你有一个每日定时任务完全可以把keyword page sort作为缓存的 keyTTL 设为 300 秒。用 Python 的字典就能做一个最简单的进程内缓存_cache {} def get_cache(keyword, page, sort): return _cache.get((keyword, page, sort)) def set_cache(keyword, page, sort, data, ttl300): _cache[(keyword, page, sort)] (time.time(), data) def is_fresh(entry, ttl300): return time.time() - entry[0] ttl正式项目中可以换成 Redis。缓存最大的好处是帮你省 API 配额也能让程序跑得更快。5. 高频踩坑签名、编码、字段类型和权限问题5.1 签名不一致的排查思路签名不一致是调用 item_search_pro 时遇到的最常见错误。我总结过一个排查顺序确认参与签名的参数是否一致。常见错误是业务参数已经加上了但签名函数里只用了公共参数或者反过来。确认参数值为空时如何处理。有的平台要求空值也参与签名有的要求排除。这里以文档为准。确认排序规则。大多数是按参数名 ASCII 码升序排列但有些平台可能要求按下划线或特定顺序。确认拼接方式。参数名和值之间有没有分隔符参数之间有没有首尾要不要加 secret。确认编码。MD5 之前要不要先做 URL 解码或者转成 UTF-8。中文参数和英文参数混在一起时容易被忽略。我通常的调试方法是把服务端返回的错误码和签名后生成的raw字符串打印出来对照文档逐字符检查。很多平台会提示“sign 不匹配”但不会告诉你哪里不一致只能自己比对。5.2 中文关键词编码问题requests库虽然会自动编码但有一个坑如果你的服务商要求参数值先进行一次 URL 编码再参与签名此时requests的自动编码会让签名字符串变得不可控。这种情况下我建议手动拼接 URL 参数from urllib.parse import quote encoded_keyword quote(keyword, safe) params_for_sign {keyword: encoded_keyword, ...}然后请求时直接传paramsparams_for_sign或手动拼接 URL。一定要先搞清楚服务商文档对编码的约定。京东系的接口一般遵循 UTF-8但有些第三方网关可能默认 GBK遇到乱码就要多留个心眼。5.3 返回 code 非 0 的常见错误为了快速定位问题我整理了一张常见的返回码排查表以我遇到的通用情况为例错误码/提示常见原因处理方式code1001参数缺失或参数名错误对照文档检查 method、keyword、pageSize 等code1002签名错误按前面 5.1 的流程排查code1003app_key 不存在或被禁用检查应用状态code1004请求频率超限降低调用频率增加延时code1005权限不足检查应用是否开通 item_search_pro 接口权限code2001关键词为空检查 keyword 字段code5000服务端内部错误稍后重试或联系服务商实际使用中每个服务商的错误码对应关系不同但排查思路是通用的先确认参数和签名再确认权限和频率最后再怀疑服务端。5.4 字段类型和缺失处理的坑我在一次采集任务中发现某个商品的价格字段返回的是空字符串直接float()会抛异常。改造后的处理方式def parse_price(price_str): try: return float(price_str) except (TypeError, ValueError): return 0.0同理销量字段可能返回--或5万这样的格式需要单独处理。这个看似简单的小问题在数据量大了之后会非常影响程序稳定性所以解析函数一定要写得健壮。5.5 合规使用提醒最后必须强调一下合规问题。接口调用前请确认你拥有该接口的合法调用权限服务商已经为你开通。你的使用方式和频率符合平台的服务条款。你采集的数据只用于合法用途不涉及用户隐私不批量存储敏感信息。不要恶意高频调用、不要滥用、不要将数据二次出售。我自己的项目原则是API 调用量尽量控制在配额范围内缓存优先减少无意义请求。这既是尊重服务商也是保证自己业务长期稳定运行的前提。6. 进一步扩展定时任务、多关键词管理和多平台对比6.1 把它封装成一个定时任务把上面的search_all_pages和parse_items封装到一个类里然后写一个定时脚本。我用schedule库做每日定时执行import schedule import time def job(): keywords [蓝牙耳机, 机械键盘, 智能手表] for kw in keywords: items search_all_pages(kw, max_pages3, page_size20) save_to_csv(kw, items) schedule.every().day.at(08:00).do(job) while True: schedule.run_pending() time.sleep(60)save_to_csv可以用标准库csv实现。这样每天上午 8 点自动抓取一次数据写入本地文件后续做价格趋势分析就非常方便。6.2 多关键词管理如果你要监控几百个关键词建议把这些关键词放在一个文本文件或数据库表里而不是硬编码在代码里。程序启动时读取关键词列表逐条处理。还可以给每个关键词加一个优先级优先采集重要关键词剩余配额再分配给次要关键词。6.3 多平台对比分析如果你同时也在调用淘宝、拼多多等平台的类似接口可以在数据落地时增加一个platform字段比如jd、taobao、pdd。这样后续做跨平台比价时就能很方便地按平台过滤和聚合。我记得当初做第一个版本时就是简单地在每条记录后面加了一个字符串后来写 SQL 时发现这个字段太有用了。收尾个人实操中的一点体会我在实际使用这个接口的过程中最大的体会是API 调用本身并不复杂把代码写出来也就是几十行真正花时间的是理解业务需求、设计数据结构和处理各种异常情况。如果你第一次接触这类接口我的建议是不要着急写完整工程先用一个小脚本把一个关键词跑通把返回 JSON 打印出来仔细看看字段结构再逐步加翻页、去重、存储和定时任务。最后再分享一个小技巧所有跟商品链接相关的字段最好都保存完整 URL不要只存 ID。因为后续你在做数据分析时可能想跳转回商品页核对信息这时候直接拼接链接要比再调一次接口方便得多。另外商品标题里经常包含很多促销词和属性词如果你后期要做文本分析建议单独建一个字段存清洗后的“纯净标题”把括号、空格、重复的促销话术去掉你会发现数据分析的准确率会上一个台阶。希望这篇逐行拆解能帮你顺利跑通京东关键词搜索数据的获取流程。
返回列表