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 等条件返回不同结果。常见原因有:
-
服务器 IP 被限制
共享主机中的其他用户可能也在大量访问同一服务,导致整个出口 IP 被限流或封禁。
-
请求频率过高
如果每次打开网页都会向 YouTube 发起请求,线上请求量通常远高于本地测试,很容易触发风控。
-
旧接口已经变更
非公开接口可能随时修改、限制访问或停止服务。即使以前可以使用,也无法保证今后仍然可用。
-
主机商使用了代理或安全网关
某些托管环境会让外部 HTTPS 请求经过透明代理。此时的
402可能来自代理或主机商,而不是目标站点。 -
请求特征不同
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()编码参数。 - 不要长期依赖未公开、已废弃或没有稳定性承诺的接口。即使暂时恢复访问,之后仍可能再次失效。
备注:内容仅供参考。
