简短说明
file_get_contents 很慢,用它抓取多个 URL 很疯狂,您必须同步等待单个请求完成,然后再发出另一个请求。
解决方案
使用 multi curl 一次打开多个连接并使用 CURLOPT_WRITEFUNCTION 异步收集响应。
基准测试
让我们尝试通过抓取 12 个 GitHub 链接来对它们进行基准测试。这个基准测试表明,multi curl 可以比file_get_contents 快大约 12.19 倍来抓取 12 个 GitHub URL。我相信随着链接数量的增加,差异会显着增加。
file_get_contents 基准测试
文件:single.php
<?php
$links = [
"https://github.com/TeaInside/teavpn2",
"https://github.com/ammarfaizi2/php-integral-obfuscator",
"https://github.com/ammarfaizi2/fresh-tea-asm",
"https://github.com/ammarfaizi2/memcpy_benchmark",
"https://github.com/ammarfaizi2/GoogleTranslate",
"https://github.com/ammarfaizi2/latex.teainside.org",
"https://github.com/torvalds/linux",
"https://github.com/torvalds/uemacs",
"https://github.com/torvalds/subsurface-for-dirk",
"https://github.com/gcc-mirror/gcc",
"https://raw.githubusercontent.com/TeaInside/teavpn2/0.0.1-rc1/Makefile",
"https://raw.githubusercontent.com/TeaInside/teavpn2/0.0.1-rc1/LICENSE",
];
foreach ($links as $link) {
$page_content = file_get_contents($link);
if (preg_match("/<title>/S", $page_content, $m)) {
echo "Link {$link} has a title\n";
} else {
echo "Link {$link} does not have a title\n";
}
}
执行
root@esteh:/tmp# time php single.php
Link https://github.com/TeaInside/teavpn2 has a title
Link https://github.com/ammarfaizi2/php-integral-obfuscator has a title
Link https://github.com/ammarfaizi2/fresh-tea-asm has a title
Link https://github.com/ammarfaizi2/memcpy_benchmark has a title
Link https://github.com/ammarfaizi2/GoogleTranslate has a title
Link https://github.com/ammarfaizi2/latex.teainside.org has a title
Link https://github.com/torvalds/linux has a title
Link https://github.com/torvalds/uemacs has a title
Link https://github.com/torvalds/subsurface-for-dirk has a title
Link https://github.com/gcc-mirror/gcc has a title
Link https://raw.githubusercontent.com/TeaInside/teavpn2/0.0.1-rc1/Makefile does not have a title
Link https://raw.githubusercontent.com/TeaInside/teavpn2/0.0.1-rc1/LICENSE does not have a title
real 0m14.721s
user 0m0.070s
sys 0m0.025s
root@esteh:/tmp#
抓取12个链接需要14.721s
多卷曲基准
文件:multiple.php
<?php
$links = [
"https://github.com/TeaInside/teavpn2",
"https://github.com/ammarfaizi2/php-integral-obfuscator",
"https://github.com/ammarfaizi2/fresh-tea-asm",
"https://github.com/ammarfaizi2/memcpy_benchmark",
"https://github.com/ammarfaizi2/GoogleTranslate",
"https://github.com/ammarfaizi2/latex.teainside.org",
"https://github.com/torvalds/linux",
"https://github.com/torvalds/uemacs",
"https://github.com/torvalds/subsurface-for-dirk",
"https://github.com/gcc-mirror/gcc",
"https://raw.githubusercontent.com/TeaInside/teavpn2/0.0.1-rc1/Makefile",
"https://raw.githubusercontent.com/TeaInside/teavpn2/0.0.1-rc1/LICENSE",
];
function visit_multiple(array $links, array &$retval): void
{
$chs = [];
$mh = curl_multi_init();
foreach ($links as $k => $link) {
$ch = curl_init($link);
$chs[] = $ch;
$retval[$k] = "";
$writeCallback = function ($ch, string $str) use ($k, &$retval) {
$retval[$k] .= $str;
return strlen($str);
};
curl_setopt_array($ch,
[
CURLOPT_USERAGENT => "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:87.0) Gecko/20100101 Firefox/87.0",
CURLOPT_WRITEFUNCTION => $writeCallback
]
);
curl_multi_add_handle($mh, $ch);
}
do {
$status = curl_multi_exec($mh, $active);
if ($active) {
curl_multi_select($mh);
}
} while ($active && $status == CURLM_OK);
foreach ($chs as $ch) {
curl_multi_remove_handle($mh, $ch);
}
curl_multi_close($mh);
}
$retval = [];
visit_multiple($links, $retval);
foreach ($retval as $k => $page_content) {
if (preg_match("/<title>/S", $page_content, $m)) {
echo "Link {$links[$k]} has a title\n";
} else {
echo "Link {$links[$k]} does not have a title\n";
}
}
执行
root@esteh:/tmp# time php multiple.php
Link https://github.com/TeaInside/teavpn2 has a title
Link https://github.com/ammarfaizi2/php-integral-obfuscator has a title
Link https://github.com/ammarfaizi2/fresh-tea-asm has a title
Link https://github.com/ammarfaizi2/memcpy_benchmark has a title
Link https://github.com/ammarfaizi2/GoogleTranslate has a title
Link https://github.com/ammarfaizi2/latex.teainside.org has a title
Link https://github.com/torvalds/linux has a title
Link https://github.com/torvalds/uemacs has a title
Link https://github.com/torvalds/subsurface-for-dirk has a title
Link https://github.com/gcc-mirror/gcc has a title
Link https://raw.githubusercontent.com/TeaInside/teavpn2/0.0.1-rc1/Makefile does not have a title
Link https://raw.githubusercontent.com/TeaInside/teavpn2/0.0.1-rc1/LICENSE does not have a title
real 0m1.207s
user 0m0.165s
sys 0m0.042s
root@esteh:/tmp#
抓取12个链接仅需1.207s
奖金
为了满足评论中的 OP 请求,我添加了标题检查。如果有标题但标题中包含“找不到页面”,那么我们假设该页面没有标题。
这是一项微不足道的任务,但没关系。
代码
<?php
$links = [
"https://amzn.to/3x4dxLF", // found
"https://amzn.to/3qQcrQf", // not found
"https://github.com/TeaInside/teavpn2", // found
"https://github.com/not_found_page", // not found
];
function visit_multiple(array $links, array &$retval): void
{
$chs = [];
$mh = curl_multi_init();
$cwd = getcwd();
foreach ($links as $k => $link) {
$ch = curl_init($link);
$chs[] = $ch;
$retval[$k] = "";
$writeCallback = function ($ch, string $str) use ($k, &$retval) {
$retval[$k] .= $str;
return strlen($str);
};
curl_setopt_array($ch,
[
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_USERAGENT => "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:87.0) Gecko/20100101 Firefox/87.0",
CURLOPT_WRITEFUNCTION => $writeCallback,
CURLOPT_COOKIEJAR => $cwd."/my_cookie{$k}.txt",
CURLOPT_COOKIEFILE => $cwd."/my_cookie{$k}.txt",
CURLOPT_ENCODING => "gzip",
CURLOPT_HTTPHEADER => [
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Accept-Language: en-US,en;q=0.5",
"Upgrade-Insecure-Requests: 1",
],
]
);
curl_multi_add_handle($mh, $ch);
}
do {
$status = curl_multi_exec($mh, $active);
if ($active) {
curl_multi_select($mh);
}
} while ($active && $status == CURLM_OK);
foreach ($chs as $k => $ch) {
curl_multi_remove_handle($mh, $ch);
@unlink($cwd."/my_cookie{$k}.txt");
}
curl_multi_close($mh);
}
$retval = [];
visit_multiple($links, $retval);
foreach ($retval as $k => $page_content) {
if (preg_match("/<title(?:\s.*?)?>(.+?)<\/title>/isS", $page_content, $m)) {
if (!preg_match("/Page not found/i", $m[1])) {
echo "Link {$links[$k]} has a title\n";
continue;
}
}
echo "Link {$links[$k]} does not have a title\n";
}
执行
ammarfaizi2@integral:~$ php test.php
Link https://amzn.to/3x4dxLF has a title
Link https://amzn.to/3qQcrQf does not have a title
Link https://github.com/TeaInside/teavpn2 has a title
Link https://github.com/not_found_page does not have a title
ammarfaizi2@integral:~$