网易考拉(现考拉.com,现部分业务并入天猫国际)曾是国内领先的跨境电商平台,聚焦海外正品美妆、母婴、保健品等品类。尽管平台业务调整,其商品详情数据(如海外直邮信息、关税、品牌资质等)仍对跨境电商分析、比价工具等场景有参考价值。由于网易考拉无公开官方 API,开发者需通过页面解析实现商品详情(item_get)的获取。本文将系统讲解接口对接逻辑、技术实现、反爬应对及数据解析要点,帮助开发者构建稳定的商品详情获取系统。
一、接口基础认知(核心功能与场景)
二、对接前置准备(环境与 URL 结构)
三、接口调用流程(基于页面解析)
四、代码实现示例(Python)
import requests
import time
import random
import re
from bs4 import BeautifulSoup
from fake_useragent import UserAgent
from typing import Dict, List
class KaolaItemApi:
def __init__(self, proxy_pool: List[str] = None):
self.base_url = "https://www.kaola.com/product/{item_id}.html"
self.inventory_api = "https://www.kaola.com/ajax/product/{item_id}/inventory" # 库存接口
self.sales_api = "https://www.kaola.com/ajax/product/{item_id}/sales" # 销量接口
self.ua = UserAgent()
self.proxy_pool = proxy_pool # 代理池列表,如["http://ip:port", ...]
def _get_headers(self) -> Dict[str, str]:
"""生成随机请求头"""
return {
"User-Agent": self.ua.random,
"Referer": "https://www.kaola.com/",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Cookie": "kaola_uuid=xxx; user_id=xxx; Hm_lvt_xxx=xxx" # 替换为实际Cookie
}
def _get_proxy(self) -> Dict[str, str]:
"""随机获取代理"""
if self.proxy_pool and len(self.proxy_pool) > 0:
proxy = random.choice(self.proxy_pool)
return {"http": proxy, "https": proxy}
return None
def _parse_price(self, price_str: str) -> float:
"""清洗价格字符串(去除¥、逗号等)"""
if not price_str:
return 0.0
price_str = re.sub(r"[^\d.]", "", price_str)
return float(price_str) if price_str else 0.0
def _parse_specs(self, spec_elements) -> Dict[str, str]:
"""解析规格参数(跨境商品核心字段)"""
specs = {}
for elem in spec_elements:
text = elem.text.strip()
if ":" in text: # 匹配“key:value”格式
key, value = text.split(":", 1)
specs[key.strip()] = value.strip()
return specs
def _parse_static_data(self, html: str) -> Dict[str, str]:
"""解析静态HTML中的基础信息"""
soup = BeautifulSoup(html, "lxml")
# 提取规格参数
spec_elements = soup.select("div.spec-list li")
specs = self._parse_specs(spec_elements)
return {
"title": soup.select_one("h1.product-title")?.text.strip() or "",
"images": [img.get("src") for img in soup.select("div.product-gallery img") if img.get("src")],
"price": {
"current": self._parse_price(soup.select_one("div.price-current")?.text or ""),
"original": self._parse_price(soup.select_one("div.price-original")?.text or ""),
"tax": self._parse_price(re.search(r"关税:¥([\d.]+)", soup.select_one("div.tax-info")?.text or "")?.group(1) or ""),
"shipping_fee": self._parse_price(re.search(r"运费:¥([\d.]+)", soup.select_one("div.shipping-info")?.text or "")?.group(1) or "")
},
"cross_border": {
"origin": soup.select_one("div.origin-info")?.text.strip().replace("产地:", "") or "",
"import_type": soup.select_one("div.import-type")?.text.strip().replace("进口方式:", "") or "",
"customs_info": soup.select_one("div.customs-info")?.text.strip() or ""
},
"specs": specs,
"brand": soup.select_one("div.brand-name a")?.text.strip() or "",
"service": {
"after_sale": soup.select_one("div.after-sale")?.text.strip() or "",
"traceable": "可溯源" in (soup.select_one("div.trace-info")?.text.strip() or "")
},
"url": soup.select_one("link[rel='canonical']")?.get("href") or ""
}
def _fetch_dynamic_data(self, item_id: str, headers: Dict[str, str], proxy: Dict[str, str]) -> Dict:
"""调用动态接口获取库存和销量"""
dynamic_data = {"stock": 0, "monthly_sales": 0, "comment_count": 0}
try:
# 获取库存
inventory_url = self.inventory_api.format(item_id=item_id)
inv_resp = requests.get(inventory_url, headers=headers, proxies=proxy, timeout=10)
inv_data = inv_resp.json()
dynamic_data["stock"] = inv_data.get("stock", 0)
# 获取销量和评价数
sales_url = self.sales_api.format(item_id=item_id)
sales_resp = requests.get(sales_url, headers=headers, proxies=proxy, timeout=10)
sales_data = sales_resp.json()
dynamic_data["monthly_sales"] = sales_data.get("monthlySales", 0)
dynamic_data["comment_count"] = sales_data.get("commentCount", 0)
except Exception as e:
print(f"动态数据获取失败: {str(e)}")
return dynamic_data
def item_get(self, item_id: str, timeout: int = 10) -> Dict:
"""
获取网易考拉商品详情
:param item_id: 商品ID(如1234567)
:param timeout: 超时时间
:return: 标准化商品数据
"""
try:
# 1. 构建URL并发送请求
url = self.base_url.format(item_id=item_id)
headers = self._get_headers()
proxy = self._get_proxy()
# 随机延迟,避免反爬
time.sleep(random.uniform(2, 4))
response = requests.get(
url=url,
headers=headers,
proxies=proxy,
timeout=timeout
)
response.raise_for_status()
html = response.text
# 2. 解析静态数据
static_data = self._parse_static_data(html)
if not static_data["title"]:
return {"success": False, "error_msg": "未找到商品信息,可能item_id错误或商品已下架"}
# 3. 获取并合并动态数据
dynamic_data = self._fetch_dynamic_data(item_id, headers, proxy)
# 4. 整合结果
result = {
"success": True,
"data": {
"item_id": item_id,** static_data,
"trade": {
"monthly_sales": dynamic_data["monthly_sales"],
"comment_count": dynamic_data["comment_count"],
"stock": dynamic_data["stock"]
},
"update_time": time.strftime("%Y-%m-%d %H:%M:%S")
}
}
return result
except requests.exceptions.HTTPError as e:
if "403" in str(e):
return {"success": False, "error_msg": "触发反爬,建议更换代理或Cookie", "code": 403}
return {"success": False, "error_msg": f"HTTP错误: {str(e)}", "code": response.status_code}
except Exception as e:
return {"success": False, "error_msg": f"获取失败: {str(e)}", "code": -1}
# 使用示例
if __name__ == "__main__":
# 代理池(替换为有效代理)
PROXIES = [
"http://123.45.67.89:8888",
"http://98.76.54.32:8080"
]
# 初始化API客户端
api = KaolaItemApi(proxy_pool=PROXIES)
# 获取商品详情(示例item_id)
item_id = "1234567" # 替换为实际商品ID
result = api.item_get(item_id)
if result["success"]:
data = result["data"]
print(f"商品标题: {data['title']}")
print(f"品牌: {data['brand']} | 产地: {data['cross_border']['origin']} | 进口方式: {data['cross_border']['import_type']}")
print(f"价格: ¥{data['price']['current']}(含关税¥{data['price']['tax']}) | 原价: ¥{data['price']['original']} | 运费: ¥{data['price']['shipping_fee']}")
print(f"库存: {data['trade']['stock']}件 | 月销: {data['trade']['monthly_sales']}件 | 评价: {data['trade']['comment_count']}条")
print(f"核心规格:")
# 打印前5条规格参数
for i, (key, value) in enumerate(list(data['specs'].items())[:5]):
print(f" {key}: {value}")
print(f"售后服务: {data['service']['after_sale']} | 可溯源: {'是' if data['service']['traceable'] else '否'}")
else:
print(f"获取失败: {result['error_msg']}(错误码: {result.get('code')})")