唯品会口红数据采集:双请求策略与JSON数据爬取实战

唯品会口红数据爬取实战

目录

引言

项目目标

效果展示

网站抓包分析过程

抓包分析步骤详解

步骤1:开启开发者工具

步骤2:识别数据接口

步骤3:分析请求详情

步骤4:认证机制分析

步骤5:构建完整请求流程

关键发现

需求分析

主要需求:

技术需求:

实现步骤

步骤1:环境准备和库导入

步骤2:Excel工作表初始化

步骤3:请求头配置和会话初始化

步骤4:Cookies配置

步骤5:API接口配置

步骤6:分页数据采集策略

步骤7:第一次请求参数配置

步骤8:发送第一次请求和获取商品ID列表

步骤9:商品ID分批处理策略

步骤10:第二次请求参数配置

步骤11:发送第二次请求和解析商品详情

步骤12:商品基本信息提取

步骤13:价格信息提取

步骤14:商品属性信息提取

步骤15:详情页链接构建

步骤16:数据保存和输出

步骤17:错误处理和资源清理

完整代码

技术深度解析

抓包分析技术要点

双请求架构优势

JSONP数据处理技术

智能分批策略

健壮的数据提取

详情页链接构建

应用场景与价值

法律和道德声明

技术总结


引言

在时尚美妆电商蓬勃发展的今天,唯品会作为中国领先的品牌特卖电商平台,汇聚了众多国内外知名美妆品牌。其中,口红作为美妆行业的明星品类,具有极高的市场关注度和研究价值。与传统的单次请求数据采集不同,本项目通过分析唯品会独特的双请求API架构,实现了高效、稳定的口红商品数据采集系统。

唯品会平台采用了复杂的数据加载机制,需要先获取商品ID列表,再通过二次请求获取详细商品信息。这种架构设计既保证了数据的安全性,又提升了用户体验。本项目通过深入分析这一机制,构建了完整的数据采集解决方案。

在数据采集过程中,我们严格遵守相关法律法规和平台使用条款,仅将数据用于技术学习和市场研究,尊重平台的数据安全和商家隐私。

项目目标

本项目旨在构建一个高效的唯品会平台口红数据采集系统,能够自动获取平台上的商品详细信息,并按标准化格式保存为可分析的数据文件。

具体目标包括:

  • 分析唯品会平台的双请求API接口结构

  • 配置完整的请求头和认证参数

  • 实现商品ID列表获取与详细数据采集的分离处理

  • 处理JSONP格式的数据响应

  • 提取商品标题、品牌、价格、属性等多个维度的关键指标

  • 处理分页加载和大批量数据获取机制

  • 构建完整的商品详情页链接

  • 将处理后的数据保存为结构化Excel文件

效果展示

网站抓包分析过程

抓包分析步骤详解

步骤1:开启开发者工具

首先打开唯品会网站,按F12键打开浏览器开发者工具:

  1. 打开唯品会官网:https://www.vip.com

  2. 按F12打开开发者工具

  3. 点击"Network"(网络)选项卡

  4. 在搜索框中输入"口红"开始搜索

  5. 刷新页面开始记录网络请求

步骤2:识别数据接口

在网站中浏览商品列表,观察网络请求的变化:

  1. 筛选XHR/Fetch请求:在筛选器中输入XHR或Fetch,过滤出数据接口

  2. 观察请求模式:注意观察在翻页或滚动加载时出现的重复请求模式

  3. 识别关键接口:发现两个关键接口:

    • 商品ID列表接口:/vips-mobile/rest/shopping/pc/search/product/rank

    • 商品详情接口:/vips-mobile/rest/shopping/pc/product/module/list/v2

步骤3:分析请求详情

点击具体的请求,查看详细信息:

第一次请求(商品ID列表)分析:

请求头分析:

GET /vips-mobile/rest/shopping/pc/search/product/rank?callback=getMerchandiseIds&app_name=shop_pc&... HTTP/1.1
Host: mapi-pc.vip.com
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
Referer: https://category.vip.com/

请求参数分析:

callback: getMerchandiseIds
app_name: shop_pc
app_version: 4.0
keyword: 口红
pageOffset: 0
batchSize: 120

响应数据分析:

getMerchandiseIds({
  "data": {
    "products": [
      {
        "pid": "6920871310745952780",
        "brandId": "1710621388"
      }
    ]
  }
})

第二次请求(商品详情)分析:

请求参数分析:

callback: getMerchandiseDroplets1
app_name: shop_pc
productIds: 6920871310745952780,6920871310745952781,...
extParams: {"stdSizeVids":"","preheatTipsVer":"3","couponVer":"v2",...}

响应数据分析:

