博客 / 詳情

返回

ThinkPHP 實現微博數據自動採集(含Cookie自動獲取+評論爬取)- 完整教程

一、教程簡介

本文基於 ThinkPHP 6.x/8.x 框架,從零到一實現一套完整的微博公開數據採集方案。核心能力包括:自動獲取微博訪問Cookie(無需手動配置)、爬取熱門時間線微博列表、採集單條微博評論、清理文本格式、標準化日期顯示,同時內置防封禁策略和完整的異常處理機制,可直接集成到你的 ThinkPHP 項目中使用。

二、前置準備

1. 環境要求

  • PHP 版本:7.4 及以上(需開啓 curl 擴展,可通過 php -m 查看)
  • 框架版本:ThinkPHP 6.x / 8.x(5.1 版本可稍作適配)
  • 工具依賴:Composer(用於安裝第三方包)
  • 服務器:任意可運行 PHP 的環境(本地/雲服務器均可)

2. 安裝核心依賴

本方案使用 GuzzleHTTP 處理 HTTP 請求(比原生 curl 更易用、更穩定),執行以下 Composer 命令安裝:


composer require guzzlehttp/guzzle

三、完整代碼實現

1. 創建控制器文件

在 ThinkPHP 項目的 app/controller 目錄下新建 WeiboController.php,寫入以下完整代碼(包含所有核心功能):


<?php
namespace app\controller;

use GuzzleHttp\Client;
use think\Controller;
use think\facade\Log;
use think\facade\Request;
use think\response\Json;
use DateTime;
use DateTimeZone;
use DateTimeException;
use Exception;

class WeiboController extends Controller
{
    /**
     * 微博數據採集入口接口
     * 訪問地址:http://你的域名/weibo/test?page=1&comment_count=10
     * @return Json
     */
    public function testweibo()
    {
        try {
            // 1. 獲取並校驗請求參數
            $page = Request::param('page', 1, 'intval');
            $commentCount = Request::param('comment_count', 20, 'intval');
            
            // 頁碼合法性校驗
            if ($page < 1) {
                return json(['code' => 0, 'msg' => '頁碼必須大於0', 'data' => []])->code(400);
            }
            
            // 2. 初始化Guzzle客户端(模擬瀏覽器請求,避免被識別為爬蟲)
            $client = new Client([
                'timeout' => 15, // 請求超時時間(秒)
                'verify' => false, // 關閉SSL證書驗證(避免服務器證書問題)
                'headers' => [
                    'referer' => 'https://weibo.com/newlogin?tabtype=weibo&gid=102803&openLoginLayer=0&url=https%3A%2F%2Fweibo.com%2F',
                    'user-agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36 Edg/137.0.0.0',
                    'Accept' => 'application/json, text/javascript, */*; q=0.01',
                    'X-Requested-With' => 'XMLHttpRequest',
                ]
            ]);
            
            // 3. 自動獲取SUB Cookie(核心:無需手動從瀏覽器複製)
            $sub = $this->getSubCookie($client);
            if (empty($sub)) {
                return json(['code' => 0, 'msg' => '獲取訪問Cookie失敗', 'data' => []])->code(500);
            }
            
            // 4. 構造Cookie數組並獲取微博熱門列表
            $cookies = ['SUB' => $sub];
            $weiboList = $this->getWeiboList($client, $cookies, $page);
            
            // 無數據時返回友好提示
            if (empty($weiboList)) {
                return json([
                    'code' => 1,
                    'msg' => '獲取微博成功,當前頁無數據',
                    'data' => ['page' => $page, 'total' => 0, 'list' => []]
                ]);
            }
            
            // 5. 處理微博數據(含評論採集,測試模式僅取前3條)
            $testLimit = min(3, count($weiboList)); // 限制測試條數,避免請求過多
            $resultList = [];
            
            for ($i = 0; $i < $testLimit; $i++) {
                $weibo = $weiboList[$i];
                
                // 格式化發佈時間(轉為標準格式)
                $formattedDate = $this->formatWeiboDate($weibo['created_at'] ?? '');
                
                // 清理微博內容(去除HTML標籤、保留表情)
                $cleanContent = $this->cleanWeiboText($weibo['text_raw'] ?? $weibo['text'] ?? '');
                
                // 獲取評論數據(添加隨機延遲防封禁)
                $comments = $this->getWeiboComments($client, $weibo['id'] ?? '', $commentCount);
                sleep(rand(1, 3)); // 1-3秒隨機延遲,降低請求頻率
                
                // 組裝結構化數據
                $resultList[] = [
                    'weibo_id' => $weibo['id'] ?? '',
                    'user_name' => $weibo['user']['screen_name'] ?? '未知用户',
                    'source' => $weibo['source'] ?? '未知來源',
                    'content' => $cleanContent,
                    'publish_time' => $formattedDate,
                    'stats' => [
                        'reposts_count' => $weibo['reposts_count'] ?? 0, // 轉發數
                        'comments_count' => $weibo['comments_count'] ?? 0, // 評論數
                        'attitudes_count' => $weibo['attitudes_count'] ?? 0 // 點贊數
                    ],
                    'comments' => $comments,
                    'comment_count' => count($comments)
                ];
            }
            
            // 6. 返回最終採集結果
            return json([
                'code' => 1,
                'msg' => '微博數據採集成功',
                'data' => [
                    'page' => $page,
                    'total_weibo' => count($weiboList), // 本次獲取的微博總數
                    'test_weibo_count' => count($resultList), // 實際處理的微博數
                    'weibo_list' => $resultList
                ]
            ]);
            
        } catch (Exception $e) {
            // 異常日誌記錄(便於排查問題)
            Log::error("微博採集失敗:{$e->getMessage()} 行號:{$e->getLine()} 文件名:{$e->getFile()}");
            return json([
                'code' => 0,
                'msg' => '採集失敗:'.$e->getMessage(),
                'data' => []
            ])->code(500);
        }
    }
    
