PHP 怎么获取网页源码?file_get_contents 与 cURL 抓取实战
“用 PHP 抓一个网页”这件事,看似一行 file_get_contents 就完事,真到生产环境就会遇到 gzip 压缩、302 跳转、超时、编码、反爬、大文件内存溢出等问题。本文把主流方案按适用场景拆开讲。

一、三种主流方案怎么选
- file_get_contents() + stream context:轻量,读静态页、内部接口足够,无额外扩展依赖。
- cURL 扩展:功能最全(重定向、cookie、并发、POST、代理、分块),生产环境首选。手册页 https://www.php.net/manual/zh/function.curl-init.php 。
- Guzzle(Composer 包):面向对象、异步并发、中间件生态,适合大量第三方 API 调用的项目。
二、file_get_contents 的正确写法
关键点:默认 allow_url_fopen 打开才能抓远程 URL;php.ini 里关掉就只能走 cURL。
必须显式设超时,否则可能挂住几十秒:
$ctx = stream_context_create([
'http' => [
'method' => 'GET',
'header' => "User-Agent: Mozilla/5.0\r\nAccept: text/html\r\n",
'timeout' => 8,
'follow_location' => 1,
'max_redirects' => 5,
'ignore_errors' => true, // 4xx/5xx 也返回响应体
],
'ssl' => ['verify_host' => 2, 'verify_peer' => true],
]);
$html = @file_get_contents('https://example.com/', false, $ctx);
if ($html === false) {
$err = error_get_last();
// 4xx/5xx 时可从 $http_response_header 取状态
}
响应头会写进 $http_response_header 数组(自动变量),用它判断状态码比 try/catch 更靠谱。gzip 页面在 zlib.output_compression 或服务器强制压缩时可能拿到乱码,加上 'Accept-Encoding: identity' 最稳。
三、cURL 实用模板
function fetch(string $url, array $opt = []): array {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true, // 返回字符串而不是直接输出
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 5,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_TIMEOUT => 15,
CURLOPT_SSL_VERIFYPEER => true, // 生产别设为 false
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_USERAGENT => 'Mozilla/5.0 (compatible; site-bot/1.0)',
CURLOPT_HTTPHEADER => ['Accept: text/html,application/xhtml+xml'],
CURLOPT_ENCODING => '', // 让 curl 自动处理 gzip/br
CURLOPT_HEADER => true,
]) ;
foreach ($opt as $k => $v) curl_setopt($ch, $k, $v);
$res = curl_exec($ch);
if ($res === false) {
$e = ['errno' => curl_errno($ch), 'error' => curl_error($ch)];
curl_close($ch); return $e;
}
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$header = substr($res, 0, $size);
$body = substr($res, $size);
curl_close($ch);
return ['code' => $code, 'header' => $header, 'body' => $body];
}
三个常被忽略的点:CURLOPT_ENCODING => '' 让 curl 自己声明并解压,否则抓 GitHub、Cloudflare 后的站点常拿到空 body;CURLOPT_SSL_VERIFYPEER 设 false 只是绕过报错,正确做法是安装/更新 cacert.pem 并用 CURLOPT_CAINFO 指定;CURLOPT_NOPROGRESS、CURLOPT_LOW_SPEED_LIMIT+LOW_SPEED_TIME 可以在慢速时主动断开。
四、并发与流式
要一次抓几十个 URL,用 curl_multi(PHP 8 起有面向对象的 curl_multi_exec 封装习惯写法):
$mh = curl_multi_init();
foreach ($urls as $u) { $ch = curl_init($u); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_multi_add_handle($mh, $ch); }
do { $st = curl_multi_exec($mh, $active); curl_multi_select($mh, 1.0); } while ($active && $st === CURLM_OK);
大文件(视频、镜像)不要用 RETURNTRANSFER,会把整个响应吃进内存,直接写文件:
$fp = fopen($out, 'wb'); curl_setopt($ch, CURLOPT_FILE, $fp); // 流式下载 curl_setopt($ch, CURLOPT_RANGE, '0-1048575'); // 断点续传/分片
五、编码、解析与常见坑
- 中文站常见
charset=gbk,直接输出会乱码。用mb_convert_encoding($html, 'UTF-8', 'auto'),或iconv('gbk','utf-8//IGNORE', $html);先读响应头Content-Type里的 charset 更准。 - 解析别用正则切 HTML。DOM 用
DOMDocument(注意libxml_use_internal_errors(true)屏蔽标签警告),或装 Composer 包symfony/dom-crawler/masterminds/html5。 - 遇到
file_get_contents(): SSL operation failed ... certificate verify failed:多半是 CA 证书过期或缺失,yum install ca-certificates后重启 PHP-FPM。 - 被 403 拦:UA/频率问题。控制并发数、加随机间隔、遵守
robots.txt,并保留Cache-Control: no-transform之类的兼容头。 - 只想要标题/描述等少量信息,抓 HEAD 或部分字节即可:
CURLOPT_NOBODY=true,或 Range 请求bytes=0-8191。
六、常见问题 FAQ
问:抓回来的页面和本地打开不一样?
答:对方按 UA 或 Cookie 返回了不同内容(JS 渲染站点尤其明显)。PHP 抓不到 JS 渲染后的 DOM,需要无头浏览器或调它的 API。
问:为什么线上能抓、本地抓不到?
答:机房 IP 段被目标风控,或本地代理/防火墙拦截,也可能 allow_url_fopen 在本地被开、线上被关。
问:抓取要不要缓存?
答:一定要。同一个 URL 短期内重复抓取既慢又不友好,落 Redis 设 5~30 分钟 TTL,并在表里记 etag/last_modified 做条件请求(If-None-Match),命中 304 直接复用旧数据。
选型的底线判断:file_get_contents 适合一次性脚本,正式项目一律上 cURL(或 Guzzle),并注意超时、重定向、编码、缓存四件套。
生产内容,同时打通搜索引擎(SEO)与AI搜索引擎(GEO)流量入口的软件! 点击进入365建站器 »
