zoukankan      html  css  js  c++  java
  • php下载远程图片方法总结(curl手动解析header)curl跳转问题解决

    常用方法一般有:、

    file_get_contents

    file_put_contents

    readfile($file) //效率很高。

    一般代码:

    /**
     * 抓取远程图片
     *
     * @param string $url 远程图片路径
     * @param string $filename 本地存储文件名
     */
    function grabImage($url, $filename = '') {
        if($url == '') {
            return false; //如果 $url 为空则返回 false;
        }
        $ext_name = strrchr($url, '.'); //获取图片的扩展名
        if($ext_name != '.gif' && $ext_name != '.jpg' && $ext_name != '.bmp' && $ext_name != '.png') {
            return false; //格式不在允许的范围
        }
        if($filename == '') {
            $filename = time().$ext_name; //以时间戳另起名
        }
        //开始捕获
        ob_start();
        readfile($url);
        $img_data = ob_get_contents();
        ob_end_clean();
        $size = strlen($img_data);
        $local_file = fopen($filename , 'a');
        fwrite($local_file, $img_data);
        fclose($local_file);
        return $filename;
    }

    我一个网址:http://www.njphp.cn/uc_server/avatar.php?uid=1&size=middle 测试一下,发现什么都没有输出。

    为什么没有输出:因为grabImage函数检测url发现扩展名不是图片格式就返回false了。我们可以不检测后缀名就可以了。

    让我们看看http://www.njphp.cn/uc_server/avatar.php?uid=1&size=middle

    这个网址,这个网址不是直接输出图片流,而是重定向了。

    在浏览器测试发现

    Status Code:

    301 Moved Permanently
    返回的301状态吗,永久性重定向到另一个页面,也就是图片的真实地址:
    http://www.njphp.cn/uc_server/data/avatar/000/00/00/01_avatar_middle.jpg。
    上面的代码无法处理重定向这种形式。

    上面的函数有几个缺点:

    1.不能自动识别图片后缀名(很多图片的url并不指向一个静态图片地址,而是直接将图片流输出到客户端)

    2.不支持图片url的302跳转  (只是return false罢了,readfile和file_get_contents是读取最终的文件的,即支持重定向)

    这个函数并不符合本人项目的需求,于是花了点时间自己写了一个下载函数,此函数支持:

    1.静态图片下载

    2.服务端直接输出图片流下载

    3.服务端使用302跳转到真实图片地址的下载(可限定跳转次数) 

    函数代码如下:

    /**
         * 下载远程图片
         * @param string $url 图片的绝对url
         * @param string $filepath 文件的完整路径(包括目录,不包括后缀名,例如/www/images/test) ,此函数会自动根据图片url和http头信息确定图片的后缀名
         * @return mixed 下载成功返回一个描述图片信息的数组,下载失败则返回false
         */
        function downloadImage($url, $filepath) {
            //服务器返回的头信息
            $responseHeaders = array();
            //原始图片名
            $originalfilename = '';
            //图片的后缀名
            $ext = '';
            $ch = curl_init($url);
            //设置curl_exec返回的值包含Http头
            curl_setopt($ch, CURLOPT_HEADER, 1);
            //设置curl_exec返回的值包含Http内容
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);        
            //设置抓取跳转(http 301,302)后的页面
            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
            //设置最多的HTTP重定向的数量
            curl_setopt($ch, CURLOPT_MAXREDIRS, 2);
    
            //服务器返回的数据(包括http头信息和内容)
            $html = curl_exec($ch);
            //获取此次抓取的相关信息
            $httpinfo = curl_getinfo($ch);
            curl_close($ch);
            if ($html !== false) {
                //分离response的header和body,由于服务器可能使用了302跳转,所以此处需要将字符串分离为 2+跳转次数 个子串
                $httpArr = explode("
    
    ", $html, 2 + $httpinfo['redirect_count']); //最后一个参数可选。规定所返回的数组元素的最大数目。
                //倒数第二段是服务器最后一次response的http头
                $header = $httpArr[count($httpArr) - 2];
                //倒数第一段是服务器最后一次response的内容
                $body = $httpArr[count($httpArr) - 1];
                $header.="
    ";
    
                //获取最后一次response的header信息
                preg_match_all('/([a-z0-9-_]+):s*([^
    ]+)
    /i', $header, $matches);
                if (!empty($matches) && count($matches) == 3 && !empty($matches[1]) && !empty($matches[1])) {
                    for ($i = 0; $i < count($matches[1]); $i++) {
                        if (array_key_exists($i, $matches[2])) {
                            $responseHeaders[$matches[1][$i]] = $matches[2][$i];
                        }
                    }
                }
                //获取图片后缀名
                if (0 < preg_match('{(?:[^/\\]+).(jpg|jpeg|gif|png|bmp)$}i', $url, $matches)) {
                    $originalfilename = $matches[0];
                    $ext = $matches[1];
                } else {
                    if (array_key_exists('Content-Type', $responseHeaders)) {
                        if (0 < preg_match('{image/(w+)}i', $responseHeaders['Content-Type'], $extmatches)) {
                            $ext = $extmatches[1];
                        }
                    }
                }
                //保存文件
                if (!empty($ext)) {
                    $filepath .= ".$ext";
                    //如果目录不存在,则先要创建目录
                    CFiles::createDirectory(dirname($filepath));
                    $local_file = fopen($filepath, 'w');
                    if (false !== $local_file) {
                        if (false !== fwrite($local_file, $body)) {
                            fclose($local_file);
                            $sizeinfo = getimagesize($filepath);
                            return array('filepath' => realpath($filepath), 'width' => $sizeinfo[0], 'height' => $sizeinfo[1], 'orginalfilename' => $originalfilename, 'filename' => pathinfo($filepath, PATHINFO_BASENAME));
                        }
                    }
                }
            }
            return false;
        }

     解决跳转问题设置:

    //设置抓取跳转(http 301,302)后的页面
            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
            //设置最多的HTTP重定向的数量
            curl_setopt($ch, CURLOPT_MAXREDIRS, 2);

    值得注意的是
    我们获取了所有跳转的页面的header:
    $httpinfo=curl_getinfo($ch);
    如何得到最后一个页面的header,用到了explode函数。
    explode(分隔符,字符串,limit);
    //最后一个参数可选。规定所返回的数组元素的最大数目。假设我们请求的页面有2次跳转
    a->b->c

    print_r ($httpinfo)结果类似:

    Array
    (
        [url] => http://c.php
        [content_type] => text/html
        [http_code] => 200
        [header_size] => 602
        [request_size] => 230
        [filetime] => -1
        [ssl_verify_result] => 0
        [redirect_count] => 2
        [total_time] => 0.281
        [namelookup_time] => 0
        [connect_time] => 0
        [pretransfer_time] => 0
        [size_upload] => 0
        [size_download] => 0
        [speed_download] => 0
        [speed_upload] => 0
        [download_content_length] => -1
        [upload_content_length] => 0
        [starttransfer_time] => 0.047
        [redirect_time] => 0.234
        [certinfo] => Array
            (
            )
    
        [primary_ip] => ::1
        [primary_port] => 80
        [local_ip] => ::1
        [local_port] => 50768
        [redirect_url] => 
    )

    里面有一个direct_count记录跳转的次数。

    我们可以利用上面两点从$html=curl_exec($ch);提取最后一个页面的header信息。

         //分离response的header和body,由于服务器可能使用了302跳转,所以此处需要将字符串分离为 2+跳转次数 个子串
                $httpArr = explode("
    
    ", $html, 2 + $httpinfo['redirect_count']);
                //倒数第二段是服务器最后一次response的http头
                $header = $httpArr[count($httpArr) - 2];
                //倒数第一段是服务器最后一次response的内容
                $body = $httpArr[count($httpArr) - 1];
                $header.="
    ";
           print $header;

    每个header信息最后都有一个 ,如:

    reponse header:

    HTTP/1.0 200 OK
    Date: Wed, 07 Aug 2013 08:15:21 GMT
    Server: Microsoft-IIS/6.0
    X-Powered-By: ASP.NET
    X-Powered-By: PHP/5.2.17
    Content-Type: text/html; charset=UTF-8
    Content-Encoding: gzip
    Vary: Accept-Encoding
    X-Cache: MISS from tesaasst.abc.com
    X-Cache-Lookup: MISS from tesaasst.abc.com:80
    Via: 1.0 tesaasst.abc.com (squid/3.0.STABLE20)
    Connection: close
    有一个换行符

    a->b->c会产生如下形式:

    a header bheader  cheader cbody.

    分成了

    2 + $httpinfo['redirect_count']片段。
    最后一个为body,倒数第二个为header。

    注意,我们想要打印最后一个页面的header,还要加上

    $header.="
    ";
    符合http 规范。

    CURLOPT_FOLLOWLOCATION

      TRUE to follow any "Location: " header that the server sends as part of the HTTP header (note this is recursive, PHP will follow as many "Location: " headers that it is sent, unless CURLOPT_MAXREDIRS is set).默认true

    CURLOPT_MAXREDIRS

      The maximum amount of HTTP redirections to follow. Use this option alongside CURLOPT_FOLLOWLOCATION.

    CURLOPT_AUTOREFERER

      TRUE to automatically set the Referer: field in requests where it follows a Location: redirect.

    CURLOPT_AUTOREFERER  :curl 会自动添加 Referer header 在每一个跳转链接,也就是一跟到底。默认true.

    参考了:

    http://www.cnblogs.com/helloprogram/archive/2012/03/25/2416492.html

    http://www.groad.net/bbs/read.php?tid-4455.html

  • 相关阅读:
    洛谷4451 整数的lqp拆分(生成函数)
    CF1137C Museums Tour(Tarjan,强连通分量)
    CF932E Team Work(第二类斯特林数)
    CF1131F Asya And Kittens(Kruskal重构树,启发式合并)
    CF1131E String Multiplication(???)
    CF438E The Child and Binary Tree(生成函数,NTT)
    [HAOI2015]按位或(min-max容斥,FWT,FMT)
    【noi 2.6_9281】技能树(DP)
    【noi 2.6_9280】&【bzoj 1089】严格n元树(DP+高精度+重载运算符)
    【noi 2.6_9277】Logs Stacking堆木头(DP)
  • 原文地址:https://www.cnblogs.com/youxin/p/3243136.html
Copyright © 2011-2022 走看看