getMerchandiseDroplets1({
  "data": {
    "products": [
      {
        "title": "迪奥烈艳蓝金唇膏",
        "brandShowName": "Dior迪奥",
        "price": {
          "salePrice": "350.00",
          "marketPrice": "450.00"
        },
        "attrs": [
          {"name": "颜色", "value": "999正红色"},
          {"name": "质地", "value": "丝绒"}
        ],
        "productId": "6920871310745952780",
        "brandId": "1710621388"
      }
    ]
  }
})
步骤4:认证机制分析

通过分析请求参数,发现唯品会平台采用了多种认证机制:

  1. 用户身份认证:user_id、mars_cid等用户标识参数

  2. API密钥验证:api_key字段,用于接口访问权限控制

  3. 时间戳防重放:_参数,使用当前时间戳防止请求重放

  4. 令牌验证:tfs_fp_token,前端生成的指纹令牌

  5. 区域验证:fdc_area_id、province_id等区域标识参数

步骤5:构建完整请求流程

基于抓包分析,构建完整的Python双请求流程:

# 第一次请求获取商品ID列表
response1 = requests.get(url1, headers=headers, cookies=cookies, params=params1)
product_ids = extract_product_ids(response1.text)
​
# 第二次请求获取商品详细信息
response2 = requests.get(url2, headers=headers, cookies=cookies, params=params2)
product_details = extract_product_details(response2.text)

关键发现

通过抓包分析,我们获得了以下重要信息:

  1. 双请求架构:唯品会采用先获取ID列表,再获取详情的双请求机制

  2. 数据接口

    • ID列表接口:https://mapi-pc.vip.com/vips-mobile/rest/shopping/pc/search/product/rank

    • 详情接口:https://mapi-pc.vip.com/vips-mobile/rest/shopping/pc/product/module/list/v2

  3. 请求方法:GET

  4. 数据格式:JSONP格式的响应,需要正则提取

  5. 分页机制:通过pageOffset和batchSize控制分页,每页120个商品

  6. 数据分批:详情请求需要将商品ID分批,每批最多50个

需求分析

唯品会网站采用了复杂的双请求API架构,需要正确的请求参数和认证信息才能获取数据。

主要需求:

  • 配置完整的请求头和认证参数

  • 实现双请求策略:先获取商品ID列表,再获取详细信息

  • 处理JSONP格式响应,使用正则表达式提取JSON数据

  • 解析复杂的嵌套JSON结构提取商品信息

  • 处理分页加载和大批量数据获取机制

  • 构建商品详情页链接

  • 提取多个维度的商品信息

技术需求:

  • 使用requests库发送HTTP请求

  • 使用re库处理JSONP响应格式

  • 使用json库处理数据格式

  • 处理复杂的分页和分批逻辑

  • 完善的错误处理和数据处理机制

实现步骤

步骤1:环境准备和库导入

首先导入必要的Python库,为项目提供基础技术支持。

import requests
import re
import json
import time
from openpyxl import Workbook

详细说明:

  • requests 提供HTTP请求功能,能够发送GET请求获取数据

  • re 提供正则表达式功能,用于处理JSONP格式响应

  • json 提供JSON数据处理功能

  • time 提供时间戳生成功能

  • openpyxl 用于Excel文件操作和数据存储,支持大规模数据导出

步骤2:Excel工作表初始化

创建Excel工作表并设置专业的表头结构,覆盖商品分析的各个维度。

# 创建excel工作表
wb = Workbook()
# 获取活动工作表
ws = wb.active
# 设置表头
ws.append(['商品标题', '品牌展会名称', '销售价格', '市场价', '其他信息', "详情页链接"])

详细说明:

  • 使用Workbook()创建新的Excel工作簿

  • ws.active获取默认的工作表

  • ws.append()设置详细的表头字段,涵盖商品基本信息、价格、品牌等关键指标

  • 表头设计考虑了美妆商品的特有属性,如品牌信息和详情页链接

步骤3:请求头配置和会话初始化

基于抓包分析结果,精心配置请求头参数,包含完整的认证信息。

headers = {
    "Accept": "*/*",
    "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
    "Connection": "keep-alive",
    "Referer": "https://category.vip.com/",
    "Sec-Fetch-Dest": "script",
    "Sec-Fetch-Mode": "no-cors",
    "Sec-Fetch-Site": "same-site",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0",
    "sec-ch-ua": "\"Chromium\";v=\"142\", \"Microsoft Edge\";v=\"142\", \"Not_A Brand\";v=\"99\"",
    "sec-ch-ua-mobile": "?0",
    "sec-ch-ua-platform": "\"Windows\""
}

