傳統方式:COOKIEJAR/COOKIEFILE(磁碟 I/O)
$cookieFile = tempnam('/tmp', 'cookie');
curl_setopt($ch, CURLOPT_COOKIEJAR, $cookieFile); // 寫入
curl_setopt($ch, CURLOPT_COOKIEFILE, $cookieFile); // 讀取
// 缺點:兩次 I/O、雲平台(BAE/SAE)無本地寫入權限、併發需加鎖
無檔案方案:手動解析 Set-Cookie Header
步驟 1:登入請求,開啟 CURLOPT_HEADER 獲取原始回應
$ch = curl_init('https://example.com/login');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => 'user=admin&pass=123456',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => true, // 關鍵:將 Header 包含在返回字串
CURLOPT_SSL_VERIFYPEER => true,
]);
$response = curl_exec($ch);
步驟 2:正則提取 Set-Cookie(支援多 Cookie、屬性)
// 匹配所有 Set-Cookie 行,取值部分(分號前)
preg_match_all('/^Set-Cookie:\s*([^;]+)/mi', $response, $matches);
$cookies = $matches[1] ?? []; // 陣列:['PHPSESSID=abc123', 'csrf_token=xyz', ...]
// 組合為 Cookie Header 字串
$cookieHeader = implode('; ', $cookies); // "PHPSESSID=abc123; csrf_token=xyz"
curl_close($ch);
步驟 3:後續請求攜帶 Cookie
$ch2 = curl_init('https://example.com/api/data');
curl_setopt_array($ch2, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_COOKIE => $cookieHeader, // 直接送出 Cookie Header
CURLOPT_SSL_VERIFYPEER => true,
]);
$data = curl_exec($ch2);
curl_close($ch2);
封裝為可重用類(支援自動更新 Cookie)
class CurlCookieHandler {
private $cookieHeader = '';
private $ch;
public function __construct() {
$this->ch = curl_init();
curl_setopt_array($this->ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HEADER => true, // 永遠開啟以捕獲新 Cookie
]);
}
public function exec(string $url, array $post = []): string {
curl_setopt($this->ch, CURLOPT_URL, $url);
if ($post) {
curl_setopt($this->ch, CURLOPT_POST, true);
curl_setopt($this->ch, CURLOPT_POSTFIELDS, http_build_query($post));
} else {
curl_setopt($this->ch, CURLOPT_POST, false);
}
// 攜帶現有 Cookie
if ($this->cookieHeader) {
curl_setopt($this->ch, CURLOPT_COOKIE, $this->cookieHeader);
}
$response = curl_exec($this->ch);
if (curl_errno($this->ch)) {
throw new \RuntimeException(curl_error($this->ch));
}
// 更新 Cookie(合併新舊,新的覆蓋同名)
$this->updateCookies($response);
return substr($response, curl_getinfo($this->ch, CURLINFO_HEADER_SIZE));
}
private function updateCookies(string $response): void {
preg_match_all('/^Set-Cookie:\s*([^;]+)/mi', $response, $m);
foreach ($m[1] as $cookie) {
[$name] = explode('=', $cookie, 2);
// 移除舊同名
$this->cookieHeader = preg_replace(
'/(^|;\s*)' . preg_quote($name, '/') . '=[^;]*(;\s*|$)/',
'$1', $this->cookieHeader
);
// 附加新
$this->cookieHeader .= ($this->cookieHeader ? '; ' : '') . $cookie;
}
}
public function __destruct() {
curl_close($this->ch);
}
}
// 使用
$client = new CurlCookieHandler();
$html = $client->exec('https://example.com/login', ['user'=>'a','pass'=>'b']);
$json = $client->exec('https://example.com/api/user'); // 自動攜帶登入 Cookie
注意事項與限制
- HttpOnly Cookie:JavaScript 無法讀取,但 cURL 可正常收發,無影響。
- Secure / SameSite:僅影響瀏覽器行為,cURL 忽略。
- 多域名:若跨域名需維護多組 Cookie,類中改用 `array $cookies[domain] => header`。
- 效能:省去磁碟 I/O,適合高併發、Serverless、容器環境。
- 完整性:此法僅處理 `Set-Cookie`,若需支援 `Cookie2`、RFC 2965 等舊標準,建議改用 `GuzzleHttp\Cookie\CookieJar`。