Thinkphp与百度物流查询接口实战(保姆级教程)
教程前言
- 本教程将带领大家基于 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 # 核心代码文件
第二步:核心思路拆解
在写代码前,先明确整个物流查询的核心流程:
- 接收并校验前端传入的物流单号 → 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 核心知识点讲解
-
Guzzle客户端配置:
- timeout:设置请求超时,避免接口长时间等待;
- verify => false:本地开发环境常缺少SSL证书,关闭验证可避免请求失败;
- User-Agent:模拟浏览器请求,百度会拦截无UA或异常UA的爬虫请求。
-
Cookie解析逻辑:
- 百度返回的Set-Cookie响应头格式为:BAIDUID=xxx; expires=xxx; path=/; domain=.baidu.com;
- 先通过explode(‘;’, $cookieStr)拆分属性,只取第一部分(键值对);
- 再通过explode(‘=’, $parts[0], 2)拆分key和value(第二个参数2表示最多拆2部分,避免value含等号导致拆分错误)。
-
核心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 关键逻辑讲解
-
Cookie字符串拼接:
Guzzle的Cookie请求头需要字符串格式(如BAIDUID=xxx; BIDUPSID=xxx),因此需要将数组转为字符串,并去除最后多余的 ; 。 -
接口响应校验:
百度该接口的标准响应格式为:
{“code”:0,“message”:“success”,“data”:{“company”:“ems”}}- 先校验code === 0(成功状态);
- 再提取data.company(快递公司编码);
- 任何一步失败都抛出异常,由上层try-catch处理。
-
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 核心知识点讲解
-
Cookie的必要性:
百度页面是否返回TokenV2取决于Cookie是否有效,不传Cookie或Cookie失效都会导致匹配不到TokenV2。 -
正则匹配原理:
- 正则表达式 /tokenV2=(.*?)"/:
- tokenV2=:匹配固定前缀;
- (.*?):非贪婪匹配(避免截取过多内容),捕获TokenV2值;
- ":匹配TokenV2的结束引号。
- $matches[1]:正则捕获组的第一个结果(即TokenV2值)。
- 正则表达式 /tokenV2=(.*?)"/:
-
请求头补充:
添加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 关键逻辑讲解
-
请求参数说明:
参数名:query_from_srcid → 作用:百度固定来源ID,值为51151(不可修改)
参数名:tokenV2 → 作用:接口校验参数(第六步抓取)
参数名:nu → 作用:物流单号
参数名:com → 作用:快递公司编码(第五步识别) -
URL拼接:
使用http_build_query($params)将数组参数转为URL编码的字符串(如tokenV2=xxx&nu=xxx),避免手动拼接出现编码问题。 -
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返回值
第十步:进阶优化建议
- 添加缓存:Cookie和TokenV2可设置5分钟缓存(避免频繁请求百度);
- 频率限制:对同一IP的请求添加频率限制(如1分钟最多10次),防止被百度风控;
- 快递公司映射:将百度返回的编码(如ems)映射为中文名称(如邮政EMS),提升用户体验;
- 异步处理:高频查询场景可改为异步队列处理,避免接口超时;
- 多源备份:百度接口失效时,可切换到其他物流查询接口(如快递100)。
教程总结
本教程从环境搭建到代码实现,完整拆解了「百度物流查询接口」的对接流程,核心要点:
- 百度接口依赖Cookie和TokenV2做鉴权,需实时抓取;
- 异常处理是接口稳定性的关键,必须覆盖每一步可能的失败场景;
- 模拟浏览器请求头(UA、Referer、Host)是避免被风控的核心;
- 标准化的JSON返回格式,便于前后端对接。
通过本教程,不仅能实现物流查询功能,还能掌握「HTTP请求」「Cookie解析」「正则匹配」「异常处理」等PHP开发核心技能。

更多推荐




所有评论(0)