教程前言

  • 本教程将带领大家基于 ThinkPHP框架 + Guzzle HTTP客户端,从零实现「仅传物流单号自动识别快递公司并查询物流详情」的功能。教程全程拆解核心逻辑,每一步都包含「代码编写+原理讲解」,即使是新手也能理解并复现。

前置条件

  • 开发环境:PHP 7.2+、Composer
  • 框架:ThinkPHP 5.x/6.x(教程兼容两种版本)
  • 依赖:Guzzle 6.x(HTTP请求工具)
  • 基础认知:了解PHP数组、JSON解析、HTTP请求原理

最终实现效果

  • 请求示例:GET /admin/express/query?nu=9820834246834
  • 响应示例:返回标准化JSON,包含快递公司和完整物流轨迹

总体思路

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


第一步:环境搭建与依赖安装

1.1 安装Guzzle HTTP客户端

Guzzle是PHP主流的HTTP请求库,用于调用百度物流接口,执行以下命令安装:

composer require guzzlehttp/guzzle:^6.0

说明:指定6.x版本是因为教程代码适配该版本的API,避免新版本兼容性问题。

1.2 确认ThinkPHP控制器结构

在ThinkPHP项目中,创建物流查询控制器:

app/
└── index/
    └── controller/
        └── Express.php  # 核心代码文件

第二步:核心思路拆解

在写代码前,先明确整个物流查询的核心流程:

  1. 接收并校验前端传入的物流单号 → 2. 抓取百度有效Cookie(接口鉴权用)→ 3. 调用百度接口识别快递公司 → 4. 抓取百度物流页面的TokenV2(接口校验用)→ 5. 调用百度接口查询物流详情 → 6. 标准化返回结果

每一步都依赖上一步的结果,且需处理异常,保证接口稳定性。


第三步:编写入口接口(query方法)

入口方法是整个功能的「总调度」,负责串联所有步骤、参数校验和异常处理。

3.1 代码编写

打开Express.php,编写基础结构和query方法:

<?php
namespace app\admin\controller;

use think\Controller;
use GuzzleHttp\Client;
use think\Log;

class Express extends Controller
{
    /**
     * 物流查询入口接口(仅传单号)
     * 请求方式:GET
     * 请求参数:nu=物流单号
     */
    public function query()
    {
        // 步骤1:获取并校验物流单号
        $nu = $this->request->param('nu', '');
        if (empty($nu)) {
            // 标准化错误返回(前后端统一格式)
            return json([
                'code' => 1001,
                'msg'  => '物流单号不能为空',
                'data' => null
            ]);
        }

        try {
            // 步骤2:获取百度Cookie(接口鉴权必需)
            $cookieArr = $this->getBaiduCookie();
            
            // 步骤3:识别快递公司
            $com = $this->getExpressCompany($nu, $cookieArr);
            if (empty($com)) {
                throw new \Exception('无法识别快递公司');
            }
            
            // 步骤4:获取TokenV2(物流详情接口校验必需)
            $tokenV2 = $this->getTokenV2($cookieArr);
            
            // 步骤5:查询物流详情
            $result = $this->getExpressInfo($nu, $com, $tokenV2, $cookieArr);
            
            // 步骤6:成功返回结果
            return json([
                'code' => 0,
                'msg'  => '查询成功',
                'data' => [
                    'company' => $com,
                    'express_info' => $result
                ]
            ]);
        } catch (\Exception $e) {
            // 全局异常捕获(避免接口崩溃,记录错误日志)
            Log::error("物流查询失败:{$e->getMessage()},单号:{$nu}");
            return json([
                'code' => 1002,
                'msg'  => $e->getMessage(),
                'data' => null
            ]);
        }
    }
}

3.2 代码详解

代码段:$nu = $this->request->param(‘nu’, ‘’);

  • 作用说明:获取GET参数中的物流单号,默认值为空字符串

代码段:empty($nu)

  • 作用说明:校验单号是否为空,为空则返回1001错误

代码段:try-catch

  • 作用说明:捕获所有业务异常,保证接口不会直接抛出错误页面

代码段:Log::error(…)

  • 作用说明:记录错误日志,便于后期排查问题

代码段:json(…)

  • 作用说明:ThinkPHP内置方法,返回JSON格式响应(前后端分离必备)

第四步:实现百度Cookie抓取(getBaiduCookie方法)

百度物流接口需要携带有效Cookie才能正常请求,该方法的作用是访问百度页面,抓取并解析核心Cookie。

4.1 代码编写

