Pobierz plik na serwer z adresu URL

341

Cóż, ten wydaje się dość prosty i jest. Aby pobrać plik na serwer, wystarczy:

file_put_contents("Tmpfile.zip", file_get_contents("http://someurl/file.zip"));

Jest tylko jeden problem. Co jeśli masz duży plik, na przykład 100 MB. Wówczas zabraknie pamięci i nie będzie można pobrać pliku.

To, czego chcę, to sposób na zapisanie pliku na dysku podczas jego pobierania. W ten sposób mogę pobierać większe pliki bez problemów z pamięcią.

Xaav
źródło
4
Jest to ustawione w konfiguracji serwera, PHP nie może tego obejść (o ile nie mam bezpośredniej edycji .ini)
Ben

Odpowiedzi:

494

Od PHP 5.1.0 file_put_contents()obsługuje pisanie kawałek po kawałku, przekazując uchwyt strumienia jako $dataparametr:

file_put_contents("Tmpfile.zip", fopen("http://someurl/file.zip", 'r'));

Z instrukcji:

Jeśli dane [to drugi argument] są zasobem strumienia, pozostały bufor tego strumienia zostanie skopiowany do określonego pliku. Podobnie jest z używaniem stream_copy_to_stream().

(Dzięki Hakre .)

alex
źródło
4
To nie byłby mój pierwszy wybór. Jeśli allow_fopen_url Offjest ustawiony w php.ini (dobry pomysł na bezpieczeństwo), twój skrypt zostałby uszkodzony.
PleaseStand
4
@idealmachine Myślę, że file_get_contents()to też by nie działało, gdyby tak było (patrz OP).
alex
10
@geoff Byłem konkretny, wspomniałem o funkcji, którą chciałeś. Być może chciałeś, żeby ktoś napisał dla ciebie kod - ale jestem pewien, że sam się nauczyłeś. Ponadto, jeśli mamy zamiar komentować wzajemne interakcje SO - proszę przyjąć więcej odpowiedzi :)
alex
@alex: Proszę zobaczyć edycję, prosimy o włączenie. daj mi znać, kiedy będę mógł usunąć ten komentarz tutaj.
hakre
4
Flaga „b” powinna być również używana w większości przypadków z fopen; zapobiega niekorzystnym skutkom obrazów i innych nie zwykłych plików tekstowych.
Wayne Weibel,
132
private function downloadFile($url, $path)
{
    $newfname = $path;
    $file = fopen ($url, 'rb');
    if ($file) {
        $newf = fopen ($newfname, 'wb');
        if ($newf) {
            while(!feof($file)) {
                fwrite($newf, fread($file, 1024 * 8), 1024 * 8);
            }
        }
    }
    if ($file) {
        fclose($file);
    }
    if ($newf) {
        fclose($newf);
    }
}
Xaav
źródło
1
dziękuję za twój snippit, ale czy byłbyś w stanie wyjaśnić swój kod @xaav? Nie jestem do końca genialny w php. Do czego służy 1024 * 8? Dzięki jeszcze raz.
vvMINOvv,
@wMINOw Długość linii.
David Bélanger
2
W szczególności oznacza to odczyt do 8 KB na raz (1024 bajty na KB * 8), ponieważ parametr jest w bajtach. Dopóki linia ma <= 8 KB, odczyta całą linię na raz.
Doktor J
1
Dlaczego nie jest to najlepsza odpowiedź?
GunJack,
1
Jak radzisz sobie z błędami przy takim podejściu? Co się stanie, jeśli 404 zostanie zwrócony lub połączenie zostanie przerwane lub upłynie limit czasu?
Adam Swinden
67

Spróbuj użyć cURL

set_time_limit(0); // unlimited max execution time
$options = array(
  CURLOPT_FILE    => '/path/to/download/the/file/to.zip',
  CURLOPT_TIMEOUT =>  28800, // set this to 8 hours so we dont timeout on big files
  CURLOPT_URL     => 'http://remoteserver.com/path/to/big/file.zip',
);

