【问题标题】:Copying images from live server to local将图像从实时服务器复制到本地
【发布时间】:2013-12-21 22:33:34
【问题描述】:

我在不同的表格中有大约 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张图像的差异。那么如何追踪哪些图片没有下载呢?

希望我已经解释清楚了。

【问题讨论】:

  • 有没有可能使用rsync
  • 这个库与我要找的无关
  • 转贴问题,如果我没有说清楚:1)即使在得到503之后它仍然继续向下图像,如何? 2)如何追踪,哪些图片没有下载?
  • @SureshKamrushi 我想为此添加一个调试角度。我建议.. 读取表条目-> 添加到临时表并复制文件-> 重复。在这种情况下,您可能会发现必须运行几次。但会克服一些超时/连接问题。就像服务器因为太多连接而丢弃您一样。这将复制文件,并记录它们,所以下次运行/刷新,它只会复制新文件。就像我说的,如果是连接问题,如果你还没有想到的话,值得一试。
  • @SureshKamrushi 检查我的答案。不要一次下载所有这些图像。分批下载。

标签: php


【解决方案1】:

最好逐批处理。

实际脚本 表结构

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>';

【讨论】:

  • 不像现在写的那样有用,其他人已经指出拆分大批量是必要的。太多的自我宣传文章也可以在这里发布,它足够短。它也类似于Javier Neyra’s answer,只是扩展了。 IMO 对该答案的评论就足够了。
  • 那是我在 SOF 担任 n00b 的时​​候:p 我已经更新了答案。
【解决方案2】:
  1. 严格检查 mysql_fetch_object 返回值在 IMO 上效果更好,因为许多类似的函数在检查松散时(例如通过 !=)可能会返回评估为 false 的非布尔值。
  2. 您没有在查询中获取id 属性。你的代码不应该像你写的那样工作。
  3. 您在结果中定义行无顺序。几乎总是希望有一个明确的顺序。
  4. LIMIT 子句导致只处理有限数量的行。如果我没听错,你想处理所有的 URL。
  5. 您正在使用已弃用的 API 访问 MySQL。您应该考虑使用更现代的。请参阅database FAQ @ PHP.net我没有修好这个。
  6. 正如已经多次说过的,copy 不会抛出,而是返回成功指示符
  7. 变量扩展很笨拙。不过,这只是表面上的改变。
  8. 为确保生成的输出尽快送达用户,使用flush。使用输出缓冲(ob_start 等)时,也需要处理。

应用修复后,代码现在如下所示:

$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 对我来说似乎是最好的选择。是否有必要从浏览器中启动这一大批量?它会运行很长时间。

【讨论】:

    【解决方案3】:

    首先...复制不会引发任何异常...因此您没有进行任何错误处理...这就是您的脚本将继续运行的原因...

    其次...您应该使用 file_get_contets 甚至更好,curl...

    例如你可以试试这个功能......(我知道......它每次都打开和关闭 curl......只是我在这里找到的一个例子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

    【讨论】:

    • @Javir :感谢您的回答。但是您建议的是复制图像的替代方法(可能更好)。但这不是我要找的。请查看我有问题的 cmets。
    • @SureshKamrushi 看看编辑...你需要检查哪些文件没有下载
    【解决方案4】:
    1. 我认为 50000 太大了。网络非常耗时,下载一张图片可能会花费超过 100 毫秒(取决于您的神经网络状况),所以 50000 张图片,在最稳定的情况下(没有超时或其他一些错误),可能需要 50000*100/1000/60 = 83 分钟,对于像 php 这样的脚本来说,这确实是一个很长的时间。如果您将此脚本作为 cgi(不是 cli)运行,通常默认情况下您只有 30 秒(没有 set_time_limit)。因此,我建议将此脚本设为 cronjob,并每 10 秒运行一次,以获取大约 50 个 url。

    2. 要使脚本每次只获取几张图像,您必须记住哪些图像已经被处理(成功)。例如,可以在 url 表中添加一个 flag 列,默认 flag = 1,如果 url 处理成功,则变为 2,否则变为 3,表示 url 出错了。并且每次脚本只能选择flag=1的那些(也可能包括3,但有时,url可能错误,重试不起作用)。

    3. 复制功能太简单了,推荐使用curl,更可靠,而且可以得到下载的准确网络信息。

    代码如下:

    //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;
    }
    

    【讨论】:

    • 感谢您的回答。但是您建议的是复制图像的替代方法(可能更好)。但这不是我要找的。请查看我有问题的 cmets。
    【解决方案5】:

    503 是一个相当普遍的错误,在这种情况下可能意味着某些事情超时。这可能是您的 Web 服务器、沿途某处的代理,甚至是 PHP。

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

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

    【讨论】:

      【解决方案6】:

      我自己没有使用过copy,我会使用file_get_contents,它适用于远程服务器。

      编辑:

      也返回 false。所以...

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

      【讨论】:

      • 我已经在进行异常处理以获取错误。当脚本在一段时间后停止运行时它会失败(请参阅我的第一个问题)。
      • 只是提供一个替代方案,我个人对 file_get_contents 没有任何问题。错误处理只是一个补充。
      • 即使这不是答案,转发它看看我的问题。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-28
      • 2014-02-26
      • 1970-01-01
      • 2016-06-19
      • 1970-01-01
      • 2016-06-30
      相关资源
      最近更新 更多