Улучшение эффективности скребка HTML с помощью pcntl_fork ()

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

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

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

 <?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 [] только в основном / родительском процессе и передать его дочернему процессу. В настоящее время все, что я пробовал, вызывает циклы в дочерних процессах. Т.е. мой hrefsArray создается в мастер и в каждом последующем дочернем процессе.

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

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

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

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

Введение

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- curl_multi_init одновременно

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

  • php – Самый быстрый способ проверить наличие текста во многих доменах (более 1000)
  • php получить все изображения с url, ширина и высота которых> = 200 быстрее
  • Как предотвратить перегрузку сервера во время запросов Curl в PHP

Решение 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); } }