详细说明:

  • 配置完整的请求头信息,模拟真实浏览器行为

  • Sec-Fetch-*系列头信息表明请求的上下文和目的

  • Referer设置为搜索页面的URL,确保请求的合法性

  • User-Agent标识浏览器类型和版本,避免被识别为爬虫

步骤4:Cookies配置

配置必要的cookies信息,维持会话状态和用户身份。

cookies1 = {......}

详细说明:

  • 配置完整的cookies信息,包含用户身份认证令牌

  • PASSPORT_ACCESS_TOKEN是用户登录凭证,对于访问受限资源至关重要

  • tfs_fp_token是前端生成的指纹令牌,用于反爬虫验证

  • 这些cookies对于维持会话状态和通过身份验证至关重要

步骤5:API接口配置

配置数据接口的URL地址。

url = "https://mapi-pc.vip.com/vips-mobile/rest/shopping/pc/search/product/rank"

详细说明:

  • 通过浏览器开发者工具分析找到数据API接口

  • URL为唯品会搜索数据接口地址

  • 这种直接调用API的方式比页面解析更高效

步骤6:分页数据采集策略

实现智能的分页数据采集,通过外层循环控制页码。

for page in range(0, 6):
    pageOffset = page * 120
    print(f"正在爬取第{page + 1}页的数据内容")

详细说明:

  • 循环控制页码(0-5页),对应1-6页的实际显示

  • pageOffset计算偏移量,唯品会使用偏移量而非页码进行分页

  • 每页获取120个商品,适合大规模数据采集

  • 实时打印采集进度,提供操作反馈

步骤7:第一次请求参数配置

基于抓包分析,配置获取商品ID列表的请求参数。

params = {
    "callback": "getMerchandiseIds",
    "app_name": "shop_pc",
    "app_version": "4.0",
    "warehouse": "VIP_NH",
    "fdc_area_id": "104104101",
    "client": "pc",
    "mobile_platform": "1",
    "province_id": "104104",
    "api_key": "70f71280d5d547b2a7bb370a529aeea1",
    "user_id": "645762488",
    "mars_cid": "1762691744964_5f9e9bc0bae26551c563c283cb2a12bb",
    "wap_consumer": "b",
    "is_default_area": "1",
    "standby_id": "nature",
    "keyword": "口红",
    "lv3CatIds": "",
    "lv2CatIds": "",
    "lv1CatIds": "",
    "brandStoreSns": "",
    "props": "",
    "priceMin": "",
    "priceMax": "",
    "vipService": "ch11",
    "sort": "0",
    "pageOffset": pageOffset,
    "channelId": "1",
    "gPlatform": "PC",
    "batchSize": "120",
    "tfs_fp_token": "BhIPr/K3uf5k4idKwcFfrUJFayPSF6qauzCjnIy+/5sBJdqdG4knl1kOMMs5UECqq3E7QzU/8nqoF16haA5KDXg==",
    "_": str(int(time.time() * 1000))
}

详细说明:

  • callback指定JSONP回调函数名

  • keyword指定搜索关键词为"口红"

  • pageOffsetbatchSize控制分页,每页120个商品

  • api_key是API访问密钥,通过抓包获取

  • tfs_fp_token是重要的反爬虫令牌

  • _参数使用当前时间戳,防止请求缓存

步骤8:发送第一次请求和获取商品ID列表

发送GET请求获取JSONP格式的商品ID数据,并进行解析。

# 响应商品ID
respond_to_the_product_id = requests.get(url, headers=headers, cookies=cookies1, params=params)
# 初始化结果列表
res_products_id_list = []
# 定义时间戳
time_str = str(int(time.time() * 1000))
​
if respond_to_the_product_id.status_code == 200:
    respond_to_the_product_id_text = respond_to_the_product_id.text
    res = re.search(r'getMerchandiseIds\((.*?)\)', respond_to_the_product_id_text)
    if res:
        respond_to_the_product_id_json_data = json.loads(res.group(1))
        # 提取商品id列表
        products_id_list = respond_to_the_product_id_json_data["data"]["products"]
        # 遍历商品id
        for num, products_id_item in enumerate(products_id_list):
            products_id = products_id_item["pid"]
            res_products_id_list.append(products_id)

详细说明:

  • 使用requests.get()发送GET请求获取数据

  • 检查响应状态码,确保请求成功

  • 使用正则表达式提取JSONP响应中的JSON数据

  • json.loads()解析JSON格式的数据

  • 从响应结构中提取商品ID列表

  • 遍历商品ID列表,收集所有商品ID

步骤9:商品ID分批处理策略

将获取的商品ID列表进行分批,为第二次请求做准备。

