PHP从Google Sheet拉取CSV脚本的报错排查与代码优化问询
问题描述
- 无PHP开发经验,靠谷歌搜资料写了个PHP脚本,通过cron每5分钟跑一次,从公开共享的Google Sheet拉数据,存成CSV文件到WordPress共享主机服务器。
- 脚本大多时候正常,但偶尔会出现「failed to open stream」相关报错:
- HTTP/1.0 500 Internal Server Error(推测是服务器自身问题)
- HTTP/1.0 400 Bad Request(推测是和Google Sheet的通信问题)
- HTTP请求失败(无状态码,推测通信问题)
- 当前脚本重复100+次相同逻辑生成120个CSV文件,总大小才60-70KB,服务器资源占用极低,现在需要解决报错问题,同时优化代码(比如用循环替代重复代码)。
原核心代码片段:
<?php /// Global variables $fileid = "2PACX-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; // Google Sheet ID from File--> Share--> Publish to Web. $save_location_Data = "/home/[ my-site ]/wp-content/uploads/database"; // where I am saving the CSV files on my server /** * MEN. */ $division = "MEN"; $sheet_[$division] = "673108837"; // Google Sheet, Tab ID $datatype = "Teams"; $range_[$division][$datatype] = "range=A1:B200"; // Selected range from tab $url_[$division][$datatype] = "https://docs.google.com/spreadsheets/d/e/".$fileid."/pub?gid=".$sheet_[$division]."&".$range_[$division][$datatype]."&single=true&output=csv"; $filename_[$division][$datatype] = ($save_location_Data."/".$division."-".$datatype."_temp_.csv"); // I am just building the full URL here. file_put_contents($filename_[$division][$datatype], file_get_contents($url_[$division][$datatype])); clearstatcache(); if(filesize($filename_[$division][$datatype]) != 0) { rename($filename_[$division][$datatype], $save_location_Data."/".$division."-".$datatype.".csv"); } else { unlink($filename_[$division][$datatype]); } // Just checking if the data is 0 bytes, if no, rename temp file to final file. If yes, delete. // I thought saving a temp file first would be quicker (more reliable) rather than file_put_contents to final file. // If the CSV data is requested by my website (in a table), it's only milliseconds the CSV file is locked to write new data so less chance of being "unavailable". $datatype = "Stats"; $range_[$division][$datatype] = "range=C1:H200"; $url_[$division][$datatype] = "https://docs.google.com/spreadsheets/d/e/".$fileid."/pub?gid=".$sheet_[$division]."&".$range_[$division][$datatype]."&single=true&output=csv"; $filename_[$division][$datatype] =."/".$division."-".$datatype."_temp_.csv"); file_put_contents($filename_[$division][$datatype], file_get_contents($url_[$division][$datatype])); clearstatcache(); if(filesize($filename_[$division][$datatype]) != 0) { rename($filename_[$division][$datatype]."/".$division."-".$datatype.".csv"); } else { unlink($filename_[$division][$datatype]); } $datatype = "Games_1"; $range_[$division][$datatype] = "range=I1:J200"; $url_[$division][$datatype] = "https://docs.google.com/spreadsheets/d/e/".$fileid."/pub?gid=".$sheet_[$division]."&".$range_[$division][$datatype]."&single=true&output=csv"; $filename_[$division][$datatype] =."/".$division."-".$datatype."_temp_.csv"); file_put_contents($filename_[$division][$datatype], file_get_contents($url_[$division][$datatype])); clearstatcache(); if(filesize($filename_[$division][$datatype]) != 0) { rename($filename_[$division][$datatype]."/".$division."-".$datatype.".csv"); } else { unlink($filename_[$division][$datatype]); } // The above code repeats about 100+ times to create 120x CSV files. // Total size of 120x CSV files is only about 60-70kb so not much data really // I wonder if I need to repeat the code? I am sure I could use a loop? // I wonder too if I am mixing strings with arrays accidentally ?>
报错解决方案
通信类报错(400/无状态码)
- 加请求重试机制:Google Sheets公开接口偶尔会因为网络波动、临时请求限制失败,给请求加3-5次重试,每次失败后等2-3秒再试,避免单次失败就报错。
- 用cURL替代file_get_contents:file_get_contents对HTTP错误处理很弱,直接会抛出致命错误;cURL能捕获状态码、设置超时,灵活处理错误,不会直接崩脚本。
- 模拟浏览器请求头:给cURL加
User-Agent等头信息,部分服务器会拦截非浏览器请求,加了之后能降低被拦截概率。
服务器500错误
- 设置脚本超时时间:共享主机通常有脚本执行时间限制,120个请求串行跑偶尔可能超时,在脚本开头加
set_time_limit(300);(设5分钟超时,足够处理)。 - 优化文件操作:别频繁调用
clearstatcache(),PHP默认会缓存文件状态,只有在刚修改完文件需要立即获取状态时才用,否则没必要。 - 检查目录权限:确保
$save_location_Data目录有读写权限,避免写入失败导致的间接错误。
代码优化(循环替代重复代码)
核心思路是把所有需要拉取的配置整理成数组,然后循环遍历处理,同时封装重复逻辑成函数,代码会简洁100倍:
优化后的完整代码
<?php // 设置超时时间,避免共享主机限制 set_time_limit(300); // 全局配置 $fileId = "2PACX-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; $saveDir = "/home/[ my-site ]/wp-content/uploads/database"; // 日志文件路径,用来记录错误 $logFile = $saveDir . "/sync_errors.log"; // 所有需要同步的配置,把你100+次的重复内容都整理到这里 $syncConfigs = [ "MEN" => [ "sheetGid" => "673108837", "dataTypes" => [ "Teams" => "range=A1:B200", "Stats" => "range=C1:H200", "Games_1" => "range=I1:J200", // 在这里继续添加其他datatype和对应的range ] ], // 如果有其他division,比如WOMEN,直接加在这里 // "WOMEN" => [ // "sheetGid" => "xxxxxxxxx", // "dataTypes" => [ // "Teams" => "range=A1:B200", // ... // ] // ] ]; // 封装请求函数,带重试机制 function fetchGoogleSheetData($url, $maxRetries = 3) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 10); // 模拟浏览器请求头 curl_setopt($ch, CURLOPT_HTTPHEADER, [ "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36" ]); $retryCount = 0; do { $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); // 如果请求成功(200状态码),直接返回数据 if ($httpCode === 200 && $response !== false) { curl_close($ch); return $response; } // 失败后等待2秒重试 sleep(2); $retryCount++; } while ($retryCount < $maxRetries); // 重试失败,记录错误信息 $error = curl_error($ch); curl_close($ch); return [ "error" => true, "message" => "请求失败,状态码:{$httpCode},错误信息:{$error}" ]; } // 封装文件处理函数 function processCsvFile($saveDir, $division, $datatype, $data) { $tempFile = "{$saveDir}/{$division}-{$datatype}_temp_.csv"; $finalFile = "{$saveDir}/{$division}-{$datatype}.csv"; // 写入临时文件 file_put_contents($tempFile, $data); // 检查文件大小 if (filesize($tempFile) > 0) { // 重命名为最终文件 rename($tempFile, $finalFile); } else { // 删除空的临时文件 unlink($tempFile); } } // 记录错误日志 function logError($logFile, $message) { $timestamp = date("[Y-m-d H:i:s]"); file_put_contents($logFile, "{$timestamp} {$message}\n", FILE_APPEND); } // 遍历配置,批量处理 foreach ($syncConfigs as $division => $config) { $sheetGid = $config["sheetGid"]; foreach ($config["dataTypes"] as $datatype => $range) { // 构建请求URL $url = "https://docs.google.com/spreadsheets/d/e/{$fileId}/pub?gid={$sheetGid}&{$range}&single=true&output=csv"; // 拉取数据 $data = fetchGoogleSheetData($url); // 处理数据 if (is_array($data) && $data["error"]) { // 记录错误 $errorMsg = "同步{$division}-{$datatype}失败:{$data['message']}"; logError($logFile, $errorMsg); continue; } // 处理文件 processCsvFile($saveDir, $division, $datatype, $data); } } ?>
优化点说明
- 结构化配置:把所有division、sheet gid、datatype和range都放到
$syncConfigs数组里,后续新增或修改直接改数组,不用重复写代码。 - 函数封装:把请求、文件处理、日志记录都封装成独立函数,代码更清晰,也方便后续修改。
- 错误处理:请求失败会记录到日志文件,方便排查哪一次同步出了问题。
- 修复原代码语法错误:原代码中存在
$filename_[$division][$datatype] =."/"...少了$save_location_Data、rename函数参数错误等问题,优化后的代码都修复了。
内容的提问来源于stack exchange,提问作者Shayne
相关产品推荐
相关产品推荐

