使用curl抓取網頁遇到HTTP跳轉時得到多個HTTP頭部的問題

weixin_33894992發表於2012-04-08

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/html

HTTP/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" 一項,正是要找的。

相關文章