您现在的位置: 365建站网 > 365文章 > PHP 怎么获取网页源码?file_get_contents 与 cURL 抓取实战

PHP 怎么获取网页源码?file_get_contents 与 cURL 抓取实战

文章来源:365jz.com 点击数:12 更新时间:2025-02-23 22:27

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

PHP 获取网页源码 file_get_contents curl

一、三种主流方案怎么选

  • 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建站器 »