for node in range(1, 4):
    print(f'正在请求第{page+1}页的第{node}个数据包......')
    if node == 1:
        callback = "getMerchandiseDroplets1"
        products_id_str = ",".join(res_products_id_list[0:50]) + ","
        print(f"数据包数量:{len(res_products_id_list[0:50])}")
    elif node == 2:
        callback = "getMerchandiseDroplets2"
        products_id_str = ",".join(res_products_id_list[50:100]) + ","
        print(f"数据包数量:{len(res_products_id_list[50:100])}")
    else:
        callback = "getMerchandiseDroplets3"
        products_id_str = ",".join(res_products_id_list[100:120]) + ","
        print(f"数据包数量:{len(res_products_id_list[100:120])}")

详细说明:

  • 将每页120个商品ID分成3个批次进行请求

  • 第一批:前50个商品ID

  • 第二批:中间50个商品ID

  • 第三批:最后20个商品ID

  • 使用不同的callback函数名区分不同批次的响应

  • 将商品ID列表转换为逗号分隔的字符串格式

  • 实时打印每个批次的数据量,提供进度反馈

步骤10:第二次请求参数配置

配置获取商品详细信息的请求参数。

params2 = {
    "callback": callback,
    "app_name": "shop_pc",
    "app_version": "4.0",
    "warehouse": "VIP_NH",
    "fdc_area_id": "104104101",
    "client": "pc",
    "mobile_platform": "1",
    "province_id": "104104",
    "api_key": "70f71280d5d547b2a7bb370a529aeea1",
    "user_id": "645762488",
    "mars_cid": "1762691744964_5f9e9bc0bae26551c563c283cb2a12bb",
    "wap_consumer": "b",
    "is_default_area": "1",
    "productIds": products_id_str,
    "scene": "search",
    "standby_id": "nature",
    "extParams": "{\"stdSizeVids\":\"\",\"preheatTipsVer\":\"3\",\"couponVer\":\"v2\",\"exclusivePrice\":\"1\",\"iconSpec\":\"2x\",\"ic2label\":1,\"superHot\":1,\"bigBrand\":\"1\"}",
    "context": "",
    "tfs_fp_token": "BicZo59YoUtMcHQ2N7Oc1sob/0fqL22e7a4+g9t3NjweEyw2j8YasJyUJqQYo6G4H6qMfwaurjfuX1MjG3CygDA==",
    "_": time_str
}

详细说明:

  • productIds参数包含批次的商品ID字符串

  • extParams包含扩展参数,以JSON字符串格式传递

  • scene参数指定请求场景为搜索

  • 使用与第一次请求相同的认证参数,确保会话一致性

  • callback参数根据批次动态设置

步骤11:发送第二次请求和解析商品详情

发送GET请求获取商品详细信息,并进行数据提取。

response2 = requests.get(url2, headers=headers, cookies=cookies2, params=params2)
if response2.status_code == 200:
    response_json_data = ""
​
    response_text = response2.text
    # 使用一个正则表达式匹配所有三种情况
    pattern = r'getMerchandiseDroplets\d+\(\s*(\{.*\})\s*\)'
    res = re.search(pattern, response_text)
​
    if res:
        response_json_data = json.loads(res.group(1))
    else:
        print('正则匹配失败')
        continue

详细说明:

  • 发送第二次请求获取商品详细信息

  • 使用动态正则表达式匹配不同批次的JSONP响应

  • json.loads()解析JSON格式的商品数据

  • 处理正则匹配失败的情况,确保程序健壮性

步骤12:商品基本信息提取

从JSON响应中提取商品的基本标识信息。

if response_json_data:
    # 商品列表
    products_list = response_json_data["data"]["products"]
    if products_list:
        print(f"获取到商品数目为:{len(products_list)}")
        # 遍历商品列表
        for products_item in products_list:
            products_item_dict = {}
            # 商品标题
            title = products_item["title"]
            products_item_dict["商品标题"] = title if title else "无"
            # 品牌展会名称
            brandShowName = products_item["brandShowName"]
            products_item_dict["品牌展会名称"] = brandShowName if brandShowName else "无"

详细说明:

  • 从JSON结构中提取商品信息列表

  • 验证数据列表是否为空,避免处理无效数据

  • 为每个商品创建字典对象存储信息

  • 提取商品标题和品牌名称,这是美妆商品的核心标识

  • 设置安全检查,为可能缺失的字段提供默认值

步骤13:价格信息提取

提取商品的销售价格和市场价信息。

            # 销售价格
            salePrice = products_item["price"]["salePrice"]
            products_item_dict["销售价格"] = salePrice if salePrice else "无"
            # 市场价
            marketPrice = products_item["price"]["marketPrice"]
            products_item_dict["市场价"] = marketPrice if marketPrice else "无"