$ch = curl_init();
curl_setopt_array($ch, $options);
curl_exec($ch);
curl_close($ch);

Nie jestem pewien, ale wierzę w CURLOPT_FILEopcję, która zapisuje podczas pobierania danych, tj. nie buforowane.

prodigitalon
źródło
2
Normalnie byłoby dobrze, ale mam ten kod w aplikacji internetowej, więc nie mogę się upewnić, że użytkownicy będą mieć zainstalowany CURL. Dałem jednak głos.
xaav,
@Geoff to rozproszona aplikacja internetowa? Ponieważ jeśli kontrolujesz hosting, nie ma to znaczenia dla twoich użytkowników (cURL to biblioteka na twoim serwerze).
alex
Nie. Nie kontroluję hostingu. Jest to rozproszona aplikacja internetowa, którą każdy może mieć.
xaav
3
Curl może brakować. Ale prawie wszystkie współdzielone firmy hostingowe mają domyślnie zainstalowany CURL. To znaczy, nie widziałem takiego, który tego nie robi.
Mangirdas Skripka
19
Z moich testów nie można bezpośrednio przypisać do CURLOPT_FILE ścieżki pliku. To musi być moduł obsługi plików. Najpierw otwórz plik za pomocą $fh = fopen('/path/to/download/the/file/to.zip', 'w');i zamknij za pomocą fclose($fh);po curl_close($ch);. I ustawCURLOPT_FILE => $fh
Gustavo
22

Odpowiedź prodigitalona nie działała dla mnie. Mam missing fopen in CURLOPT_FILE więcej szczegółów .

To zadziałało dla mnie, w tym lokalnych adresów URL:

function downloadUrlToFile($url, $outFileName)
{   
    if(is_file($url)) {
        copy($url, $outFileName); 
    } else {
        $options = array(
          CURLOPT_FILE    => fopen($outFileName, 'w'),
          CURLOPT_TIMEOUT =>  28800, // set this to 8 hours so we dont timeout on big files
          CURLOPT_URL     => $url
        );

        $ch = curl_init();
        curl_setopt_array($ch, $options);
        curl_exec($ch);
        curl_close($ch);
    }
}
Kamil Kiełczewski
źródło
19
  1. Utwórz folder o nazwie „pliki do pobrania” na serwerze docelowym
  2. Zapisz [ten kod] do .phppliku i uruchom na serwerze docelowym

Downloader:

<html>
<form method="post">
<input name="url" size="50" />
<input name="submit" type="submit" />
</form>
<?php
    // maximum execution time in seconds
    set_time_limit (24 * 60 * 60);

    if (!isset($_POST['submit'])) die();

    // folder to save downloaded files to. must end with slash
    $destination_folder = 'downloads/';

    $url = $_POST['url'];
    $newfname = $destination_folder . basename($url);

    $file = fopen ($url, "rb");
    if ($file) {
      $newf = fopen ($newfname, "wb");

      if ($newf)
      while(!feof($file)) {
        fwrite($newf, fread($file, 1024 * 8 ), 1024 * 8 );
      }
    }

    if ($file) {
      fclose($file);
    }

    if ($newf) {
      fclose($newf);
    }