在Express.php中新增getBaiduCookie方法:
/**
 * 抓取百度核心Cookie(实时获取,无缓存)
 * @return array Cookie键值对数组
 */
protected function getBaiduCookie(): array
{
    // 1. 初始化Guzzle客户端
    $client = new Client([
        'timeout' => 10,          // 请求超时时间(秒)
        'verify' => false,        // 关闭SSL证书验证(避免本地环境证书问题)
        'headers' => [
            // 模拟浏览器UA(避免被百度识别为爬虫)
            'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/128.0.0.0 Safari/537.36',
        ]
    ]);

    // 2. 请求百度快递搜索页面(触发Cookie返回)
    $response = $client->get('http://www.baidu.com/s?ie=utf-8&f=8&wd=%E5%BF%AB%E9%80%92');

    // 3. 解析响应头中的Set-Cookie
    $cookieArr = [];
    $setCookies = $response->getHeader('Set-Cookie');
    Log::info('【百度Cookie响应头】' . json_encode($setCookies, JSON_UNESCAPED_UNICODE));

    foreach ($setCookies as $cookieStr) {
        // 拆分Cookie属性(如expires、path等),只取键值对部分
        $parts = explode(';', $cookieStr);
        if (empty($parts[0])) continue;

        // 拆分Cookie的key和value(最多拆2部分,避免value含等号)
        $cookiePair = explode('=', $parts[0], 2);
        if (count($cookiePair) != 2) continue;

        $key = trim($cookiePair[0]);
        $value = trim($cookiePair[1]);

        // 只保留百度物流接口必需的核心Cookie
        $coreCookies = ['BAIDUID', 'BIDUPSID', 'H_PS_PSSID', 'BDORZ', 'BAIDUID_BFESS'];
        if (in_array($key, $coreCookies)) {
            $cookieArr[$key] = $value;
        }
    }

    return $cookieArr;
}

4.2 核心知识点讲解

  1. Guzzle客户端配置:

    • timeout:设置请求超时,避免接口长时间等待;
    • verify => false:本地开发环境常缺少SSL证书,关闭验证可避免请求失败;
    • User-Agent:模拟浏览器请求,百度会拦截无UA或异常UA的爬虫请求。
  2. Cookie解析逻辑:

    • 百度返回的Set-Cookie响应头格式为:BAIDUID=xxx; expires=xxx; path=/; domain=.baidu.com;
    • 先通过explode(‘;’, $cookieStr)拆分属性,只取第一部分(键值对);
    • 再通过explode(‘=’, $parts[0], 2)拆分key和value(第二个参数2表示最多拆2部分,避免value含等号导致拆分错误)。
  3. 核心Cookie筛选:
    只保留BAIDUID等关键Cookie,减少无效参数传递,提升请求效率。


第五步:实现快递公司识别(getExpressCompany方法)

传入物流单号和Cookie,调用百度接口识别对应的快递公司(如ems、sf、yt等)。

5.1 代码编写

新增getExpressCompany方法:

/**
 * 调用百度接口识别快递公司
 * @param string $nu 物流单号
 * @param array $cookieArr 百度Cookie数组
 * @return string 快递公司编码(如ems、sf)
 * @throws \Exception 识别失败抛出异常
 */
protected function getExpressCompany(string $nu, array $cookieArr): string
{
    // 1. 拼接Cookie字符串(Guzzle请求头需要字符串格式)
    $cookieStr = '';
    foreach ($cookieArr as $k => $v) {
        $cookieStr .= $k . '=' . $v . '; ';
    }
    $cookieStr = rtrim($cookieStr, '; '); // 去除最后一个分号和空格

    // 2. 百度快递公司识别接口地址
    $url = "http://alayn.baidu.com/express/appdetail/get_com?num={$nu}";

    // 3. 发起请求
    $client = new Client([
        'timeout' => 10,
        'verify' => false,
        'headers' => [
            'Cookie' => $cookieStr,
            'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0',
        ]
    ]);
    $response = $client->get($url);
    $result = $response->getBody()->getContents();

    // 4. 解析JSON响应
    $resultArr = json_decode($result, true);
    if (json_last_error() !== JSON_ERROR_NONE) {
        throw new \Exception('快递公司识别接口返回格式异常:' . $result);
    }

    // 5. 校验接口响应状态
    $code = $resultArr['code'] ?? -1;
    if ($code !== 0) {
        $msg = $resultArr['message'] ?? '接口返回非成功状态';
        throw new \Exception('识别快递公司失败:' . $msg);
    }

    // 6. 提取快递公司名称
    $company = trim($resultArr['data']['company'] ?? '');
    if (empty($company)) {
        throw new \Exception('接口未返回有效快递公司,返回数据:' . json_encode($resultArr));
    }

    Log::info("成功识别快递公司:{$company},单号:{$nu}");
    return $company;
}

