Pergunta

Eu tenho um script PHP que leva muito tempo (5-30 minutos) para concluir. Caso seja importante, o script está usando o CLL para raspar dados de outro servidor. Esta é a razão pela qual está demorando tanto; Ele deve esperar que cada página seja carregada antes de processá -la e passar para a próxima.

Quero poder iniciar o script e deixar que ele seja feito, o que definirá um sinalizador em uma tabela de banco de dados.

O que eu preciso saber é como ser capaz de encerrar a solicitação HTTP antes que o script termine de execução. Além disso, um script PHP é a melhor maneira de fazer isso?

Foi útil?

Solução

Certamente, isso pode ser feito com o PHP, no entanto, você não deve fazer isso como uma tarefa em segundo plano - o novo processo deve ser dissocado do grupo de processos onde é iniciado.

Como as pessoas continuam dando a mesma resposta errada a esta FAQ, escrevi uma resposta mais completa aqui:

http://symcbean.blogspot.com/2010/02/php-and-long-drunning-processes.html

Dos comentários:

A versão curta é shell_exec('echo /usr/bin/php -q longThing.php | at now'); Mas as razões pelas quais são um pouco longas para a inclusão aqui.

Outras dicas

A maneira rápida e suja seria usar o ignore_user_abort função em php. Isso basicamente diz: Não se importe com o que o usuário faz, execute esse script até que ele seja concluído. Isso é um pouco perigoso se for um site público -público (porque é possível, você acaba tendo 20 versões ++ do script em execução ao mesmo tempo, se for iniciado 20 vezes).

A maneira "limpa" (pelo menos o IMHO) é definir um sinalizador (no banco de dados, por exemplo) quando você deseja iniciar o processo e executar um cronjob a cada hora (ou mais) para verificar se esse sinalizador está definido. Se estiver definido, o script de longa execução inicia, se não estiver definido, nada acontecerá.

Você poderia usar exec ou sistema Para iniciar um trabalho de fundo e depois fazer o trabalho nisso.

Além disso, existem melhores abordagens para raspar a web que você está usando. Você pode usar uma abordagem rosqueada (vários threads fazendo uma página de cada vez) ou um usando um Eventloop (um thread fazendo várias páginas em AT AT). Minha abordagem pessoal usando Perl seria usar Anyevent :: http.

ETA: Symcbean explicou como destacar o processo de fundo corretamente aqui.

Não, o PHP não é a melhor solução.

Não tenho certeza sobre Ruby ou Perl, mas com o Python, você pode reescrever seu raspador de página para ser multi-threaded e provavelmente funcionaria pelo menos 20x mais rápido. Escrever aplicativos com vários threads pode ser um desafio, mas o primeiro aplicativo Python que escrevi foi o raspador de página Mutlti-thread. E você pode simplesmente chamar o script python de dentro da sua página PHP usando uma das funções de execução do shell.

Sim, você pode fazer isso no PHP. Mas, além do PHP, seria aconselhável usar um gerente de filas. Aqui está a estratégia:

  1. Divida sua grande tarefa em tarefas menores. No seu caso, cada tarefa pode estar carregando uma única página.

  2. Envie cada pequena tarefa para a fila.

  3. Execute seus trabalhadores da fila em algum lugar.

O uso desta estratégia tem as seguintes vantagens:

  1. Para tarefas de longa execução, ele tem a capacidade de se recuperar, caso ocorra um problema fatal no meio da corrida - não há necessidade de começar do início.

  2. Se suas tarefas não precisarem ser executadas sequencialmente, você poderá executar vários trabalhadores para executar tarefas simultaneamente.

