Python 按 ASIN 读取Amazon商品详情:先拆解 JSON,再保存价格和规格 把 HTTP 200 当作商品查询成功代码会在错误的位置继续往下跑。本轮父 ASIN 返回 HTTP 200但 MCP isErrortrue。本文从调用和解析层级展开附与真实响应一起验证的提取函数。样本记录2026-09-28详见下文范围说明未独立核对 Amazon 页面不把本轮返回值当持续报价。第三步如何连接 MCP 并调用商品工具本轮通过 Streamable HTTP 与远程 MCP 服务完成 initialize、initialized 通知和 tools/list再调用 pangolinfo_capabilities。服务返回名称 pangolinfo-mcp、版本 0.9.2工具发现结果有 21 项。本文使用的是连接当时的工具定义不把官网某处显示的工具数量当成固定契约。下面是通用 MCP 客户端配置示意。密钥占位值要替换为你自己的凭证具体客户端字段以其支持方式为准本轮使用的是临时会话客户端未把凭证写进项目也没有修改用户全局客户端配置。接入说明见连接与鉴权文档。{mcpServers:{pangolinfo:{url:https://mcp.pangolinfo.com/mcp,headers:{Authorization:Bearer YOUR_API_KEY}}}}工具参数应来自 tools/list而不是照着模型记忆拼接。下面这组参数已用于本轮 MCP 白色款请求。注意 MCP 的 zipcode 在 arguments 顶层REST 则把它放到 bizContext 中两者不能不加转换就复制。{name:get_amazon_product,arguments:{asin:B0CMZFCQ6D,site:amz_us,zipcode:10041,format:json}}你也可以向已接入 MCP 的 Agent 明确交代任务“查询美国站 ASIN B0CMZFCQ6D邮区 10041。只调用商品详情返回标题、原始价格、卖家、父 ASIN、RAM 和存储容量记录缺失字段不追加评论采集。”这个提示把目标和调用范围一起给出避免一个详情问题扩展成不必要的评论或搜索任务。MCP 连接失败应该先查哪一层本轮第一次用 Python urllib 客户端时收到 Cloudflare 403 / 1010响应说明限制了客户端签名。之后改用标准 curl HTTP 客户端同一服务的初始化与鉴权成功。这是本机接入过程记录不意味着遇到 403 都能用同一办法解决也不意味着密钥曾经失效。区分网络接入、MCP 握手、工具参数和商品业务结果可以减少无效排查。401 需要检查凭证工具 BAD_INPUT 需要改参数父编号的 SERVER 错误需要核对对象或有限复测。我们对父编号复测一次仍失败后停止没有依赖 retriableyes 无限重复请求。第四步REST 怎么调用JSON 应从哪里读取若要用 Python 获取亚马逊商品详情可以直接调用 REST 商品接口。下面给出请求体示例 ASIN 与本轮 REST 请求一致。url 留空content 填 ASINparserName 使用 amzProductDetail按 ASIN 与按完整 URL 两种输入方式不要混成互相矛盾的目标。{url:,parserName:amzProductDetail,site:amz_us,content:B0CMZFCQ6D,format:json,bizContext:{zipcode:10041}}请求发送到商品文档列出的/api/v1/scrape鉴权放 Authorization 头内容类型为 application/json。REST 本轮返回 HTTP 200、外层 code 0、pointCost 1.0再进入 data.json 数组。MCP 则先返回 JSON-RPC 结果商品数据位于 result.content 中的文本 JSON。两条路径最终出现相同的商品结果层级。先看请求是否取得响应再检查 MCP isError 或 JSON-RPC error然后检查业务 code、任务 code 和 results。只有这些检查都完成才轮到比对返回 ASIN。解析流程如果直接写成 response.price读到的是不存在的路径如果只判断 HTTP 200父编号与格式错误也会被误记成成功。真实响应里哪些字段需要保留原义以下是 MCP 美国站 10041 白色款响应的字段节选。为了阅读省略了其他字段和数组元素结构与展示值来自原始返回不是重新模拟的响应。它代表 2026-09-28 本轮采样不能作为发布日或阅读日的报价。{asin:B0CMZFCQ6D,parentAsin:B0GP8D698X,title:Apple iPhone 15 Pro, 512GB, White Titanium - Unlocked (Renewed),price:$619.00,size:8 GB,color:,seller:{hasLink:true,name:Prime Renewed Store,link:/gp/help/seller/at-a-glance.html/refdp_merchant_link?ieUTF8sellerA1NY8YUA22BXMMasinB0CMZFCQ6Dref_dp_merchant_link,id:A1NY8YUA22BXMM},delivery:{deliveryTime:Tomorrow, September 29,fastestDelivery:},attributes:[{value:8 GB,key:RAM Memory Installed},{value:512 GB,key:Memory Storage Capacity},{value:White Titanium,key:Color}]}这条商品结果有 51 个顶层键、49 项 attributes、3 个 images 元素另有不同用途的图片数组。键数量不是 51 项业务条件都已满足更不表示每个键都有值。读取图片时也应明确选择 images 还是高分辨率数组去重和尺寸判断由自己的展示需求决定。RAM Memory Installed 的值是 8 GBMemory Storage Capacity 是 512 GBColor 是 White Titanium。顶层 size 同样为 8 GB而 color 是空字符串。一个只靠顶层 size 和 color 的目录导入程序会把 RAM 当成存储或错过已经存在于 attributes 的颜色。处理方法是按属性名建立映射同时保存原数组不能按数组第几项取值。商品标题中的 Renewed 是购买对象的一部分不能在比较新品报价时删去。seller.name 与 seller.id 应和 price 一起保存这次两个邮区返回不同卖家若只导出 ASIN 和价格就丢掉了解释差异所需的信息。price 的美元符号也不应被默默删除后直接跨市场求差。为什么 18 个变体条目不等于 18 件商品白色款 variantDetails 有 18 项但按 ASIN 去重只有 15 个值。同一个子 ASIN 在容量、颜色、运营商或商品状态选项里可重复出现所以这个数组首先是页面选项信息不是经过验收的独立商品清单。返回的变体价格为空也不能把当前选中价格复制到所有选项。黑色子 ASIN B0CMZ5KBNS 在另一次请求中返回 $617.95卖家为 Cell Surfers它与白色款共享同一个 parentAsin。这个样本支持我们把两者作为需要分别查询的购买选项但仍不证明已覆盖整个商品族。详情对象与变体映射的完整设计会放到后续专题。详情页评论同样有范围限制。10041 白色款响应有 13 个 reviews 元素90001 响应则为 null。这个差异不能被解释成“该地区没有评论”。若业务需要分页、筛选或评论归属研究应单独评估 Amazon Review API不要用详情页片段替代评论数据集。MCP 与 REST 商品响应的读取路径依据 2026-09-28 实测响应绘制。第五步怎样用 Python 保存 JSON 和 CSV最小请求能解释接口保存程序则需要为失败留痕。配套脚本 product_by_asin.py 支持实时 REST 调用与离线证据读取凭证从环境变量获取经标准输入传给 curl原始响应先保存再检查状态和字段。运行环境需要 Python 3.10 及以上和 curl密钥由运行环境注入不写进源代码。python3 examples/product_by_asin.py--asinB0CMZFCQ6D--siteamz_us--zipcode10041--outputcaptures复现文章里的既有结果时可以使用 fixture 模式它读取随项目保存的证据不再调用远程接口。不要把离线回放写成一次新的实时查询。输出会标明 fixture 或 live并且检查输入 ASIN、站点和邮区是否与证据记录一致避免把别的样本贴上新参数标签。python3 examples/product_by_asin.py--asinB0CMZFCQ6D--siteamz_us--zipcode10041--fixture04_元数据/evidence/product-us-10041.json--outputreplay下面是解析商品结果的核心代码。完整可运行脚本与文章一起交付包含网络调用、原始记录保存、JSON 输出和 CSV 导出这里集中展示最容易被忽略的状态检查。代码兼容任务数组元素是对象或 JSON 字符串两种形态本轮实际收到的是对象。importjsondefextract(response):iferrorinresponse:raiseValueError(JSON-RPC error; inspect saved evidence)ifresultinresponse:resultresponse[result]ifresult.get(isError):raiseValueError(MCP tool error; inspect saved evidence)parts[x[text]forxinresult.get(content,[])ifx.get(type)text]iflen(parts)!1:raiseValueError(Unexpected MCP content shape)responsejson.loads(parts[0])ifresponse.get(code)!0:raiseValueError(API business error)tasksresponse.get(data,{}).get(json)ifnotisinstance(tasks,list)ornottasks:raiseValueError(Missing task list)rows[]fortaskintasks:taskjson.loads(task)ifisinstance(task,str)elsetaskifnotisinstance(task,dict)ortask.get(code)!0:raiseValueError(Task error)itemstask.get(data,{}).get(results)ifnotisinstance(items,list)ornotitemsornotall(isinstance(x,dict)forxinitems):raiseValueError(Missing product results)rows.extend(items)returnrowsFetch a single ASIN through REST, or inspect a saved MCP/REST capture. Requires Python 3.10 and curl for live requests. No credentials in files. importargparseimportcsvimportjsonimportosfrompathlibimportPathimportreimportsubprocessfromdatetimeimportdatetime,timezonefromuuidimportuuid4defextract(response):iferrorinresponse:raiseValueError(JSON-RPC error; inspect saved evidence)ifresultinresponse:resultresponse[result]ifresult.get(isError):raiseValueError(MCP tool error; inspect saved evidence)parts[x[text]forxinresult.get(content,[])ifx.get(type)text]iflen(parts)!1:raiseValueError(Unexpected MCP content shape)responsejson.loads(parts[0])ifresponse.get(code)!0:raiseValueError(API business error)tasksresponse.get(data,{}).get(json)ifnotisinstance(tasks,list)ornottasks:raiseValueError(Missing task list)rows[]fortaskintasks:taskjson.loads(task)ifisinstance(task,str)elsetaskifnotisinstance(task,dict)ortask.get(code)!0:raiseValueError(Task error)itemstask.get(data,{}).get(results)ifnotisinstance(items,list)ornotitemsornotall(isinstance(x,dict)forxinitems):raiseValueError(Missing product results)rows.extend(items)returnrowsdefcsv_safe(value):textifvalueisNoneelsestr(value)returntextiftext.lstrip().startswith((,,-,))elsetextdefmain():parserargparse.ArgumentParser()parser.add_argument(--asin,requiredTrue)parser.add_argument(--site,defaultamz_us)parser.add_argument(--zipcode,requiredTrue)parser.add_argument(--fixture,typePath)parser.add_argument(--output,typePath,defaultPath(captures))argsparser.parse_args()asinargs.asin.strip().upper()ifnotre.fullmatch(r[A-Z0-9]{10},asin):parser.error(ASIN must contain 10 letters/digits)args.output.mkdir(parentsTrue,exist_okTrue)ifargs.fixture:recordjson.loads(args.fixture.read_text())reqrecord.get(request,{})contextreq.get(params,{}).get(arguments,{})ifreq.get(method)tools/callelsereq captured_asincontext.get(asin,context.get(content))captured_zipcontext.get(zipcode,context.get(bizContext,{}).get(zipcode))if(captured_asin,context.get(site),captured_zip)!(asin,args.site,args.zipcode):raiseValueError(Fixture request differs from CLI context)modefixtureelse:keyos.environ.get(PANGOLINFO_API_KEY,)ifnotkeyor\ninkeyor\rinkey:parser.error(Set PANGOLINFO_API_KEY in the process environment)payload{url:,parserName:amzProductDetail,site:args.site,content:asin,format:json,bizContext:{zipcode:args.zipcode}}starteddatetime.now(timezone.utc).isoformat()configurl https://scrapeapi.pangolinfo.com/api/v1/scrape\nrequest POST\nforhin[Content-Type: application/json,Authorization: Bearer key]:configheader json.dumps(h)\nconfigdata json.dumps(json.dumps(payload))\nenv{k:vfork,vinos.environ.items()ifk!PANGOLINFO_API_KEY}procsubprocess.run([curl,-sS,--max-time,60,--config,-,--write-out,\n%{http_code}],inputconfig,textTrue,capture_outputTrue,envenv)rawproc.stdout.replace(key,[REDACTED])body,_,statusraw.rpartition(\n)record{request:payload,started_at_utc:started,received_at_utc:datetime.now(timezone.utc).isoformat(),http_status:int(status)ifstatus.isdigit()else0,curl_exit:proc.returncode,raw_body:body}try:record[response]json.loads(body)exceptValueError:record[response]{}modelivestemargs.output/(mode-uuid4().hex)stem.with_suffix(.raw.json).write_text(json.dumps(record,ensure_asciiFalse,indent2))ifrecord.get(http_status)!200orrecord.get(curl_exit,0):raiseValueError(Transport failure; inspect saved evidence)rowsextract(record[response])outputs[]foriteminrows:ifitem.get(asin)!asin:raiseValueError(Returned ASIN mismatch; inspect saved evidence)attrs{}forainitem.get(attributes)or[]:attrs.setdefault(a.get(key),[]).append(a.get(value))outputs.append({mode:mode,requested_asin:asin,returned_asin:item[asin],site:args.site,requested_zipcode:args.zipcode,received_at_utc:record.get(received_at_utc),source_captured_at:None,destination_verified:None,title:item.get(title),price_raw:item.get(price),size_raw:item.get(size),attributes_by_name:attrs,ready_for_price_comparison:False})stem.with_suffix(.json).write_text(json.dumps(outputs,ensure_asciiFalse,indent2))columns[requested_asin,returned_asin,site,requested_zipcode,title,price_raw]withstem.with_suffix(.csv).open(w,newline,encodingutf-8-sig)ashandle:writercsv.DictWriter(handle,fieldnamescolumns)writer.writeheader()writer.writerows({k:csv_safe(row.get(k))forkincolumns}forrowinoutputs)print(json.dumps({mode:mode,records:len(outputs),output_stem:str(stem)}))if__name____main__:main()导出的 JSON 保留原始价格、size、按名称分组的属性、请求邮区和客户端接收时间。source_captured_at 与 destination_verified 在缺少证据时保持 nullready_for_price_comparison 明确设为 false因为通过结构和身份检查还不能确认价格可比。CSV 是便于查看的平面投影不包含原始响应的全部信息。属性名重复时程序保留多个值不用后一个静默覆盖前一个。缺失值不补成 0。针对外部文本导出到表格的场景CSV 还处理了可能被表格软件解释为公式的起始字符原始 JSON 不因此被改写。这里的保存规则是应用侧设计不是 Pangolinfo 响应字段。实际接入时哪些结果应该停下来核对观察到的结果本轮证据应采取的动作HTTP 200但 isErrortrue父编号和格式错误先识别工具错误禁止继续读商品字段size 与业务容量不同8 GB 与属性中的 512 GB按属性语义映射保留原值相同 ASIN卖家与价格不同两个邮区的白色款比较上下文不单独归因于邮区格式正确、code 0但内容冲突构造编号的标题与类目隔离记录补页面或其他证据shippingFee0、部分配送字段为空本轮正常手机响应保留未知状态不推定到手价这张表不是建议每次请求都做人工审核。结构、身份、必填字段和已知格式可以自动检查标题与类目冲突、属性语义变化或卖家切换则进入更具体的业务规则。规则要服务于任务商品图片补全与价格告警不必共用一条相同的通过标准。想看其他业务怎样利用采集数据可以阅读Pangolinfo 数据采集案例。这些案例是进一步阅读本篇的实时记录是独立实验不借其他案例的规模或效果为本轮请求背书。使用 Agent 工作流时也可了解 Amazon Scraper Skill它不会替你消除字段含义和比较口径的问题。按 ASIN 获取亚马逊商品详情第一步是拿到数据下一步是让每条值保留它属于谁、在哪个市场、按什么条件取得。可以从本例的一个子 ASIN 开始保存原始响应和请求参数再检查自己最关心的字段。你的任务若是比价先确认卖家、规格、币种和配送条件若是补目录先确认属性映射和来源。三次子商品请求的价格与卖家对照2026-09-28 非同时请求价差不能单独归因于邮区。实测对象为手机图中鞋子图标仅作示意。