PHP 黄金价格 API 教程:cURL、Guzzle 与 WordPress 短代码
在 PHP 中用 cURL 或 Guzzle 调用黄金价格 API,再用 transient 和 WP-Cron 实现带缓存的 WordPress [gold_price] 短代码,或改用免代码小组件。
PHP 黄金价格 API:基础知识
黄金价格 API 以 JSON 格式为您的 PHP 代码提供当前金价,可用于商品定价、主题中的价格栏或每日报表。本教程涵盖使用 cURL 的纯 PHP、Guzzle HTTP 客户端以及 WordPress:在 WordPress 中,一个由 WP-Cron 刷新、带缓存的 [gold_price] 短代码可以把金价放进任意文章或页面。教程使用 MetalAPI,一个提供贵金属、工业金属、能源、货币和加密货币数据的 JSON REST API。
您需要从 MetalAPI 控制台获取一个 API 密钥。密钥以 mk_live_ 开头;Free 套餐每月包含 100 次请求,价格每日更新,无需信用卡。请不要把密钥写在代码里:纯 PHP 中放在环境变量里,WordPress 中放在 wp-config.php 里。
每个端点都是 https://metalapi.com/api/v1 下的 GET 请求。请在 X-API-Key 请求头(或 Authorization: Bearer)中发送密钥,而不要使用 api_key 查询参数,以免密钥出现在访问日志中。以下是使用文档示例数值的 /latest 响应:
{
"success": true,
"timestamp": 1792834892,
"date": "2026-10-24",
"base": "USD",
"rates": {
"XAU": 0.0002501319,
"USDXAU": 3997.89,
"EUR": 0.8925,
"USDEUR": 1.1204
},
"rate_limit": { "...": "..." }
}rates.USDXAU 是 1 金衡盎司黄金的美元价格;rates.XAU 是它的倒数,即每美元可兑换的盎司数,因此切勿将其显示为价格。rates.USDEUR 是 1 欧元的美元价格,WordPress 插件用它来换算货币。timestamp 是价格数据的时间(Unix 秒)。
如何用 cURL 在 PHP 中获取金价
几乎所有 PHP 安装都自带 cURL 扩展。下面的函数在请求头中发送密钥、设置超时,并将网络故障和 API 错误转换为异常:
<?php
// PHP 8+,需要 curl 扩展。运行:METALAPI_KEY=mk_live_... php gold.php
function metalapi(string $path, array $params = []): array
{
$ch = curl_init('https://metalapi.com/api/v1/' . $path . '?' . http_build_query($params));
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => ['X-API-Key: ' . getenv('METALAPI_KEY')],
]);
$body = curl_exec($ch);
if ($body === false) {
throw new RuntimeException('Network error: ' . curl_error($ch));
}
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
$data = json_decode($body, true);
if (empty($data['success'])) {
$code = $data['error']['code'] ?? $status;
$info = $data['error']['info'] ?? "HTTP $status";
throw new RuntimeException("MetalAPI $code: $info", $code);
}
return $data;
}
$data = metalapi('latest', ['base' => 'USD', 'currencies' => 'XAU']);
printf("Gold: %.2f USD per troy oz\n", $data['rates']['USDXAU']);
printf("Time: %s UTC\n", gmdate('Y-m-d H:i', $data['timestamp']));使用示例数值时,它会打印 Gold: 3997.89 USD per troy oz。异常代码就是 MetalAPI 的错误代码:对 104(每分钟请求限制)在等待 Retry-After 秒后重试,对 500 或 503 使用退避重试;而对于 102(密钥无效)、105(每月配额已用完)或 106(套餐不包含该端点)等代码,请修正其原因。错误处理指南列出了所有错误代码。
使用 Guzzle 代替 cURL
在 Composer 项目中,Guzzle 能用更少的代码完成同样的事情。请保留 base_uri 末尾的斜杠,并请求 latest 这样的相对路径,因为 Guzzle 会将 /latest 解析为相对于域名根目录的路径。另外请关闭 http_errors,这样错误响应会返回 MetalAPI 的 JSON 正文,而不是抛出异常:
<?php
// composer require guzzlehttp/guzzle
require __DIR__ . '/vendor/autoload.php';
$client = new GuzzleHttp\Client([
'base_uri' => 'https://metalapi.com/api/v1/',
'timeout' => 10,
'http_errors' => false,
'headers' => ['X-API-Key' => (string) getenv('METALAPI_KEY')],
]);
$res = $client->get('latest', ['query' => ['base' => 'EUR', 'currencies' => 'XAU', 'unit' => 'gram']]);
$data = json_decode((string) $res->getBody(), true);
if (empty($data['success'])) {
$code = $data['error']['code'] ?? $res->getStatusCode();
throw new RuntimeException("MetalAPI $code: " . ($data['error']['info'] ?? $res->getReasonPhrase()), $code);
}
printf("Gold: %.2f EUR per gram\n", $data['rates']['EURXAU']);超时和其他网络故障仍会抛出 ConnectException,因此请在不能因请求失败而导致页面出错的地方捕获它。
WordPress 中的金价:带缓存的 [gold_price] 短代码
在 WordPress 中,请改用内置 HTTP API 提供的 wp_remote_get()。两条设计原则能让短代码既快速又节省请求:
- 不要在页面渲染时调用 API。插件将价格保存在由 WP-Cron 在后台刷新的 transient 中。只有当 transient 不存在时(例如插件刚刚启用之后),访问者才会触发请求。
- 在一次调用中请求所有货币。以
USD为基础货币并设置currencies=XAU,EUR,GBP时,响应中包含来自同一快照的黄金和每种货币的美元价格,因此以欧元计的金价为USDXAU ÷ USDEUR,克价则可以根据每金衡盎司 31.1034768 克推算出来。每次刷新只需一次请求,即可覆盖所有货币和单位。
transient 可能会在过期之前消失,例如被持久化对象缓存逐出,因此插件还会把最后一次成功获取的价格保存在一个选项(option)中。如果刷新失败,插件会记录错误,并在五分钟内提供这份副本。首先,将密钥添加到 wp-config.php 中,放在提示停止编辑的那一行之上:
define( 'METALAPI_KEY', 'mk_live_your_key_here' );然后将插件保存为 wp-content/plugins/metalapi-gold-price/metalapi-gold-price.php,并在 插件(Plugins)中启用它:
<?php
/**
* Plugin Name: MetalAPI Gold Price
* Description: [gold_price] shortcode, cached in a transient and refreshed by WP-Cron.
* Requires PHP: 8.0
*/
defined( 'ABSPATH' ) || exit;
const MGP_INTERVAL = 600; // 单位为秒。Standard:600,Lite:3600,Free:86400
const MGP_CURRENCIES = [ 'EUR', 'GBP' ]; // 除 USD 外,短代码用到的所有货币
const MGP_GRAMS = [ 'troy_oz' => 31.1034768, 'gram' => 1, 'kilogram' => 1000 ];
function mgp_refresh(): ?array {
if ( ! defined( 'METALAPI_KEY' ) ) {
return null;
}
$url = add_query_arg(
[ 'base' => 'USD', 'currencies' => implode( ',', [ 'XAU', ...MGP_CURRENCIES ] ) ],
'https://metalapi.com/api/v1/latest'
);
$res = wp_remote_get( $url, [ 'timeout' => 8, 'headers' => [ 'X-API-Key' => METALAPI_KEY ] ] );
$data = is_wp_error( $res ) ? null : json_decode( wp_remote_retrieve_body( $res ), true );
if ( empty( $data['success'] ) ) {
error_log( 'MetalAPI refresh failed: ' . ( is_wp_error( $res ) ? $res->get_error_message() : wp_remote_retrieve_body( $res ) ) );
$last = get_option( 'mgp_prices_last' );
if ( $last ) {
set_transient( 'mgp_prices', $last, 5 * MINUTE_IN_SECONDS ); // 暂缓重试,保留最后一次成功获取的价格
}
return $last ?: null;
}
$per_oz = [ 'USD' => $data['rates']['USDXAU'] ];
foreach ( MGP_CURRENCIES as $cur ) {
if ( ! empty( $data['rates'][ "USD$cur" ] ) ) {
$per_oz[ $cur ] = $data['rates']['USDXAU'] / $data['rates'][ "USD$cur" ];
}
}
$prices = [ 'per_oz' => $per_oz, 'time' => $data['timestamp'] ];
set_transient( 'mgp_prices', $prices, 2 * MGP_INTERVAL ); // 可容忍一次延迟的 cron 运行
update_option( 'mgp_prices_last', $prices, false ); // transient 被逐出时的后备副本
return $prices;
}
function mgp_prices(): ?array {
return get_transient( 'mgp_prices' ) ?: mgp_refresh();
}
add_shortcode( 'gold_price', function ( $atts ): string {
$a = shortcode_atts( [ 'currency' => 'USD', 'unit' => 'troy_oz', 'decimals' => 2 ], $atts, 'gold_price' );
$cur = strtoupper( $a['currency'] );
$unit = strtolower( $a['unit'] );
$p = mgp_prices();
if ( ! $p || ! isset( $p['per_oz'][ $cur ] ) || ! array_key_exists( $unit, MGP_GRAMS ) ) {
return '';
}
$price = $p['per_oz'][ $cur ] / MGP_GRAMS['troy_oz'] * MGP_GRAMS[ $unit ];
return sprintf(
'<span class="gold-price">%s %s / %s</span> <small>%s</small>',
esc_html( number_format_i18n( $price, absint( $a['decimals'] ) ) ),
esc_html( $cur ),
esc_html( str_replace( '_', ' ', $unit ) ),
esc_html( wp_date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $p['time'] ) )
);
} );
// WP-Cron:每个间隔在后台刷新一次。
add_filter( 'cron_schedules', function ( array $schedules ): array {
$schedules['mgp_interval'] = [ 'interval' => MGP_INTERVAL, 'display' => 'MetalAPI refresh' ];
return $schedules;
} );
add_action( 'mgp_refresh_event', 'mgp_refresh' );
register_activation_hook( __FILE__, function (): void {
if ( ! wp_next_scheduled( 'mgp_refresh_event' ) ) {
wp_schedule_event( time(), 'mgp_interval', 'mgp_refresh_event' );
}
} );
register_deactivation_hook( __FILE__, function (): void {
wp_clear_scheduled_hook( 'mgp_refresh_event' );
} );在任意文章、页面或短代码(Shortcode)区块中以 [gold_price] 或 [gold_price currency="EUR" unit="gram"] 的形式使用它。使用示例汇率时,第二种写法会显示 114.72 EUR / gram:3997.89 ÷ 1.1204 得到每金衡盎司 3,568.27 欧元,再除以 31.1034768 即得到克价。未列在 MGP_CURRENCIES 中的货币不会输出任何内容,因此请列出短代码用到的所有货币。
用 WP-Cron 刷新金价
启用钩子会按照通过 cron_schedules 注册的自定义间隔安排刷新任务,停用插件时则会将其移除。请将 MGP_INTERVAL 设置为您套餐的更新间隔;如果配额不够,可以设得更长:
| 套餐 | MGP_INTERVAL | 31 天月份的请求数 | 每月配额 |
|---|---|---|---|
| Free(每日更新) | 86400 | 31 | 100 |
| Lite(30 分钟更新) | 3600 | 744 | 1,000 |
| Standard(10 分钟更新) | 600 | 4,464 | 10,000 |
WP-Cron 并不是真正的调度器:它在有人访问网站时才运行到期的事件,因此在访问量很小的网站上,刷新可能会延迟;transient 的双倍有效期和保存的副本可以弥补这一点。如需精确的时间,请将 define( 'DISABLE_WP_CRON', true ); 添加到 wp-config.php 中,并通过 WP-CLI 由系统 cron 运行到期的事件:
*/10 * * * * cd /var/www/html && wp cron event run --due-now > /dev/null 2>&1还要考虑页面缓存:整页缓存会把短代码的输出与页面一起保存,因此访问者看到的是页面被缓存时的价格。请将价格页面的缓存有效期设为不超过刷新间隔。
什么情况下无需代码的小组件是更好的选择
当价格必须成为 HTML 的一部分,或者 PHP 需要使用这个数字(例如为商品重新定价)时,短代码是合适的工具。如果只是想显示价格,MetalAPI 小组件更简单:
- 无需 API 密钥,无需 PHP。您在控制台中构建小组件,然后将其嵌入代码粘贴到 自定义 HTML(Custom HTML)区块中。以后修改品种或布局时,无需改动您的网站。
- 不消耗配额。小组件的页面浏览不会使用您的每月请求,并且所有套餐都包含小组件:Free 1 个,Lite 3 个,Standard 10 个。
- 可放心使用页面缓存。价格在 iframe 内加载,因此缓存的页面绝不会把价格冻结住。
<div id="metalapi-widget-w_abc123"></div>
<script src="https://metalapi.com/js/widget.js" data-widget="w_abc123" async></script>如果您的主机会移除 <script> 标签,请使用 iframe 版本的嵌入代码。如何在网站上添加实时金价小组件介绍了构建器、在 WordPress 中的放置位置和套餐限制;小组件页面展示了各种布局。
后续步骤
创建免费账户,将密钥添加到 wp-config.php 中,然后安装插件并将 MGP_INTERVAL 设置为 86400:每天刷新一次会使用 Free 套餐每月 100 次请求中的 31 次。当您需要更新鲜的价格时,请将间隔设置为新套餐的更新间隔,并在定价页面上比较各套餐。对于 Node.js 后端,JavaScript 黄金价格 API 教程展示了在 Express 和 Next.js 中实现的相同模式。
常见问题
在 WordPress 中应该把 API 密钥存放在哪里?
以常量形式存放在 wp-config.php 中,或者存放在环境变量中并在该文件里读取。切勿放在主题文件中(它们常常会被提交到 Git),也不要放在 JavaScript 中(任何访问者都能读取)。PHP 请求不会发送 Origin 或 Referer 请求头,因此即使是域名限制密钥,也可以在服务器端使用。
为什么短代码整个周末都显示同一个金价?
现货黄金从纽约时间周五 17:00 到周日 17:00 停止交易,因此在市场重新开盘之前,API 会返回最后的价格。在 Free 套餐中,价格本来也每天只变化一次。
插件会消耗多少次 API 请求?
每次刷新一次,与访问者、页面或短代码的数量无关,因为一次调用就会返回所有货币,单位则在本地换算:间隔为 10 分钟时,每月约 4,500 次。
可以在 WooCommerce 商品价格中使用金价吗?
可以。在 PHP 中调用 mgp_prices(),例如在 woocommerce_product_get_price 过滤器中调用,然后将每克价格乘以商品的重量和纯度。请为每笔订单保存所使用的金价,以免日后发票金额发生变化。
在 WordPress 中需要使用 cURL 或 Guzzle 吗?
不需要。wp_remote_get() 使用 WordPress HTTP API,它会自动选择可用的传输方式并遵循代理设置。cURL 或 Guzzle 请留给纯 PHP 以及 Laravel、Symfony 等框架使用。
继续阅读
XAU/USD 是什么意思:XAU、XAG、XPT 和 XPD 代码详解
XAU/USD 是什么意思?详解黄金为何拥有 ISO 4217 货币代码、XAG、XPT 和 XPD 分别代表什么,以及如何在价格和 API 数据中判断报价方向。
免费黄金价格 API:每月 100 次请求能做什么
如实介绍免费黄金价格 API:MetalAPI Free 套餐包含哪些功能,如何通过缓存和合并请求用好每月 100 次请求,以及何时该升级。
Python 黄金价格 API 教程:获取、缓存并绘制金价
在 Python 中使用黄金价格 API:用 requests 获取实时金价,处理错误与重试,缓存响应,并用 pandas 和 matplotlib 绘制金价时间序列图。