将图像从实时服务器复制到本地

时间:2013-12-05 05:40:39

标签: php

我在不同的表中有大约600k的图像URL,并且使用下面的代码下载所有图像,并且工作正常。 (我知道FTP是最好的选择,但我不能用它。)

$queryRes = mysql_query("SELECT url FROM tablName LIMIT 50000"); // everytime I am using LIMIT
while ($row = mysql_fetch_object($queryRes)) {
    $info = pathinfo($row->url);
    $fileName = $info['filename'];
    $fileExtension = $info['extension'];

    try {
        copy("http:".$row->url, "img/$fileName"."_".$row->id.".".$fileExtension);
    } catch(Exception $e) {
        echo "<br/>\n unable to copy '$fileName'. Error:$e";
    }
}

问题是:

  1. 过了一段时间,说10分钟,脚本会出现503错误。但仍然继续下载图像。为什么,它应该停止复制呢?
  2. 并且它不会下载所有图像,每次会有100到150张图像的差异。那么如何追踪未下载的图像呢?
  3. 我希望我能解释清楚。

6 个答案:

答案 0 :(得分:3)

首先......复制不会抛出任何异常...所以你没有做任何错误处理......这就是为什么你的脚本会继续运行......

第二......你应该使用file_get_contets甚至更好,卷曲......

