Woody的技術Blog » 使用curl抓取網頁遇到HTTP跳轉時得到多個HTTP頭部的問題
使用curl抓取網頁遇到HTTP跳轉時得到多個HTTP頭部的問題
在PHP的CURL擴充套件中,是可以通過CURL自身的設計自動處理HTTP 30X的跳轉的。這個特性在使用起來很簡單:
$ch = curl_init(); curl_setopt($ch, CURLOPT_URL, 'http://www.example.com'); curl_setopt($ch, CURLOPT_HEADER, true); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); $content = curl_exec($ch);
正常情況下,$content 中的結果包括了 HTTP 頭和 body 的資訊,且以 "\r\n\r\n" 分隔。因此可以用
list($header, $body) = explode("\r\n\r\n", $content, 2);
來分別取得這兩部分。不過如果在要訪問的地址上發生了 HTTP 跳轉,這時 curl_exec 執行得到的結果中就包含了兩次訪問的頭部。例如把上面的程式碼的 URL 部分換成我這裡的:
curl_setopt($ch, CURLOPT_URL, 'http://cn.programmingnote.com');
當訪問 cn.programmingnote.com 時,會觸發一個 302 跳轉。此時 curl_exec 返回內容的開頭是:
HTTP/1.1 302 Found
Date: Tue, 21 Jun 2011 08:15:58 GMT
Server: Apache/2.2.16 (Ubuntu)
X-Powered-By: PHP/5.3.3-1ubuntu9.5
Location: blog/
Vary: Accept-Encoding
Content-Length: 0
Content-Type: text/htmlHTTP/1.1 200 OK
Date: Tue, 21 Jun 2011 08:15:58 GMT
Server: Apache/2.2.16 (Ubuntu)
X-Powered-By: PHP/5.3.3-1ubuntu9.5
X-Pingback: http://cn.programmingnote.com/blog/xmlrpc.php
Vary: Accept-Encoding
Transfer-Encoding: chunked
Content-Type: text/html; charset=UTF-8很明顯包含了兩次的 HTTP 頭部資訊。此時再用 explode("\r\n\r\n", $content, 2) 的方法會把下面的頭部資訊歸到 HTTP Body 部分裡去。
而我在命令列下直接使用 curl 來訪問包含跳轉的地址時,卻發現可以正確地把頭部和 body 部分割槽別開。因此我想到可能是 PHP 的 curl 擴充套件在實現方面有些問題。於是我在 curl 擴充套件的程式碼中找到了 curl_exec 的實現:
/* {{{ proto bool curl_exec(resource ch) Perform a cURL session */ PHP_FUNCTION(curl_exec) { CURLcode error; zval *zid; php_curl *ch; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &zid) == FAILURE) { return; } ZEND_FETCH_RESOURCE(ch, php_curl *, &zid, -1, le_curl_name, le_curl); _php_curl_cleanup_handle(ch); error = curl_easy_perform(ch->cp); SAVE_CURL_ERROR(ch, error); /* CURLE_PARTIAL_FILE is returned by HEAD requests */ if (error != CURLE_OK && error != CURLE_PARTIAL_FILE) { if (ch->handlers->write->buf.len > 0) { smart_str_free(&ch->handlers->write->buf); } RETURN_FALSE; } // more code ... }從名字上可以看出,真正處理 HTTP 訪問的程式碼應該是在 curl_easy_perform 做的。經查證,這個函式是屬於 libcurl 這個庫的,和 PHP 擴充套件已經沒有關係了。並且,我寫了一段直接使用 libcurl 庫的程式碼,和 PHP 中的用法並沒有太大的區別,也沒有特殊的引數用來設計是保留多次的 HTTP Header 還是隻保留最後的一次 Header。
#include <curl/curl.h> #include <stdio.h> int main() { CURL *handler = curl_easy_init(); curl_easy_setopt(handler, CURLOPT_HEADER, 1); curl_easy_setopt(handler, CURLOPT_FOLLOWLOCATION, 1); curl_easy_setopt(handler, CURLOPT_URL, "http://cn.programmingnote.com"); int res = curl_easy_perform(handler); printf("%d\n", res); return 0; }和在 PHP 中測試的結果一樣,依然是記錄了兩個 Header。
既然如此,想要把最後跳轉到的地址的 Header 和 Body 區別出來,如果能知道跳轉的次數就好了。畢竟每多跳一次,就多了一個 Header 部分,而且多個 Header 之間仍然是以 "\r\n\r\n" 分隔的。於是看 PHP curl 的 curl_getinfo,在 Return Values 中看到了 "redirect_count" 一項,正是要找的。