?>
</html> 
stra8edge
źródło
Zakłada się, że użytkownik chce autonomicznego skryptu, a nie rozwiązania, które będzie działać w istniejącej aplikacji PHP, i uważam, że ten drugi jest tym, czego OP i większość innych szuka. Wyjaśnienie byłoby również pomocne dla osób, które chcą zrozumieć to podejście.
Sean the Bean
1
za każdym razem, gdy próbuję, mój przesyłany rozmiar pliku to 50816, ale mój rozmiar jest większy niż ten ... 120 MB .. Wiesz, dlaczego to jest?
Riffaz Starr
set_time_limit (24 * 60 * 60);musi być umieszczony w pętli. Na początku skryptu nie działa.
Viktor Joras
16
set_time_limit(0); 
$file = file_get_contents('path of your file');
file_put_contents('file.ext', $file);
Dimmy
źródło
twoja odpowiedź jest bardzo prosta i dobrze działa, pomogła mi tam, gdzie cURL nie mógł pobrać pliku, to zadziałało. Dzięki :)
Tommix
2
Możesz wyjaśnić, co to właściwie robi.
alex
6
To nie rozwiązuje problemu PO przekroczenia limitu pamięci PHP.
user9645
To jest dość proste i jednoznaczne. Przydatne w prostszych przypadkach, gdy pliki są małe lub środowisko jest opracowaniem lokalnym.
Valentine Shi
jakiś pomysł na pliki .xlsx? Przechowuje pusty plik z pamięcią 0 bajtów.
Dhruv Thakkar
9

Istnieją 3 sposoby:

  1. file_get_contents i file_put_contents
  2. KĘDZIOR
  3. fopen

Można znaleźć przykłady stąd .

Hoan Huynh
źródło
8

Użyj prostej metody w php copy()

copy($source_url, $local_path_with_file_name);

Uwaga: jeśli plik docelowy już istnieje, zostanie zastąpiony

Funkcja PHP copy ()

Uwaga: Musisz ustawić uprawnienie 777 dla folderu docelowego. Użyj tej metody podczas pobierania na komputer lokalny.

Uwaga specjalna: 777 to uprawnienie w systemie uniksowym z pełnym uprawnieniem do odczytu / zapisu / wykonania dla właściciela, grupy i wszystkich. Zasadniczo dajemy to zezwolenie na zasoby, które nie są zbytnio potrzebne do ukrywania przed publicznością na serwerze WWW. Przykład: folder zdjęć.

Pradeep Kumar Prabaharan
źródło
1
Nigdy nigdy nie ustawię 777 jako perms na serwerze WWW i wykopię każdego programistę, który ma zły pomysł, aby to zrobić. Zawsze i wszędzie. Bądź ostrożny ! Nie możesz tego zrobić ! Pomyśl o bezpieczeństwie. Przestrzeganie zasad OWASP nie wystarczy. Ważne jest dobre myślenie o prostych rzeczach.
ThierryB
@ThierryB. Uwaga: podałem ścieżkę lokalną. i można to wykorzystać w aplikacjach wewnętrznych. Dobra znajomość i zrozumienie pytań i odpowiedzi. Pomyśl o różnych scenariuszach. I to nie jest akceptowane / najlepsza odpowiedź. Każde pytanie ma inne odpowiedzi, w tym plusy i minusy. Przykład, który musisz zrozumieć: nawet Fibonacci ma wiele unikalnych rozwiązań, w których tylko jedno będzie najlepsze. Inne zostaną wykorzystane w różnych scenariuszach.
Pradeep Kumar Prabaharan
Ok, ale poświęcenie czasu na przemyślenie najlepszych praktyk i wdrożenie ich w zabezpieczonych miejscach zapewni lepsze zrozumienie pojęć, które należy wdrożyć. Może jeśli intruz jest w twoim domu ($), robienie pułapek lub budowanie rzeczy najlepiej, jak możesz, sprawi mu ból głowy;)
ThierryB
5

Używam tego do pobierania pliku

function cURLcheckBasicFunctions()
{
  if( !function_exists("curl_init") &&
      !function_exists("curl_setopt") &&
      !function_exists("curl_exec") &&
      !function_exists("curl_close") ) return false;
  else return true;
}

/*
 * Returns string status information.
 * Can be changed to int or bool return types.
 */
