PHP通过file_get_contents下载URL:浏览器正常,Cron任务失败求助
这种情况我之前排查过好多次,核心原因基本都是Cron的运行环境和Web浏览器访问时的环境不一样,给你几个具体的排查方向和解决办法,按顺序试应该能解决:
Cron跑的是PHP的命令行(CLI)模式,它用的php.ini大概率和Web服务器(Apache/Nginx)用的不是同一个文件。你先在服务器命令行里敲这个命令,看看CLI模式下是否允许访问URL:
php -i | grep allow_url_fopen
如果输出是allow_url_fopen => Off => Off,那就是问题所在了。你可以用php --ini找到CLI对应的php.ini路径,打开文件把allow_url_fopen = On改好,然后再运行脚本测试。
很多网站会拦截没有浏览器标识的请求,浏览器访问时自动带了User-Agent,但file_get_contents默认是空的。你给它加个模拟浏览器的请求头试试:
// 构造请求上下文,添加User-Agent $context = stream_context_create([ 'http' => [ 'header' => "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36\r\n" ] ]); // 用带上下文的方式获取内容 $filen = file_get_contents('你的目标URL', false, $context);
这个方法解决过我好几个类似的Cron请求被拦截的问题。
Cron默认的工作目录是执行任务的用户的家目录(比如/home/xxx/),如果你的脚本里有依赖相对路径的操作(哪怕是本地临时文件缓存),可能会因为权限不足出问题。你可以在Cron任务里先切换到脚本所在目录再执行,比如:
* * * * * cd /usr/local/nginx/html/your_script_dir && php your_script.php
另外,也有可能是服务器防火墙限制了CLI用户的出站请求,或者目标网站把服务器的某个IP拉黑了(比如Web用的是公网IP,CLI走的是内网IP),这个可以找服务器运维帮忙排查下网络层面的问题。
你可以把Cron的运行输出重定向到日志文件,方便抓错误:
* * * * * php /path/to/your_script.php >> /var/log/cron_script.log 2>&1
然后查看/var/log/cron_script.log,或者在脚本里加一行错误捕获:
// 在file_get_contents之后加 if ($filen === false) { var_dump(error_get_last()); }
这样能直接看到file_get_contents失败的具体原因,比如是DNS解析失败还是被403拦截了。
如果上面的方法都不行,你先手动模拟Cron的运行环境:比如Cron是用root用户设置的,就用su root -c "php /path/to/your_script.php"来运行脚本。如果手动运行也失败,那问题出在CLI环境本身;如果手动运行正常,那就是Cron的环境变量缺失,你可以在Cron任务里先加载系统环境变量:
* * * * * source /etc/profile && php /path/to/your_script.php
内容的提问来源于stack exchange,提问作者user3713442

