淘宝拍立淘API实战:如何用Python快速搭建一个比价工具(附完整代码)
淘宝拍立淘API实战:如何用Python快速搭建一个比价工具(附完整代码)
你是否曾在社交媒体上看到一件心仪的商品,却不知道它叫什么名字,更无从查起价格?或者,作为一个小型电商运营者,你是否想快速了解某个竞品在淘宝上的价格分布,却苦于手动搜索效率低下?这正是图像搜索技术大显身手的场景。淘宝的拍立淘功能,早已从手机App里的一个趣味功能,演变成了开发者手中强大的数据抓取和分析工具。它背后的API接口,为我们打开了一扇门,让我们能够用程序化的方式,将一张图片转化为结构化的商品信息流。
今天,我们不谈枯燥的理论,直接上手实战。我将带你从零开始,用Python构建一个功能完整的比价工具。这个工具的核心,就是调用淘宝拍立淘API。但我们的目标远不止于调用一个接口那么简单。我们将深入探讨如何优化图像以提升识别率、如何处理API的限流与错误、如何将原始数据清洗并可视化,最终打造一个能稳定运行、提供商业洞察的实用工具。无论你是个人开发者想做个有趣的小项目,还是中小电商团队的成员希望提升选品效率,这篇文章都将提供一条清晰的路径。
1. 环境准备与API接入:迈出第一步
在开始写代码之前,我们需要把“战场”布置好。这包括准备好Python开发环境,以及最关键的一步——获取淘宝开放平台的API访问权限。别被“开放平台”吓到,整个过程就像注册一个普通网站账号一样简单,只是多了一些针对开发者的信息填写。
首先,确保你的电脑上安装了Python 3.7或更高版本。我强烈建议使用虚拟环境来管理项目依赖,这能避免不同项目间的包版本冲突。打开你的终端或命令行工具,执行以下命令来创建并激活一个虚拟环境:
# 创建虚拟环境,命名为 `taobao_env`
python -m venv taobao_env
# 激活虚拟环境
# 在 Windows 上:
taobao_env\Scripts\activate
# 在 macOS 或 Linux 上:
source taobao_env/bin/activate
激活后,你的命令行提示符前会出现 (taobao_env) 字样。接下来,安装我们项目所需的几个核心库:
pip install requests pillow opencv-python pandas matplotlib
简单解释一下这几个库的用途:requests 用于发送HTTP请求调用API;Pillow (PIL) 是强大的图像处理库;opencv-python (cv2) 提供更专业的图像预处理功能;pandas 和 matplotlib 则负责后续的数据分析和可视化。
注意:
opencv-python在某些系统上可能需要额外的系统依赖。如果安装失败,可以尝试先安装numpy,或者查阅OpenCV官方文档解决。
环境准备好后,重头戏来了:获取API密钥。访问淘宝开放平台官网,用你的淘宝或支付宝账号登录。在控制台里,点击“创建应用”。这里有个关键选择:应用类型。对于个人学习或内部工具开发,选择“自用型应用”即可,它审核相对宽松。填写应用名称、描述等信息后,你就能获得一对至关重要的密钥:App Key 和 App Secret。请像保管密码一样保管好它们,后续所有API请求的签名验证都靠它俩。
创建应用后,你还需要为它添加API权限。在应用管理的“接口管理”或“API权限”页面,搜索并添加 taobao.item_search_img 或 taobao.picture.search 接口(不同时期接口名可能有微调)。提交一个简单的使用场景说明,比如“用于个人学习的商品图像搜索与比价工具”,通常很快就能通过审核。
至此,我们的开发“粮草”已备齐。接下来,让我们进入最核心的环节:与API对话。
2. 核心接口调用与签名机制解析
拿到了App Key和Secret,就像拿到了大门的钥匙,但想进门,还得按特定的节奏敲门(签名)。淘宝API为了安全,要求对所有请求参数进行签名,服务器端会以同样的规则验签,不一致则拒绝请求。很多新手在这里栽跟头,所以我们先彻底搞懂这个签名流程。
签名的大致流程是:将所有请求参数(包括公共参数和业务参数)按参数名的ASCII码从小到大排序,然后拼接成字符串,最后在头尾加上你的App Secret,对这个整体字符串进行MD5加密,并转为大写。听上去有点绕,我们直接看代码如何实现一个健壮的签名函数:
import hashlib
import time
from urllib.parse import quote_plus
def generate_taobao_sign(params, app_secret):
"""
生成淘宝API请求签名。
参数:
params (dict): 所有请求参数字典。
app_secret (str): 应用的App Secret。
返回:
str: 大写的MD5签名字符串。
"""
# 1. 过滤掉值为None的参数,并按参数名ASCII升序排序
filtered_params = {k: v for k, v in params.items() if v is not None}
sorted_items = sorted(filtered_params.items(), key=lambda x: x[0])
# 2. 拼接键值对,值需要做URL编码(尤其是中文)
sign_string = app_secret
for key, value in sorted_items:
# 淘宝签名要求对参数值进行UTF-8编码的URL编码
encoded_value = quote_plus(str(value), safe='')
sign_string += f"{key}{encoded_value}"
sign_string += app_secret
# 3. 计算MD5并转为大写
md5_hash = hashlib.md5(sign_string.encode('utf-8'))
return md5_hash.hexdigest().upper()
这个函数有两个关键点:一是排序,二是对参数值进行 quote_plus 编码,这能正确处理中文等特殊字符。接下来,我们利用这个签名函数,构建一个完整的API请求类。这个类会封装公共参数、签名生成和请求发送,让后续调用变得清爽。
import requests
import json
class TaobaoImageSearcher:
def __init__(self, app_key, app_secret):
self.app_key = app_key
self.app_secret = app_secret
self.api_url = "https://eco.taobao.com/router/rest"
self.session = requests.Session() # 使用Session保持连接,提升效率
def call_api(self, method, **kwargs):
"""
调用淘宝API的通用方法。
参数:
method (str): API方法名,如 'taobao.item.search.img'。
**kwargs: 具体的业务参数。
返回:
dict: API返回的JSON数据解析后的字典。
"""
# 公共参数
common_params = {
'method': method,
'app_key': self.app_key,
'timestamp': time.strftime('%Y-%m-%d %H:%M:%S'),
'format': 'json',
'v': '2.0',
'sign_method': 'md5',
}
# 合并公共参数和业务参数
all_params = {**common_params, **kwargs}
# 生成签名
sign = generate_taobao_sign(all_params, self.app_secret)
all_params['sign'] = sign
# 发送POST请求
try:
# 注意:淘宝API通常要求表单格式(application/x-www-form-urlencoded)
headers = {'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'}
response = self.session.post(self.api_url, data=all_params, headers=headers, timeout=10)
response.raise_for_status() # 检查HTTP错误
return response.json()
except requests.exceptions.RequestException as e:
print(f"网络请求失败: {e}")
return None
except json.JSONDecodeError as e:
print(f"响应JSON解析失败: {e}")
return None
def search_by_image(self, image_base64, cat_id=None, page_no=1, page_size=20):
"""
使用图片Base64编码进行搜索。
参数:
image_base64 (str): 图片的Base64编码字符串。
cat_id (str, optional): 商品类目ID,用于缩小搜索范围。
page_no (int): 页码。
page_size (int): 每页数量,最大通常为20。
返回:
dict: 搜索结果。
"""
biz_params = {
'image': image_base64,
'page_no': page_no,
'page_size': page_size,
}
if cat_id:
biz_params['cat'] = cat_id
result = self.call_api('taobao.item.search.img', **biz_params)
return result
现在,我们已经有了一个可以工作的API调用器。你可以用你的密钥初始化这个类,然后调用 search_by_image 方法。但是,直接丢一张手机拍的原图进去,效果往往不尽人意。识别率低、返回结果不相关是常见问题。问题的根源,常常出在图片本身。
3. 图像预处理:将识别率从40%提升至85%的关键
淘宝的图像识别引擎非常强大,但它对输入图片的质量也有一定要求。一张模糊、背景杂乱、主体不突出的图片,就像让一个近视眼在雾天找东西,再好的算法也无力回天。因此,在将图片发送给API之前,进行一系列预处理是至关重要的一步。这步做得好,能将有效识别率提升一倍以上。
我们的预处理目标很明确:突出主体、统一尺寸、优化画质、压缩体积。下面这个 ImageOptimizer 类,封装了我经过多次试验总结出的最佳处理流程。
import base64
from io import BytesIO
from PIL import Image, ImageOps
import cv2
import numpy as np
class ImageOptimizer:
@staticmethod
def load_and_convert(image_path):
"""加载图片并确保为RGB格式,处理Alpha通道。"""
with Image.open(image_path) as img:
if img.mode in ('RGBA', 'P'):
# 创建白色背景,将RGBA或P模式图片合成上去
background = Image.new('RGB', img.size, (255, 255, 255))
if img.mode == 'P':
img = img.convert('RGBA')
background.paste(img, mask=img.split()[-1] if img.mode == 'RGBA' else None)
img = background
else:
img = img.convert('RGB')
return img
@staticmethod
def resize_with_padding(img, target_size=(800, 800)):
"""
等比例缩放图片,不足处用白色填充,避免拉伸变形。
参数:
img (PIL.Image): 原始图片。
target_size (tuple): 目标尺寸 (宽, 高)。
返回:
PIL.Image: 处理后的图片。
"""
# 计算缩放比例
ratio = min(target_size[0] / img.width, target_size[1] / img.height)
new_size = (int(img.width * ratio), int(img.height * ratio))
img_resized = img.resize(new_size, Image.Resampling.LANCZOS)
# 创建目标画布并居中粘贴
new_img = Image.new('RGB', target_size, (255, 255, 255))
paste_position = ((target_size[0] - new_size[0]) // 2,
(target_size[1] - new_size[1]) // 2)
new_img.paste(img_resized, paste_position)
return new_img
@staticmethod
def denoise_with_opencv(pil_image):
"""使用OpenCV进行轻度降噪,去除细小噪点。"""
# 将PIL图像转为OpenCV格式 (BGR)
open_cv_image = np.array(pil_image)
open_cv_image = cv2.cvtColor(open_cv_image, cv2.COLOR_RGB2BGR)
# 使用非局部均值去噪,效果较好但较慢。对于速度要求高可用高斯模糊。
# denoised = cv2.fastNlMeansDenoisingColored(open_cv_image, None, 10, 10, 7, 21)
# 使用高斯模糊,速度更快
denoised = cv2.GaussianBlur(open_cv_image, (3, 3), 1.5)
# 转回PIL格式 (RGB)
denoised_rgb = cv2.cvtColor(denoised, cv2.COLOR_BGR2RGB)
return Image.fromarray(denoised_rgb)
@staticmethod
def optimize_for_taobao(image_path, max_size_kb=1024):
"""
完整的预处理流水线。
参数:
image_path (str): 图片文件路径。
max_size_kb (int): 目标最大文件大小(KB),淘宝限制通常为2MB。
返回:
str: 优化后的Base64编码字符串(无换行符)。
"""
# 1. 加载并转换
img = ImageOptimizer.load_and_convert(image_path)
# 2. 缩放与填充 (建议尺寸在600-1000像素之间)
img = ImageOptimizer.resize_with_padding(img, (800, 800))
# 3. 降噪处理(可选,对照片类图片效果显著)
# img = ImageOptimizer.denoise_with_opencv(img)
# 4. 压缩并转换为Base64
buffer = BytesIO()
# 渐进式JPEG,网络加载体验更好
img.save(buffer, format='JPEG', quality=85, optimize=True, progressive=True)
img_data = buffer.getvalue()
# 5. 检查文件大小,如果过大则进一步降低质量
if len(img_data) > max_size_kb * 1024:
quality = 70
while len(img_data) > max_size_kb * 1024 and quality > 10:
buffer = BytesIO()
img.save(buffer, format='JPEG', quality=quality, optimize=True)
img_data = buffer.getvalue()
quality -= 5
# 6. 生成Base64,并移除换行符(淘宝API要求)
base64_str = base64.b64encode(img_data).decode('utf-8')
# 确保没有换行符,这是一个常见的坑
base64_str = base64_str.replace('\n', '').replace('\r', '')
print(f"图片预处理完成。原始路径: {image_path}, 处理后Base64长度: {len(base64_str)}")
return base64_str
这个优化器做了几件重要的事:首先,它统一将图片转为RGB模式,解决了PNG透明背景可能带来的问题;其次,它采用“等比例缩放+白边填充”的方式,避免了图像变形,确保主体居中;最后,它控制输出图片的质量和大小,在清晰度和网络传输间取得平衡,并生成符合API要求的无换行符Base64字符串。
提示:
denoise_with_opencv方法被注释掉了,因为对于大多数商品图(白底图)来说,降噪步骤并非必需,有时甚至会抹掉一些有用的纹理细节。但对于实拍的生活照,开启降噪能有效提升识别率。
有了强大的图像预处理,我们的API调用就有了高质量的“弹药”。接下来,我们需要考虑如何高效、稳定地“发射”这些请求,并优雅地处理可能出现的各种状况。
4. 构建健壮的比价工具:错误处理、限流与数据解析
直接调用API拿到数据只是第一步。在真实的生产环境中,网络会波动,API会限流,返回的数据格式也可能出乎意料。一个健壮的工具必须能妥善处理这些情况。同时,原始API返回的JSON数据比较冗长,我们需要从中提取出比价所需的核心信息。
首先,让我们增强之前的 TaobaoImageSearcher 类,为其添加错误处理、重试逻辑和结果解析能力。
import time
from typing import List, Dict, Any, Optional
class RobustTaobaoSearcher(TaobaoImageSearcher):
def __init__(self, app_key, app_secret, max_retries=3, delay=1):
super().__init__(app_key, app_secret)
self.max_retries = max_retries
self.delay = delay # 重试间隔(秒)
self.last_call_time = 0 # 用于简单限流
def call_api_with_retry(self, method, **kwargs):
"""带重试机制的API调用。"""
for attempt in range(self.max_retries):
try:
# 简单的QPS控制:确保两次调用间隔至少0.5秒(假设QPS限制为2)
elapsed = time.time() - self.last_call_time
if elapsed < 0.5:
time.sleep(0.5 - elapsed)
result = self.call_api(method, **kwargs)
self.last_call_time = time.time()
if result is None:
print(f"第{attempt+1}次尝试:请求失败,准备重试...")
time.sleep(self.delay * (attempt + 1)) # 延迟递增
continue
# 检查API业务错误
if 'error_response' in result:
error_msg = result['error_response'].get('msg', '未知错误')
error_code = result['error_response'].get('code', '未知代码')
print(f"API返回业务错误: [{error_code}] {error_msg}")
# 如果是限流错误,等待更长时间
if '流量控制' in error_msg or 'limit' in error_msg.lower():
wait_time = 5 * (attempt + 1)
print(f"触发限流,等待{wait_time}秒后重试...")
time.sleep(wait_time)
continue
else:
# 其他业务错误,可能无法通过重试解决
return {'success': False, 'error': error_msg}
# 请求成功
return {'success': True, 'data': result}
except Exception as e:
print(f"第{attempt+1}次尝试发生异常: {e}")
if attempt == self.max_retries - 1:
return {'success': False, 'error': str(e)}
time.sleep(self.delay * (attempt + 1))
return {'success': False, 'error': '达到最大重试次数'}
def search_and_parse(self, image_base64, cat_id=None, page_no=1, page_size=20):
"""搜索并解析结果,返回结构化的商品列表。"""
raw_result = self.call_api_with_retry('taobao.item.search.img',
image=image_base64,
cat=cat_id,
page_no=page_no,
page_size=page_size)
if not raw_result['success']:
return raw_result # 返回错误信息
response_data = raw_result['data']
# 解析响应结构,不同接口版本路径可能略有不同
items = []
try:
# 尝试常见的响应路径
resp_root = response_data.get('item_search_img_response', {})
if not resp_root:
resp_root = response_data.get('tbk_item_search_img_response', {})
if not resp_root:
# 如果都没有,尝试直接找`items`
resp_root = response_data
item_list = resp_root.get('items', {}).get('item', [])
if not item_list and isinstance(resp_root.get('items'), list):
item_list = resp_root['items']
for item in item_list:
# 提取核心字段,提供默认值
parsed_item = {
'item_id': item.get('num_iid') or item.get('item_id') or item.get('num_iid'),
'title': item.get('title', ''),
'price': float(item.get('price') or item.get('zk_final_price') or 0),
'promotion_price': float(item.get('promotion_price') or item.get('price') or 0),
'sales': int(item.get('volume') or item.get('sales') or 0),
'pic_url': item.get('pic_url') or item.get('pict_url', ''),
'shop_name': item.get('shop_name') or item.get('nick', ''),
'detail_url': item.get('detail_url') or f"https://item.taobao.com/item.htm?id={item.get('num_iid', '')}",
'is_tmall': item.get('is_tmall', 'false') == 'true' or item.get('user_type') == '1',
'location': item.get('area') or item.get('provcity', ''),
}
# 尝试获取相似度分数(如果API提供)
if 'similarity' in item:
parsed_item['similarity_score'] = float(item.get('similarity', 0))
items.append(parsed_item)
total_results = resp_root.get('total_results', len(items))
return {
'success': True,
'total': total_results,
'page': page_no,
'page_size': page_size,
'items': items
}
except (KeyError, TypeError, ValueError) as e:
print(f"解析API响应时出错: {e}")
print(f"原始响应: {response_data}")
return {'success': False, 'error': f'解析失败: {e}'}
这个增强版的搜索器做了三件重要的事:第一,实现了简单的QPS控制,通过记录上次调用时间,避免请求过快触发限流。第二,加入了指数退避的重试机制,对于网络错误或短暂的限流,能自动重试。第三,提供了一个健壮的解析器,能适应API响应结构的微小变化,并提取出我们关心的字段。
现在,我们已经能稳定地获取到结构化的商品数据了。是时候将这些数据转化为直观的、可操作的洞察了。一个命令行工具固然可以运行,但一个带有图形界面的应用,或者至少是一个能生成可视化报告的工具,其价值要大得多。
5. 从数据到洞察:结果可视化与报告生成
一堆JSON数据摆在那里,很难快速看出门道。价格区间分布如何?天猫店和淘宝C店的比例怎样?哪个地区的卖家最多?这些问题的答案,就藏在数据可视化里。我们将使用 pandas 进行数据分析,并用 matplotlib 生成图表。
首先,我们创建一个 PriceAnalyzer 类,它接收上一步解析好的商品列表,并进行深度分析。
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib import font_manager
import numpy as np
class PriceAnalyzer:
def __init__(self, items_list):
"""
初始化分析器。
参数:
items_list (list): 由 `search_and_parse` 返回的商品字典列表。
"""
self.df = pd.DataFrame(items_list)
if self.df.empty:
print("警告:输入的商品列表为空。")
def generate_summary(self):
"""生成基础统计摘要。"""
if self.df.empty:
return "无商品数据可供分析。"
summary_lines = []
summary_lines.append("=" * 50)
summary_lines.append("商品比价分析报告")
summary_lines.append("=" * 50)
summary_lines.append(f"分析商品总数: {len(self.df)}")
summary_lines.append(f"价格区间: ¥{self.df['price'].min():.2f} - ¥{self.df['price'].max():.2f}")
summary_lines.append(f"平均价格: ¥{self.df['price'].mean():.2f}")
summary_lines.append(f"价格中位数: ¥{self.df['price'].median():.2f}")
# 平台分布
if 'is_tmall' in self.df.columns:
tmall_count = self.df['is_tmall'].sum()
taobao_count = len(self.df) - tmall_count
summary_lines.append(f"天猫商品: {tmall_count} 个 ({tmall_count/len(self.df)*100:.1f}%)")
summary_lines.append(f"淘宝商品: {taobao_count} 个 ({taobao_count/len(self.df)*100:.1f}%)")
# 销量Top 3
if 'sales' in self.df.columns and not self.df['sales'].isnull().all():
top_sales = self.df.nlargest(3, 'sales')[['title', 'price', 'sales']]
summary_lines.append("\n销量前三商品:")
for idx, row in top_sales.iterrows():
title_short = (row['title'][:30] + '...') if len(row['title']) > 30 else row['title']
summary_lines.append(f" {idx+1}. {title_short}")
summary_lines.append(f" 价格: ¥{row['price']:.2f}, 销量: {row['sales']}")
# 最便宜的三款
cheapest = self.df.nsmallest(3, 'price')[['title', 'price', 'shop_name']]
summary_lines.append("\n价格最低的三款商品:")
for idx, row in cheapest.iterrows():
title_short = (row['title'][:30] + '...') if len(row['title']) > 30 else row['title']
shop_short = (row['shop_name'][:15] + '...') if len(row['shop_name']) > 15 else row['shop_name']
summary_lines.append(f" {idx+1}. {title_short}")
summary_lines.append(f" 价格: ¥{row['price']:.2f}, 店铺: {shop_short}")
return '\n'.join(summary_lines)
def plot_price_distribution(self, save_path=None):
"""绘制价格分布直方图。"""
if self.df.empty or 'price' not in self.df.columns:
print("无法生成价格分布图:缺少价格数据。")
return
plt.figure(figsize=(10, 6))
prices = self.df['price'].dropna()
# 自动确定合适的直方图区间
if len(prices) > 1:
bin_width = max(1, (prices.max() - prices.min()) / 15)
bins = np.arange(prices.min(), prices.max() + bin_width, bin_width)
else:
bins = 10
plt.hist(prices, bins=bins, edgecolor='black', alpha=0.7, color='skyblue')
plt.axvline(prices.mean(), color='red', linestyle='--', linewidth=2, label=f'平均价: ¥{prices.mean():.2f}')
plt.axvline(prices.median(), color='green', linestyle='-.', linewidth=2, label=f'中位数: ¥{prices.median():.2f}')
plt.xlabel('商品价格 (元)', fontsize=12)
plt.ylabel('商品数量', fontsize=12)
plt.title('相似商品价格分布', fontsize=14, fontweight='bold')
plt.grid(axis='y', alpha=0.3)
plt.legend()
plt.tight_layout()
if save_path:
plt.savefig(save_path, dpi=150)
print(f"价格分布图已保存至: {save_path}")
else:
plt.show()
def plot_price_vs_sales(self, save_path=None):
"""绘制价格与销量散点图(如果数据可用)。"""
if self.df.empty or 'price' not in self.df.columns or 'sales' not in self.df.columns:
print("无法生成价格-销量图:缺少必要数据。")
return
# 过滤掉销量为0或异常的数据
plot_df = self.df[(self.df['sales'] > 0) & (self.df['sales'] < self.df['sales'].quantile(0.95))]
if plot_df.empty:
print("销量数据不足或异常,无法生成散点图。")
return
plt.figure(figsize=(10, 6))
scatter = plt.scatter(plot_df['price'], plot_df['sales'],
alpha=0.6, c=np.log10(plot_df['sales']+1), cmap='viridis', s=100)
plt.colorbar(scatter, label='销量对数尺度')
plt.xlabel('商品价格 (元)', fontsize=12)
plt.ylabel('商品销量', fontsize=12)
plt.title('商品价格 vs. 销量关系', fontsize=14, fontweight='bold')
plt.grid(alpha=0.3)
plt.tight_layout()
if save_path:
plt.savefig(save_path, dpi=150)
print(f"价格-销量关系图已保存至: {save_path}")
else:
plt.show()
def export_to_csv(self, filepath='taobao_comparison.csv'):
"""将分析结果导出为CSV文件。"""
if self.df.empty:
print("无数据可导出。")
return False
try:
# 选择要导出的列
export_columns = ['title', 'price', 'promotion_price', 'sales', 'shop_name', 'location', 'is_tmall', 'detail_url']
available_columns = [col for col in export_columns if col in self.df.columns]
self.df[available_columns].to_csv(filepath, index=False, encoding='utf-8-sig')
print(f"数据已成功导出至: {filepath}")
return True
except Exception as e:
print(f"导出CSV失败: {e}")
return False
这个分析器不仅能生成文本报告,还能画出直观的图表。价格分布直方图能让你一眼看出商品主要集中在哪个价位段;价格-销量散点图则能揭示“高价高销”或“低价跑量”等市场策略。最后,export_to_csv 方法让你能把所有原始数据轻松导出,用于进一步分析或存档。
现在,让我们把前面所有的模块像拼积木一样组合起来,形成一个完整的、可以一键运行的比价工具。
6. 完整工具集成与实战演示
我们已经有了图像优化器、健壮的API调用器、强大的数据分析器。现在是时候创建一个主程序,让用户通过简单的命令行交互,就能完成整个比价流程。这个工具将允许用户输入图片路径,自动完成从预处理、搜索、分析到报告生成的全过程。
下面是一个完整的脚本示例 taobao_price_comparison.py:
#!/usr/bin/env python3
"""
淘宝拍立淘比价工具 - 完整版
使用方法: python taobao_price_comparison.py <图片路径> [--category <类目ID>] [--pages <页数>]
"""
import sys
import os
import argparse
from configparser import ConfigParser
# 假设之前的类都定义在同一个文件或已导入
# from your_module import ImageOptimizer, RobustTaobaoSearcher, PriceAnalyzer
def load_config(config_file='config.ini'):
"""从配置文件加载API密钥。"""
config = ConfigParser()
if os.path.exists(config_file):
config.read(config_file, encoding='utf-8')
app_key = config.get('taobao', 'app_key', fallback='')
app_secret = config.get('taobao', 'app_secret', fallback='')
return app_key, app_secret
else:
# 如果配置文件不存在,提示用户创建
print(f"配置文件 {config_file} 未找到。")
print("请创建该文件并填入以下内容:")
print("[taobao]")
print("app_key = YOUR_APP_KEY")
print("app_secret = YOUR_APP_SECRET")
return None, None
def main():
parser = argparse.ArgumentParser(description='淘宝拍立淘商品比价工具')
parser.add_argument('image_path', help='待搜索的商品图片路径')
parser.add_argument('--category', '-c', type=str, help='商品类目ID (可选),如女装: 50010788')
parser.add_argument('--pages', '-p', type=int, default=3, help='搜索的页数 (每页最多20条),默认3页')
parser.add_argument('--output', '-o', default='./output', help='结果输出目录,默认 ./output')
args = parser.parse_args()
# 1. 检查图片文件
if not os.path.exists(args.image_path):
print(f"错误:图片文件 '{args.image_path}' 不存在。")
sys.exit(1)
# 2. 加载配置
app_key, app_secret = load_config()
if not app_key or not app_secret:
print("错误:未配置有效的App Key和App Secret。")
sys.exit(1)
# 3. 创建输出目录
os.makedirs(args.output, exist_ok=True)
base_name = os.path.splitext(os.path.basename(args.image_path))[0]
# 4. 图像预处理
print("步骤1/4: 正在优化图片...")
try:
image_base64 = ImageOptimizer.optimize_for_taobao(args.image_path)
if not image_base64:
print("图片预处理失败,请检查图片格式。")
sys.exit(1)
except Exception as e:
print(f"图片预处理出错: {e}")
sys.exit(1)
# 5. 初始化搜索器并搜索
print("步骤2/4: 正在调用淘宝拍立淘API搜索商品...")
searcher = RobustTaobaoSearcher(app_key, app_secret)
all_items = []
for page in range(1, args.pages + 1):
print(f" 正在获取第 {page} 页...")
result = searcher.search_and_parse(image_base64, cat_id=args.category, page_no=page)
if not result['success']:
print(f" 获取第 {page} 页失败: {result.get('error', '未知错误')}")
if page == 1:
# 第一页就失败,可能问题严重
sys.exit(1)
else:
# 后续页面失败,可能已无更多数据
break
items = result.get('items', [])
if not items:
print(f" 第 {page} 页无数据,停止搜索。")
break
all_items.extend(items)
print(f" 已找到 {len(items)} 个商品,累计 {len(all_items)} 个。")
# 礼貌性延迟,避免请求过快
if page < args.pages:
time.sleep(0.8)
if not all_items:
print("未搜索到任何商品。请尝试更换图片或检查网络。")
sys.exit(0)
print(f"搜索完成,共找到 {len(all_items)} 个相关商品。")
# 6. 数据分析与可视化
print("步骤3/4: 正在分析数据并生成报告...")
analyzer = PriceAnalyzer(all_items)
# 生成文本报告
report_txt = os.path.join(args.output, f"{base_name}_report.txt")
with open(report_txt, 'w', encoding='utf-8') as f:
f.write(analyzer.generate_summary())
print(f"文本报告已生成: {report_txt}")
# 生成图表
chart_price = os.path.join(args.output, f"{base_name}_price_dist.png")
analyzer.plot_price_distribution(save_path=chart_price)
chart_scatter = os.path.join(args.output, f"{base_name}_price_vs_sales.png")
analyzer.plot_price_vs_sales(save_path=chart_scatter)
# 导出原始数据
csv_file = os.path.join(args.output, f"{base_name}_data.csv")
analyzer.export_to_csv(csv_file)
print("步骤4/4: 完成!")
print(f"所有结果已保存至目录: {args.output}")
print("\n--- 报告摘要 ---")
print(analyzer.generate_summary())
if __name__ == '__main__':
main()
这个脚本的使用非常简单。假设你的API密钥已经保存在同目录下的 config.ini 文件里,你只需要在命令行中执行:
python taobao_price_comparison.py ./my_shoes.jpg --category 50011740 --pages 5
它就会自动搜索与 my_shoes.jpg 图片相似的鞋子(类目ID 50011740大致对应女鞋),获取5页结果,然后生成包含文本报告、价格分布图、价格-销量关系图以及完整CSV数据文件的结果包。
在实际使用中,你可能会遇到一些具体问题。例如,如何找到准确的类目ID?一个实用的技巧是,先不用类目ID进行搜索,从返回结果中观察商品的 cat_id 或 category_id 字段,或者直接去淘宝开放平台的类目表里查询。再比如,对于某些特定商品(如家具、艺术品),识别率可能不高,这时可以尝试对原图进行裁剪,只保留商品主体部分,或者调整预处理中的图片尺寸和质量参数。
这个工具的核心框架已经搭建完毕,但它还有巨大的扩展空间。你可以为其添加图形用户界面(GUI),使用 tkinter 或 PyQt;可以增加批量处理功能,一次性分析多张图片;甚至可以将其部署为Web服务,结合数据库,长期追踪某些商品的价格走势。技术本身是工具,而如何用它解决实际业务问题,创造真正的价值,才是更值得思考的方向。
更多推荐



所有评论(0)