Você tem uma variedade de opções (são apenas algumas):

  1. RabbitMQ (https://www.rabbitmq.com/tutorials/tutorial-one-php.html)
  2. Zeromq (http://zeromq.org/bindings:php)
  3. Se você estiver usando a estrutura do Laravel, as filas estão embutidas (https://laravel.com/docs/5.4/queues), com motoristas para a AWS SES, Redis, Beanstalkd

O PHP pode ou não ser a melhor ferramenta, mas você sabe como usá -la e o restante do seu aplicativo é escrito usando -o. Essas duas qualidades, combinadas com o fato de que o PHP é "bom o suficiente" defendem bastante usá -lo, em vez de Perl, Ruby ou Python.

Se o seu objetivo é aprender outro idioma, escolha um e usá -lo. Qualquer idioma que você mencionou fará o trabalho, sem problemas. Por acaso, gosto de Perl, mas o que você gosta pode ser diferente.

O Symcbean tem alguns bons conselhos sobre como gerenciar processos em segundo plano em seu link.

Em resumo, escreva um script CLI PHP para lidar com os bits longos. Certifique -se de que ele relate o status de alguma maneira. Faça uma página PHP para lidar com atualizações de status, usando o AJAX ou os métodos tradicionais. Seu script de kickoff iniciará o processo em execução em sua própria sessão e retornará a confirmação de que o processo está indo.

Boa sorte.

Concordo com as respostas que dizem que isso deve ser executado em um processo de segundo plano. Mas também é importante que você relate o status para que o usuário saiba que o trabalho está sendo realizado.

Ao receber a solicitação PHP para iniciar o processo, você pode armazenar em um banco de dados uma representação da tarefa com um identificador exclusivo. Em seguida, inicie o processo de eliminação de tela, passando o identificador exclusivo. Relate ao aplicativo para iPhone que a tarefa foi iniciada e que deve verificar um URL especificado, contendo o novo ID da tarefa, para obter o status mais recente. O aplicativo para iPhone agora pode pesquisar (ou até mesmo "enquete longa") este URL. Enquanto isso, o processo em segundo plano atualizaria a representação do banco de dados da tarefa, pois trabalhava com uma porcentagem de conclusão, etapa atual ou qualquer outro indicador de status que você queira. E quando terminar, definiria um sinalizador preenchido.

Você pode enviá -lo como uma solicitação XHR (AJAX). Os clientes geralmente não têm tempo limite para XHRs, diferentemente das solicitações HTTP normais.

Sei que essa é uma pergunta bastante antiga, mas gostaria de tentar. Esse script tenta abordar a chamada inicial para terminar rapidamente e diminuir a carga pesada em pedaços menores. Eu não testei esta solução.

<?php
/**
 * crawler.php located at http://mysite.com/crawler.php
 */

// Make sure this script will keep on runing after we close the connection with
// it.
ignore_user_abort(TRUE);


function get_remote_sources_to_crawl() {
  // Do a database or a log file query here.

  $query_result = array (
    1 => 'http://exemple.com',
    2 => 'http://exemple1.com',
    3 => 'http://exemple2.com',
    4 => 'http://exemple3.com',
    // ... and so on.
  );

  // Returns the first one on the list.
  foreach ($query_result as $id => $url) {
    return $url;
  }
  return FALSE;
}

function update_remote_sources_to_crawl($id) {
  // Update my database or log file list so the $id record wont show up
  // on my next call to get_remote_sources_to_crawl()
}

$crawling_source = get_remote_sources_to_crawl();

if ($crawling_source) {


  // Run your scraping code on $crawling_source here.


  if ($your_scraping_has_finished) {
    // Update you database or log file.
    update_remote_sources_to_crawl($id);

    $ctx = stream_context_create(array(
      'http' => array(
        // I am not quite sure but I reckon the timeout set here actually
        // starts rolling after the connection to the remote server is made
        // limiting only how long the downloading of the remote content should take.
        // So as we are only interested to trigger this script again, 5 seconds 
        // should be plenty of time.
        'timeout' => 5,
      )
    ));

    // Open a new connection to this script and close it after 5 seconds in.
    file_get_contents('http://' . $_SERVER['HTTP_HOST'] . '/crawler.php', FALSE, $ctx);

    print 'The cronjob kick off has been initiated.';
  }
}
else {
  print 'Yay! The whole thing is done.';
}

Gostaria de propor uma solução um pouco diferente da do Symcbean, principalmente porque tenho requisitos adicionais de que o processo de longa execução precise ser executado como outro usuário, e não como o usuário do Apache / WWW-Data.

Primeira solução usando o CRON para pesquisar uma tabela de tarefas de fundo:

  • A página da web do PHP é inserida em uma tabela de tarefas em segundo plano, Estado 'enviado'
  • Cron funciona uma vez a cada 3 minutos, usando outro usuário, executando o script PHP CLI que verifica a tabela de tarefas em segundo plano para linhas 'enviadas'
  • O PHP CLI atualizará a coluna do estado na linha para o 'processamento' e começará a processamento, após a conclusão, será atualizada para 'concluída'

Segunda solução usando o Linux Inotify Facility:

  • A página da web do PHP atualiza um arquivo de controle com os parâmetros definidos pelo usuário e também fornecendo um ID de tarefa
  • shell script (como usuário não www) em execução notifywait aguardará a escrita do arquivo de controle
  • Depois que o arquivo de controle for escrito, um evento Close_write será aumentado e o script do shell continuará
  • O shell script executa o PHP CLI para fazer o processo de longa execução
  • O PHP CLI escreve a saída em um arquivo de log identificado por ID de tarefa ou atualiza alternativamente o progresso em uma tabela de status
  • A página da web do PHP pode pesquisar o arquivo de log (com base no ID da tarefa) para mostrar o progresso do processo de longa execução, ou também pode consultar a tabela de status

Algumas informações adicionais podem ser encontradas no meu post: http://inventorsparadox.blogspot.co.id/2016/01/long-running-process-in-linux-using-php.html

Eu fiz coisas semelhantes com Perl, Double Fork () e Desaparecimento do processo pai. Todo o trabalho de busca de HTTP deve ser realizado em processo bifurcado.

Use um proxy para delegar a solicitação.

O que eu sempre uso é uma dessas variantes (porque diferentes sabores do Linux têm regras diferentes sobre como lidar com a saída/alguns programas de saída de maneira diferente):

Variante i@exec ('./ myscript.php 1>/dev/null 2>/dev/null &');

Variante II@exec ('php -f myscript.php 1>/dev/null 2>/dev/null &');

Variante III@exec ('nohup myscript.php 1>/dev/null 2>/dev/null &');

Você pode ter instalação "nohup". Mas, por exemplo, quando eu estava automatizando conversões de vídeo FFMPEG, a interface de saída de alguma forma não foi 100% tratada por redirecionando os fluxos de saída 1 e 2, então usei nohup e redireci a saída.

Se você tiver script longo, divida a página funcionar com a ajuda do parâmetro de entrada para cada tarefa. (Então cada página age como thread), por exemplo de magic ou cornjobpage.php (no exemplo a seguir)

E para o trabalhador de fundo, acho que você deve tentar essa técnica, ajudará a ligar para o número de páginas que você gosta de todas as páginas, será executado imediatamente de forma independente, sem esperar por cada resposta como assíncrona.

cornjobpage.php // mainpage

    <?php

post_async("http://localhost/projectname/testpage.php", "Keywordname=testValue");
//post_async("http://localhost/projectname/testpage.php", "Keywordname=testValue2");
//post_async("http://localhost/projectname/otherpage.php", "Keywordname=anyValue");
//call as many as pages you like all pages will run at once independently without waiting for each page response as asynchronous.
            ?>
            <?php

            /*
             * Executes a PHP page asynchronously so the current page does not have to wait for it to     finish running.
             *  
             */
            function post_async($url,$params)
            {

                $post_string = $params;

                $parts=parse_url($url);

                $fp = fsockopen($parts['host'],
                    isset($parts['port'])?$parts['port']:80,
                    $errno, $errstr, 30);

                $out = "GET ".$parts['path']."?$post_string"." HTTP/1.1\r\n";//you can use POST instead of GET if you like
                $out.= "Host: ".$parts['host']."\r\n";
                $out.= "Content-Type: application/x-www-form-urlencoded\r\n";
                $out.= "Content-Length: ".strlen($post_string)."\r\n";
                $out.= "Connection: Close\r\n\r\n";
                fwrite($fp, $out);
                fclose($fp);
            }
            ?>

testpage.php

    <?
    echo $_REQUEST["Keywordname"];//case1 Output > testValue
    ?>

PS: Se você deseja enviar parâmetros de URL como loop, siga esta resposta:https://stackoverflow.com/a/41225209/6295712

Não é a melhor abordagem, como muitos declararam aqui, mas isso pode ajudar:

ignore_user_abort(1); // run script in background even if user closes browser
set_time_limit(1800); // run it for 30 minutes

// Long running script here

Se a saída desejada do seu script for um processamento, não uma página da web, acredito que a solução desejada é executar seu script do shell, simplesmente como

php my_script.php

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top