详细说明:

  • 从嵌套的价格结构中提取销售价格和市场价

  • 销售价格是唯品会的特卖价格

  • 市场价是商品的参考原价

  • 价格信息对于价格分析和优惠力度计算很重要

步骤14:商品属性信息提取

提取商品的详细属性信息。

            # 其他信息
            attrs = products_item.get("attrs", "")
            # 初始化其他信息列表
            attrs_list = []
            if attrs:
                for attr in attrs:
                    name = attr["name"]
                    value = attr["value"]
                    if name and value:
                        attrs_list.append(name + ":" + value)
            else:
                attrs_list.append("无")
            # 其他信息字符串
            attrs_str = "---".join(attrs_list)
            products_item_dict["其他信息"] = attrs_str if attrs_str else "无"

详细说明:

  • 提取商品的属性列表,包含颜色、质地等美妆商品特有属性

  • 遍历属性列表,格式化每个属性信息

  • 使用分隔符连接多个属性,便于存储和分析

  • 处理属性为空的情况,提供默认值

  • 这种处理方式能够有效提取复杂的商品属性信息

步骤15:详情页链接构建

构建商品的详情页链接。

            # 获取商品id
            productId = products_item["productId"]
            # 获取品牌id
            brandId = products_item["brandId"]
            # 构建详情页链接https://www.vipglobal.hk/detail-1710621388-6920871310745952780.html
            info_link = f"https://www.vipglobal.hk/detail-{brandId}-{productId}.html"
            products_item_dict["详情页链接"] = info_link if info_link else "无"

详细说明:

  • 从商品数据中提取商品ID和品牌ID

  • 根据唯品会的URL规则构建详情页链接

  • 详情页链接提供了查看商品完整信息的入口

  • 这种动态构建链接的方式适用于所有商品

步骤16:数据保存和输出

将处理完成的商品数据保存到Excel文件。

            print(products_item_dict)
            ws.append([
                products_item_dict['商品标题'],
                products_item_dict['品牌展会名称'],
                products_item_dict['销售价格'],
                products_item_dict['市场价'],
                products_item_dict['其他信息'],
                products_item_dict["详情页链接"]
            ])

详细说明:

  • 实时打印采集到的数据,提供进度反馈

  • 使用ws.append()将数据写入Excel行

  • 按表头顺序组织字段数据,确保数据结构一致性

  • 完整的字段映射确保数据准确存储

步骤17:错误处理和资源清理

处理请求失败的情况,完成数据采集后保存文件。

    else:
        print("请求失败,程序退出......")
        quit()
else:
    print("请求失败,程序退出")
    quit()
wb.save("唯品会口红数据.xlsx")
print("数据保存完毕......")

详细说明:

  • 处理请求失败的情况,提供错误提示并安全退出

  • 使用wb.save()保存完整的Excel文件

  • 文件命名为"唯品会口红数据.xlsx",清晰标识数据来源

  • 提供完成提示,确认数据保存成功

完整代码

# 爬取唯品会口红数据
import requests
import re
import json
import time
from openpyxl import Workbook
​
# 创建excel工作表
wb = Workbook()
# 获取活动工作表
ws = wb.active
# 设置表头
ws.append(['商品标题', '品牌展会名称', '销售价格', '市场价', '其他信息', "详情页链接"])
​
headers = {
    "Accept": "*/*",
    "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
    "Connection": "keep-alive",
    "Referer": "https://category.vip.com/",
    "Sec-Fetch-Dest": "script",
    "Sec-Fetch-Mode": "no-cors",
    "Sec-Fetch-Site": "same-site",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0",
    "sec-ch-ua": "\"Chromium\";v=\"142\", \"Microsoft Edge\";v=\"142\", \"Not_A Brand\";v=\"99\"",
    "sec-ch-ua-mobile": "?0",
    "sec-ch-ua-platform": "\"Windows\""
}
cookies1 = {......}
url = "https://mapi-pc.vip.com/vips-mobile/rest/shopping/pc/search/product/rank"
for page in range(0, 6):
    pageOffset = page * 120
    print(f"正在爬取第{page + 1}页的数据内容")
    params = {
        "callback": "getMerchandiseIds",
        "app_name": "shop_pc",
        "app_version": "4.0",
        "warehouse": "VIP_NH",
        "fdc_area_id": "104104101",
        "client": "pc",
        "mobile_platform": "1",
        "province_id": "104104",
        "api_key": "70f71280d5d547b2a7bb370a529aeea1",
        "user_id": "645762488",
        "mars_cid": "1762691744964_5f9e9bc0bae26551c563c283cb2a12bb",
        "wap_consumer": "b",
        "is_default_area": "1",
        "standby_id": "nature",
        "keyword": "口红",
        "lv3CatIds": "",
        "lv2CatIds": "",
        "lv1CatIds": "",
        "brandStoreSns": "",
        "props": "",
        "priceMin": "",
        "priceMax": "",
        "vipService": "ch11",
        "sort": "0",
        "pageOffset": pageOffset,
        "channelId": "1",
        "gPlatform": "PC",
        "batchSize": "120",
        "tfs_fp_token": "BhIPr/K3uf5k4idKwcFfrUJFayPSF6qauzCjnIy+/5sBJdqdG4knl1kOMMs5UECqq3E7QzU/8nqoF16haA5KDXg==",
        "_": str(int(time.time() * 1000))
    }
    """
        第一次请求:获取商品id列表
        一次获取120个商品id,由于第二次的参数里面携带了这些
    """
    # 响应商品ID
    respond_to_the_product_id = requests.get(url, headers=headers, cookies=cookies1, params=params)
    # 初始化结果列表
    res_products_id_list = []
    # 定义时间戳
    time_str = str(int(time.time() * 1000))
