淘宝图片搜索(拍立淘)+ 店铺全商品爬虫 深度实战指南(Python)

本文聚焦淘宝图片搜索(拍立淘)店铺全商品抓取的核心实现逻辑,从「抓包分析→反爬破解→代码落地→异常处理」全流程拆解,同时强调合规性与风控规避,适合有基础 Python 爬虫经验的开发者学习(仅用于技术研究,严禁商用)。

一、前置核心认知(必看)

1. 合规红线(不可触碰)

  • 淘宝《平台服务协议》明确禁止未经授权的爬虫行为,批量抓取 / 商用数据可能导致 IP 封禁、账号冻结,甚至承担法律责任;
  • 唯一合规路径:接入「淘宝开放平台 API」(需企业资质 + 官方授权,个人无法申请);
  • 本文代码仅用于技术学习,建议单店铺 / 单图片小规模测试,避免高频请求。

2. 淘宝反爬核心机制

反爬类型 表现形式 应对思路
登录态验证 无 Cookie 请求返回验证码 / 空白页 手动登录获取 Cookie+tb_token
签名加密 请求参数sign由前端 JS 动态生成 抓包提取加密 JS,用 execjs 执行
频率限制 单 IP / 账号高频请求触发 403 / 限流 随机延迟 + 代理 IP + 分散请求时间
设备指纹 检测 WebDriver / 固定 UA 伪造 UA + 禁用 webdriver 特征
验证码 滑块验证 / 短信验证(高频请求触发) 手动验证 / 对接打码平台(如超级鹰)

3. 必备工具 & 环境

(1)依赖库安装

bash

运行

复制代码
pip install requests parsel execjs pycryptodome pillow fake-useragent requests-toolbelt retry
  • execjs:执行前端加密 JS;pycryptodome:处理加密 / 解密;
  • pillow:图片预处理;retry:请求重试;requests-toolbelt:处理大文件请求;
  • 可选:mitmproxy(抓包)、Charles(抓包)、Node.js(替代 execjs 执行 JS,效率更高)。
(2)抓包工具(核心)
  • 推荐Charles/Fiddler/ 浏览器 F12(Network):用于抓取真实接口、参数、加密 JS;
  • 关键设置:开启 HTTPS 抓包(安装证书),过滤淘宝域名(taobao.com/tmall.com)。

二、淘宝图片搜索(拍立淘)爬虫实现

1. 接口抓包分析(第一步)

(1)手动触发拍立淘
  1. 登录淘宝网页版 → 点击首页「拍照搜」→ 上传本地图片;
  2. 打开浏览器 F12→Network→筛选「XHR/JSON」→ 找到核心请求:
    • 接口地址:https://s.taobao.com/simba/imgSearch.htm(POST);

    • 请求方式:POST(表单提交);

    • 核心参数:

      参数名 说明
      imageBase64 图片 Base64 编码(无data:image/jpeg;base64,前缀)
      _tb_token_ 登录态 Token(从 Cookie / 页面源码提取)
      sign 请求签名(前端 JS 加密生成)
      timestamp 毫秒级时间戳
    • 响应格式:JSON,包含相似商品列表(data.list)。

(2)图片预处理(Base64 编码)

淘宝对图片大小 / 格式有要求(建议≤1MB,JPG/PNG),需压缩 + 转 Base64:

python

运行

复制代码
import base64
from PIL import Image
from io import BytesIO

def image_to_base64(image_path, max_size=(800, 800)):
    """
    图片转Base64(压缩+去除前缀)
    :param image_path: 本地图片路径/网络图片URL
    :param max_size: 最大尺寸(宽,高)
    :return: Base64字符串
    """
    try:
        # 处理本地图片
        if image_path.startswith(('http://', 'https://')):
            import requests
            resp = requests.get(image_path, timeout=10)
            img = Image.open(BytesIO(resp.content))
        else:
            img = Image.open(image_path)
        
        # 压缩图片(保持比例)
        img.thumbnail(max_size)
        
        # 转JPEG格式(避免PNG透明通道问题)
        buffer = BytesIO()
        img.save(buffer, format='JPEG', quality=80)
        
        # 转Base64并去除前缀
        base64_str = base64.b64encode(buffer.getvalue()).decode('utf-8')
        return base64_str
    except Exception as e:
        raise ValueError(f"图片转Base64失败:{str(e)}")