5.2 关键逻辑讲解

  1. Cookie字符串拼接:
    Guzzle的Cookie请求头需要字符串格式(如BAIDUID=xxx; BIDUPSID=xxx),因此需要将数组转为字符串,并去除最后多余的 ; 。

  2. 接口响应校验:
    百度该接口的标准响应格式为:
    {“code”:0,“message”:“success”,“data”:{“company”:“ems”}}

    • 先校验code === 0(成功状态);
    • 再提取data.company(快递公司编码);
    • 任何一步失败都抛出异常,由上层try-catch处理。
  3. JSON解析校验:
    使用json_last_error() !== JSON_ERROR_NONE检查JSON解析是否成功,避免接口返回非JSON格式导致程序报错。


第六步:实现TokenV2抓取(getTokenV2方法)

百度物流详情接口需要TokenV2参数做校验,该参数嵌入在百度快递页面的HTML中,需通过正则匹配提取。

6.1 代码编写

新增getTokenV2方法:

/**
 * 从百度页面抓取TokenV2(物流详情接口必需)
 * @param array $cookieArr 百度Cookie数组
 * @return string TokenV2值
 * @throws \Exception 获取失败抛出异常
 */
protected function getTokenV2(array $cookieArr): string
{
    // 1. 拼接Cookie字符串
    $cookieStr = '';
    foreach ($cookieArr as $k => $v) {
        $cookieStr .= $k . '=' . $v . '; ';
    }
    $cookieStr = rtrim($cookieStr, '; ');
    Log::info('【TokenV2请求Cookie】' . $cookieStr);

    // 2. 发起请求获取百度快递页面
    $client = new Client([
        'timeout' => 10,
        'verify' => false,
        'headers' => [
            'Cookie' => $cookieStr, // 必须传Cookie,否则页面不返回TokenV2
            'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/93.0.4577.63 Safari/537.36',
            'Host' => 'www.baidu.com',
            'Referer' => 'https://www.baidu.com/',
        ]
    ]);
    $response = $client->get('http://www.baidu.com/s?ie=utf-8&f=8&wd=%E5%BF%AB%E9%80%92');
    $html = $response->getBody()->getContents();
    Log::info('【百度快递页面HTML】' . $html);

    // 3. 正则匹配TokenV2(页面格式:tokenV2="xxx")
    preg_match('/tokenV2=(.*?)"/', $html, $matches);
    if (empty($matches[1])) {
        throw new \Exception('未从百度页面获取到TokenV2');
    }

    return $matches[1];
}

6.2 核心知识点讲解

  1. Cookie的必要性:
    百度页面是否返回TokenV2取决于Cookie是否有效,不传Cookie或Cookie失效都会导致匹配不到TokenV2。

  2. 正则匹配原理:

    • 正则表达式 /tokenV2=(.*?)"/:
      • tokenV2=:匹配固定前缀;
      • (.*?):非贪婪匹配(避免截取过多内容),捕获TokenV2值;
      • ":匹配TokenV2的结束引号。
    • $matches[1]:正则捕获组的第一个结果(即TokenV2值)。
  3. 请求头补充:
    添加Host和Referer请求头,模拟真实浏览器行为,降低被百度风控的概率。


第七步:实现物流详情查询(getExpressInfo方法)

携带单号、快递公司、TokenV2、Cookie,调用百度物流详情接口,返回完整物流轨迹。

7.1 代码编写

新增getExpressInfo方法:

/**
 * 调用百度接口查询物流详情
 * @param string $nu 物流单号
 * @param string $com 快递公司编码
 * @param string $tokenV2 TokenV2值
 * @param array $cookieArr 百度Cookie数组
 * @return array 物流详情数组
 * @throws \Exception 查询失败抛出异常
 */
