将 Lazada 爬取结果落地 MySQL:PHP 数据持久化实战

简介: 将 Lazada 爬取结果落地 MySQL:PHP 数据持久化实战

引言
在跨境电商选品和竞品监控中,Lazada 的商品页包含标题、价格、评分、库存等结构化字段,是比价与趋势分析的数据来源。爬虫把页面拉到内存只是流程的前半段:内存中的数据进程结束后即丢弃,无法支撑跨周期的查询、去重和增量更新。本文描述一条从采集到落库的完整链路,使用 PHP 作为采集与写入语言,MySQL 作为存储,全文覆盖表结构设计、代理接入、字段解析、批量 upsert 与容错策略。
一、为什么采集后必须落库
爬虫输出停留在内存或临时 CSV 时,每一次分析都要重新发起请求,既消耗带宽,又放大触达风控的概率。落库要解决三个具体的工程问题:
首先是幂等性。同一商品会被周期性重复抓取,写入逻辑必须保证重复执行不产生重复行,否则后续统计会被脏数据污染。
其次是可查询性。价格曲线、库存波动、类目分布都需要按时间维度和商品维度做聚合,关系型数据库比文件系统更胜任这件事。
最后是可追溯性。保留每条记录的抓取时间和来源站点,出现解析异常时能定位是哪一次采集引入了错误字段。
MySQL 在百万级商品规模下,配合合理索引和批量写入,吞吐和运维成本都可控,适合作为该类项目的主存储。
二、Lazada 的访问限制与亿牛云隧道代理
Lazada 对来源 IP 的请求频率敏感。单 IP 短时间高频访问会触发 403、验证码页或按 IP 的限流,部分接口还会校验 X-CSRF-TOKEN 之类的动态参数。直连采集几千条商品后失败率通常明显上升。
亿牛云(16yun)提供面向采集场景的代理产品,其中隧道代理(Tunnel)适合本场景:业务侧只配置一个固定的入口地址,由亿牛云在后端自动轮换出口 IP,开发者不需要自建 IP 池或做可用性探活。对 Lazada 这类按 IP 限流的站点,分散出口能直接降低单一 IP 被封的概率。
接入方式是带账密鉴权的代理 URL:


凭证应放在环境变量或配置文件中,不要硬编码进源码。需要留意两点:隧道代理相比直连会引入额外延迟,QPS 规划时要预留这部分开销;亿牛云按套餐限制并发通道数,采集并发度不能超过套餐上限,否则请求会在代理层排队。
三、MySQL 表结构设计
商品数据兼有宽表属性和缓慢变化特征:标题、类目相对静态,价格、库存频繁变动。设计两张表,主表存最新快照,历史表存价格时序。
```CREATE TABLE lazada_products (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
item_id VARCHAR(32) NOT NULL COMMENT 'Lazada 商品ID',
title VARCHAR(512) NOT NULL DEFAULT '',
seller_name VARCHAR(128) NOT NULL DEFAULT '',
price DECIMAL(12,2) NOT NULL DEFAULT 0,
original_price DECIMAL(12,2) NOT NULL DEFAULT 0,
currency CHAR(3) NOT NULL DEFAULT 'USD',
rating DECIMAL(3,2) NOT NULL DEFAULT 0,
review_count INT UNSIGNED NOT NULL DEFAULT 0,
stock INT NOT NULL DEFAULT 0,
category VARCHAR(128) NOT NULL DEFAULT '',
image_url VARCHAR(1024) NOT NULL DEFAULT '',
url VARCHAR(1024) NOT NULL DEFAULT '',
country CHAR(2) NOT NULL DEFAULT 'SG' COMMENT '站点国家',
last_crawled DATETIME NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_item (item_id, country)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE lazada_price_history (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
item_id VARCHAR(32) NOT NULL,
price DECIMAL(12,2) NOT NULL,
crawled_at DATETIME NOT NULL,
KEY idx_item (item_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;


设计要点说明。字符集用 utf8mb4 而不是 utf8,因为 Lazada 商品标题会出现 emoji 和超出基本多文种平面的字符,utf8 会截断。金额用 DECIMAL 而非 FLOAT,避免二进制浮点带来的精度误差,价格比较和汇总才有确定结果。uk_item 以 item_id 加 country 作为唯一约束,是因为同一商品 ID 在不同国家站点(sg、my、ph 等)是不同货品,需要区分后各自 upsert。
四、PHP 采集与字段解析
Lazada 商品页是前端渲染的 SPA,核心数据放在页面的 __INITIAL_STATE__ 这个内联 JSON 状态块里,比解析 DOM 更稳定,因为 DOM 结构随改版变动更频繁。先安装依赖:
composer require guzzlehttp/guzzle symfony/dom-crawler symfony/css-selector
采集与解析代码:
```<?php
require 'vendor/autoload.php';