    /**
     * 自動獲取SUB Cookie(微博核心認證字段)
     * @param Client $client Guzzle客户端實例
     * @return string SUB Cookie值(空字符串表示失敗)
     */
    private function getSubCookie($client)
    {
        // 微博訪客Cookie生成接口
        $url = "https://passport.weibo.com/visitor/genvisitor2";
        $postData = [
            "cb" => "visitor_gray_callback",
            "tid" => "01AUXHE0uWNcmbV0Qlq3L-R4dZHGS_3E7eKqUtdA9HiUgQ",
            "from" => "weibo",
            "webdriver" => "false"
        ];
        
        // 發送POST請求獲取Cookie
        $response = $client->request('POST', $url, [
            'form_params' => $postData
        ]);
        
        $responseBody = $response->getBody()->getContents();
        
        // 正則匹配返回結果中的SUB字段
        if (preg_match('/"sub":"([^"]+)"/', $responseBody, $matches)) {
            return $matches[1];
        }
        
        return '';
    }
    
    /**
     * 獲取微博熱門時間線列表
     * @param Client $client Guzzle客户端實例
     * @param array $cookies Cookie數組(含SUB)
     * @param int $page 分頁參數(max_id)
     * @return array 微博列表數據
     */
    private function getWeiboList($client, $cookies, $page)
    {
        $url = 'https://weibo.com/ajax/feed/hottimeline';
        // 核心請求參數(微博熱門接口固定參數)
        $params = [
            'refresh' => '2',
            'group_id' => '102803', // 熱門分組ID(推薦流)
            'containerid' => '102803',
            'extparam' => 'discover|new_feed',
            'max_id' => $page, // 分頁參數(頁碼)
            'count' => '10', // 每頁獲取10條
        ];
        
        // 構建Cookie字符串
        $cookieStr = '';
        foreach ($cookies as $key => $value) {
            $cookieStr .= "$key=$value; ";
        }
        
        // 發送GET請求獲取微博列表
        $response = $client->request('GET', $url, [
            'query' => $params,
            'headers' => [
                'Cookie' => rtrim($cookieStr, '; ') // 去除末尾多餘的分號和空格
            ]
        ]);
        
        $responseBody = $response->getBody()->getContents();
        $jsonData = json_decode($responseBody, true);
        
        // 返回微博列表(無數據時返回空數組)
        return $jsonData['statuses'] ?? [];
    }
    
    /**
     * 獲取單條微博的評論數據
     * @param Client $client Guzzle客户端實例
     * @param string $weiboId 微博ID
     * @param int $count 要獲取的評論條數(最大20條)
     * @return array 格式化後的評論列表
     */
    private function getWeiboComments($client, $weiboId, $count = 20)
    {
        // 微博ID為空時直接返回空
        if (empty($weiboId)) {
            return [];
        }
        
        $url = 'https://weibo.com/ajax/statuses/buildComments';
        $params = [
            'flow' => 0,
            'is_reload' => '1',
            'id' => $weiboId, // 目標微博ID
            'is_show_bulletin' => '2',
            'is_mix' => '0',
            'count' => min($count, 20), // 限制最大20條(接口限制)
            'uid' => '1700720163',
            'fetch_level' => '0',
            'locale' => 'zh-CN'
        ];
        
        // 發送GET請求獲取評論
        $response = $client->request('GET', $url, [
            'query' => $params
        ]);
        
        $responseBody = $response->getBody()->getContents();
        $jsonData = json_decode($responseBody, true);
        
        // 格式化評論數據
        $comments = [];
        foreach ($jsonData['data'] ?? [] as $item) {
            $comments[] = [
                'comment_id' => $item['id'] ?? '',
                'user_name' => $item['user']['screen_name'] ?? '未知用户',
                'content' => $this->cleanWeiboText($item['text'] ?? ''),
                'publish_time' => $this->formatWeiboDate($item['created_at'] ?? ''),
                'like_count' => $item['like_counts'] ?? 0
            ];
        }
        
        return $comments;
    }
    