# 测试
if __name__ == "__main__":
    print(image_to_base64("./test.jpg"))
(3)签名破解(核心难点)

淘宝sign参数由前端 JS 加密生成,步骤如下:

  1. 抓包找到生成sign的 JS 文件(浏览器 F12→Sources→搜索sign/md5);
  2. 提取加密逻辑(示例,需根据实际抓包更新);
  3. execjs执行 JS 生成sign

python

运行

复制代码
import execjs
import time

# 淘宝拍立淘加密JS(需根据实际抓包更新,示例为简化版)
ENCRYPT_JS = """
const crypto = require('crypto');
function getSign(params, tb_token) {
    // 1. 参数按key排序
    const keys = Object.keys(params).sort();
    let str = '';
    for (let k of keys) {
        str += k + params[k];
    }
    // 2. 拼接tb_token
    str += tb_token;
    // 3. MD5加密并转大写
    return crypto.createHash('md5').update(str).digest('hex').toUpperCase();
}
"""

def generate_sign(params, tb_token):
    """生成sign签名"""
    try:
        # 编译JS
        ctx = execjs.compile(ENCRYPT_JS)
        # 调用函数
        sign = ctx.call("getSign", params, tb_token)
        return sign
    except Exception as e:
        raise RuntimeError(f"生成sign失败:{str(e)}")

# 测试
if __name__ == "__main__":
    params = {
        "imageBase64": "测试Base64",
        "t": str(int(time.time()*1000)),
        "_tb_token_": "你的_tb_token_"
    }
    print(generate_sign(params, params["_tb_token_"]))
(4)完整拍立淘爬虫代码(带重试 + 反爬)

python

运行

复制代码
import requests
import time
from fake_useragent import UserAgent
from retry import retry

# -------------------------- 配置项 --------------------------
# 登录后从浏览器复制(F12→Network→任意请求→Cookie)
COOKIE = "你的淘宝Cookie"
# 登录后从页面源码/请求参数提取(F12→Elements→搜索_tb_token_)
TB_TOKEN = "你的_tb_token_"
# 代理(可选,建议高匿代理)
PROXIES = {
    # "http": "http://127.0.0.1:7890",
    # "https": "http://127.0.0.1:7890"
}
# 随机UA
UA = UserAgent()

# -------------------------- 核心函数 --------------------------
@retry(tries=3, delay=2, backoff=2)  # 重试3次,延迟递增
def taobao_image_search(image_path):
    """
    淘宝图片搜索(拍立淘)
    :param image_path: 本地图片路径/网络图片URL
    :return: 解析后的商品列表
    """
    # 1. 图片转Base64
    image_base64 = image_to_base64(image_path)
    
    # 2. 构造基础参数
    timestamp = str(int(time.time() * 1000))
    base_params = {
        "action": "imgSearch",
        "imageBase64": image_base64,
        "t": timestamp,
        "_tb_token_": TB_TOKEN,
        "q": "",
        "spm": "a21bo.jianhua.201866-taobao-item.1",
        "callback": f"jsonp_{int(time.time()*1000)}_{int(time.time()*1000)}"
    }
    
    # 3. 生成sign签名
    base_params["sign"] = generate_sign(base_params, TB_TOKEN)
    
    # 4. 构造请求头
    headers = {
        "User-Agent": UA.random,
        "Cookie": COOKIE,
        "Referer": "https://shang.taobao.com/",
        "Origin": "https://shang.taobao.com",
        "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
        "Accept": "application/json, text/javascript, */*; q=0.01",
        "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8"
    }
    
    # 5. 发送请求
    try:
        resp = requests.post(
            url="https://s.taobao.com/simba/imgSearch.htm",
            data=base_params,
            headers=headers,
            proxies=PROXIES,
            timeout=20,
            verify=False  # 忽略SSL验证(可选)
        )
        resp.raise_for_status()  # 抛出HTTP错误
        
        # 处理JSONP响应(若有)
        resp_text = resp.text
        if resp_text.startswith("jsonp_"):
            resp_text = resp_text[resp_text.find("(")+1 : resp_text.rfind(")")]
        
        # 解析JSON
        result = resp.json()
        if not result.get("success"):
            raise ValueError(f"接口返回失败:{result.get('msg')}")
        
        # 提取核心商品数据
        goods_list = result.get("data", {}).get("list", [])
        parsed_data = []
        for goods in goods_list:
            parsed_data.append({
                "商品ID": goods.get("itemId"),
                "商品标题": goods.get("title"),
                "商品价格": goods.get("price"),
                "商品链接": goods.get("clickUrl"),
                "店铺名称": goods.get("shopName"),
                "店铺ID": goods.get("shopId"),
                "销量": goods.get("sales"),
                "相似度": goods.get("similarity")  # 图片相似度
            })
        return parsed_data
    
    except requests.exceptions.RequestException as e:
        raise RuntimeError(f"请求失败:{str(e)}")
    except Exception as e:
        raise RuntimeError(f"解析失败:{str(e)}")