function cURLdownload($url, $file)
{
  if( !cURLcheckBasicFunctions() ) return "UNAVAILABLE: cURL Basic Functions";
  $ch = curl_init();
  if($ch)
  {

    $fp = fopen($file, "w");
    if($fp)
    {
      if( !curl_setopt($ch, CURLOPT_URL, $url) )
      {
        fclose($fp); // to match fopen()
        curl_close($ch); // to match curl_init()
        return "FAIL: curl_setopt(CURLOPT_URL)";
      }
      if ((!ini_get('open_basedir') && !ini_get('safe_mode')) || $redirects < 1) {
        curl_setopt($ch, CURLOPT_USERAGENT, '"Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.11) Gecko/20071204 Ubuntu/7.10 (gutsy) Firefox/2.0.0.11');
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        //curl_setopt($ch, CURLOPT_REFERER, 'http://domain.com/');
        if( !curl_setopt($ch, CURLOPT_HEADER, $curlopt_header)) return "FAIL: curl_setopt(CURLOPT_HEADER)";
        if( !curl_setopt($ch, CURLOPT_FOLLOWLOCATION, $redirects > 0)) return "FAIL: curl_setopt(CURLOPT_FOLLOWLOCATION)";
        if( !curl_setopt($ch, CURLOPT_FILE, $fp) ) return "FAIL: curl_setopt(CURLOPT_FILE)";
        if( !curl_setopt($ch, CURLOPT_MAXREDIRS, $redirects) ) return "FAIL: curl_setopt(CURLOPT_MAXREDIRS)";

        return curl_exec($ch);
    } else {
        curl_setopt($ch, CURLOPT_USERAGENT, '"Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.11) Gecko/20071204 Ubuntu/7.10 (gutsy) Firefox/2.0.0.11');
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        //curl_setopt($ch, CURLOPT_REFERER, 'http://domain.com/');
        if( !curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false)) return "FAIL: curl_setopt(CURLOPT_FOLLOWLOCATION)";
        if( !curl_setopt($ch, CURLOPT_FILE, $fp) ) return "FAIL: curl_setopt(CURLOPT_FILE)";
        if( !curl_setopt($ch, CURLOPT_HEADER, true)) return "FAIL: curl_setopt(CURLOPT_HEADER)";
        if( !curl_setopt($ch, CURLOPT_RETURNTRANSFER, true)) return "FAIL: curl_setopt(CURLOPT_RETURNTRANSFER)";
        if( !curl_setopt($ch, CURLOPT_FORBID_REUSE, false)) return "FAIL: curl_setopt(CURLOPT_FORBID_REUSE)";
        curl_setopt($ch, CURLOPT_USERAGENT, '"Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.11) Gecko/20071204 Ubuntu/7.10 (gutsy) Firefox/2.0.0.11');
    }
      // if( !curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true) ) return "FAIL: curl_setopt(CURLOPT_FOLLOWLOCATION)";
      // if( !curl_setopt($ch, CURLOPT_FILE, $fp) ) return "FAIL: curl_setopt(CURLOPT_FILE)";
      // if( !curl_setopt($ch, CURLOPT_HEADER, 0) ) return "FAIL: curl_setopt(CURLOPT_HEADER)";
      if( !curl_exec($ch) ) return "FAIL: curl_exec()";
      curl_close($ch);
      fclose($fp);
      return "SUCCESS: $file [$url]";
    }
    else return "FAIL: fopen()";
  }
  else return "FAIL: curl_init()";
}
Hoàng Vũ Tgtt
źródło
4

Rozwiązanie PHP 4 i 5:

readfile () nie stwarza żadnych problemów z pamięcią, nawet przy wysyłaniu dużych plików. Za pomocą tej funkcji można użyć adresu URL jako nazwy pliku, jeśli włączone są opakowania fopen.

http://php.net/manual/en/function.readfile.php

Eric Leroy
źródło
1
To nie odpowiada na pytanie, ponieważ pytanie dotyczy zapisu na dysku, a nie do bufora wyjściowego.
Lorenz Meyer