Повышение эффективности скребка HTML с помощью pcntl_fork () - PullRequest
2 голосов
/ 19 мая 2010

С помощью двух предыдущих вопросов у меня теперь есть рабочий HTML-скребок, который подает информацию о продукте в базу данных. То, что я сейчас пытаюсь сделать, - это улучшить эффективность, обернув свой мозг, заставив мой скребок работать с pcntl_fork.

Если я разделю свой скрипт php5-cli на 10 отдельных кусков, я значительно увеличу общее время выполнения, так что я знаю, что я не привязан ни к вводу / выводу, ни к процессору, а просто ограничен линейным характером моих функций очистки.

Используя код, который я собрал из нескольких источников, у меня есть этот рабочий тест:

<?php
libxml_use_internal_errors(true);
ini_set('max_execution_time', 0); 
ini_set('max_input_time', 0); 
set_time_limit(0);

$hrefArray = array("http://slashdot.org", "http://slashdot.org", "http://slashdot.org", "http://slashdot.org");

function doDomStuff($singleHref,$childPid) {
    $html = new DOMDocument();
    $html->loadHtmlFile($singleHref);

    $xPath = new DOMXPath($html);

    $domQuery = '//div[@id="slogan"]/h2';
    $domReturn = $xPath->query($domQuery);

    foreach($domReturn as $return) {
        $slogan = $return->nodeValue;
        echo "Child PID #" . $childPid . " says: " . $slogan . "\n";
    }
}

$pids = array();
foreach ($hrefArray as $singleHref) {
    $pid = pcntl_fork();

    if ($pid == -1) {
        die("Couldn't fork, error!");
    } elseif ($pid > 0) {
        // We are the parent
        $pids[] = $pid;
    } else {
        // We are the child
        $childPid = posix_getpid();
        doDomStuff($singleHref,$childPid);
        exit(0);
    }
}

foreach ($pids as $pid) {
    pcntl_waitpid($pid, $status);
}

// Clear the libxml buffer so it doesn't fill up
libxml_clear_errors();

Что поднимает следующие вопросы:

1) Учитывая, что мой hrefArray содержит 4 URL-адреса - если бы массив должен был содержать, скажем, 1000 URL-адресов продуктов, этот код породил бы 1000 дочерних процессов? Если это так, то каков наилучший способ ограничить количество процессов, скажем, до 10, и, опять же, 1000 URL в качестве примера, разделить дочернюю рабочую нагрузку до 100 продуктов на одного ребенка (10 x 100).

2) Я узнал, что pcntl_fork создает копию процесса и всех переменных, классов и т. Д. Я хотел бы заменить мою переменную hrefArray на запрос DOMDocument, который строит список продуктов для очистки, и затем передает их дочерним процессам для выполнения обработки - таким образом, нагрузка распределяется на 10 дочерних процессов.

Мой мозг говорит, что мне нужно сделать что-то вроде следующего (очевидно, это не работает, поэтому не запускайте его):

<?php
libxml_use_internal_errors(true);
ini_set('max_execution_time', 0); 
ini_set('max_input_time', 0); 
set_time_limit(0);
$maxChildWorkers = 10;

$html = new DOMDocument();
$html->loadHtmlFile('http://xxxx');
$xPath = new DOMXPath($html);

$domQuery = '//div[@id=productDetail]/a';
$domReturn = $xPath->query($domQuery);

$hrefsArray[] = $domReturn->getAttribute('href');

function doDomStuff($singleHref) {
    // Do stuff here with each product
}

// To figure out: Split href array into $maxChilderWorks # of workArray1, workArray2 ... workArray10. 
$pids = array();
foreach ($workArray(1,2,3 ... 10) as $singleHref) {
    $pid = pcntl_fork();

    if ($pid == -1) {
        die("Couldn't fork, error!");
    } elseif ($pid > 0) {
        // We are the parent
        $pids[] = $pid;
    } else {
        // We are the child
        $childPid = posix_getpid();
        doDomStuff($singleHref);
        exit(0);
    }
}


foreach ($pids as $pid) {
    pcntl_waitpid($pid, $status);
}

