我有一个PHP脚本,需要很长时间(5-30分钟)才能完成。以防万一重要,脚本正在使用卷曲来从另一台服务器刮擦数据。这就是它花了这么长时间的原因。它必须等待每个页面加载,然后再进行处理并移至下一个页面。

我希望能够启动脚本,并让它成为直到完成为止,该脚本将在数据库表中设置标志。

我需要知道的是如何在脚本完成之前结束HTTP请求。另外,PHP脚本是最好的方法吗?

有帮助吗?

解决方案

当然可以使用PHP来完成,但是您不应该将其作为背景任务 - 必须将新过程与启动的过程组分离。

由于人们一直在为此常见问题解答提供同样的错误答案,因此我在这里写了一个更完整的答案:

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

从评论中:

简短版本是 shell_exec('echo /usr/bin/php -q longThing.php | at now'); 但是,为什么在这里包含一些很长的原因。

其他提示

快速而肮脏的方法是使用 ignore_user_abort PHP的功能。这基本上说:不在乎用户的作用,请运行此脚本直到完成。如果它是一个公开的站点,这有些危险(因为有可能,如果启动20次,您最终会同时拥有20 ++版本的脚本)。

当您想启动该过程并每小时(或so)运行一个cronjob以检查是否设置该标志时,“清洁”方式(至少恕我直言)是设置标志(例如在DB中)。如果设置了它,则长期运行的脚本启动,如果未设置,则会发生。

您可以使用 执行 或者 系统 开始背景工作,然后在此工作。

另外,还有更好的方法可以刮擦您使用的网络。您可以使用螺纹方法(一次执行一页的多个线程),或使用Eventloop(一个线程在当时执行多个页面)。我使用perl的个人方法将使用 AnyEvent :: HTTP.

ETA: Symcbean 解释了如何正确分离背景过程 这里.

不,PHP不是最好的解决方案。

我不确定Ruby或Perl,但是使用Python,您可以将页面刮板重写为多线程,并且可能会更快地运行20倍。编写多线程应用程序可能会有些挑战,但是我写的第一个Python应用程序是mutlti-threaded page scraper。而且,您可以通过使用外壳执行函数之一在PHP页面中从PHP页面中调用Python脚本。

是的,您可以在PHP中进行。但是除了PHP外,使用队列管理器也是明智的。这是策略:

  1. 将您的庞大任务分解为较小的任务。在您的情况下,每个任务都可以加载一个页面。

  2. 将每个小任务发送到队列。

  3. 在某个地方运行队列工人。

使用此策略具有以下优势:

  1. 对于长期运行的任务,如果在运行的中间发生致命问题,则可以恢复 - 无需从头开始。

  2. 如果您的任务不必顺序运行,则可以运行多个工人来同时运行任务。

您有多种选择(这只是几个):

  1. RABBITMQ(https://www.rabbitmq.com/tutorials/tutorial-one-php.html)
  2. Zeromq(http://zeromq.org/bindings:php)
  3. 如果您使用的是Laravel框架,队列是内置的(https://laravel.com/docs/5.4/queues),带有AWS SES,REDIS,BEANSTALKD的驱动程序

PHP可能是最好的工具,但您知道如何使用它,并且您的其余应用程序都是使用它编写的。这两种素质加上PHP“足够好”的事实,可以使用它,而不是Perl,Ruby或Python。

如果您的目标是学习另一种语言,请选择一种语言并使用它。您提到的任何语言都会完成这项工作,没问题。我碰巧喜欢Perl,但是您喜欢的可能与众不同。

Symcbean关于如何在其链接中管理背景过程有一些很好的建议。

简而言之,写一个CLI PHP脚本来处理长位。确保它以某种方式报告状态。使用AJAX或传统方法制作PHP页面以处理状态更新。您的开球脚本将启动在自己的会话中运行的过程,并返回确认该过程正在进行。

祝你好运。

我同意答案,说这应该在后台过程中运行。但是,重要的是您要报告状态也很重要,以便用户知道正在完成工作。

在接收PHP请求以启动该过程时,您可以将其存储在数据库中a用唯一标识符的任务表示。然后,启动屏幕craping过程,将其传递给唯一标识符。向iPhone应用报告任务已启动,并应检查包含新任务ID的指定URL以获取最新状态。 iPhone应用程序现在可以进行调查(甚至是“长民意调查”)。同时,背景过程将更新任务的数据库表示,因为它具有完成百分比,当前步骤或您想要的其他状态指标。当它完成后,它将设置一个完整的标志。

您可以将其作为XHR(AJAX)请求发送。与普通的HTTP请求不同,客户端通常没有XHR的超时时间。

我意识到这是一个非常古老的问题,但想试一试。该脚本试图解决最初的踢路通话以快速完成,并将重负荷切成较小的块。我没有测试过这个解决方案。

<?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.';
}

我想提出一个与Symcbean有些不同的解决方案,主要是因为我还需要长期运行的过程作为另一个用户运行,而不是Apache / www-data用户。

首先使用Cron进行轮询背景任务表的解决方案:

  • PHP网页插入背景任务表中,状态为“提交”
  • Cron每3分钟运行一次,使用另一个用户运行PHP CLI脚本,该脚本检查“提交”行的背景任务表
  • PHP CLI将将行中的状态列更新为“处理”并开始处理,完成后,它将更新为“已完成”

使用Linux Inotify设施的第二个解决方案:

  • PHP网页更新了一个控制文件,其中包括用户设置的参数,并提供任务ID
  • 运行InotifyWait的Shell脚本(作为非www用户)将等待编写控制文件
  • 编写控制文件后,将提出一个CLOSE_WRITE事件,Shell脚本将继续
  • Shell脚本执行PHP CLI进行长期运行过程
  • PHP CLI将输出写入任务ID标识的日志文件,或者更新状态表中的进度
  • PHP网页可以轮询日志文件(基于任务ID),以显示长期运行过程的进度,或者还可以查询状态表

可以在我的帖子中找到一些其他信息: http://inventorsparadox.blogspot.co.id/2016/01/long-ronning-process-in-linux-using-php.html

我已经使用Perl,Double Fork()和与父过程分离了类似的事情。所有HTTP提取工作均应在分叉过程中完成。

使用代理来委派请求。

我一直使用的是这些变体之一(因为Linux的不同口味对处理输出/某些程序的输出有不同的规则):

变体i@Exec('./ myScript.php 1>/dev/null 2>/dev/null&');

变体II@Exec('php -f myScript.php 1>/dev/null 2>/dev/null&');

变体III@Exec('nohup myScript.php 1>/dev/null 2>/dev/null&');

您可能会安装“ nohup”。但是,例如,当我自动执行FFMPEG视频转换时,输出接口以某种方式不是通过重定向输出流1和2来100%处理,因此我使用了NOHUP并将输出重定向。

如果您有长脚本,则在每个任务的输入参数的帮助下进行划分。(每个页面都像线程一样),即,如果页面有1个lac product_keywords long Process循环,则代替循环为一个关键字制作逻辑并传递此关键字来自魔术或cornjobpage.php(在下面的示例中)

对于背景工人,我认为您应该尝试此技术,将有助于与您喜欢所有页面的页面一样多,而无需等待每个页面响应均为异步。

cornjobpage.php //主页

    <?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:如果您想以循环发送URL参数,请按照以下答案:https://stackoverflow.com/a/41225209/6295712

正如许多人所说的那样,不是最好的方法,但这可能会有所帮助:

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

如果您的脚本的所需输出是一些处理,而不是网页,那么我相信所需的解决方案是从Shell运行您的脚本,只是

php my_script.php

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top