​
    if respond_to_the_product_id.status_code == 200:
        respond_to_the_product_id_text = respond_to_the_product_id.text
        res = re.search(r'getMerchandiseIds\((.*?)\)', respond_to_the_product_id_text)
        if res:
            respond_to_the_product_id_json_data = json.loads(res.group(1))
            # 提取商品id列表
            products_id_list = respond_to_the_product_id_json_data["data"]["products"]
            # 遍历商品id
            for num, products_id_item in enumerate(products_id_list):
                products_id = products_id_item["pid"]
                res_products_id_list.append(products_id)
            # print(res_products_id_list)
            for node in range(1, 4):
                print(f'正在请求第{page+1}页的第{node}个数据包......')
                if node == 1:
                    callback = "getMerchandiseDroplets1"
                    products_id_str = ",".join(res_products_id_list[0:50]) + ","
                    print(f"数据包数量:{len(res_products_id_list[0:50])}")
                elif node == 2:
                    callback = "getMerchandiseDroplets2"
                    products_id_str = ",".join(res_products_id_list[50:100]) + ","
                    print(f"数据包数量:{len(res_products_id_list[50:100])}")
                else:
                    callback = "getMerchandiseDroplets3"
                    products_id_str = ",".join(res_products_id_list[100:120]) + ","
                    print(f"数据包数量:{len(res_products_id_list[100:120])}")
                # print(products_id_str)
                """
                    发送第二次请求:获取商品信息内容
                    一页数据分为3个数据包,第一个50,第二个50,第三个20
                """
                cookies2 = {......}
                url2 = "https://mapi-pc.vip.com/vips-mobile/rest/shopping/pc/product/module/list/v2"
                params2 = {
                    "callback": callback,
                    "app_name": "shop_pc",
                    "app_version": "4.0",
                    "warehouse": "VIP_NH",
                    "fdc_area_id": "104104101",
                    "client": "pc",
                    "mobile_platform": "1",
                    "province_id": "104104",
                    "api_key": "70f71280d5d547b2a7bb370a529aeea1",
                    "user_id": "645762488",
                    "mars_cid": "1762691744964_5f9e9bc0bae26551c563c283cb2a12bb",
                    "wap_consumer": "b",
                    "is_default_area": "1",
                    "productIds": products_id_str,
                    "scene": "search",
                    "standby_id": "nature",
                    "extParams": "{\"stdSizeVids\":\"\",\"preheatTipsVer\":\"3\",\"couponVer\":\"v2\",\"exclusivePrice\":\"1\",\"iconSpec\":\"2x\",\"ic2label\":1,\"superHot\":1,\"bigBrand\":\"1\"}",
                    "context": "",
                    "tfs_fp_token": "BicZo59YoUtMcHQ2N7Oc1sob/0fqL22e7a4+g9t3NjweEyw2j8YasJyUJqQYo6G4H6qMfwaurjfuX1MjG3CygDA==",
                    "_": time_str
                }
                response2 = requests.get(url2, headers=headers, cookies=cookies2, params=params2)
                if response2.status_code == 200:
                    response_json_data = ""
​
                    response_text = response2.text
                    # 使用一个正则表达式匹配所有三种情况
                    pattern = r'getMerchandiseDroplets\d+\(\s*(\{.*\})\s*\)'
                    res = re.search(pattern, response_text)
​
                    if res:
                        # print('正则匹配结果',res.group(1))
                        response_json_data = json.loads(res.group(1))
                        # print('json字符串转python的字典结果',response_json_data)
                    else:
                        print('正则匹配失败')
                        continue
                    if response_json_data:
