Google Apps Script的 API调用不使用API密钥
我是初学者,正在为一个小项目尝试处理实时API数据。
我想创建一个HTML页面(以后用于iframe),用于获取布鲁塞尔一个特定地铁站的实时数据。
问题在于,无论我怎么尝试,它都不会使用我的API密钥,而是走公开API,配额非常有限。
我已经尝试了我能想到的一切(包括AI能想到的),但都没用。
这里是我最新的code.gs(不含API密钥),如果你想试,可以在 https://api-management-opendata-production.developer.azure-api.net/apis 上轻松生成一个。我的参数是where=pointid: 8271和 8272。
const POINT_IDS = ["8271", "8272"];
const BASE_URL = "https://api-management-discovery-production.azure-api.net/api/datasets/stibmivb/rt/WaitingTimes";
const API_KEY = "--------";
const CACHE_KEY = "stib_arrivals";
function doGet(e) {
if (e && e.parameter && e.parameter.data === "1") {
const cache = CacheService.getScriptCache();
const cached = cache.get(CACHE_KEY);
const data = cached || "[]";
return ContentService
.createTextOutput(data)
.setMimeType(ContentService.MimeType.JSON);
} else {
return HtmlService.createTemplateFromFile("Page").evaluate()
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}
}
function refreshCache() {
const allArrivals = [];
const cache = CacheService.getScriptCache();
for (const pointId of POINT_IDS) {
const url = `${BASE_URL}?where=pointid%3D%22${pointId}%22&limit=10`;
let response;
try {
response = UrlFetchApp.fetch(url, {
headers: {
"bmc-partner-key": API_KEY // Custom header name
},
muteHttpExceptions: true
});
} catch (err) {
Logger.log(`Fetch error ${pointId}: ${err.message}`);
continue;
}
if (response.getResponseCode() !== 200) {
Logger.log(`HTTP ${response.getResponseCode()} ${pointId}: ${response.getContentText()}`);
continue;
}
let json;
try {
json = JSON.parse(response.getContentText());
} catch (err) {
Logger.log(`JSON parse error ${pointId}: ${err.message}`);
continue;
}
const records = json.results || [];
for (const record of records) {
let times;
try {
// From curl: \\\"destination\\\" → replace \\\" → "
const cleanTimes = record.passingtimes.replace(/\\\"/g, '"');
times = JSON.parse(cleanTimes);
} catch (err) {
Logger.log(`passingtimes error ${pointId}: ${err.message}`);
continue;
}
for (const pt of times) {
if (!pt?.destination) continue;
allArrivals.push({
line: pt.lineId || "?",
dest: pt.destination.fr || pt.destination.nl || "—",
arrival: pt.expectedArrivalTime,
pointid: pointId
});
}
}
}
Logger.log(`Cached ${allArrivals.length} arrivals`);
cache.put(CACHE_KEY, JSON.stringify(allArrivals), 120);
}
下面是我的page.html(我觉得问题不在这里,但万一出现问题,我还是把它放在这里)
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<style>
* { box-sizing: border-box; }
body {
margin: 0;
padding: 10px;
font-family: sans-serif;
background: #1c1c1c;
color: #e1e1e1;
}
h4 {
margin: 10px 0 6px 0;
font-size: 0.78em;
text-transform: uppercase;
letter-spacing: 0.1em;
opacity: 0.5;
font-weight: normal;
}
h4:first-of-type { margin-top: 0; }
table {
width: 100%;
border-collapse: collapse;
font-size: 0.92em;
margin-bottom: 14px;
}
th {
text-align: left;
padding: 4px 8px;
font-weight: normal;
opacity: 0.45;
font-size: 0.82em;
}
td { padding: 7px 8px; }
tr:not(:last-child) td {
}
.badge {
display: inline-block;
border-radius: 5px;
padding: 2px 8px;
font-weight: bold;
font-size: 0.88em;
min-width: 22px;
text-align: center;
color: #fff;
}
.line-1 { background: #B9378D; }
.line-5 { background: #FFB537; color: #1c1c1c; }
.normal { font-weight: bold; }
.soon { font-weight: bold; color: #f28b31; }
.asap { font-weight: bold; color: #e05c5c; }
.divider {
border: none;
border-top: 1px solid rgba(255,255,255,0.1);
margin: 4px 0 12px 0;
}
#footer {
font-size: 0.68em;
opacity: 0.3;
text-align: right;
margin-top: 4px;
}
</style>
</head>
<body>
<table>
<thead><tr><th>Ligne</th><th>Direction</th><th>Dans</th></tr></thead>
<tbody id="tbody-a"></tbody>
</table>
<hr class="divider"/>
<table>
<thead><tr><th>Ligne</th><th>Direction</th><th>Dans</th></tr></thead>
<tbody id="tbody-b"></tbody>
</table>
<div id="footer">—</div>
<script>
const PROXY_URL = "<?= ScriptApp.getService().getUrl() ?>?data=1";
const DEST_A = ["ERASME", "GARE DE L'OUEST"];
const DEST_B = ["STOCKEL", "HERRMANN-DEBROUX"];
function showUnavailable() {
["tbody-a", "tbody-b"].forEach(id => {
document.getElementById(id).innerHTML =
`<tr><td colspan="3" style="opacity:0.5; padding:8px;">🚧 Service indisponible</td></tr>`;
});
}
function renderTable(tbodyId, arrivals) {
const tbody = document.getElementById(tbodyId);
if (arrivals.length === 0) {
tbody.innerHTML = `<tr><td colspan="3" style="opacity:0.4; padding:8px;">Aucun passage prévu.</td></tr>`;
return;
}
tbody.innerHTML = arrivals.map(a => {
const mins = Math.floor(a.diffMs / 60000);
const secs = Math.floor((a.diffMs % 60000) / 1000);
let label, cls;
if (mins < 1) { label = `${secs} sec`; cls = "asap"; }
else if (mins < 3) { label = `${mins} min`; cls = "soon"; }
else { label = `${mins} min`; cls = "normal"; }
const badgeCls = `badge line-${a.line}`;
return `<tr>
<td><span class="${badgeCls}">${a.line}</span></td>
<td>${a.dest}</td>
<td><span class="${cls}">${label}</span></td>
</tr>`;
}).join("");
}
async function refresh() {
const footer = document.getElementById("footer");
try {
const res = await fetch(PROXY_URL);
const arrivals = await res.json();
const now = new Date();
// API is up but returning no data — service outage
if (!Array.isArray(arrivals) || arrivals.length === 0) {
showUnavailable();
footer.textContent = "API hors service – " + now.toLocaleTimeString("fr-BE");
return;
}
const upcoming = arrivals
.map(a => ({ ...a, diffMs: new Date(a.arrival) - now }))
.filter(a => a.diffMs > 0)
.sort((a, b) => a.diffMs - b.diffMs);
const groupA = upcoming.filter(a => DEST_A.includes(a.dest.toUpperCase()));
const groupB = upcoming.filter(a => DEST_B.includes(a.dest.toUpperCase()));
renderTable("tbody-a", groupA);
renderTable("tbody-b", groupB);
footer.textContent = "Mis à jour : " + now.toLocaleTimeString("fr-BE");
} catch (err) {
["tbody-a","tbody-b"].forEach(id => {
document.getElementById(id).innerHTML =
`<tr><td colspan="3" style="color:#e05c5c; padding:8px;">⚠️ Erreur : ${err.message}</td></tr>`;
});
footer.textContent = "Échec – " + new Date().toLocaleTimeString("fr-BE");
}
}
refresh();
setInterval(refresh, 20000);
</script>
</body>
</html>
解决方案
经过一些研究,我找到了答案。如果有人遇到同样的问题,这里是修改后的代码。
const POINT_IDS = ["8271", "8272"];
const BASE_URL = "https://api-management-opendata-production.azure-api.net/api/datasets/stibmivb/rt/WaitingTimes/";
const API_KEY = "YOUR-API-KEY";
const CACHE_KEY = "stib_arrivals";
function doGet(e) {
if (e && e.parameter && e.parameter.data === "1") {
// Serve from cache — fast, always available
const cache = CacheService.getScriptCache();
const cached = cache.get(CACHE_KEY);
const data = cached || "[]";
return ContentService
.createTextOutput(data)
.setMimeType(ContentService.MimeType.JSON);
} else {
return HtmlService.createTemplateFromFile("Page").evaluate()
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}
}
// This function is called by the time-based trigger, NOT by the page
function refreshCache() {
const allArrivals = [];
const cache = CacheService.getScriptCache();
for (const pointId of POINT_IDS) {
const url = `${BASE_URL}?where=pointid=%22${pointId}%22`;
let response;
try {
response = UrlFetchApp.fetch(url, {
method: "get",
headers: {
"Cache-Control": "no-cache",
"bmc-partner-key": API_KEY
},
muteHttpExceptions: true
});
} catch (err) {
Logger.log(`Fetch error ${pointId}: ${err.message}`);
continue;
}
Logger.log(`HTTP ${response.getResponseCode()} ${pointId}`);
Logger.log(response.getContentText());
if (response.getResponseCode() !== 200) {
continue;
}
let json;
try {
json = JSON.parse(response.getContentText());
} catch (err) {
Logger.log(`JSON parse error ${pointId}: ${err.message}`);
continue;
}
const records = json.results || [];
for (const record of records) {
let times;
try {
const clean = record.passingtimes.replace(/\\\"/g, '"');
times = JSON.parse(clean);
} catch (err) {
Logger.log(`passingtimes error ${pointId}: ${err.message}`);
continue;
}
for (const pt of times) {
if (!pt?.destination) continue;
allArrivals.push({
line: pt.lineId || "?",
dest: pt.destination.fr || pt.destination.nl || "—",
arrival: pt.expectedArrivalTime,
pointid: pointId
});
}
}
}
Logger.log(`Cached ${allArrivals.length} arrivals`);
cache.put(CACHE_KEY, JSON.stringify(allArrivals), 120);
}
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。