# -------------------------- 测试调用 --------------------------
if __name__ == "__main__":
    try:
        # 替换为你的图片路径
        result = taobao_image_search("./test.jpg")
        print(f"搜索到{len(result)}件相似商品:")
        for idx, goods in enumerate(result, 1):
            print(f"\n【{idx}】{goods}")
    except Exception as e:
        print(f"执行失败:{str(e)}")

2. 拍立淘常见问题 & 解决方案

问题 原因 解决方案
sign 验证失败 JS 加密逻辑过期 重新抓包提取最新加密 JS
登录态失效 Cookie/_tb_token_过期 重新登录淘宝,复制最新 Cookie/tb_token
返回验证码页面 高频请求 / IP 风控 更换 IP + 增加延迟 + 手动完成滑块验证
图片解析失败 图片格式 / 大小不符合要求 转 JPEG + 压缩至 800x800 以内

三、淘宝店铺全商品爬虫实现

1. 核心分析

(1)店铺 ID 提取(关键)

淘宝店铺链接有多种格式,需兼容提取shop_id

python

运行

复制代码
import re

def extract_shop_id(shop_url):
    """
    从店铺链接提取shop_id
    支持格式:
    1. https://shop12345678.taobao.com/
    2. https://xxx.taobao.com/shop/view_shop.htm?user_number_id=12345678
    3. https://detail.tmall.com/seller_view.htm?user_id=12345678
    """
    # 匹配shopXXXXXX.taobao.com
    pattern1 = r"shop(\d+)\.taobao\.com"
    # 匹配user_number_id=XXXXXX
    pattern2 = r"user_number_id=(\d+)"
    # 匹配tmall的user_id=XXXXXX
    pattern3 = r"user_id=(\d+)"
    
    match1 = re.search(pattern1, shop_url)
    match2 = re.search(pattern2, shop_url)
    match3 = re.search(pattern3, shop_url)
    
    if match1:
        return match1.group(1)
    elif match2:
        return match2.group(1)
    elif match3:
        return match3.group(1)
    else:
        raise ValueError(f"无法提取店铺ID:{shop_url}")

# 测试
if __name__ == "__main__":
    print(extract_shop_id("https://shop12345678.taobao.com/"))  # 输出12345678
(2)店铺商品接口分析
  • 核心接口:https://s.taobao.com/search?q=&seller_id={shop_id}&page={page}(GET);
  • 分页规则:每页 44 件商品,page从 1 开始,无商品时返回空列表;
  • 数据载体:接口返回 HTML,核心数据内嵌在g_page_config JS 变量中(JSON 格式)。

2. 完整店铺商品爬虫代码(带分页 + 数据保存)

python

运行

复制代码
import re
import json
import time
import requests
from parsel import Selector
from fake_useragent import UserAgent
from retry import retry

# -------------------------- 配置项 --------------------------
COOKIE = "你的淘宝Cookie"
PROXIES = {
    # "http": "http://127.0.0.1:7890",
    # "https": "http://127.0.0.1:7890"
}
REQUEST_DELAY = 3  # 请求延迟(秒)
MAX_PAGE = 50      # 最大抓取页数(防止无限循环)
UA = UserAgent()