protected function getExpressInfo(string $nu, string $com, string $tokenV2, array $cookieArr): array
{
    // 1. 拼接Cookie字符串
    $cookieStr = '';
    foreach ($cookieArr as $k => $v) {
        $cookieStr .= $k . '=' . $v . '; ';
    }
    $cookieStr = rtrim($cookieStr, '; ');

    // 2. 拼接请求参数
    $params = [
        'query_from_srcid' => 51151, // 百度固定来源ID(不可修改)
        'tokenV2' => $tokenV2,
        'nu' => $nu,
        'com' => $com
    ];
    $url = 'https://alayn.baidu.com/express/appdetail/get_detail?' . http_build_query($params);

    // 3. 发起请求
    $client = new Client([
        'timeout' => 10,
        'verify' => false,
        'headers' => [
            'Cookie' => $cookieStr,
            'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/128.0.0.0 Safari/537.36',
            'Referer' => 'https://www.baidu.com',
            'Host' => 'alayn.baidu.com'
        ]
    ]);
    $response = $client->get($url);
    $result = $response->getBody()->getContents();

    // 4. 解析响应
    $resultArr = json_decode($result, true);
    if (json_last_error() !== JSON_ERROR_NONE) {
        throw new \Exception('物流详情接口返回格式异常,解析失败');
    }

    return $resultArr;
}

7.2 关键逻辑讲解

  1. 请求参数说明:
    参数名:query_from_srcid → 作用:百度固定来源ID,值为51151(不可修改)
    参数名:tokenV2 → 作用:接口校验参数(第六步抓取)
    参数名:nu → 作用:物流单号
    参数名:com → 作用:快递公司编码(第五步识别)

  2. URL拼接:
    使用http_build_query($params)将数组参数转为URL编码的字符串(如tokenV2=xxx&nu=xxx),避免手动拼接出现编码问题。

  3. Host请求头:
    目标接口域名是alayn.baidu.com,必须指定Host请求头,否则百度服务器无法正确路由请求。


第八步:测试接口

8.1 访问接口

启动ThinkPHP项目,通过浏览器/Postman访问:
http://你的域名/admin/express/query?nu=9820834246834

8.2 响应示例

成功响应
{
    "code": 0,
    "msg": "查询成功",
    "data": {
        "company": "ems",
        "express_info": {
            "code": 0,
            "message": "success",
            "data": {
                "list": [
                    {
                        "time": "2025-01-01 10:00:00",
                        "content": "【北京市】快递已揽收"
                    },
                    {
                        "time": "2025-01-02 12:00:00",
                        "content": "【上海市】快递已派送"
                    }
                ],
                "status": "已签收"
            }
        }
    }
}
失败响应
{
    "code": 1002,
    "msg": "无法识别快递公司",
    "data": null
}

第九步:常见问题与解决方案

问题现象:Cookie获取为空 → 原因分析:1. UA模拟不真实;2. 网络无法访问百度 → 解决方案:1. 更换真实浏览器UA;2. 检查服务器网络
问题现象:TokenV2匹配不到 → 原因分析:1. Cookie失效;2. 正则表达式不匹配 → 解决方案:1. 重新抓取Cookie;2. 查看HTML日志,调整正则
问题现象:快递公司识别失败 → 原因分析:1. 单号错误;2. 百度接口风控 → 解决方案:1. 核对单号;2. 降低请求频率,更换UA
问题现象:物流详情返回空 → 原因分析:1. TokenV2失效;2. 快递公司编码错误 → 解决方案:1. 重新抓取TokenV2;2. 检查getExpressCompany返回值


第十步:进阶优化建议

  1. 添加缓存:Cookie和TokenV2可设置5分钟缓存(避免频繁请求百度);
  2. 频率限制:对同一IP的请求添加频率限制(如1分钟最多10次),防止被百度风控;
  3. 快递公司映射:将百度返回的编码(如ems)映射为中文名称(如邮政EMS),提升用户体验;
  4. 异步处理:高频查询场景可改为异步队列处理,避免接口超时;
  5. 多源备份:百度接口失效时,可切换到其他物流查询接口(如快递100)。

教程总结

本教程从环境搭建到代码实现,完整拆解了「百度物流查询接口」的对接流程,核心要点:

  1. 百度接口依赖Cookie和TokenV2做鉴权,需实时抓取;
  2. 异常处理是接口稳定性的关键,必须覆盖每一步可能的失败场景;
  3. 模拟浏览器请求头(UA、Referer、Host)是避免被风控的核心;
  4. 标准化的JSON返回格式,便于前后端对接。

通过本教程,不仅能实现物流查询功能,还能掌握「HTTP请求」「Cookie解析」「正则匹配」「异常处理」等PHP开发核心技能。

在这里插入图片描述

Logo

电商企业物流数字化转型必备!快递鸟 API 接口,72 小时快速完成物流系统集成。全流程实战1V1指导,营造开放的API技术生态圈。

更多推荐