use GuzzleHttp\Client;
use Symfony\Component\DomCrawler\Crawler;

$proxy = sprintf(
    'http://%s:%s@tunnel.16yun.cn:3100',
    getenv('YINIU_USER'),
    getenv('YINIU_PASS')
);

$client = new Client([
    'proxy' => $proxy,
    'timeout' => 15,
    'headers' => [
        'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
        'Accept-Language' => 'en-US,en;q=0.9',
    ],
]);

function fetchProduct(Client $client, string $url): array
{
    $html = $client->get($url)->getBody()->getContents();
    $crawler = new Crawler($html);

    $script = $crawler->filter('script:contains("__INITIAL_STATE__")')->first()->text();
    preg_match('/__INITIAL_STATE__\s*=\s*(\{.*?\});/', $script, $m);
    $data = json_decode($m[1], true);

    $p = $data['mods']['pageProduct'] ?? [];
    return [
        'item_id'     => $p['itemId'] ?? '',
        'title'       => $p['title'] ?? '',
        'price'       => ($p['price'] ?? 0) / 100,   // Lazada 金额以最小货币单位存储,需除以 100
        'seller_name' => $p['sellerName'] ?? '',
        'rating'      => $p['ratingScore'] ?? 0,
        'review_count'=> $p['review'] ?? 0,
        'stock'       => $p['stock'] ?? 0,
        'image_url'   => $p['image'] ?? '',
        'url'         => $url,
    ];
}

Guzzle 的 Client 实例复用连接,避免每条请求重建 TCP 握手。经隧道代理发出的请求由亿牛云轮换出口,配合真实 UA 和语言头,能通过 Lazada 的基础反爬校验。解析时需要注意,Lazada 接口返回的金额以最小货币单位(分)计,必须除以 100 还原成元,否则落库价格会放大一百倍。
五、数据清洗与批量 upsert
写入前先做清洗:修剪首尾空白、过滤 item_id 为空的记录、校验价格非负。然后用 INSERT ... ON DUPLICATE KEY UPDATE 做 upsert,命中 uk_item 时更新快照字段,未命中时插入新行:

$pdo = new PDO(
    'mysql:host=127.0.0.1;dbname=lazada;charset=utf8mb4',
    getenv('DB_USER'), getenv('DB_PASS'),
    [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);

function persist(PDO $pdo, array $row, string $country)
{
    $now = date('Y-m-d H:i:s');
    $sql = "INSERT INTO lazada_products
      (item_id, title, seller_name, price, currency, rating, review_count, stock, image_url, url, country, last_crawled)
      VALUES (?,?,?,?, 'USD', ?,?,?,?,?,?,?)
      ON DUPLICATE KEY UPDATE
        title=VALUES(title), price=VALUES(price), rating=VALUES(rating),
        review_count=VALUES(review_count), stock=VALUES(stock),
        last_crawled=VALUES(last_crawled)";
    $pdo->prepare($sql)->execute([
        $row['item_id'], $row['title'], $row['seller_name'],
        $row['price'], $row['rating'], $row['review_count'],
        $row['stock'], $row['image_url'], $row['url'], $country, $now
    ]);

    // 仅当价格相对上次发生变化时记录历史,避免历史表被重复值撑大
    $prev = $pdo->prepare("SELECT price FROM lazada_price_history WHERE item_id = ? ORDER BY crawled_at DESC LIMIT 1");
    $prev->execute([$row['item_id']]);
    if ($prev->fetchColumn() != $row['price']) {
        $pdo->prepare("INSERT INTO lazada_price_history (item_id, price, crawled_at) VALUES (?,?,?)")
            ->execute([$row['item_id'], $row['price'], $now]);
    }
}

参数化查询(? 占位符)是强制项,不能直接拼接 $row 到 SQL 字符串,否则商品标题中的特殊字符会破坏语句,甚至引入注入风险。价格历史只在价格相对上次记录发生变化时才写入,否则每天的全量巡检会把历史表撑成大量重复行,拖慢按 item_id 的检索。
批量场景用事务包裹,每累积 500 行提交一次,比逐条 autocommit 减少磁盘 fsync 次数,写入吞吐可提升数倍。注意事务不要开得过大,否则长事务会占用 undo 日志并阻塞备份。
六、性能优化与容错
连接复用。PDO 可加 PDO::ATTR_PERSISTENT => true 复用数据库连接,Guzzle Client 实例在循环外创建,两者都避免重复建连的开销。
并发与限流。Guzzle 的异步 Promise 或 Swoole 协程能把串行等待改成并发,但并发度要受亿牛云套餐通道数和 Lazada 频率限制的双重约束,不能只看本机 CPU。
失败重试。对 403 和超时做指数退避重试,间隔取 1s、2s、4s,并加入随机抖动避免多个任务同步重试形成尖峰。超过重试上限的记录写入 failed_queue 表,由独立任务延后补偿,不阻塞主流程。
校验与隔离。入库前校验 item_id 非空、price >= 0、评分在合理区间。不合规则的记录进异常表,不写主表,保证主表数据可直接用于分析。
增量调度。按 last_crawled 排序,只抓取超过设定阈值(如 24 小时)未更新的商品,整体请求量随之下降。
结语
从页面解析到 MySQL 落库,链路的每个环节都有明确的工程约束:字符集决定能否完整存下标题,DECIMAL 决定金额是否可信,唯一键决定能否安全 upsert,代理决定采集能否持续。把这些约束前置到表结构和代码里,后续的比价、预警和趋势分析才站得住脚。

相关文章
|
5天前
|
人工智能 安全 测试技术
|
7天前
|
云安全 人工智能 安全
阿里云 Agentic SOC 位居 IDC MarketScape安全运营智能体2026领导者类别
以 Agentic AI 重构安全运营闭环,阿里云云安全在产品能力与市场份额
1199 3
|
8天前
|
缓存 UED 开发者
Codex109天重置23次,明天还要再送一次
Codex近109天完成23次额度重置,7月14日将迎来第24次。Tibo高频响应用户反馈:优化GPT-5.6高消耗问题、补发失效福利、调整重置时间——形成“反馈→回应→修复→补偿”正向闭环,彰显以用户为中心的产品哲学。(239字)
762 12
|
1天前
|
人工智能 运维 数据挖掘
最新版通义千问(Qwen3.8-Max-Preview)功能介绍
2026年7月,阿里云通义千问正式对外开放**Qwen3.8-Max-Preview旗舰预览模型**,作为目前千问系列规格最高、综合性能最强的新一代万亿级AI模型,该模型搭载2.4T超大参数架构,是阿里云首款突破万亿参数的原生多模态旗舰模型,全面覆盖文本、图像、视频、文档多维度处理能力。相较于前代热门Qwen3.7-Max版本,本次预览版实现全方位跨越式升级,在真实工程开发、多智能体长周期任务、全链路办公自动化、海量数据分析等高阶场景中,综合能力已达到全球顶尖模型水准。现阶段该模型已正式开放抢先体验通道,依托阿里云百炼Token Plan、Qoder编码平台、QoderWork办公终端三大专属
1455 0
|
7天前
|
数据采集 机器学习/深度学习 人工智能
田间杂草定位与检测4200张YOLO智慧农业数据集分享
本数据集含4200张真实农田图像,YOLO格式,单类别(杂草)高质量标注,覆盖多作物、多光照、多生长阶段等复杂场景,专为智慧农业杂草检测与智能除草设备研发设计,支持YOLOv5/v8/v10等主流模型训练。
378 94
|
11天前
|
存储 人工智能 JSON
Qwen 本地部署搭配 ComfyUI 生成 AI 漫剧完整实操指南(小白零基础可落地,零成本无限生成+角色一致性天花板)
2026全网最优本地漫剧流水线:零成本、离线运行、角色统一、低配(8G显卡)可跑。融合Qwen本地大模型+ComfyUI双引擎,实现剧本生成→分镜绘图→动态成片全自动,隐私安全、无审核限流,新手30分钟上手,日更无忧。(239字)
|
2天前
|
人工智能
Qwen3.8抢先体验!正式版即将发布并开源!
千问Qwen3.8即将开源,参数达2.4T,进化速度以“天”计,实力媲美Fable 5。预览版Qwen3.8-Max已上线阿里Token Plan等平台,限时优惠:日间Credits低至1折,夜间更优,个人/团队版月付仅35元起!
398 15
|
5天前
|
Web App开发 数据采集 人工智能
|
6天前
|
人工智能 自然语言处理 云计算
2026阿里云大使招募:抢占AI先机,轻松赚取最高30%返佣,享官方全程陪跑支持!
阿里云2026云大使计划全新升级!无门槛加入,覆盖个人与企业。推广400+款产品(含热门MAAS产品,如秒悟、百炼等),享高额返佣+长周期收益。官方提供培训、方案落地、客户陪跑全链路支持,助你成为AI时代超级连接者。会分享,就能赚!