将 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天前
|
存储 弹性计算 缓存
阿里云服务器租赁费用:新版租赁收费标准及活动报价参考
本文更新了2026年阿里云全系列云服务器租赁活动报价,所有特惠资源均可前往阿里云活动中心选购,整体覆盖从个人入门到企业级高性能场景的全梯度需求。其中轻量应用服务器主打极致性价比,2核2G峰值200M带宽配置每日10点、15点限时抢购价仅38元/年,2核4G配置379元/年起;高性价比的经济型e实例、通用算力型u2i实例覆盖2核4G至4核32G全档位,适配开发测试与中小型企业业务;搭载英特尔至强6处理器的第九代c9i企业级实例算力较上代提升20%,支撑高并发生产环境,不同实例规格价差清晰,用户可根据自身业务负载与预算灵活选型。
1574 112
|
12天前
|
云安全 人工智能 运维
阿里云联动百位企业安全专家,共识Agent防御最佳实践
当Agent成为新员工,你的安全边界在哪里?
1942 8
阿里云联动百位企业安全专家,共识Agent防御最佳实践
|
6天前
|
人工智能 程序员 API
Codex 接入 DeepSeek-V4-Flash:还能补上识图,提供两套方案
Codex 接入 DeepSeek-V4-Flash 怎么配?本文覆盖 CLI 与桌面端,再用 qwen3-vl-flash 补识图,两套方案可直接照做
1000 3
|
6天前
|
编解码 人工智能 安全
2核4G/4核8G/8核16G阿里云服务器如何选择实例?经济型e、通用算力型u2i与计算型c9i选哪个?
本文介绍了阿里云2核4G、4核8G、8核16G三档主流配置下经济型e、通用算力型u2i和计算型c9i三种实例的最新活动价格与适用场景。同配置下三者价差显著,以2核4G为例,经济型e低至599.93元/年,计算型c9i则高达1742.08元/年。文章详细解析了各实例的性能定位:经济型e适合轻负载入门场景,u2i兼顾稳定算力与性价比,c9i凭借第9代至强处理器与芯片级安全能力支撑高性能业务。同时提示用户可叠加满减优惠券享受折上折,建议根据业务负载与预算综合决策。
529 112
|
18天前
|
人工智能 前端开发 Linux
Codex 桌面版安装 + CC Switch 接入第三方 API 完整教程(2026 最新)
2026最新教程:手把手教你安装Codex桌面版,通过CC Switch v3.17.0一键接入Fenno等国产API(兼容OpenAI Responses格式),跳过账号登录,完整启用代码审查、多步任务与上下文感知功能。零基础友好,全程图文实操。(239字)
2567 4
|
10天前
|
存储 人工智能 关系型数据库
阿里云AI产品与云产品最新组合套餐:Token Plan、AI coding及云服务器和建站等组合优惠价
阿里云推出全新“算力+模型+应用”一站式云与AI组合套餐活动,覆盖从个人开发者到中大型企业的全场景需求。核心亮点为分三档定价的Token Plan订阅服务,支持Qwen3.8-Max-Preview大模型调用,错峰时段最低可享0.2折优惠。活动同步推出AI Coding、智能体部署、云电脑托管、0代码建站等十余类场景化组合,搭配99元/年的普惠云服务器、88元/年的入门数据库等经典特惠产品,还为企业提供1V1定制化AI转型方案,大幅降低了不同用户群体拥抱AI的技术门槛与采购成本。
723 111
|
20天前
|
人工智能 JSON 安全
Fastjson远程代码执行漏洞,阿里云AI安全为您保驾护航
阿里云AI安全产品联动防御Fastjson攻击
2636 13
Fastjson远程代码执行漏洞,阿里云AI安全为您保驾护航
|
6天前
|
人工智能 JSON Shell
2026AI漫剧本地全开源方案(附各个软件模型链接),8G显卡也能流畅运行
这是一套完全本地化部署的AI漫剧生成技术链路:涵盖LLM剧本分镜生成、FLUX文生图(IP-Adapter人脸锁定)、StoryDiffusion时序连贯控制、LTX-2.3唇形同步视频生成,及ComfyUI全流程调度。零云端费用,仅耗硬件算力,单集2–4小时可产出竖屏短视频,适配抖音/B站分发。
|
7天前
Qoder 一周年 × Qwen3.8-Max 正式上线,多重好礼限时领
8月3日,Qwen3.8-Max 正式上线Qoder,迎来Qoder一周年。新老用户可领800次免费调用,下单再赠2000次;夜间(22:00–08:00)调用5折;邀请好友双方得积分与调用额度。
449 1

热门文章

最新文章