# -------------------------- 工具函数 --------------------------
def parse_taobao_goods_html(html):
    """解析淘宝商品列表HTML,提取商品数据"""
    selector = Selector(text=html)
    
    # 提取g_page_config(核心数据)
    config_str = selector.css("script:contains('g_page_config')::text").get()
    if not config_str:
        return []
    
    # 清洗JSON字符串(去除多余代码)
    try:
        config_str = re.search(r"g_page_config = (.*?);\s+g_srp_loadCss", config_str).group(1)
        config_data = json.loads(config_str)
    except (re.error, json.JSONDecodeError):
        return []
    
    # 提取商品列表
    goods_list = config_data.get("mods", {}).get("itemlist", {}).get("data", {}).get("auctions", [])
    parsed_data = []
    for goods in goods_list:
        parsed_data.append({
            "商品ID": goods.get("nid"),
            "商品标题": goods.get("raw_title"),
            "商品价格": goods.get("view_price"),
            "销量": goods.get("view_sales"),
            "商品链接": f"https://item.taobao.com/item.htm?id={goods.get('nid')}",
            "店铺名称": goods.get("nick"),
            "店铺ID": goods.get("user_id"),
            "发货地": goods.get("item_loc"),
            "是否天猫": goods.get("is_tmall", False)
        })
    return parsed_data

@retry(tries=2, delay=2)
def crawl_shop_page(shop_id, page_no):
    """抓取单页商品"""
    url = f"https://s.taobao.com/search?q=&seller_id={shop_id}&page={page_no}"
    headers = {
        "User-Agent": UA.random,
        "Cookie": COOKIE,
        "Referer": f"https://shop{shop_id}.taobao.com/",
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
        "Accept-Language": "zh-CN,zh;q=0.9"
    }
    
    resp = requests.get(
        url=url,
        headers=headers,
        proxies=PROXIES,
        timeout=20,
        verify=False
    )
    resp.raise_for_status()
    return parse_taobao_goods_html(resp.text)

# -------------------------- 核心爬虫 --------------------------
def crawl_taobao_shop_all_goods(shop_url):
    """抓取店铺所有商品"""
    # 1. 提取店铺ID
    shop_id = extract_shop_id(shop_url)
    print(f"开始抓取店铺ID:{shop_id}")
    
    # 2. 分页抓取
    all_goods = []
    page_no = 1
    while page_no <= MAX_PAGE:
        print(f"抓取第{page_no}页...")
        try:
            # 随机延迟(避免固定间隔)
            time.sleep(REQUEST_DELAY + float(time.time() % 1))
            
            # 抓取单页
            page_goods = crawl_shop_page(shop_id, page_no)
            if not page_goods:
                print(f"第{page_no}页无商品,抓取结束")
                break
            
            # 合并数据
            all_goods.extend(page_goods)
            page_no += 1
            
        except Exception as e:
            print(f"第{page_no}页抓取失败:{str(e)}")
            # 失败后更换代理/增加延迟,此处简化为跳过
            page_no += 1
            continue
    
    # 3. 保存数据
    with open(f"taobao_shop_{shop_id}_goods.json", "w", encoding="utf-8") as f:
        json.dump(all_goods, f, ensure_ascii=False, indent=4)
    
    print(f"抓取完成!共{len(all_goods)}件商品,已保存至taobao_shop_{shop_id}_goods.json")
    return all_goods

# -------------------------- 测试调用 --------------------------
if __name__ == "__main__":
    try:
        # 替换为目标店铺链接
        crawl_taobao_shop_all_goods("https://shop12345678.taobao.com/")
    except Exception as e:
        print(f"执行失败:{str(e)}")

3. 店铺爬虫优化技巧

  1. 动态页数判断 :通过商品总数(g_page_configtotalCount)计算总页数,避免MAX_PAGE限制;

  2. 代理池集成 :对接动态代理池(如阿布云),每次请求切换 IP,示例:

    python

    运行

    复制代码
    def get_proxy():
        """从代理池获取IP"""
        resp = requests.get("http://你的代理池地址/get")
        return {"http": resp.text, "https": resp.text}
    # 在请求时使用:proxies=get_proxy()
  3. 数据去重:基于商品 ID 去重(部分店铺会重复展示商品);

  4. 断点续爬:将已抓取的页数 / 商品 ID 保存到本地,中断后可继续抓取。

