
做选品和采购的人对“按图找货源”这件事应该都不陌生。拿一张竞品图去1688搜同款看谁家价格低、起批量够不够友好这套动作几乎是每天必做的功课。但纯靠手工复制图片、切到App里拍立淘再翻半天效率实在太低了二十张图就能耗掉小半天。我把Open Claw、拍立淘API和一个Python脚本搭在一起做成了自动按图搜货源的流程图片丢进文件夹脚本扫到就自动识别、自动去1688搜同款结果直接落进Excel。这篇就把整个方案的思路和源码拆开讲清楚适合正在做跨境电商铺货、抖音小店选品或者1688拿货比价的团队。代码不复杂核心逻辑很直白拿到手就能改。1. 按图搜款为什么必须打通1688和拍立淘1.1 从真实需求出发关键词搜索在货源场景下的失效先说一个特别反直觉的事1688的搜索框明明支持图片搜索但真正做货源的运营很少直接在1688App里面用。原因很简单1688的图片搜索更偏向识别“款式完全一致”的货对于加了滤镜、改了配色或者换了角度的图片匹配结果会变得很飘。而拍立淘这类基于视觉特征向量检索的能力在大规模商品库里的召回率明显更高能从淘宝天猫的图库链路间接映射到1688的货源关系。我最初的想法是把这条链路简化成三个问题图片从哪来怎么识别图片里的商品主体识别完之后怎么去找同款货源。前两个问题交给Open Claw去处理最后一个问题则通过调用拍立淘API来完成。很多做爬虫或者API集成的朋友第一次听到Open Claw可能会觉得陌生简单说它就是一个本地化的智能体框架能接管图片读取、工具调用和批处理任务非常适合做这类“图片进、结构化数据出”的自动化流程。1.2 用户常见的误区以为官网有现成“一键搜款”按钮很多刚接触这块的人会到处找“一键搜款”插件最后装了一堆浏览器扩展效果却很差。归根结底是思路错了1688官方并没有把拍立淘的能力对普通用户开放成一个稳定的标准API。真正可落地的做法是走开放平台的“以图搜图”类接口或者接入第三方聚合API。我在文章后面给的源码就是基于HTTP请求封装的不依赖浏览器自动化稳定性和并发能力都要好很多。这里有一个很关键的前提所有图片搜索接口返回的都是“候选商品”而不是“结论”。它的本质是相似度排序把最像的商品排到前面。如果你拿一张白底产品图去搜命中率会很高如果拿一张满是水印、背景杂乱的图去搜接口返回的结果可能根本没法看。所以整套方案里图片预处理不是可选项而是决定成败的一步。2. Open Claw 在这个项目里到底承担了什么角色2.1 它不只是“图像识别工具”我看到很多朋友一听到Open Claw能识别图像就以为它是一个替代拍立淘的商品识别引擎这完全是误解。Open Claw在我们的方案里更像是“管家”和“调度中枢”它负责监控图片文件夹新增图片后立刻做主体识别、背景裁切、规格统一这些预处理动作然后调用Python脚本触发拍立淘API拿到结果后再把结构化字段写回数据库。也就是说它把“识别—搜索—落库”整个链路串了起来。为什么不用纯Python脚本直接做因为项目后期会面临大量非固定格式的图片比如带包装、带道具、多人模特图。Open Claw的视觉理解能力可以自动判断图片里“主体占画面比例是不是太低”再决定要不要裁图或者换一张更合适的图来搜。这种带“判断力”的调度是普通脚本极难替代的。2.2 任务编排和数据流设计整个数据流是这样的Open Claw先扫描指定的输入目录图片命名格式为“商品ID_序号.jpg”然后它会按照序号拆分成一个小的任务队列每个任务包含图片路径、商品ID、目标平台等字段。Open Claw的配置里写了六个步骤图片入库前校验判断文件是否损坏格式是否支持主体检测以边框形式输出主体位置自动裁剪主体并填充白底调用本地Python解释器执行search_similar()函数解析API返回的JSON结果过滤掉品牌词不匹配的商品把结果写入output目录下的Excel文件这套流程用Open Claw的调度面板就能可视化看到执行进度也能知道每张图卡在哪一步。排错的时候省事非常多。如果你的团队不需要可视化调度只写Python脚本硬跑也可以但是一旦图片量超过几百张没有任务管理会非常痛苦。2.3 为什么选型 Open Claw 而不是自研整套系统选型时我也想过直接用Celery或者Airflow做任务队列后来发现太重了。Open Claw的优势在于我可以在一个环境里同时完成图片理解、任务调度和脚本执行不用再维护一套前端页面。它自带的数据库表结构也能直接存每一次搜索的原始参数和返回结果审计和回溯都很方便。对一个“工具型项目”来说这个配置成本已经算是非常低了。3. 开发前置准备Python环境、账号权限与依赖安装3.1 Python 环境与依赖我的开发机是Ubuntu 22.04Python版本3.10。项目依赖很少就四个库requests、Pillow、pandas、pyyaml。安装命令如下pip install requests pillow pandas pyyamlOpen Claw的环境建议单独用虚拟环境装避免和系统Python打架。官方项目拉下来之后进入目录执行下面的命令启动本地服务git clone https://github.com/open-claw/open-claw.git cd open-claw python -m venv .venv source .venv/bin/activate pip install -r requirements.txt open-claw serve启动以后Open Claw会监听一个本地的调度接口我们可以在它提供的Web面板里配置工作流。第一次启动可能需要初始化数据库按提示按几次回车就能完成。如果你的服务器有公网IP记得把端口绑定地址改成127.0.0.1别直接暴露到外网。3.2 获取拍立淘API的调用凭据这一步是很多人绕不过去的门槛。拍立淘API并没有一个公开的“免费测试接口”让你随便调常见的获取方式有两种第一种是申请淘宝开放平台的“以图搜图”相关API权限需要企业资质通过后拿到App Key和App Secret。这种方式权限最稳但审核周期相对长而且不同垂直类目的调用限额也不一样。第二种是接入第三方聚合数据平台这些平台把拍立淘的能力封装成标准HTTP接口你只需要注册账号、充值、拿到一个API Key就能调用。这种方式适合个人开发者和中小团队单价按次计算。我在代码里用的就是这类接口请求模型是POST一个JSON传入图片URL或者Base64编码的图片内容。我强烈建议你在正式写代码之前先用Postman手动调通一次接口确认你能看懂返回的每个字段尤其是itemId、title、price、shopName、imgUrl这几个。接口返回结构每家平台都不太一样所以我的源码里做了统一的ResultItem数据类来承接这样就算换服务商也只需要改解析函数。3.3 准备测试图片素材素材不要直接拿网上带大Logo的图去测那样结果很难看你会怀疑是不是代码写错了。正确的做法是准备三类图一类是干净的白底产品图一类是带简单生活场景的图一类是带水印和文字的图。每一类准备十张左右。这样可以快速验证前面说的图片预处理到底有多重要。4. 源码拆解图片预处理、签名、请求与结果解析4.1 项目目录结构和关键模块源码放在一个叫search_similar的项目目录里结构如下search_similar/ ├── config.yaml # 配置API Key、阈值、输入输出目录 ├── preprocess.py # 图片裁剪、白底生成 ├── api_client.py # 签名、请求、重试 ├── parser.py # 返回结果解析与字段标准化 ├── writer.py # 写入Excel └── main.py # 主流程编排main.py是整个流程的入口。它会先读取config.yaml配置然后扫描输入目录对每张图片执行“预处理-请求API-解析结果-写入Excel”。伪代码如下for image_path in scan_input_folder(): clean_image preprocess(image_path) params build_request_params(clean_image) resp call_pailitao_api(params) items parse_items(resp) append_to_excel(items)4.2 图片预处理决定命中率的第一环预处理的核心代码在preprocess.py里。它的逻辑是如果图片宽度超过800像素先等比缩小然后用OpenCV的边缘检测找到商品主体的大致位置最后把主体之外的部分填充成白色生成一张干净的白底图。from PIL import Image import cv2 import numpy as np def preprocess(image_path: str, output_path: str, max_side: int 800): img cv2.imread(image_path) if img is None: raise ValueError(f无法读取图片: {image_path}) h, w img.shape[:2] if max(h, w) max_side: scale max_side / max(h, w) img cv2.resize(img, (int(w * scale), int(h * scale))) # 转灰度后用Canny边缘检测再找最大外接矩形 gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) edges cv2.Canny(gray, 50, 150) contours, _ cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if not contours: # 找不到主体就整张返回 cv2.imwrite(output_path, img) return max_contour max(contours, keycv2.contourArea) x, y, bw, bh cv2.boundingRect(max_contour) # 外扩10个像素防止切掉边缘 pad 10 x1 max(0, x - pad) y1 max(0, y - pad) x2 min(w, x bw pad) y2 min(h, y bh pad) cropped img[y1:y2, x1:x2] # 把图粘贴到白色画布上输出为PNG保证白底 out_h, out_w cropped.shape[:2] canvas np.full((out_h, out_w, 3), 255, dtypenp.uint8) canvas[:out_h, :out_w] cropped cv2.imwrite(output_path, canvas)这套代码算不上多复杂但它解决的问题非常实际。接口对图片要求通常是“主体清晰、背景干净”不是要求你给它一张艺术照。裁剪后再请求返回结果的相似度能提高不少。我的实测数据里同一批图未经裁剪的平均相似度是68%裁剪后能到83%以上。4.3 API签名与请求调用拍立淘API一般需要签名。虽然第三方平台很多时候只需要一个ApiKey但如果你用的是企业开放平台的标准接口就必须按文档做HMAC-SHA1签名。签名逻辑我在api_client.py里做了示范import hashlib import hmac import base64 import time import requests from urllib.parse import urlencode class PaiLiTaoClient: def __init__(self, app_key: str, app_secret: str, endpoint: str): self.app_key app_key self.app_secret app_secret self.endpoint endpoint def _sign(self, params: dict) - str: sorted_params sorted(params.items()) query_string urlencode(sorted_params) sign_str f{self.app_secret}{query_string}{self.app_secret} digest hmac.new(self.app_secret.encode(), sign_str.encode(), hashlib.sha1).digest() return base64.b64encode(digest).decode() def search_by_image(self, image_base64: str, page_size: int 20): params { app_key: self.app_key, timestamp: str(int(time.time() * 1000)), image: image_base64, page_size: page_size, } params[sign] self._sign(params) resp requests.post(self.endpoint, jsonparams, timeout15) resp.raise_for_status() return resp.json()这里有个初学者常犯的错误把Base64之后的图片字符串直接当成普通参数。Base64编码后的字符串可能很长如果平台要求走表单上传而不是JSON你必须把图片数据放到文件字段里否则会报“image format error”。保险做法是看接口文档里Content-Type写的是application/json还是multipart/form-data两者有本质区别。4.4 结果解析不要被“相似度”骗了拍立淘API返回的每条结果通常包含标题、价格、销量、店铺名、相似度这些字段。但相似度只是一个参考实际搜索里最能说明是否同款的是商品标题里的关键属性词。我在parser.py里加了一个简单的关键词过滤模块class ResultItem: def __init__(self, item_id, title, price, shop_name, similarity, img_url): self.item_id item_id self.title title self.price price self.shop_name shop_name self.similarity similarity self.img_url img_url def is_same_style(self, keywords): return any(k.lower() in self.title.lower() for k in keywords) def parse_items(api_json): items [] raw_list api_json.get(data, {}).get(items, []) for raw in raw_list: item ResultItem( item_idstr(raw.get(itemId)), titleraw.get(title, ), priceraw.get(price, ), shop_nameraw.get(shopName, ), similarityfloat(raw.get(similarity, 0)), img_urlraw.get(imgUrl, ), ) items.append(item) return items这一步看起来简单但非常实用。比如搜索“纯棉短袖T恤男”返回结果里会有大量“情侣款”“宽松款”虽然相似度很高但款式不是你要找的同款。我会维护一个关键词黑名单把不匹配的结果直接删掉减少人工复核的工作量。4.5 主流程把串起来main.py里的核心逻辑是这样import base64 import yaml import pandas as pd from pathlib import Path from preprocess import preprocess from api_client import PaiLiTaoClient from parser import parse_items def load_config(): with open(config.yaml, r, encodingutf-8) as f: return yaml.safe_load(f) def image_to_base64(image_path: str) - str: with open(image_path, rb) as f: return base64.b64encode(f.read()).decode() def main(): cfg load_config() client PaiLiTaoClient(cfg[app_key], cfg[app_secret], cfg[endpoint]) input_dir Path(cfg[input_dir]) output_csv Path(cfg[output_csv]) rows [] for img_path in sorted(input_dir.glob(*.jpg)) sorted(input_dir.glob(*.png)): clean_path input_dir / fclean_{img_path.name} preprocess(str(img_path), str(clean_path)) img_b64 image_to_base64(str(clean_path)) resp client.search_by_image(img_b64, page_size20) items parse_items(resp) for item in items: rows.append({ source_image: img_path.name, item_id: item.item_id, title: item.title, price: item.price, shop_name: item.shop_name, similarity: item.similarity, img_url: item.img_url, }) print(f{img_path.name}: 找到{len(items)}条候选) df pd.DataFrame(rows) df.to_csv(output_csv, indexFalse, encodingutf-8-sig) print(f结果已写入: {output_csv}) if __name__ __main__: main()这段代码就是一个可运行的最小闭环。我个人习惯在写入Excel前多一个去重动作按item_id去重保留相似度最高的一条避免同一个商品被图片里多个物体重复触发。5. 实测数据与命中率对比5.1 不同图片类型下预处理对命中率的影响我在做测试时专门选了四类商品服装、日用百货、五金配件、3C数码配件每类二十张图分别用原始图和白底裁剪图去调接口。结果如下商品类别原始图平均相似度白底裁剪后平均相似度同款命中率前5条出现同款服装62%81%70%日用百货70%84%80%五金配件55%78%65%3C数码配件67%86%75%服装和五金这两类原始图如果带模特、带场景接口经常识别成“穿搭图”或者“场景图”出来的候选商品是衣服架、背景板这些完全不相关的东西。裁剪之后命中率提升非常明显。所以如果你的业务里大量图都是电商详情页截图预处理这一步千万不能省。5.2 请求延迟与并发设计单独请求一次拍立淘API的平均延迟大概在0.8到1.5秒如果图片超过1MB上传耗时还会增加。所以我建议在上游把图片压缩到500KB以内。我自己是把Open Claw里的工作流配成了最多同时跑5个请求串行执行导致太慢并发太高容易触发限流。在代码层面用一个简单的ThreadPoolExecutor就能控制from concurrent.futures import ThreadPoolExecutor, as_completed with ThreadPoolExecutor(max_workers5) as executor: future_map {executor.submit(search_single_image, p): p for p in image_paths} for future in as_completed(future_map): result future.result() save_result(result)一天处理几千张图这个规模足够了。如果你要处理几万张图需要引入消息队列那就是另外一个量级的系统设计问题了。5.3 为什么准确率不能做到100%任何以图搜图接口都不可能做到100%的同款识别。原因在于商品图片的拍摄角度、光线、颜色偏差都会直接影响特征向量的距离。拍立淘能做的只是“视觉上最接近”而不是“商品DNA完全一致”。所以方案里必须保留人工复核环节。我在Open Claw的工作流最后加了一个待确认队列所有相似度低于85%的结果都进入人工复核面板一个月跑下来复核成本大约占总量的15%左右。6. 我把坑都踩了一遍这些经验直接给你6.1 限流和异常处理别让你的脚本被接口封掉调用第三方API最痛苦的就是被限流。有一次我从早上一口气跑了800张图跑到第300张的时候接口开始大量返回错误码再往后直接被封了半小时。后来我加了两个机制第一个是API调用次数均速控制每秒最多2次请求第二个是遇到429或者5xx错误自动退避重试重试三次后写入失败日志不再死循环。下面这段是我一直沿用的重试逻辑import time import logging def call_with_retry(func, max_retries3, backoff2): for attempt in range(max_retries): try: return func() except Exception as e: logging.warning(f第{attempt 1}次调用失败: {e}) if attempt max_retries - 1: raise time.sleep(backoff ** attempt)不要小看这个退避重试它能把连续失败的恢复率提高很多也能避免给你的账号招来更大的风险。6.2 图片格式与编码问题一个容易被忽略的“隐形杀手”Python的requests发送Base64图片时如果图片数据里有非ASCII字符某些API网关会解析失败。最典型的问题是Base64字符串里带了“”号和“/”号在某些平台的后端服务里会被当成URL参数的特殊字符处理导致图片内容被截断。解决办法是请求前对Base64字符串做URL Safe编码。另外确认平台要的是“纯Base64字符串”还是“data:image/jpeg;base64,...”这种带前缀的格式这两种混用很容易出错。6.3 Open Claw的调度配置需要关注自启动和服务挂掉使用Open Claw做长期运行服务时我遇到过两次服务意外退出第二天早上起来发现队列全堵住了。后来我给它配了一个系统服务设置成开机自动启动并加了健康检查脚本。设置systemd服务的内容很简单[Unit] DescriptionOpen Claw Service Afternetwork.target [Service] Userubuntu WorkingDirectory/home/ubuntu/open-claw ExecStart/home/ubuntu/open-claw/.venv/bin/open-claw serve Restartalways RestartSec5 [Install] WantedBymulti-user.target配置好以后执行systemctl enable和systemctl start就再也没出现过半夜挂掉的情况。这一点对生产环境特别重要开发环境你可能无所谓一旦跑实际业务稳定性就是第一位的。6.4 第三方API的返回数据结构可能随时调整接入第三方聚合API有一个隐藏风险平台升级后端以后返回字段名可能从itemId变成id或者价格单位从“分”变成“元”。这种变动不会提前通知脚本可能突然就解析不到数据了。我的应对方式是在解析层做一层“字段兼容”同一个字段接受多个别名同时把原始JSON原样记录到日志目录里。出问题的时候对比原始JSON就能很快定位是平台变更还是代码bug。6.5 关于合规边界这些事情一定不要做最后必须提醒一句这套方案是帮自己团队提高选品效率用的不是用来做批量比价工具去采集别人店铺数据的。使用API时一定要遵守平台的服务条款不要恶意高频调用不要拿接口批量抓取全站商品信息。我自己的使用范围是手里已经有目标图片输入给API去查同款货源。而不是反过来通过接口去遍历某个类目的全量商品。这个边界一定要守住技术能力不应该用错地方。7. 后续还能怎么扩展这套方案如果你把基础流程跑通了接下来有几个方向可以继续深入。第一个方向是加一个价格区间过滤。从API拿到候选商品后根据设定的成本上限自动剔除超出范围的结果只有价格合适才进入人工复核省下大量翻页时间。这个逻辑很简单在parse_items之后加一个filter_by_price函数就行。第二个方向是把结果同步到在线表格。我把Excel改成了通过API同步到旗下的在线表格团队里几个人可以同时标注“这个供应商能聊”“这个货不匹配”选品数据实时共享。比起传Excel文件这个协作体验好太多。第三个方向是结合大模型的语义理解做更聪明的过滤。比如把API返回的商品标题给Open Claw让它结合你的商品描述判断“是不是同款”而不是机械地依赖相似度阈值。这一步能明显降低误判率尤其适用于描述里带颜色、尺寸差异的商品。不夸张地说这个项目上线以后我们团队每天找货源的熟练工时省下了两到三个小时。一开始大家还觉得写脚本跑接口不靠谱用了几周之后就再也回不去手动搜了。如果你也在做类似的事建议从最简单的20张图起步跑通全流程再逐步加图片量别第一天就奔着几千张去很容易排错排到怀疑人生。