​
                        # 商品列表
                        products_list = response_json_data["data"]["products"]
                        if products_list:
                            print(f"获取到商品数目为:{len(products_list)}")
                            # 遍历商品列表
                            for products_item in products_list:
                                products_item_dict = {}
                                # 商品标题
                                title = products_item["title"]
                                products_item_dict["商品标题"] = title if title else "无"
                                # 品牌展会名称
                                brandShowName = products_item["brandShowName"]
                                products_item_dict["品牌展会名称"] = brandShowName if brandShowName else "无"
                                # 销售价格
                                salePrice = products_item["price"]["salePrice"]
                                products_item_dict["销售价格"] = salePrice if salePrice else "无"
                                # 市场价
                                marketPrice = products_item["price"]["marketPrice"]
                                products_item_dict["市场价"] = marketPrice if marketPrice else "无"
                                # 其他信息
                                attrs = products_item.get("attrs", "")
                                # 初始化其他信息列表
                                attrs_list = []
                                if attrs:
                                    for attr in attrs:
                                        name = attr["name"]
                                        value = attr["value"]
                                        if name and value:
                                            attrs_list.append(name + ":" + value)
                                else:
                                    attrs_list.append("无")
                                # 其他信息字符串
                                attrs_str = "---".join(attrs_list)
                                products_item_dict["其他信息"] = attrs_str if attrs_str else "无"
                                # 获取商品id
                                productId = products_item["productId"]
                                # 获取品牌id
                                brandId = products_item["brandId"]
                                # 构建详情页链接https://www.vipglobal.hk/detail-1710621388-6920871310745952780.html
                                info_link = f"https://www.vipglobal.hk/detail-{brandId}-{productId}.html"
                                products_item_dict["详情页链接"] = info_link if info_link else "无"
                                print(products_item_dict)
                                ws.append([
                                    products_item_dict['商品标题'],
                                    products_item_dict['品牌展会名称'],
                                    products_item_dict['销售价格'],
                                    products_item_dict['市场价'],
                                    products_item_dict['其他信息'],
                                    products_item_dict["详情页链接"]
                                           ])
​
                else:
                    print("请求失败,程序退出......")
                    quit()
        else:
            print("正则无法匹配")
    else:
        print("请求失败,程序退出")
        quit()
wb.save("唯品会口红数据.xlsx")
print("数据保存完毕......")

技术深度解析

抓包分析技术要点

通过抓包分析,我们掌握了以下关键技术点:

  1. 双请求架构识别:通过观察网络请求顺序,识别出唯品会独特的先ID后详情的双请求机制

  2. JSONP响应处理:分析JSONP格式的响应,使用正则表达式提取有效JSON数据

  3. 认证机制分析:分析复杂的认证参数,包括API密钥、用户标识、指纹令牌等

  4. 分页机制理解:识别基于偏移量的分页机制,而非传统的页码分页

  5. 数据分批策略:理解商品ID分批请求的设计原理和实现方式

双请求架构优势

与传统的单次请求相比,唯品会的双请求架构具有显著优势:

# 第一次请求:获取商品ID列表
response1 = requests.get(url1, params=params1)
product_ids = extract_ids(response1.text)
​
# 第二次请求:获取商品详细信息  
response2 = requests.get(url2, params=params2)
product_details = extract_details(response2.text)
  • 数据分离:ID列表和详细信息分离,降低单次请求的数据量

  • 灵活性:可以根据需要选择获取哪些商品的详细信息

  • 性能优化:减少不必要的数据传输,提升响应速度

  • 错误恢复:单个商品详情获取失败不影响其他商品

JSONP数据处理技术

唯品会采用JSONP格式返回数据,需要特殊处理:

pattern = r'getMerchandiseDroplets\d+\(\s*(\{.*\})\s*\)'
res = re.search(pattern, response_text)
if res:
    response_json_data = json.loads(res.group(1))
  • 正则表达式匹配:使用动态正则匹配不同回调函数的JSONP响应

  • JSON提取:从JSONP包装中提取纯JSON数据

  • 错误处理:处理正则匹配失败的情况,确保程序稳定性

智能分批策略

针对唯品会的限制,实现了智能的商品ID分批:

for node in range(1, 4):
    if node == 1:
        products_id_str = ",".join(res_products_id_list[0:50]) + ","
    elif node == 2:
        products_id_str = ",".join(res_products_id_list[50:100]) + ","
    else:
        products_id_str = ",".join(res_products_id_list[100:120]) + ","
  • 批量限制:每批次最多50个商品ID

  • 灵活分割:根据实际商品数量动态分割

  • 进度跟踪:实时显示每个批次的处理进度

  • 容错处理:处理商品数量不足的情况

