在前两个问题的帮助下,我现在有一个可用的HTML scraper,可以将产品信息提供给数据库。我现在要做的是通过缠绕我的大脑并使我的刮刀与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个网址 - 如果数组包含1000个产品网址,那么此代码会产生1,000个子进程吗?如果是这样,将进程数量限制为10的最佳方法是什么,并且再次将1,000个URL作为示例将子工作负载分成每个子项100个产品(10 x 100)。
2)我已经知道pcntl_fork创建了一个进程副本以及所有变量,类等。我想要做的是用一个DOMDocument查询替换我的hrefArray变量,该查询构建要刮擦的产品列表,以及然后将它们送到子进程进行处理 - 将负载分散到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在master中以及每个后续子进程中构建。
我确信我所说的一切都完全错了,所以非常感谢在正确方向上的一般推动。
答案 0 :(得分:4)
pcntl_fork()
并不是提高效果的唯一方法HTML scraper
虽然使用Message Queue
提出Charles
可能是个好主意,但您仍然需要更快捷的有效方法在workers
使用curl_multi_init
... curl实际上更快,使用多卷曲可以进行并行处理
来自PHP DOC
curl_multi_init允许并行处理多个cURL句柄。
因此,您可以使用$html->loadHtmlFile('http://xxxx');
同时加载多个网址,而不是使用curl_multi_init
多次加载文件
以下是一些有趣的实现
您可以使用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);
}
}
答案 1 :(得分:2)
好像我每天都这样建议,但你看过Gearman了吗?甚至有一个记录良好的PECL class。
Gearman是一个工作队列系统。您将创建连接和侦听作业的工作者,以及连接和发送作业的客户端。客户端可以等待所请求的作业完成,也可以触发并忘记。根据您的选择,工作人员甚至可以发回状态更新,以及他们的流程。
换句话说,您可以获得多个进程或线程的好处,而无需担心进程和线程。客户和工人甚至可以在不同的机器上。