PAYATHON 2026

file_get_contents 出现 HTTP/1.0 402 错误

支付阿杰

结论

file_get_contents() 本身不收费。错误中的 HTTP/1.0 402 Payment Required 是远端服务器针对网站服务器这次请求返回的 HTTP 状态码。

localhost 正常、线上失败,通常是因为两次请求的来源不同,例如公网出口 IP、网络区域、请求频率、代理配置或请求头不同。线上服务器的 IP 可能被限流、封禁,或被识别为数据中心流量,也可能经过主机商的代理。因此,只修改 PHP 代码未必能解决问题。

另外,https://www.youtube.com/get_video_info 是旧接口,不能当作稳定且受支持的公开 API。新项目不应继续依赖它。

为什么本地可以,服务器不可以

实际发起请求的是运行 PHP 的机器:

  • 在 localhost 运行时,请求来自家庭或办公网络的公网 IP。
  • 部署后,请求来自托管服务器或共享主机的公网 IP。

远端服务可以根据来源 IP、访问频率、地区、Cookie、User-Agent 等条件返回不同结果。常见原因有:

  1. 服务器 IP 被限制

    共享主机中的其他用户可能也在大量访问同一服务,导致整个出口 IP 被限流或封禁。

  2. 请求频率过高

    如果每次打开网页都会向 YouTube 发起请求,线上请求量通常远高于本地测试,很容易触发风控。

  3. 旧接口已经变更

    非公开接口可能随时修改、限制访问或停止服务。即使以前可以使用,也无法保证今后仍然可用。

  4. 主机商使用了代理或安全网关

    某些托管环境会让外部 HTTPS 请求经过透明代理。此时的 402 可能来自代理或主机商,而不是目标站点。

  5. 请求特征不同

    file_get_contents() 默认发送的 HTTP 请求头比较简单。来源 IP 不同,再加上缺少 User-Agent、Cookie 等信息,可能触发不同的处理策略。

先确认 402 是谁返回的

不要用 @file_get_contents() 隐藏警告。应检查响应头和响应正文:

<?php

$videoId = $_GET['id'] ?? '';

if (!preg_match('/^[A-Za-z0-9_-]{11}$/', $videoId)) {
    http_response_code(400);
    exit('Invalid video ID');
}

$url = 'https://www.youtube.com/get_video_info?' . http_build_query([
    'hl'       => 'en',
    'video_id' => $videoId,
    'eurl'     => '',
    'el'       => 'detailpage',
    'gl'       => 'US',
    'ps'       => 'default',
]);

$context = stream_context_create([
    'http' => [
        'method'          => 'GET',
        'header'          => "User-Agent: Mozilla/5.0\r\nAccept: */*\r\n",
        'timeout'         => 10,
        'ignore_errors'   => true,
        'follow_location' => 1,
    ],
]);

$data = file_get_contents($url, false, $context);

var_dump($http_response_header ?? []);
var_dump($data);

ignore_errors 只是让程序读取非 2xx 响应的正文,便于诊断,并不会把 402 变成成功响应。

检查时主要关注:

  • Server、Via 等响应头;
  • 是否带有主机商或代理的标识;
  • 响应正文是否提到限流、封禁或付费要求;
  • 同一个请求在本地和线上得到的响应头是否不同。

更合适的处理方式

1. 改用受支持的官方 API

如果需要获取视频标题、频道、时长或状态等元数据,应使用 YouTube Data API,不要继续调用旧的 get_video_info 地址。

示例:

<?php

$videoId = $_GET['id'] ?? '';

if (!preg_match('/^[A-Za-z0-9_-]{11}$/', $videoId)) {
    http_response_code(400);
    exit('Invalid video ID');
}

$apiKey = getenv('YOUTUBE_API_KEY');

if (!$apiKey) {
    throw new RuntimeException('YOUTUBE_API_KEY is not configured');
}

$url = 'https://www.googleapis.com/youtube/v3/videos?' . http_build_query([
    'part' => 'snippet,contentDetails,status',
    'id'   => $videoId,
    'key'  => $apiKey,
]);

$context = stream_context_create([
    'http' => [
        'method'        => 'GET',
        'timeout'       => 10,
        'ignore_errors' => true,
        'header'        => "Accept: application/json\r\n",
    ],
]);

$body = file_get_contents($url, false, $context);

if ($body === false) {
    throw new RuntimeException('Unable to contact YouTube Data API');
}

$statusLine = $http_response_header[0] ?? '';

if (!preg_match('#\s2\d{2}\s#', $statusLine)) {
    throw new RuntimeException(
        'YouTube Data API request failed: ' . $statusLine . '; body: ' . $body
    );
}

$result = json_decode($body, true, 512, JSON_THROW_ON_ERROR);

API 密钥应保存在环境变量或服务器的密钥管理系统中,不要提交到公开仓库,也不要直接暴露给浏览器。

2. 检查线上服务器的网络环境

如果只是排查现有故障,可以请主机商确认:

  • 服务器出口 IP 是否被目标服务限制;
  • 是否存在出站 HTTPS 代理;
  • 是否限制访问 YouTube 或 Google 域名;
  • 共享 IP 是否产生过异常流量。

如果同一 URL 从另一台公网 IP 可以访问,而当前服务器始终返回 402,问题更可能出在来源 IP 或网络路径上。

3. 添加缓存和频率控制

不要在每次页面请求时都实时访问外部服务。可以缓存成功结果,并在请求失败后采用退避策略,避免短时间内连续重试:

$cacheSeconds = 3600;

具体缓存时间取决于数据更新频率和业务要求。遇到 402、429 或 5xx 时,不应立即高频重试。

注意事项

  • allow_url_fopen 已经能够发起请求,否则通常不会收到远端返回的 HTTP 状态码,所以它不是这个 402 的主要原因。
  • 更换 User-Agent 可以帮助诊断请求差异,但不能保证解除 IP 限制,也不应作为绕过服务限制的方法。
  • cURL 可以提供更完整的状态码、响应头和网络错误信息,但把 file_get_contents() 换成 cURL,并不会自动消除远端返回的 402。
  • 不要把未经验证的 $_GET['id'] 直接拼接到 URL 中。应先验证视频 ID,再用 http_build_query() 编码参数。
  • 不要长期依赖未公开、已废弃或没有稳定性承诺的接口。即使暂时恢复访问,之后仍可能再次失效。

备注:内容仅供参考。