    /**
     * 清理微博文本(去除HTML標籤、保留表情符號)
     * @param string $text 原始微博文本
     * @return string 清理後的純文本
     */
    private function cleanWeiboText($text)
    {
        if (empty($text)) {
            return '無內容';
        }
        
        // 保留img標籤的alt屬性(表情符號,如[微笑])
        $text = preg_replace('/<img\s+[^>]*alt="(\[[^\]]+\])"[^>]*>/', '$1', $text);
        
        // 去除所有剩餘HTML標籤(a、span、div等)
        $text = preg_replace('/<[^>]+>/', '', $text);
        
        // 去除用户卡片的特殊標記
        $text = preg_replace('/ usercard="[^"]*"/', '', $text);
        
        // 保留鏈接文本,去除href屬性
        $text = preg_replace('/<a\s+[^>]*href=[^>]*>([^<]+)<\/a>/', '$1', $text);
        
        // 去除換行符和多餘空格
        $text = str_replace("\n", ' ', $text);
        $text = preg_replace('/\s+/', ' ', trim($text));
        
        return $text;
    }
    
    /**
     * 格式化微博日期(轉為Y-m-d H:i:s標準格式)
     * @param string $dateStr 原始日期字符串(如 Wed Sep 18 10:22:33 +0800 2024)
     * @return string 標準化時間
     */
    private function formatWeiboDate($dateStr)
    {
        if (empty($dateStr)) {
            return '未知時間';
        }
        
        try {
            // 解析微博默認日期格式
            $date = DateTime::createFromFormat('D M d H:i:s O Y', $dateStr, new DateTimeZone('Asia/Shanghai'));
            
            if ($date) {
                return $date->format('Y-m-d H:i:s');
            }
            
            // 兼容其他日期格式
            $date = new DateTime($dateStr, new DateTimeZone('Asia/Shanghai'));
            return $date->format('Y-m-d H:i:s');
            
        } catch (DateTimeException $e) {
            return '格式錯誤';
        }
    }
}

2. 配置路由

在 ThinkPHP 項目的 route/app.php 文件中添加以下路由配置,用於訪問微博採集接口:


use think\facade\Route;

// 微博數據採集接口(GET請求)
Route::get('/weibo/test', 'WeiboController@testweibo');

四、核心功能詳解

1. 自動獲取 SUB Cookie

功能説明

微博接口需要 SUB Cookie 進行身份認證,本方案通過調用微博官方的訪客 Cookie 生成接口(genvisitor2),自動獲取 SUB 值,無需手動從瀏覽器複製 Cookie,解決了 Cookie 過期、配置繁瑣的問題。

關鍵邏輯

  • 向 https://passport.weibo.com/visitor/genvisitor2 發送 POST 請求,攜帶固定參數
  • 通過正則表達式 /"sub":"(+)"/ 匹配返回結果中的 SUB 值
  • 若獲取失敗,直接返回錯誤提示

2. 微博熱門列表爬取

接口説明

使用微博熱門時間線接口 https://weibo.com/ajax/feed/hottimeline,返回推薦流的熱門微博數據。

核心參數

參數名 取值 説明
group_id 102803 熱門分組ID(固定值)
containerid 102803 容器ID(與group_id一致)
max_id 頁碼(如1) 分頁參數,控制獲取第幾頁
count 10 每頁獲取的微博條數

3. 評論採集

接口説明

通過 https://weibo.com/ajax/statuses/buildComments 接口獲取單條微博的評論數據,接口限制最多返回 20 條/次。

防封禁策略

  • 每條評論請求後添加 1-3 秒隨機延遲(sleep(rand(1,3)))
  • 限制測試模式下僅採集前 3 條微博的評論
  • 模擬瀏覽器請求頭,避免被識別為爬蟲

4. 文本與日期格式化

文本清理

  • 保留表情符號(如 [微笑]):通過正則匹配 img 標籤的 alt 屬性
  • 去除所有 HTML 標籤:避免前端展示時出現亂碼
  • 清理多餘空格和換行:統一文本格式

