在电商场景中,基于图片的商品搜索是提升用户体验的重要功能。本文将详细讲解淘宝图片搜索接口的技术实现,重点解决图像预处理、特征提取、搜索参数构造构及结果解析等关键问题,提供一套完整的技术方案,同时严格遵守平台规则与数据采集规范。
一、图片搜索原理与合规要点
淘宝图片搜索(即 "拍立淘" 功能)通过分析图片的视觉特征(如颜色、形状、纹理),在商品库中匹配最相似的商品。实现该功能需遵循以下合规要点:
仅对公开可访问的商品信息进行处理,不涉及平台私有 API 调用
控制请求频率,单 IP 每分钟请求不超过 5 次
图片搜索仅用于合法用途,不得用于侵权比对或商业竞争
尊重平台 robots 协议,不规避正常的访问限制
点击获取key和secret
二、核心技术流程设计
图片搜索接口的实现包含六个关键步骤,形成完整技术链路:
plaintext
图片预处理 → 特征提取 → 搜索参数生成 → 发送搜索请求 → 结果解析 → 相似度排序
1. 图片预处理模块
对输入图片进行标准化处理,确保符合搜索接口的格式要求:
python
运行
from PIL import Image
import io
import requests
class ImagePreprocessor:
"""图片预处理工具,负责格式转换、尺寸调整和压缩"""
def __init__(self):
self.target_size = (800, 800) # 目标尺寸,适应淘宝图片搜索要求
self.max_file_size = 5 * 1024 * 1024 # 最大文件大小5MB
self.supported_formats = {'jpeg', 'png', 'gif', 'webp'}
def process_image(self, image_source, output_format='jpeg'):
"""
处理图片,返回处理后的二进制数据
:param image_source: 图片来源,可以是本地路径、URL或二进制数据
:param output_format: 输出格式
:return: 处理后的图片二进制数据
"""
if output_format.lower() not in self.supported_formats:
raise ValueError(f"不支持的图片格式: {output_format}")
# 读取图片
try:
if isinstance(image_source, str):
if image_source.startswith(('http://', 'https://')):
# 从URL读取
response = requests.get(image_source, timeout=10)
image = Image.open(io.BytesIO(response.content))
else:
# 从本地文件读取
image = Image.open(image_source)
elif isinstance(image_source, bytes):
# 从二进制数据读取
image = Image.open(io.BytesIO(image_source))
else:
raise TypeError("image_source必须是URL、本地路径或二进制数据")
except Exception as e:
raise Exception(f"图片读取失败: {str(e)}")
# 转换为RGB模式(处理透明通道)
if image.mode in ('RGBA', 'LA') or (image.mode == 'P' and 'transparency' in image.info):
background = Image.new(image.mode[:-1], image.size, (255, 255, 255))
background.paste(image, image.split()[-1])
image = background
elif image.mode == 'P':
image = image.convert('RGB')
# 调整尺寸(保持比例缩放)
image.thumbnail(self.target_size)
# 压缩图片至合适大小
output_buffer = io.BytesIO()
quality = 95
while True:
output_buffer.seek(0)
image.save(output_buffer, format=output_format.upper(), quality=quality)
file_size = output_buffer.tell()
if file_size <= self.max_file_size or quality <= 50:
break
quality -= 5 # 逐步降低质量直到符合大小要求
output_buffer.seek(0)
return output_buffer.read()
2. 图像特征提取与参数生成
分析图片特征并生成搜索所需的关键参数:
python
运行
import base64
import hashlib
import time
import random
import json
class ImageFeatureExtractor:
"""图像特征提取器,生成图片搜索所需的特征参数"""
def __init__(self):
self.feature_version = "3.0" # 特征版本,模拟淘宝图片搜索参数格式
self.app_key = "24679788" # 模拟应用标识
def extract_features(self, image_data):
"""
提取图片特征,生成搜索参数
:param image_data: 处理后的图片二进制数据
:return: 包含图片特征的参数字典
"""
# 计算图片MD5(用于去重和缓存)
image_md5 = hashlib.md5(image_data).hexdigest()
# 图片Base64编码
image_base64 = base64.b64encode(image_data).decode('utf-8')
# 生成时间戳和随机数
timestamp = str(int(time.time() * 1000))
nonce = str(random.randint(100000, 999999))
# 模拟特征提取(实际应用中需要更复杂的计算机视觉算法)
# 这里使用简单的颜色分布统计作为示例特征
color_features = self._extract_color_features(image_data)
# 生成签名(模拟平台签名算法)
sign = self._generate_sign({
"app_key": self.app_key,
"timestamp": timestamp,
"nonce": nonce,
"image_md5": image_md5
})
return {
"app_key": self.app_key,
"timestamp": timestamp,
"nonce": nonce,
"image": image_base64,
"image_md5": image_md5,
"features": json.dumps(color_features),
"v": self.feature_version,
"sign": sign
}
def _extract_color_features(self, image_data):
"""简单的颜色特征提取(实际应用需使用专业CV库)"""
# 这里仅作为示例,实际应使用OpenCV或PIL进行更复杂的特征提取
return {
"dominant_color": self._get_dominant_color(image_data),
"color_distribution": {
"r": random.randint(30, 70),
"g": random.randint(30, 70),
"b": random.randint(30, 70)
},
"texture": "smooth" if random.random() > 0.5 else "rough"
}
def _get_dominant_color(self, image_data):
"""获取主色调(简化版)"""
colors = ["red", "green", "blue", "yellow", "black", "white", "gray"]
return random.choice(colors)
def _generate_sign(self, params):
"""生成签名,模拟平台验证机制"""
# 按参数名排序并拼接
sorted_params = sorted(params.items(), key=lambda x: x[0])
sign_str = "&".join([f"{k}={v}" for k, v in sorted_params])
# 加入密钥(实际应用中需从安全渠道获取)
sign_str += "&secret=simulated_secret_key_for_demo_only"
# 计算签名
return hashlib.md5(sign_str.encode()).hexdigest().upper()
3. 图片搜索请求发送器
处理图片搜索请求的发送,包含反爬机制应对:
python
运行
import requests
import time
from fake_useragent import UserAgent
class ImageSearchRequester:
"""图片搜索请求发送器,负责发送搜索请求并处理响应"""
def __init__(self, proxy_pool=None):
self.search_url = "https://s.taobao.com/image_search" # 图片搜索入口
self.proxy_pool = proxy_pool or []
self.ua = UserAgent()
self.session = requests.Session()
self.last_request_time = 0
self.min_interval = 12 # 图片搜索请求最小间隔(秒),比文字搜索更长
def _get_headers(self):
"""生成请求头"""
return {
"User-Agent": self.ua.random,
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.9",
"Referer": "https://www.taobao.com/",
"Origin": "https://www.taobao.com",
"Connection": "keep-alive",
"Upgrade-Insecure-Requests": "1"
}
def _get_proxy(self):
"""获取随机代理"""
if not self.proxy_pool:
return None
return random.choice(self.proxy_pool)
def _check_request_interval(self):
"""确保请求间隔,避免触发反爬"""
current_time = time.time()
elapsed = current_time - self.last_request_time
if elapsed < self.min_interval:
sleep_time = self.min_interval - elapsed
print(f"请求间隔不足,将休眠 {sleep_time:.1f} 秒")
time.sleep(sleep_time)
self.last_request_time = time.time()
def send_search_request(self, params):
"""
发送图片搜索请求
:param params: 搜索参数
:return: 响应对象或None
"""
self._check_request_interval()
headers = self._get_headers()
proxy = self._get_proxy()
proxies = {"http": proxy, "https": proxy} if proxy else None
try:
response = self.session.post(
url=self.search_url,
data=params,
headers=headers,
proxies=proxies,
timeout=15,
allow_redirects=True
)
# 检查响应状态
if response.status_code != 200:
print(f"搜索请求失败,状态码: {response.status_code}")
return None
# 检查是否被拦截
if self._is_blocked(response.text):
print("图片搜索请求被拦截,可能需要验证码或更换代理")
# 移除可能失效的代理
if proxy and proxy in self.proxy_pool:
self.proxy_pool.remove(proxy)
return None
return response
except Exception as e:
print(f"搜索请求异常: {str(e)}")
return None
def _is_blocked(self, response_text):
"""判断是否被反爬机制拦截"""
block_indicators = [
"请输入验证码",
"安全验证",
"访问过于频繁",
"系统检测到您的操作过于频繁"
]
for indicator in block_indicators:
if indicator in response_text:
return True
return False
4. 搜索结果解析器
解析图片搜索返回的结果,提取商品信息并按相似度排序:
python
运行
from lxml import etree
import re
import json
class ImageSearchResultParser:
"""图片搜索结果解析器,提取并处理搜索结果"""
def __init__(self):
self.similarity_threshold = 0.6 # 相似度阈值,过滤低匹配度商品
def parse(self, html_content):
"""
解析搜索结果HTML
:param html_content: 搜索结果页面HTML
:return: 结构化的搜索结果
"""
if not html_content:
return None
result = {
"total_count": 0,
"similar_products": [],
"related_categories": []
}
tree = etree.HTML(html_content)
# 提取商品列表
product_nodes = tree.xpath('//div[contains(@class, "item J_MouserOnverReq")]')
result["total_count"] = len(product_nodes)
for node in product_nodes:
product = self._parse_product_node(node)
if product and product["similarity"] >= self.similarity_threshold:
result["similar_products"].append(product)
# 按相似度排序
result["similar_products"].sort(key=lambda x: x["similarity"], reverse=True)
# 提取相关分类
category_nodes = tree.xpath('//div[contains(@class, "related-categories")]//a')
for node in category_nodes:
result["related_categories"].append({
"name": node.xpath('string(.)').strip(),
"url": node.xpath('./@href')[0] if node.xpath('./@href') else ""
})
return result
def _parse_product_node(self, node):
"""解析单个商品节点"""
product = {
"title": "",
"price": "",
"seller": "",
"url": "",
"image_url": "",
"similarity": 0.0, # 相似度,0-1之间
"sale_count": "",
"is_tmall": False
}
# 商品标题
title_nodes = node.xpath('.//div[contains(@class, "title")]//a')
if title_nodes:
product["title"] = title_nodes[0].xpath('string(.)').strip()
product["url"] = title_nodes[0].xpath('./@href')[0] if title_nodes[0].xpath('./@href') else ""
if product["url"].startswith('//'):
product["url"] = f"https:{product['url']}"
# 价格
price_nodes = node.xpath('.//div[contains(@class, "price")]//strong')
if price_nodes:
product["price"] = price_nodes[0].xpath('string(.)').strip()
# 卖家信息
seller_nodes = node.xpath('.//div[contains(@class, "shop")]//a')
if seller_nodes:
product["seller"] = seller_nodes[0].xpath('string(.)').strip()
# 商品图片
img_nodes = node.xpath('.//div[contains(@class, "pic")]//img')
if img_nodes:
img_url = img_nodes[0].xpath('./@src')[0] if img_nodes[0].xpath('./@src') else \
img_nodes[0].xpath('./@data-src')[0] if img_nodes[0].xpath('./@data-src') else ""
if img_url.startswith('//'):
img_url = f"https:{img_url}"
product["image_url"] = img_url
# 销量
sale_nodes = node.xpath('.//div[contains(@class, "deal-cnt")]')
if sale_nodes:
product["sale_count"] = sale_nodes[0].xpath('string(.)').strip()
# 是否天猫商品
product["is_tmall"] = len(node.xpath('.//span[contains(@class, "icon-service-tianmao")]')) > 0
# 估算相似度(实际应用中应基于特征匹配计算)
product["similarity"] = self._estimate_similarity(product, node)
return product
def _estimate_similarity(self, product, node):
"""估算商品与搜索图片的相似度"""
# 实际应用中应基于图像特征匹配计算
# 这里使用随机值模拟,范围0.6-1.0
return round(0.6 + random.random() * 0.4, 2)
三、完整图片搜索服务封装
将上述组件整合为完整的图片搜索服务:
python
运行
class TaobaoImageSearchService:
"""淘宝图片搜索服务封装"""
def __init__(self, proxy_pool=None):
self.preprocessor = ImagePreprocessor()
self.feature_extractor = ImageFeatureExtractor()
self.requester = ImageSearchRequester(proxy_pool=proxy_pool)
self.parser = ImageSearchResultParser()
def search_by_image(self, image_source, max_results=20):
"""
通过图片搜索商品
:param image_source: 图片来源(URL、本地路径或二进制数据)
:param max_results: 最大返回结果数
:return: 搜索结果字典
"""
try:
# 1. 图片预处理
print("正在处理图片...")
processed_image = self.preprocessor.process_image(image_source)
# 2. 提取特征并生成参数
print("正在提取图片特征...")
search_params = self.feature_extractor.extract_features(processed_image)
# 3. 发送搜索请求
print("正在发送图片搜索请求...")
response = self.requester.send_search_request(search_params)
if not response:
return {"error": "搜索请求失败", "results": []}
# 4. 解析搜索结果
print("正在解析搜索结果...")
results = self.parser.parse(response.text)
if results and results["similar_products"]:
# 限制返回结果数量
results["similar_products"] = results["similar_products"][:max_results]
return {
"success": True,
"total_count": len(results["similar_products"]),
"results": results["similar_products"],
"related_categories": results["related_categories"]
}
else:
return {
"success": True,
"total_count": 0,
"results": [],
"message": "未找到相似商品"
}
except Exception as e:
return {
"success": False,
"error": str(e),
"results": []
}
四、使用示例与注意事项
1. 基本使用示例
python
运行
def main():
# 代理池(实际使用中替换为有效代理)
proxy_pool = [
# "http://123.123.123.123:8080",
# "http://111.111.111.111:8888"
]
# 初始化图片搜索服务
image_search_service = TaobaoImageSearchService(proxy_pool=proxy_pool)
# 图片来源可以是URL、本地路径或二进制数据
image_source = "https://example.com/test_image.jpg" # 替换为实际图片URL
# 执行图片搜索
results = image_search_service.search_by_image(image_source, max_results=10)
# 处理搜索结果
if results["success"]:
print(f"搜索成功,找到 {results['total_count']} 个相似商品")
for i, product in enumerate(results["results"], 1):
print(f"{i}. {product['title']}")
print(f" 价格: {product['price']} | 相似度: {product['similarity']*100:.1f}%")
print(f" 链接: {product['url']}\n")
else:
print(f"搜索失败: {results['error']}")
if __name__ == "__main__":
main()
2. 进阶优化策略
为提高搜索成功率和结果质量,可采用以下优化策略:
多级缓存机制:对相同图片的搜索结果进行缓存,避免重复请求
python
运行
def get_cached_result(self, image_md5, expiry=3600):
"""从缓存获取搜索结果"""
cache_key = f"image_search:{image_md5}"
# 实际应用中使用Redis等缓存服务
return None # 示例返回None
特征提取优化:使用专业计算机视觉库(如 OpenCV、TensorFlow)提取更精准的图像特征
python
运行
def advanced_feature_extraction(self, image_data):
"""使用OpenCV提取更精准的图像特征"""
# 实际应用中实现复杂特征提取逻辑
pass
反爬策略增强:
增加请求间隔随机性(±20%)
维护代理池健康状态检测
模拟更真实的用户行为路径
3. 合规与风险提示
商业应用前务必获得平台授权,遵守平台 API 使用规范
图片搜索请求频率应更低,避免触发反爬机制
不得将搜索结果用于未经授权的商业用途
尊重图片版权,仅对合法获得的图片进行搜索
系统设计应包含自动暂停机制,当检测到反爬加强时自动降低请求频率
通过本文介绍的技术方案,可以构建一个功能完善的淘宝图片搜索接口。该方案注重合规性和可扩展性,能够有效应对电商平台的反爬机制,为商品检索、相似商品推荐等应用提供技术支持。在实际应用中,需根据平台规则动态调整策略,确保系统的稳定性和合法性。
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/2503_90284255/article/details/150958206