博客 / 詳情

返回

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,包含快遞公司和完整物流軌跡

總體思路

image.png
image.png
image.png


第一步:環境搭建與依賴安裝

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開發核心技能。

image.png

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.