日期格式化

  • 解析微博默認日期格式(如 Wed Sep 18 10:22:33 +0800 2024)
  • 轉為 Y-m-d H:i:s 標準格式,便於存儲和展示
  • 異常處理:解析失敗時返回「格式錯誤」

五、接口調用與測試

1. 調用方式

GET 請求示例


# 基礎調用(默認頁碼1,每條微博獲取20條評論)
http://你的域名/weibo/test

# 自定義參數(頁碼2,每條微博獲取10條評論)
http://你的域名/weibo/test?page=2&comment_count=10

2. 參數説明

參數名 類型 默認值 取值範圍 説明
page int 1 ≥1 微博列表的頁碼
comment_count int 20 1~20 每條微博要獲取的評論條數

3. 返回結果示例


{
  "code": 1,
  "msg": "微博數據採集成功",
  "data": {
    "page": 1,
    "total_weibo": 10,
    "test_weibo_count": 3,
    "weibo_list": [
      {
        "weibo_id": "1234567890123456",
        "user_name": "微博官方",
        "source": "微博客户端",
        "content": "這是一條測試微博[微笑]",
        "publish_time": "2024-09-18 10:22:33",
        "stats": {
          "reposts_count": 1200,
          "comments_count": 500,
          "attitudes_count": 3000
        },
        "comments": [
          {
            "comment_id": "9876543210987654",
            "user_name": "普通用户",
            "content": "這條微博很有意義",
            "publish_time": "2024-09-18 10:30:00",
            "like_count": 15
          }
        ],
        "comment_count": 1
      }
    ]
  }
}

4. 錯誤碼説明

code 説明 解決方案
0 請求失敗 查看msg字段的錯誤提示,檢查日誌
1 請求成功 正常處理返回數據
400 參數錯誤 確保page參數≥1,comment_count參數1~20
500 服務器內部錯誤 檢查Cookie獲取是否成功,或接口是否正常訪問

六、注意事項

1. 防封禁注意事項

  • 請求頻率:避免短時間內大量請求,建議單IP每分鐘請求不超過20次
  • 請求頭配置:必須模擬瀏覽器的 user-agent、referer,否則接口會返回403
  • SSL驗證:關閉 verify => false 避免證書問題導致請求失敗
  • IP封禁:若出現訪問失敗,可更換IP或等待1-2小時後重試

2. 兼容性適配

ThinkPHP 5.1 適配

  • 將 use think\facade\Request; 改為 use Request;
  • 將 return json()->code(400); 改為 return json()->header('', '', 400);
  • 將 Log::error() 改為 \think\Log::error()

3. 合法性説明

  • 本方案僅用於爬取微博公開數據,嚴禁用於商業爬蟲、惡意採集
  • 遵守《網絡安全法》和微博平台用户協議,控制爬取規模
  • 不得將採集的數據用於違法違規場景,否則後果自負

七、擴展優化方向

1. 功能擴展

  • 分頁爬取:解析接口返回的 max_id,實現多頁微博自動採集
  • 評論分頁:通過評論接口的 max_id 參數實現評論分頁獲取
  • 數據存儲:將採集的微博/評論存入 MySQL/Redis(使用 ThinkPHP 模型)
  • 關鍵詞過濾:添加關鍵詞篩選,僅採集包含指定關鍵詞的微博
  • 多賬號輪換:配置多個 SUB Cookie 輪換使用,降低單賬號封禁風險

2. 性能優化

  • Cookie 緩存:將獲取的 SUB Cookie 緩存到 Redis,有效期內無需重複請求
  • 異步請求:使用 Guzzle 異步請求批量獲取評論,提升採集效率
  • 連接池:配置 Guzzle 連接池,減少 TCP 連接建立開銷
  • 數據壓縮:返回數據時開啓 Gzip 壓縮,減少傳輸體積

3. 穩定性優化

  • 重試機制:請求失敗時添加重試邏輯(最多3次),提升成功率
  • 動態 User-Agent:隨機切換 User-Agent 列表,降低被識別為爬蟲的概率
  • 監控告警:添加接口可用性監控,異常時觸發郵件/短信告警
  • 熔斷機制:連續失敗次數達到閾值時暫停採集,避免無效請求

八、常見問題排查

1. Cookie 獲取失敗

  • 檢查 Guzzle 客户端是否配置了正確的請求頭
  • 確認服務器可以訪問 passport.weibo.com(可通過 curl 測試)
  • 檢查正則表達式是否匹配最新的返回格式

2. 微博列表返回空

  • 確認 SUB Cookie 有效(可手動替換為瀏覽器的 SUB 測試)
  • 檢查分頁參數 max_id 是否正確
  • 確認請求頭的 referer、user-agent 配置正確

image.png

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

發佈 評論

Some HTML is okay.