例如你可以试试这个功能...(我知道......它每次打开并关闭卷曲......只是我在这里找到的一个例子https://stackoverflow.com/a/6307010/1164866

function getimg($url) {         
    $headers[] = 'Accept: image/gif, image/x-bitmap, image/jpeg, image/pjpeg';              
    $headers[] = 'Connection: Keep-Alive';         
    $headers[] = 'Content-type: application/x-www-form-urlencoded;charset=UTF-8';         
    $user_agent = 'php';         
    $process = curl_init($url);         
    curl_setopt($process, CURLOPT_HTTPHEADER, $headers);         
    curl_setopt($process, CURLOPT_HEADER, 0);         
    curl_setopt($process, CURLOPT_USERAGENT, $useragent);         
    curl_setopt($process, CURLOPT_TIMEOUT, 30);         
    curl_setopt($process, CURLOPT_RETURNTRANSFER, 1);         
    curl_setopt($process, CURLOPT_FOLLOWLOCATION, 1);         
    $return = curl_exec($process);         
    curl_close($process);         
    return $return;     
} 

甚至..尝试使用curl_multi_exec并将您的文件并行下载,这将会快得多

看看这里:

http://www.php.net/manual/en/function.curl-multi-exec.php

编辑

要跟踪无法下载的文件,您需要执行此类操作

$queryRes = mysql_query("select url from tablName limit 50000"); //everytime i am using limit
while($row = mysql_fetch_object($queryRes)) {

    $info = pathinfo($row->url);    
    $fileName = $info['filename'];
    $fileExtension = $info['extension'];    

    if (!@copy("http:".$row->url, "img/$fileName"."_".$row->id.".".$fileExtension)) {
       $errors= error_get_last();
       echo "COPY ERROR: ".$errors['type'];
       echo "<br />\n".$errors['message'];
       //you can add what ever code you wnat here... out put to conselo, log in a file put an exit() to stop dowloading... 
    }
}

更多信息:http://www.php.net/manual/es/function.copy.php#83955

答案 1 :(得分:0)

我自己没有使用copy,我使用file_get_contents它可以正常使用远程服务器。

编辑:

也会返回false。所以...

if( false === file_get_contents(...) )
    trigger_error(...);

答案 2 :(得分:0)

  1. 我认为50000太大了。网络是每次消耗,下载图像可能花费超过100毫秒(取决于你的网络条件),所以50000图像,在最稳定的情况下(没有超时或一些其他错误),可能花费50000 * 100/1000/60 = 83分钟,这对于像php这样的脚本真的很长。如果您将此脚本作为cgi(而不是cli)运行,通常默认情况下只有30秒(没有set_time_limit)。因此,我建议将此脚本设为cronjob并每隔10秒运行一次,以获取大约50个网址。

  2. 要使脚本每次只获取一些图像,您必须记住哪些已经处理(成功)。例如,你可以在url表中添加一个标志列,默认情况下,flag = 1,如果url成功处理,它变为2,或者它变为3,这意味着url出错了。每次,脚本只能选择标志= 1的那些(也可能包含3个,但有时,网址可能错误,因此重试不起作用)。

  3. 复制功能太简单了,我建议使用curl,它更可靠,你可以得到下载的完整网络信息。

  4. 这里是代码:

    //only fetch 50 urls each time
    $queryRes = mysql_query ( "select id, url from tablName where flag=1 limit  50" );
    
    //just prefer absolute path
    $imgDirPath = dirname ( __FILE__ ) + '/';
    
    while ( $row = mysql_fetch_object ( $queryRes ) )
    {
        $info = pathinfo ( $row->url );
        $fileName = $info ['filename'];
        $fileExtension = $info ['extension'];
    
        //url in the table is like //www.example.com???
        $result = fetchUrl ( "http:" . $row->url, 
                $imgDirPath + "img/$fileName" . "_" . $row->id . "." . $fileExtension );
    
        if ($result !== true)
        {
            echo "<br/>\n unable to copy '$fileName'. Error:$result";
            //update flag to 3, finish this func yourself
            set_row_flag ( 3, $row->id );
        }
        else
        {
            //update flag to 3
            set_row_flag ( 2, $row->id );
        }
    }
    
    function fetchUrl($url, $saveto)
    {
        $ch = curl_init ( $url );
    
        curl_setopt ( $ch, CURLOPT_FOLLOWLOCATION, true );
        curl_setopt ( $ch, CURLOPT_MAXREDIRS, 3 );
        curl_setopt ( $ch, CURLOPT_HEADER, false );
        curl_setopt ( $ch, CURLOPT_RETURNTRANSFER, true );
        curl_setopt ( $ch, CURLOPT_CONNECTTIMEOUT, 7 );
        curl_setopt ( $ch, CURLOPT_TIMEOUT, 60 );
    
        $raw = curl_exec ( $ch );
    
        $error = false;
    
        if (curl_errno ( $ch ))
        {
            $error = curl_error ( $ch );
        }
        else
        {
            $httpCode = curl_getinfo ( $ch, CURLINFO_HTTP_CODE );
    
            if ($httpCode != 200)
            {
                $error = 'HTTP code not 200: ' . $httpCode;
            }
        }
    
        curl_close ( $ch );
    
        if ($error)
        {
            return $error;
        }
    
        file_put_contents ( $saveto, $raw );
    
        return true;
    }
    

答案 3 :(得分:0)

  1. 严格检查mysql_fetch_object返回值是更好的IMO,因为许多类似的函数可能会在松散地检查时返回非布尔值,并将其评估为false(例如,通过!=)。 / LI>
  2. 您的查询中没有获取id属性。您的代码不应该像您编写的那样工作。
  3. 您在结果中定义无行顺序。几乎总是希望有一个明确的订单。
  4. LIMIT子句导致只处理有限数量的行。如果我得到了正确的答案,您需要处理所有网址。
  5. 您正在使用已弃用的API来访问MySQL 。你应该考虑使用更现代的一个。请参阅database FAQ @ PHP.net我没有解决这个问题。
  6. 正如已多次说过的那样, copy不会抛出,它会返回成功指标
  7. 可变扩张是笨拙的。但这个只是化妆品的变化。
  8. 为确保生成的输出尽快到达用户,使用flush 。使用输出缓冲(ob_start等)时,也需要处理它。
  9. 应用修补程序后,代码现在如下所示:

    $queryRes = mysql_query("SELECT id, url FROM tablName ORDER BY id");
    while (($row = mysql_fetch_object($queryRes)) !== false) {
        $info = pathinfo($row->url);
        $fn = $info['filename'];
        if (copy(
            'http:' . $row->url,
            "img/{$fn}_{$row->id}.{$info['extension']}"
        )) {
            echo "success: $fn\n";
        } else {
            echo "fail: $fn\n";
        }
        flush();
    }
    

    问题#2由此解决。您将看到哪些文件已被复制,哪些未被复制。如果进程(及其输出)过早停止,则您知道最后处理的行的ID,并且可以查询数据库以查找更高的行(未处理)。另一种方法是向copied添加一个布尔列tblName,并在成功复制文件后立即更新它。然后,您可能希望更改上面代码中的查询,以便不包含已设置copied = 1的行。

    问题#1在SO Long computation in php results in 503 error和SU上503 service unavailable when debugging PHP script in Zend Studio处理。我建议将大批量拆分为较小的批次,以固定的间隔启动。 Cron似乎是我最好的选择。有没有必要从浏览器中获取这个庞大的批次?它会运行很长时间。

答案 4 :(得分:0)

批量处理更好。

实际脚本 表结构

CREATE TABLE IF NOT EXISTS `images` (
  `id` int(60) NOT NULL AUTO_INCREMENTh,
  `link` varchar(1024) NOT NULL,
  `status` enum('not fetched','fetched') NOT NULL DEFAULT 'not fetched',
  `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
);

脚本

<?php
// how many images to download in one go?
$limit = 100;
/* if set to true, the scraper reloads itself. Good for running on localhost without cron job support. Just keep the browser open and the script runs by itself ( javascript is needed) */ 
$reload = false;
// to prevent php timeout
set_time_limit(0);
// db connection ( you need pdo enabled)   
 try {
       $host = 'localhost';
       $dbname= 'mydbname';
       $user = 'root';
       $pass = '';
      $DBH = new PDO("mysql:host=$host;dbname=$dbname", $user, $pass);     
    }  
    catch(PDOException $e) {  
        echo $e->getMessage();  
    } 
$DBH->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );

// get n number of images that are not fetched
$query = $DBH->prepare("SELECT * FROM images WHERE  status = 'not fetched' LIMIT {$limit}");
$query->execute();
$files = $query->fetchAll();
// if no result, don't run
if(empty($files)){
    echo 'All files have been fetched!!!';
    die();
}
// where to save the images?
$savepath = dirname(__FILE__).'/scrapped/';
// fetch 'em!
foreach($files as $file){
        // get_url_content uses curl. Function defined later-on
    $content = get_url_content($file['link']);
        // get the file name from the url. You can use random name too. 
        $url_parts_array = explode('/' , $file['link']);
        /* assuming the image url as http:// abc . com/images/myimage.png , if we explode the string by /, the last element of the exploded array would have the filename */
        $filename = $url_parts_array[count($url_parts_array) - 1]; 
        // save fetched image
    file_put_contents($savepath.$filename , $content);
    // did the image save?
       if(file_exists($savepath.$file['link']))
       {
        // yes? Okay, let's save the status
              $query = $DBH->prepare("update images set status = 'fetched' WHERE id = ".$file['id']);
        // output the name of the file that just got downloaded
                echo $file['link']; echo '<br/>';
        $query->execute();  
    }
}

// function definition get_url_content()
function get_url_content($url){
        // ummm let's make our bot look like human
    $agent= 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.0.3705; .NET CLR 1.1.4322)';
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_VERBOSE, true);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_BINARYTRANSFER, 1);
    curl_setopt($ch, CURLOPT_USERAGENT, $agent);
    curl_setopt($ch, CURLOPT_URL,$url);
    return curl_exec($ch);
}
//reload enabled? Reload!
if($reload)
    echo '<script>location.reload(true);</script>';

答案 5 :(得分:-1)

503是一个相当普通的错误,在这种情况下可能意味着超时。这可能是您的Web服务器,一路上的代理,甚至是PHP。

您需要确定哪个组件超时。如果它是PHP,您可以使用set_time_limit。

另一种选择可能是打破工作,以便每个请求只处理一个文件,然后重定向回相同的脚本继续处理其余的。您必须以某种方式维护在调用之间处理了哪些文件的列表。或者按照数据库ID的顺序进行处理,并在重定向时将最后使用的id传递给脚本。