我正在尝试创建一个程序,从网站上抓取数据x次,我正在寻找一种方法来实现这一目标,而不会在此过程中出现大的延迟。
目前我使用下面的代码,而且速度相当慢(即使它只抓住了4个人的名字,我希望一次只做100个):
$skills = array(
"overall", "attack", "defense", "strength", "constitution", "ranged",
"prayer", "magic", "cooking", "woodcutting", "fletching", "fishing",
"firemaking", "crafting", "smithing", "mining", "herblore", "agility",
"thieving", "slayer", "farming", "runecrafting", "hunter", "construction",
"summoning", "dungeoneering"
);
$participants = array("Zezima", "Allar", "Foot", "Arma150", "Green098", "Skiller 703", "Quuxx");//explode("\r\n", $_POST['names']);
$skill = isset($_GET['skill']) ? array_search($skills, $_GET['skill']) : 0;
display($participants, $skills, array_search($_GET['skill'], $skills));
function getAllStats($participants) {
$stats = array();
for ($i = 0; $i < count($participants); $i++) {
$stats[] = getStats($participants[$i]);
}
return $stats;
}
function display($participants, $skills, $stat) {
$all = getAllStats($participants);
for ($i = 0; $i < count($participants); $i++) {
$rank = getSkillData($all[$i], 0, $stat);
$level = getSkillData($all[$i], 1, $stat);
$experience = getSkillData($all[$i], 3, $stat);
}
}
function getStats($username) {
$curl = curl_init("http://hiscore.runescape.com/index_lite.ws?player=" . $username);
curl_setopt ($curl, CURLOPT_CONNECTTIMEOUT, $timeout);
curl_setopt ($curl, CURLOPT_USERAGENT, sprintf("Mozilla/%d.0", rand(4, 5)));
curl_setopt ($curl, CURLOPT_HEADER, (int) $header);
curl_setopt ($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt ($curl, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt ($curl, CURLOPT_VERBOSE, 1);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$output = curl_exec($curl);
curl_close ($curl);
if (strstr($output, "<html><head><title>")) {
return false;
}
return $output;
}
function getSkillData($stats, $row, $skill) {
$stats = explode("\n", $stats);
$levels = explode(",", $stats[$skill]);
return $levels[$row];
}
当我对此进行基准测试时,花了大约5秒钟,这不是太坏,但想象一下,如果我这样做了93次。我知道它不会是即时的,但我想拍摄不到30秒。我知道这是可能的,因为我看到网站做了类似的事情,他们在30秒的时间内采取行动。
我已经阅读了有关使用缓存数据的内容,但这不起作用,因为它只是旧的。我正在使用一个数据库(更进一步,我尚未得到那个部分)来存储旧数据并检索实时的新数据(你在下面看到的)。
有没有办法在没有大量延迟的情况下实现这样的事情(并且可能会使我正在阅读的服务器超载)?
P.S:我正在阅读的网站只是文字,它没有任何要解析的HTML,应该减少加载时间。这是一个页面看起来像的例子(它们都是相同的,只是不同的数字):
69,2496,1285458634 10982,99,33055154 6608,99,30955066 6978,99,40342518 12092,99,36496288 13247,99,21606979 2812,99,13977759 926,99,36988378 415,99,153324269 329,99,59553081 472,99,40595060 2703,99,28297122 281,99,36937100 1017,99,19418910 276,99,27539259 792,99,34289312 3040,99,16675156 82,99,39712827 80,99,104504543 2386,99,21236188 655,99,28714439 852,99,30069730 29,99,200000000 3366,99,15332729 2216,99,15836767 154,120,200000000 -1,-1 -1,-1 -1,-1 -1,-1 -1,-1 30086,2183 54640,1225 89164,1028 123432,1455 -1,-1 -1,-1
我之前使用此方法的基准与curl_multi_exec
:
function getTime() {
$timer = explode(' ', microtime());
$timer = $timer[1] + $timer[0];
return $timer;
}
function benchmarkFunctions() {
$start = getTime();
old_f();
$end = getTime();
echo 'function old_f() took ' . round($end - $start, 4) . ' seconds to complete<br><br>';
$startt = getTime();
new_f();
$endd = getTime();
echo 'function new_f() took ' . round($endd - $startt, 4) . ' seconds to complete';
}
function old_f() {
$test = array("A E T", "Ts Danne", "Funkymunky11", "Fast993", "Fast99Three", "Jeba", "Quuxx");
getAllStats($test);
}
function new_f() {
$test = array("A E T", "Ts Danne", "Funkymunky11", "Fast993", "Fast99Three", "Jeba", "Quuxx");
$curl_arr = array();
$master = curl_multi_init();
$amt = count($test);
for ($i = 0; $i < $amt; $i++) {
$curl_arr[$i] = curl_init('http://hiscore.runescape.com/index_lite.ws?player=' . $test[$i]);
curl_setopt($curl_arr[$i], CURLOPT_RETURNTRANSFER, true);
curl_multi_add_handle($master, $curl_arr[$i]);
}
do {
curl_multi_exec($master, $running);
} while ($running > 0);
for ($i = 0; $i < $amt; $i++) {
$results = curl_exec($curl_arr[$i]);
}
}
答案 0 :(得分:2)
当你做这样的大量网络请求时,你将受到网络和远程服务器的支配,他们会花多少时间来回应。
因此,在最短的时间内完成所有请求的最佳方法可能是一次完成所有请求。为每个人生成一个新线程。对于您正在使用的数据的大小,它可能很可能一次完成,但如果这是一个问题,那么可以一次尝试20个左右。
编辑:我刚刚意识到你正在使用没有线程的PHP。对于初学者来说,可能是一种糟糕的语言选择。但是您可以通过分支新进程来模拟线程。但是,如果PHP在Web服务器进程内运行,这可能是一个残骸,因为它将克隆整个服务器。我将研究PHP是否提供某种可能产生类似效果的异步Web请求。编辑2:
这是一个讨论如何使用PHP在后台启动HTTP请求的页面:
http://w-shadow.com/blog/2007/10/16/how-to-run-a-php-script-in-the-background/
然而,这是“火上浇油而忘记”,它不会让你接受对你的请求的回应并用它做点什么。但是,您可以采用的一种方法是使用此方法将许多请求发送到您自己服务器上的不同页面,并让这些页面中的每一个向远程服务器发出单个请求。 (或者,如果您不想一次启动太多请求,则每个工作者请求可以处理一批请求。)
您仍然需要一种方法来组合所有结果,以及一种检测整个过程何时完成的方法,以便您可以显示结果。我可能会使用数据库或文件系统来协调不同的进程。
(同样,为此任务选择更强大的语言可能会有所帮助。在类似于PHP的语言领域,我知道Perl会使用“use threads”轻松处理这个问题,我想Python或Ruby会同样。)
编辑3:
另一个解决方案,这个使用UNIX shell通过在单独的进程中完成工作来解决PHP的局限性。您可以执行以下命令:
echo '$urlList' | xargs -P 10 -r -n1 wget
您可能希望稍微使用wget
选项,例如明确指定输出文件,但这是一般的想法。代替wget,你也可以使用curl
,或者如果你想完全控制获取页面的工作,甚至只需调用一个旨在从命令行运行的PHP脚本。
同样,使用此解决方案,您仍然可以识别作业何时完成,以便显示结果。
我从这个页面得到了这个方法的想法:
http://www.commandlinefu.com/commands/view/3269/parallel-file-downloading-with-wget
答案 1 :(得分:1)
由于您向同一主机发出多个请求,因此您可以重新使用curl句柄,如果该站点支持保持活动请求,则可以在许多请求中加快您的流程速度。
您可以像这样更改您的功能:
function getStats($username) {
static $curl = null;
if ($curl == null) {
$curl = curl_init();
}
curl_setopt($curl, CURLOPT_URL, "http://hiscore.runescape.com/index_lite.ws?player=" . $username);
curl_setopt ($curl, CURLOPT_HTTPHEADER, array('Connection: Keep-Alive'));
//...
// remove curl_close($curl)
}
执行此操作将使您不必为每个用户请求关闭并重新建立套接字。它将对所有请求使用相同的连接。
答案 2 :(得分:1)
您可以重复使用curl连接。此外,我更改了您的代码以检查httpCode
而非使用strstr
。应该更快。
另外,你可以设置curl并行执行,这是我从未尝试过的。见http://www.php.net/manual/en/function.curl-multi-exec.php
带有重复使用的卷曲手柄的改进版getStats()
。
function getStats(&$curl,$username) {
curl_setopt($curl, CURLOPT_URL, "http://hiscore.runescape.com/index_lite.ws?player=" . $username);
$output = curl_exec($curl);
if (curl_getinfo($curl, CURLINFO_HTTP_CODE)!='200') {
return null;
}
return $output;
}
<强>用法:强>
$participants = array("Zezima", "Allar", "Foot", "Arma150", "Green098", "Skiller 703", "Quuxx");
$curl = curl_init();
curl_setopt ($curl, CURLOPT_CONNECTTIMEOUT, 0); //dangerous! will wait indefinitely
curl_setopt ($curl, CURLOPT_USERAGENT, sprintf("Mozilla/%d.0", rand(4, 5)));
curl_setopt ($curl, CURLOPT_HEADER, false);
curl_setopt ($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt ($curl, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt ($curl, CURLOPT_VERBOSE, 1);
//try:
curl_setopt($curl, CURLOPT_HTTPHEADER, array(
'Connection: Keep-Alive',
'Keep-Alive: 300'
));
header('Content-type:text/plain');
foreach($participants as &$user) {
$stats = getStats($curl, $user);
if($stats!==null) {
echo $stats."\r\n";
}
}
curl_close($curl);
答案 3 :(得分:0)
curl
是阅读网站内容的一种非常好的方式 - 我想您的问题是因为需要下载 ONE 页面的时间。如果您可以并行获得所有100个页面,那么您可能会在10秒内完成所有页面的处理。
为了避免使用线程,锁,信号量以及线程上所有具有挑战性的东西,请阅读this article并找到一种方法使您的应用程序几乎是免费并行的。