健壮的数据提取

安全的数据提取策略确保程序稳定性:

title = products_item["title"]
products_item_dict["商品标题"] = title if title else "无"
  • 直接访问JSON字段,效率高

  • 使用条件判断处理可能为空的字段

  • 为所有字段提供合理的默认值

  • 确保程序在数据不完整时仍能正常运行

详情页链接构建

动态构建商品详情页链接:

info_link = f"https://www.vipglobal.hk/detail-{brandId}-{productId}.html"
  • 基于品牌ID和商品ID构建标准URL

  • 适用于所有商品的通用链接格式

  • 提供查看商品完整信息的入口

应用场景与价值

这个唯品会口红数据采集系统可以应用于多个重要场景:

  1. 美妆市场分析:分析口红市场的品牌分布、价格区间和产品趋势

  2. 竞品监控:跟踪竞争对手的产品策略和价格变动

  3. 价格策略研究:研究唯品会的特卖价格策略和折扣力度

  4. 品牌影响力分析:分析各品牌在唯品会平台的展示和销售情况

  5. 产品选品参考:为美妆零售商提供产品选品的数据支持

  6. 消费者偏好研究:通过商品属性分析消费者对口红色号、质地的偏好

  7. 学术研究:为美妆电商和消费者行为研究提供真实的数据支持

法律和道德声明

在使用本数据采集程序时,请务必严格遵守以下原则:

  1. 遵守法律法规:严格遵循《网络安全法》、《电子商务法》及相关互联网监管规定

  2. 尊重平台规则:严格遵守唯品会平台的使用条款和robots.txt协议

  3. 控制访问频率:合理设置请求间隔,不对平台服务器造成压力

  4. 合法使用数据:仅将数据用于技术学习和市场研究,不用于商业竞争

  5. 尊重知识产权:承认数据来源,不将爬取数据用于商业用途

  6. 隐私保护:不采集、使用或传播涉及用户隐私的信息

  7. 商业道德:不利用获取的数据进行不正当竞争或损害平台利益的行为

技术总结

本文详细介绍了通过双请求API接口爬取唯品会平台口红数据的完整技术方案,特别强调了抓包分析和JSONP数据处理的重要性。通过这个项目,我们掌握了以下核心技术:

  1. 复杂架构分析:分析并理解唯品会的双请求API架构

  2. JSONP处理技术:使用正则表达式处理JSONP格式的响应数据

  3. 分批请求策略:实现智能的商品ID分批和详情获取

  4. 认证机制处理:配置复杂的用户认证和API访问参数

  5. 分页采集机制:实现基于偏移量的分页数据采集

  6. 数据标准化:将复杂的商品数据保存为可分析的标准格式

与传统的单次请求采集相比,这种基于双请求调用的方案具有独特优势:

  • 适应现代电商平台的复杂数据加载机制

  • 更高的数据获取效率和稳定性

  • 更好的错误恢复能力

  • 适用于采用先进前端技术的电商平台

关于认证参数的重要说明:

虽然在当前实现中我们直接使用了抓包获取的认证参数,但在实际生产环境中,需要理解这些参数的生成机制:

# 认证参数生成伪代码(示例)
def generate_auth_params(user_id, api_key):
    timestamp = int(time.time() * 1000)
    fingerprint = generate_fingerprint()
    return {
        "user_id": user_id,
        "api_key": api_key,
        "tfs_fp_token": fingerprint,
        "_": timestamp
    }

在实际应用中,应该分析认证参数的生成逻辑,实现自动化的参数生成,而不是硬编码固定的参数值。

抓包分析是现代网络数据采集的关键技能,通过深入分析网络请求,我们可以:

  • 理解网站的数据传输架构

  • 识别必要的认证和反爬虫机制

  • 找到最高效的数据获取方式

  • 优化数据采集的性能和稳定性

掌握这种基于抓包分析和API调用的数据采集技术,在电子商务和美妆市场分析领域具有重要价值。无论是美妆品牌商、市场研究人员还是电商分析师,都能够通过这种技术获取宝贵的市场洞察。

在网络数据采集技术的学习道路上,我们既要追求技术精进,更要坚守法律和道德底线。特别是在商业数据采集领域,必须确保技术的合法合规使用,尊重数据知识产权和市场秩序。


注意:本文仅供技术学习交流,实际应用中请严格遵守相关法律法规和平台使用条款。所有数据采集行为应仅限于个人学习研究,不得用于任何商业用途或竞争行为。商业决策请以官方渠道信息为准。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

python码上全栈

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值