// Clear the libxml buffer so it doesn't fill up
libxml_clear_errors();

Но я не могу понять, как построить мой hrefsArray [] только в процессе master / parent и передать его дочернему процессу. В настоящее время все, что я пробовал, вызывает циклы в дочерних процессах. То есть Мой hrefsArray встроен в мастер и в каждый последующий дочерний процесс.

Я уверен, что я все делаю совершенно неправильно, поэтому буду очень признателен, если просто подтолкнете в правильном направлении.

Ответы [ 2 ]

4 голосов
/ 29 марта 2013

Введение

pcntl_fork() - не единственный способ повысить производительность HTML scraper, хотя было бы неплохо использовать Message Queue с предложением Charles, но вам все еще нужен более быстрый и эффективный способ выполнить этот запрос в workers

Раствор 1

Использование curl_multi_init ... curl на самом деле быстрее, а использование multi curl обеспечивает параллельную обработку

Из PHP DOC

curl_multi_init Разрешает параллельную обработку нескольких дескрипторов cURL.

Таким образом, вместо использования $html->loadHtmlFile('http://xxxx'); для загрузки файлов несколько раз, вы можете просто использовать curl_multi_init для одновременной загрузки нескольких URL

Вот некоторые интересные реализации

Решение 2

Вы можете использовать pthreads , чтобы использовать многопоточность в PHP

Пример

// Number of threads you want
$threads = 10;

// Treads storage
$ts = array();

// Your list of URLS // range just for demo
$urls = range(1, 50);

// Group Urls
$urlsGroup = array_chunk($urls, floor(count($urls) / $threads));

printf("%s:PROCESS  #load\n", date("g:i:s"));

$name = range("A", "Z");
$i = 0;
foreach ( $urlsGroup as $group ) {
    $ts[] = new AsyncScraper($group, $name[$i ++]);
}

printf("%s:PROCESS  #join\n", date("g:i:s"));

// wait for all Threads to complete
foreach ( $ts as $t ) {
    $t->join();
}

printf("%s:PROCESS  #finish\n", date("g:i:s"));

Выход

9:18:00:PROCESS  #load
9:18:00:START  #5592     A
9:18:00:START  #9620     B
9:18:00:START  #11684    C
9:18:00:START  #11156    D
9:18:00:START  #11216    E
9:18:00:START  #11568    F
9:18:00:START  #2920     G
9:18:00:START  #10296    H
9:18:00:START  #11696    I
9:18:00:PROCESS  #join
9:18:00:START  #6692     J
9:18:01:END  #9620       B
9:18:01:END  #11216      E
9:18:01:END  #10296      H
9:18:02:END  #2920       G
9:18:02:END  #11696      I
9:18:04:END  #5592       A
9:18:04:END  #11568      F
9:18:04:END  #6692       J
9:18:05:END  #11684      C
9:18:05:END  #11156      D
9:18:05:PROCESS  #finish

Класс используется

class AsyncScraper extends Thread {

    public function __construct(array $urls, $name) {
        $this->urls = $urls;
        $this->name = $name;
        $this->start();
    }

    public function run() {
        printf("%s:START  #%lu \t %s \n", date("g:i:s"), $this->getThreadId(), $this->name);
        if ($this->urls) {
            // Load with CURL
            // Parse with DOM
            // Do some work

            sleep(mt_rand(1, 5));
        }
        printf("%s:END  #%lu \t %s \n", date("g:i:s"), $this->getThreadId(), $this->name);
    }
}
2 голосов
/ 04 июня 2010

Кажется, я предлагаю это ежедневно, но вы смотрели на Gearman ? Есть даже хорошо документированный класс PECL .

Gearman - система рабочих очередей. Вы бы создали рабочих, которые подключаются и прослушивают задания, а также клиентов, которые подключают и отправляют задания. Клиент может либо дождаться завершения запрошенного задания, либо запустить его и забыть. По вашему выбору работники могут даже отправлять обратно обновления статуса и информацию о том, как далеко они продвинулись в этом процессе.

Другими словами, вы получаете преимущества нескольких процессов или потоков, не беспокоясь о процессах и потоках. Клиенты и работники могут даже находиться на разных машинах.

...