cURL 是一个利用URL语法规定来传输文件和数据的工具,支持不少协议,如HTTP、FTP、TELNET等。最爽的是,PHP也支持 cURL 库。本文将介绍 cURL 的一些高级特性,以及在PHP中如何运用它。 为何要用 cURL? 是的,咱们能够经过其余办法获取网页内容。大多数时候,我由于想偷懒,都直接用简单的PHP函数: 如下为引用的内容: $content = file_get_contents("http://www.nettuts.com"); // or $lines = file("http://www.nettuts.com"); // or readfile(http://www.nettuts.com); 不过,这种作法缺少灵活性和有效的错误处理。并且,你也不能用它完成一些高难度任务——好比处理coockies、验证、表单提交、文件上传等等。 引用: cURL 是一种功能强大的库,支持不少不一样的协议、选项,能提供 URL 请求相关的各类细节信息。 基本结构 在学习更为复杂的功能以前,先来看一下在PHP中创建cURL请求的基本步骤: 初始化 设置变量 执行并获取结果 释放cURL句柄 如下为引用的内容: // 1. 初始化 $ch = curl_init(); // 2. 设置选项,包括URL curl_setopt($ch, CURLOPT_URL, "http://www.nettuts.com"); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HEADER, 0); // 3. 执行并获取HTML文档内容 $output = curl_exec($ch); // 4. 释放curl句柄 curl_close($ch); 第二步(也就是 curl_setopt() )最为重要,一切玄妙均在此。有一长串cURL参数可供设置,它们能指定URL请求的各个细节。要一次性所有看完并理解可能比较困难,因此今天咱们只试一下那些更经常使用也更有用的选项。 检查错误 你能够加一段检查错误的语句(虽然这并非必需的): 如下为引用的内容: // ... $output = curl_exec($ch); if ($output === FALSE) { echo "cURL Error: " . curl_error($ch); } // ... 请注意,比较的时候咱们用的是“=== FALSE”,而非“== FALSE”。由于咱们得区分 空输出 和 布尔值FALSE,后者才是真正的错误。 获取信息 这是另外一个可选的设置项,可以在cURL执行后获取这一请求的有关信息: 如下为引用的内容: // ... curl_exec($ch); $info = curl_getinfo($ch); echo '获取'. $info['url'] . '耗时'. $info['total_time'] . '秒'; // ... 返回的数组中包括了如下信息: “url” //资源网络地址 “content_type” //内容编码 “http_code” //HTTP状态码 “header_size” //header的大小 “request_size” //请求的大小 “filetime” //文件建立时间 “ssl_verify_result” //SSL验证结果 “redirect_count” //跳转技术 “total_time” //总耗时 “namelookup_time” //DNS查询耗时 “connect_time” //等待链接耗时 “pretransfer_time” //传输前准备耗时 “size_upload” //上传数据的大小 “size_download” //下载数据的大小 “speed_download” //下载速度 “speed_upload” //上传速度 “download_content_length”//下载内容的长度 “upload_content_length” //上传内容的长度 “starttransfer_time” //开始传输的时间 “redirect_time”//重定向耗时 基于浏览器的重定向 在第一个例子中,咱们将提供一段用于侦测服务器是否有基于浏览器的重定向的代码。例如,有些网站会根据是不是手机浏览器甚至用户来自哪一个国家来重定向网页。 咱们利用 CURLOPT_HTTPHEADER 选项来设定咱们发送出的HTTP请求头信息(http headers),包括user agent信息和默认语言。而后咱们来看看这些特定网站是否会把咱们重定向到不一样的URL。 如下为引用的内容: // 测试用的URL $urls = array( "http://www.cnn.com", "http://www.mozilla.com", "http://www.facebook.com" ); // 测试用的浏览器信息 $browsers = array( "standard" => array ( "user_agent" => "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 (.NET CLR 3.5.30729)", "language" => "en-us,en;q=0.5" ), "iphone" => array ( "user_agent" => "Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleWebKit/420+ (KHTML, like Gecko) Version/3.0 Mobile/1A537a Safari/419.3", "language" => "en" ), "french" => array ( "user_agent" => "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; GTB6; .NET CLR 2.0.50727)", "language" => "fr,fr-FR;q=0.5" ) ); foreach ($urls as $url) { echo "URL: $url\n"; foreach ($browsers as $test_name => $browser) { $ch = curl_init(); // 设置 url curl_setopt($ch, CURLOPT_URL, $url); // 设置浏览器的特定header curl_setopt($ch, CURLOPT_HTTPHEADER, array( "User-Agent: {$browser['user_agent']}", "Accept-Language: {$browser['language']}" )); // 页面内容咱们并不须要 curl_setopt($ch, CURLOPT_NOBODY, 1); // 只需返回HTTP header curl_setopt($ch, CURLOPT_HEADER, 1); // 返回结果,而不是输出它 curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $output = curl_exec($ch); curl_close($ch); // 有重定向的HTTP头信息吗? if (preg_match("!Location: (.*)!", $output, $matches)) { echo "$test_name: redirects to $matches[1]\n"; } else { echo "$test_name: no redirection\n"; } } echo "\n\n"; } 首先,咱们创建一组须要测试的URL,接着指定一组须要测试的浏览器信息。最后经过循环测试各类URL和浏览器匹配可能产生的状况。 由于咱们指定了cURL选项,因此返回的输出内容则只包括HTTP头信息(被存放于 $output 中)。利用一个简单的正则,咱们检查这个头信息中是否包含了“Location:”字样。 运行这段代码应该会返回以下结果: 用POST方法发送数据 当发起GET请求时,数据能够经过“查询字串”(query string)传递给一个URL。例如,在google中搜索时,搜索关键即为URL的查询字串的一部分: http://www.google.com/search?q=nettuts 这种状况下你可能并不须要cURL来模拟。把这个URL丢给“file_get_contents()”就能获得相同结果。 不过有一些HTML表单是用POST方法提交的。这种表单提交时,数据是经过 HTTP请求体(request body) 发送,而不是查询字串。例如,当使用CodeIgniter论坛的表单,不管你输入什么关键字,老是被POST到以下页面: http://codeigniter.com/forums/do_search/ 你能够用PHP脚原本模拟这种URL请求。首先,新建一个能够接受并显示POST数据的文件,咱们给它命名为post_output.php: print_r($_POST); 接下来,写一段PHP脚原本执行cURL请求: 如下为引用的内容: $url = "http://localhost/post_output.php"; $post_data = array ( "foo" => "bar", "query" => "Nettuts", "action" => "Submit" ); $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // 咱们在POST数据哦! curl_setopt($ch, CURLOPT_POST, 1); // 把post的变量加上 curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data); $output = curl_exec($ch); curl_close($ch); echo $output; 执行代码后应该会获得如下结果: 这段脚本发送一个POST请求给 post_output.php ,这个页面 $_POST 变量并返回,咱们利用cURL捕捉了这个输出。 文件上传 上传文件和前面的POST十分类似。由于全部的文件上传表单都是经过POST方法提交的。 首先新建一个接收文件的页面,命名为 upload_output.php: print_r($_FILES); 如下是真正执行文件上传任务的脚本: 如下为引用的内容: $url = "http://localhost/upload_output.php"; $post_data = array ( "foo" => "bar", // 要上传的本地文件地址 "upload" => "@C:/wamp/www/test.zip" ); $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data); $output = curl_exec($ch); curl_close($ch); echo $output; 若是你须要上传一个文件,只须要把文件路径像一个post变量同样传过去,不过记得在前面加上@符号。执行这段脚本应该会获得以下输出: cURL批处理(multi cURL) cURL还有一个高级特性——批处理句柄(handle)。这一特性容许你同时或异步地打开多个URL链接。 下面是来自来自php.net的示例代码: 如下为引用的内容: // 建立两个cURL资源 $ch1 = curl_init(); $ch2 = curl_init(); // 指定URL和适当的参数 curl_setopt($ch1, CURLOPT_URL, "http://lxr.php.net/"); curl_setopt($ch1, CURLOPT_HEADER, 0); curl_setopt($ch2, CURLOPT_URL, "http://www.php.net/"); curl_setopt($ch2, CURLOPT_HEADER, 0); // 建立cURL批处理句柄 $mh = curl_multi_init(); // 加上前面两个资源句柄 curl_multi_add_handle($mh,$ch1); curl_multi_add_handle($mh,$ch2); // 预约义一个状态变量 $active = null; // 执行批处理 do { $mrc = curl_multi_exec($mh, $active); } while ($mrc == CURLM_CALL_MULTI_PERFORM); while ($active && $mrc == CURLM_OK) { if (curl_multi_select($mh) != -1) { do { $mrc = curl_multi_exec($mh, $active); } while ($mrc == CURLM_CALL_MULTI_PERFORM); } } // 关闭各个句柄 curl_multi_remove_handle($mh, $ch1); curl_multi_remove_handle($mh, $ch2); curl_multi_close($mh); 这里要作的就是打开多个cURL句柄并指派给一个批处理句柄。而后你就只需在一个while循环里等它执行完毕。 这个示例中有两个主要循环。第一个 do-while 循环重复调用 curl_multi_exec() 。这个函数是无隔断(non-blocking)的,但会尽量少地执行。它返回一个状态值,只要这个值等于常量 CURLM_CALL_MULTI_PERFORM ,就表明还有一些刻不容缓的工做要作(例如,把对应URL的http头信息发送出去)。也就是说,咱们须要不断调用该函数,直到返回值发生改变。 而接下来的 while 循环,只在 $active 变量为 true 时继续。这一变量以前做为第二个参数传给了 curl_multi_exec() ,表明只要批处理句柄中是否还有活动链接。接着,咱们调用 curl_multi_select() ,在活动链接(例如接受服务器响应)出现以前,它都是被“屏蔽”的。这个函数成功执行后,咱们又会进入另外一个 do-while 循环,继续下一条URL。 仍是来看一看怎么把这一功能用到实处吧: WordPress 链接检查器 想象一下你有一个文章数目庞大的博客,这些文章中包含了大量外部网站连接。一段时间以后,由于这样那样的缘由,这些连接中至关数量都失效了。要么是被和谐了,要么是整个站点都被功夫网了... 咱们下面创建一个脚本,分析全部这些连接,找出打不开或者404的网站/网页,并生成一个报告。 请注意,如下并非一个真正可用的WordPress插件,仅仅是一段独立功能的脚本而已,仅供演示,谢谢。 好,开始吧。首先,从数据库中读取全部这些连接: 如下为引用的内容: // CONFIG $db_host = 'localhost'; $db_user = 'root'; $db_pass = ''; $db_name = 'wordpress'; $excluded_domains = array( 'localhost', 'www.mydomain.com'); $max_connections = 10; // 初始化一些变量 $url_list = array(); $working_urls = array(); $dead_urls = array(); $not_found_urls = array(); $active = null; // 连到 MySQL if (!mysql_connect($db_host, $db_user, $db_pass)) { die('Could not connect: ' . mysql_error()); } if (!mysql_select_db($db_name)) { die('Could not select db: ' . mysql_error()); } // 找出全部含有连接的文章 $q = "SELECT post_content FROM wp_posts WHERE post_content LIKE '%href=%' AND post_status = 'publish' AND post_type = 'post'"; $r = mysql_query($q) or die(mysql_error()); while ($d = mysql_fetch_assoc($r)) { // 用正则匹配连接 if (preg_match_all("!href=\"(.*?)\"!", $d['post_content'], $matches)) { foreach ($matches[1] as $url) { // exclude some domains $tmp = parse_url($url); if (in_array($tmp['host'], $excluded_domains)) { continue; } // store the url $url_list []= $url; } } } // 移除重复连接 $url_list = array_values(array_unique($url_list)); if (!$url_list) { die('No URL to check'); } 咱们首先配置好数据库,一系列要排除的域名($excluded_domains),以及最大并发链接数($max_connections)。而后,链接数据库,获取文章和包含的连接,把它们收集到一个数组中($url_list)。 下面的代码有点复杂了,所以我将一小步一小步地详细解释: 如下为引用的内容: // 1. 批处理器 $mh = curl_multi_init(); // 2. 加入需批量处理的URL for ($i = 0; $i < $max_connections; $i++) { add_url_to_multi_handle($mh, $url_list); } // 3. 初始处理 do { $mrc = curl_multi_exec($mh, $active); } while ($mrc == CURLM_CALL_MULTI_PERFORM); // 4. 主循环 while ($active && $mrc == CURLM_OK) { // 5. 有活动链接 if (curl_multi_select($mh) != -1) { // 6. 干活 do { $mrc = curl_multi_exec($mh, $active); } while ($mrc == CURLM_CALL_MULTI_PERFORM); // 7. 有信息否? if ($mhinfo = curl_multi_info_read($mh)) { // 意味着该链接正常结束 // 8. 从curl句柄获取信息 $chinfo = curl_getinfo($mhinfo['handle']); // 9. 死链么? if (!$chinfo['http_code']) { $dead_urls []= $chinfo['url']; // 10. 404了? } else if ($chinfo['http_code'] == 404) { $not_found_urls []= $chinfo['url']; // 11. 还能用 } else { $working_urls []= $chinfo['url']; } // 12. 移除句柄 curl_multi_remove_handle($mh, $mhinfo['handle']); curl_close($mhinfo['handle']); // 13. 加入新URL,干活 if (add_url_to_multi_handle($mh, $url_list)) { do { $mrc = curl_multi_exec($mh, $active); } while ($mrc == CURLM_CALL_MULTI_PERFORM); } } } } // 14. 完了 curl_multi_close($mh); echo "==Dead URLs==\n"; echo implode("\n",$dead_urls) . "\n\n"; echo "==404 URLs==\n"; echo implode("\n",$not_found_urls) . "\n\n"; echo "==Working URLs==\n"; echo implode("\n",$working_urls); // 15. 向批处理器添加url function add_url_to_multi_handle($mh, $url_list) { static $index = 0; // 若是还剩url没用 if ($url_list[$index]) { // 新建curl句柄 $ch = curl_init(); // 配置url curl_setopt($ch, CURLOPT_URL, $url_list[$index]); // 不想输出返回的内容 curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // 重定向到哪儿咱们就去哪儿 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); // 不须要内容体,可以节约带宽和时间 curl_setopt($ch, CURLOPT_NOBODY, 1); // 加入到批处理器中 curl_multi_add_handle($mh, $ch); // 拨一下计数器,下次调用该函数就能添加下一个url了 $index++; return true; } else { // 没有新的URL须要处理了 return false; } } 下面解释一下以上代码。列表的序号对应着代码注释中的顺序数字。 新建一个批处理器。Created a multi handle. 稍后咱们将建立一个把URL加入批处理器的函数 add_url_to_multi_handle() 。每当这个函数被调用,就有一个新url被加入批处理器。一开始,咱们给批处理器添加了10个URL(这一数字由 $max_connections 所决定)。 运行 curl_multi_exec() 进行初始化工做是必须的,只要它返回 CURLM_CALL_MULTI_PERFORM 就还有事情要作。这么作主要是为了建立链接,它不会等待完整的URL响应。 只要批处理中还有活动链接主循环就会一直持续。 curl_multi_select() 会一直等待,直到某个URL查询产生活动链接。 cURL的活儿又来了,主要是获取响应数据。 检查各类信息。当一个URL请求完成时,会返回一个数组。 在返回的数组中有一个 cURL 句柄。咱们利用其获取单个cURL请求的相应信息。 若是这是一个死链或者请求超时,不会返回http状态码。 若是这个页面找不到了,会返回404状态码。 其余状况咱们都认为这个连接是可用的(固然,你也能够再检查一下500错误之类...)。 从该批次移除这个cURL句柄,由于它已经没有利用价值了,关了它! 很好,如今能够另外加一个URL进来了。再一次地,初始化工做又开始进行... 嗯,该干的都干了。关闭批处理器,生成报告。 回过头来看给批处理器添加新URL的函数。这个函数每调用一次,静态变量 $index 就递增一次,这样咱们才能知道还剩多少URL没处理。 我把这个脚本在个人博客上跑了一遍(测试须要,有一些错误连接是故意加上的),结果以下: 如下为引用的内容: <img border="0" src="http://nettuts.s3.cdn.plus.org/534_curl/ss_4.png" /> 共检查约40个URL,只耗费两秒不到。当须要检查更加大量的URL时,其省心省力的效果可想而知!若是你同时打开10个链接,还能再快上10倍!另外,你还能够利用cURL批处理的无隔断特性来处理大量URL请求,而不会阻塞你的Web脚本。 另外一些有用的cURL 选项 HTTP 认证 若是某个URL请求须要基于 HTTP 的身份验证,你可使用下面的代码: 复制内容到剪贴板代码: 如下为引用的内容: $url = "http://www.somesite.com/members/"; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // 发送用户名和密码 curl_setopt($ch, CURLOPT_USERPWD, "myusername:mypassword"); // 你能够容许其重定向 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); // 下面的选项让 cURL 在重定向后 // 也能发送用户名和密码 curl_setopt($ch, CURLOPT_UNRESTRICTED_AUTH, 1); $output = curl_exec($ch); curl_close($ch); FTP 上传 PHP 自带有 FTP 类库, 但你也能用 cURL: 如下为引用的内容: // 开一个文件指针 $file = fopen("/path/to/file", "r"); // url里包含了大部分所需信息 $url = "ftp://username:password@mydomain.com:21/path/to/new/file"; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // 上传相关的选项 curl_setopt($ch, CURLOPT_UPLOAD, 1); curl_setopt($ch, CURLOPT_INFILE, $fp); curl_setopt($ch, CURLOPT_INFILESIZE, filesize("/path/to/file")); // 是否开启ASCII模式 (上传文本文件时有用) curl_setopt($ch, CURLOPT_FTPASCII, 1); $output = curl_exec($ch); curl_close($ch); FQ术 你能够用代理发起cURL请求: 如下为引用的内容: $ch = curl_init(); curl_setopt($ch, CURLOPT_URL,'http://www.example.com'); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // 指定代理地址 curl_setopt($ch, CURLOPT_PROXY, '11.11.11.11:8080'); // 若是须要的话,提供用户名和密码 curl_setopt($ch, CURLOPT_PROXYUSERPWD,'user:pass'); $output = curl_exec($ch); curl_close ($ch); 回调函数 能够在一个URL请求过程当中,让cURL调用某指定的回调函数。例如,在内容或者响应下载的过程当中马上开始利用数据,而不用等到彻底下载完。 如下为引用的内容: $ch = curl_init(); curl_setopt($ch, CURLOPT_URL,'http://net.tutsplus.com'); curl_setopt($ch, CURLOPT_WRITEFUNCTION,"progress_function"); curl_exec($ch); curl_close ($ch); function progress_function($ch,$str) { echo $str; return strlen($str); } 这个回调函数必须返回字串的长度,否则此功能将没法正常使用。 在URL响应接收的过程当中,只要收到一个数据包,这个函数就会被调用。 小结 今天咱们一块儿学习了cURL库的强大功能和灵活的扩展性。但愿你喜欢。下一次要发起URL请求时,考虑下cURL吧!