You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PHP从Google Sheet拉取CSV脚本的报错排查与代码优化问询

问题描述
  • 无PHP开发经验,靠谷歌搜资料写了个PHP脚本,通过cron每5分钟跑一次,从公开共享的Google Sheet拉数据,存成CSV文件到WordPress共享主机服务器。
  • 脚本大多时候正常,但偶尔会出现「failed to open stream」相关报错:
    1. HTTP/1.0 500 Internal Server Error(推测是服务器自身问题)
    2. HTTP/1.0 400 Bad Request(推测是和Google Sheet的通信问题)
    3. 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] =.&quot;/&quot;...少了$save_location_Data、rename函数参数错误等问题,优化后的代码都修复了。

内容的提问来源于stack exchange,提问作者Shayne

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 00:14:53