四、高级反爬应对策略

1. 登录态自动维护

  • 使用selenium模拟登录(需处理滑块验证码),自动提取 Cookie/tb_token

  • 对接打码平台(如超级鹰),自动识别滑块验证码: python

    运行

    复制代码
    # 示例:超级鹰打码(需注册账号)
    def get_captcha_result(captcha_img_path):
        import requests
        params = {
            "user": "你的账号",
            "pass2": "你的密码MD5",
            "softid": "你的软件ID",
            "codetype": "1902"  # 滑块验证码类型
        }
        files = {"userfile": open(captcha_img_path, "rb")}
        resp = requests.post("http://upload.chaojiying.net/Upload/Processing.php", data=params, files=files)
        return resp.json()["pic_str"]

2. JS 加密逻辑持久化

  • 将提取的加密 JS 保存为.js文件,避免重复编译:

    python

    运行

    复制代码
    # 读取外部JS文件
    def load_encrypt_js(js_path):
        with open(js_path, "r", encoding="utf-8") as f:
            return f.read()
    ctx = execjs.compile(load_encrypt_js("./taobao_sign.js"))

3. 设备指纹伪装

  • 禁用webdriver特征(若用 selenium):

    python

    运行

    复制代码
    from selenium import webdriver
    options = webdriver.ChromeOptions()
    options.add_experimental_option("excludeSwitches", ["enable-automation"])
    options.add_experimental_option('useAutomationExtension', False)
    driver = webdriver.Chrome(options=options)
    driver.execute_script("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})")

五、合规替代方案:淘宝开放平台 API

若需长期稳定使用,必须接入官方 API:

  1. 注册地址:https://open.taobao.com/

  2. 核心接口:

    • 图片搜索:taobao.pai.litao.search(需申请权限);
    • 店铺商品:taobao.seller.item.search(企业资质);
  3. API 调用示例(简化版): python

    运行

    复制代码
    import requests
    import hashlib
    import time
    
    def taobao_open_api(app_key, app_secret, method, params):
        """淘宝开放平台API调用"""
        timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
        params.update({
            "method": method,
            "app_key": app_key,
            "timestamp": timestamp,
            "format": "json",
            "v": "2.0",
            "sign_method": "md5"
        })
        # 生成签名
        sign_str = ''.join([f"{k}{v}" for k, v in sorted(params.items())]) + app_secret
        params["sign"] = hashlib.md5(sign_str.encode()).hexdigest().upper()
        
        resp = requests.get("https://eco.taobao.com/router/rest", params=params)
        return resp.json()
    
    # 调用示例(需替换为真实AppKey/AppSecret)
    # result = taobao_open_api(
    #     app_key="你的AppKey",
    #     app_secret="你的AppSecret",
    #     method="taobao.seller.item.search",
    #     params={"seller_id": "店铺ID", "page_no": 1}
    # )
相关推荐
尘诞辰2 小时前
【C语言】数据在内存中的储存
c语言·开发语言·数据结构·c++
JPX-NO2 小时前
Rust + Rocket + Diesel构建的RESTful API示例(CRUD)
开发语言·rust·restful
无敌最俊朗@2 小时前
STL-关联容器(面试复习4)
开发语言·c++
JHC0000002 小时前
119. 杨辉三角 II
python·算法·面试
bybitq2 小时前
string,byte,rune,character?详解Golang编码-UTF-8
开发语言·后端·golang
加成BUFF2 小时前
树莓派5控制WS2812 RGB灯带:原理、接线与实战指南
python·嵌入式硬件·远程连接·ws2812·树莓派5
wjs20242 小时前
HTML 框架:构建网页结构的基础
开发语言
无限进步_2 小时前
【C语言】栈(Stack)数据结构的实现与应用
c语言·开发语言·数据结构·c++·后端·visual studio
embrace992 小时前
【C语言学习】预处理详解
java·c语言·开发语言·数据结构·c++·学习·算法