教程前言
- 本教程將帶領大家基於 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值)。
-
- 請求頭補充:
添加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開發核心技能。