【问题标题】:Download millions of images from external website从外部网站下载数百万张图片
【发布时间】:2015-03-15 11:16:27
【问题描述】:

我正在开发一个房地产网站,我们即将获得大约 100 万个列表的外部提要。假设每个列表有大约 10 张与之关联的照片,那就是大约 1000 万张照片,我们需要将每张照片下载到我们的服务器,以免“热链接”到它们。

我完全不知道如何有效地做到这一点。我玩了一些数字,我得出结论,基于每张图片下载速度为 0.5 秒,这可能需要大约 58 天才能完成(从外部服务器下载大约 1000 万张图片)。这显然是不可接受的。

每张照片的大小似乎约为 50KB,但可能会有所不同,有些更大,有些更大,有些则更小。

我一直在简单地使用:

copy(http://www.external-site.com/image1.jpg, /path/to/folder/image1.jpg)

我也尝试过 cURL、wget 等。

我知道其他网站会这样做,而且规模要大得多,但我一点也不知道他们是如何在不花几个月时间的情况下管理这类事情的。

基于我们要接收的 XML 提要的 Sudo 代码。我们正在使用 PHP 解析 XML:

<listing>
    <listing_id>12345</listing_id>
    <listing_photos>
        <photo>http://example.com/photo1.jpg</photo>
        <photo>http://example.com/photo2.jpg</photo>
        <photo>http://example.com/photo3.jpg</photo>
        <photo>http://example.com/photo4.jpg</photo>
        <photo>http://example.com/photo5.jpg</photo>
        <photo>http://example.com/photo6.jpg</photo>
        <photo>http://example.com/photo7.jpg</photo>
        <photo>http://example.com/photo8.jpg</photo>
        <photo>http://example.com/photo9.jpg</photo>
        <photo>http://example.com/photo10.jpg</photo>
    </listing_photos>
</listing>

所以我的脚本将遍历每张照片以获取特定列表并将照片下载到我们的服务器,并将照片名称插入我们的照片数据库(插入部分已经完成,没有问题)。

有什么想法吗?

【问题讨论】:

  • 按需下载?
  • 最好在初始解析(XML 提要文件)时。但也欢迎其他想法。
  • 下载不必串行完成 - 您可以同时获取所有 10 个,这可以显着减少下载时间。
  • 在多个“后台任务”中使用“wget”。从您拥有的“详细信息”中生成源和目的地详细信息。时间限制取决于您的互联网连接速度和服务器的处理能力 - 取决于您可以同时运行多少个“wget”任务。
  • 您可能还想询问当前图像主机他们可以处理多少请求。您不希望在不事先通知他们的情况下以 1000 个请求/秒的速度淹没他们。

标签: php


【解决方案1】:

我很惊讶供应商不允许您进行热链接。事实是您不会每个月都提供每张图片,那么为什么要下载每张图片呢?让你热链接是更好地利用每个人的带宽。

我管理一个包含数百万个项目的目录,其中数据是本地的,但图像大多是热链接的。有时我们需要隐藏图像的来源,或者供应商要求我们缓存图像。为了实现这两个目标,我们使用代理。我们编写了自己的代理,但您可能会找到一些可以满足您需求的开源代码。

代理的工作方式是我们对加密的 URL 字符串进行加密和 URL 编码。所以http://yourvendor.com/img1.jpg 变成了 xtX957z。在我们的标记中,img src 标签类似于http://ourproxy.com/getImage.ashx?image=xtX957z

当我们的代理收到图像请求时,它会解密图像 URL。代理首先在磁盘上查找图像。我们从 URL 派生图像名称,因此它正在寻找类似 yourvendorcom.img1.jpg 的内容。如果代理在磁盘上找不到图像,则它使用解密的 URL 从供应商处获取图像。然后它将图像写入磁盘并将其返回给客户端。这种方法的优点是按需提供,不会浪费带宽。我只得到我需要的图像,而且我只得到一次。

【讨论】:

  • 我同意。但是供应商将他们的数据联合到大约 20 个其他站点,所以我相信他们希望节省自己的带宽,因为担心某些图像每天被查看数百甚至数千次。我喜欢你的想法。我将进一步研究该方法!
  • 经过无数小时的调查,这似乎是最好的解决方案。当一些,可能是大多数,甚至不会被用户查看时,下载所有图像似乎毫无意义.. 至少在很长一段时间内不会。按需是最有意义的。干杯。
【解决方案2】:

您可以将所有链接保存到某个数据库表中(这将是您的“作业队列”), 然后您可以创建一个脚本,该脚本在循环中获取作业并执行(获取单个链接的图像并将作业记录标记为已完成) 您可以多次执行的脚本 f.e.使用supervisord。所以作业队列将被并行处理。如果速度很慢,您可以执行另一个工作脚本(如果带宽不会减慢您的速度)

如果任何脚本由于某种原因挂起,您可以轻松地再次运行它以仅获取尚未下载的图像。顺便说一句,supervisord 可以配置为在每个脚本失败时自动重启。

另一个优点是您可以随时通过 supervisorctl 检查这些脚本的输出。要检查还有多少图像仍在等待,您可以轻松查询“作业队列”表。

【讨论】:

    【解决方案3】:

    在你这样做之前

    就像@BrokenBinar 在 cmets 中所说的那样。考虑主机每秒可以提供多少请求。您不想在他们不知情的情况下向他们发送大量请求。然后使用sleep 之类的东西来限制您的请求,无论他们可以提供多少。

    多卷曲

    无论如何,使用 Curl。有点重复的答案,但无论如何都复制了:

    $nodes = array($url1, $url2, $url3);
    $node_count = count($nodes);
    
    $curl_arr = array();
    $master = curl_multi_init();
    
    for($i = 0; $i < $node_count; $i++)
    {
        $url =$nodes[$i];
        $curl_arr[$i] = curl_init($url);
        curl_setopt($curl_arr[$i], CURLOPT_RETURNTRANSFER, true);
        curl_multi_add_handle($master, $curl_arr[$i]);
    }
    
    do {
        curl_multi_exec($master,$running);
    } while($running > 0);
    
    
    for($i = 0; $i < $node_count; $i++)
    {
        $results[] = curl_multi_getcontent  ( $curl_arr[$i]  );
    }
    print_r($results);
    

    发件人:PHP Parallel curl requests

    另一种解决方案:

    线程

    <?php
    
    class WebRequest extends Stackable {
        public $request_url;
        public $response_body;
    
        public function __construct($request_url) {
            $this->request_url = $request_url;
        }
    
        public function run(){
            $this->response_body = file_get_contents(
                $this->request_url);
        }
    }
    
    class WebWorker extends Worker {
        public function run(){}
    }
    
    $list = array(
        new WebRequest("http://google.com"),
        new WebRequest("http://www.php.net")
    );
    
    $max = 8;
    $threads = array();
    $start = microtime(true);
    
    /* start some workers */
    while (@$thread++<$max) {
        $threads[$thread] = new WebWorker();
        $threads[$thread]->start();
    }
    
    /* stack the jobs onto workers */
    foreach ($list as $job) {
        $threads[array_rand($threads)]->stack(
            $job);
    }
    
    /* wait for completion */
    foreach ($threads as $thread) {
        $thread->shutdown();
    }
    
    $time = microtime(true) - $start;
    
    /* tell you all about it */
    printf("Fetched %d responses in %.3f seconds\n", count($list), $time);
    $length = 0;
    foreach ($list as $listed) {
        $length += strlen($listed["response_body"]);
    }
    printf("Total of %d bytes\n", $length);
    ?>
    

    来源:PHP testing between pthreads and curl

    你真的应该使用搜索功能,你知道 :)

    【讨论】:

    • 优秀。谢谢泰克。我只是试了一下,它似乎将处理时间缩短了一半……甚至超过一半。但我的过程似乎与每秒约 13 张照片挂钩。不会超过那个。有没有办法真正“打开它”并每秒抓取更多图像?或者这真的只是基于你认为的硬件。
    • @Marcus 试试我发布的其他解决方案
    • 我知道.. 但我确实进行了搜索!大声笑似乎你比我好多了。上面的代码似乎是更大类的一部分。对它到底在做什么有什么想法吗?
    • @Marcus 在不知道您正在运行什么样的环境的情况下,我可以提出很多建议。我会试一试。啊,是的,我已经得到了几次。好事情只有在蓝月亮中发生一次:P
    猜你喜欢
    • 1970-01-01
    • 2012-03-31
    • 1970-01-01
    • 2013-04-11
    • 1970-01-01
    • 2021-12-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多