C#集成Amazon Products API遇内部错误,求可行实现方案
C#调用Amazon Products API返回InternalFailure的解决方法
有开发者成功用C#实现Amazon Products API吗?我将scratchpad网站上的PHP示例代码转换为C#后,调用时收到如下响应:
{"Output":{"__type":"com.amazon.coral.service#InternalFailure","message":null},"Version":"1.0"}
截至2023年,Amazon似乎未为该API提供官方SDK,以下是我的C#代码:
public class Program { static async Task Main(string[] args) { string serviceName = "ProductAdvertisingAPI"; string region = "us-east-1"; string accessKey = "**************"; string secretKey = "**************"; string payload = "{" + "\"Keywords\": \"notebook\"," + "\"PartnerTag\": \"x0d-20\"," + "\"PartnerType\": \"Associates\"," + "\"Marketplace\": \"www.amazon.com\"" + "}"; string host = "webservices.amazon.com"; string uriPath = "/paapi5/searchitems"; AwsV4 awsv4 = new AwsV4(accessKey, secretKey); awsv4.SetRegionName(region); awsv4.SetServiceName(serviceName); awsv4.SetPath(uriPath); awsv4.SetPayload(payload); awsv4.SetRequestMethod("POST"); awsv4.AddHeader("content-encoding", "amz-1.0"); awsv4.AddHeader("content-type", "application/json; charset=utf-8"); awsv4.AddHeader("host", host); awsv4.AddHeader("x-amz-target", "com.amazon.paapi5.v1.ProductAdvertisingAPIv1.SearchItems"); Dictionary<string, string> headers = awsv4.GetHeaders(); using (HttpClient client = new HttpClient()) { HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, $"https://{host}{uriPath}"); foreach (var header in headers) { request.Headers.TryAddWithoutValidation(header.Key, header.Value); } request.Content = new StringContent(payload, Encoding.UTF8, "application/json"); HttpResponseMessage response = await client.SendAsync(request); string responseContent = await response.Content.ReadAsStringAsync(); Console.WriteLine(responseContent); } Console.ReadLine(); } public class AwsV4 { private string accessKey = null; private string secretKey = null; private string path = null; private string regionName = null; private string serviceName = null; private string httpMethodName = null; private Dictionary<string, string> queryParameters = new Dictionary<string, string>(); private Dictionary<string, string> awsHeaders = new Dictionary<string, string>(); private string payload = ""; private string HMACAlgorithm = "AWS4-HMAC-SHA256"; private string aws4Request = "aws4_request"; private string strSignedHeader = null; private string xAmzDate = null; private string currentDate = null; public AwsV4(string accessKey, string secretKey) { this.accessKey = accessKey; this.secretKey = secretKey; this.xAmzDate = GetTimeStamp(); this.currentDate = GetDate(); } public void SetPath(string path) { this.path = path; } public void SetServiceName(string serviceName) { this.serviceName = serviceName; } public void SetRegionName(string regionName) { this.regionName = regionName; } public void SetPayload(string payload) { this.payload = payload; } public void SetRequestMethod(string method) { this.httpMethodName = method; } public void AddHeader(string headerName, string headerValue) { this.awsHeaders[headerName] = headerValue; } private string PrepareCanonicalRequest() { StringBuilder canonicalURL = new StringBuilder(); canonicalURL.Append(httpMethodName).Append("\n"); canonicalURL.Append(path).Append("\n").Append("\n"); string signedHeaders = ""; foreach (var header in awsHeaders) { signedHeaders += header.Key + ";"; canonicalURL.Append(header.Key).Append(":").Append(header.Value).Append("\n"); } canonicalURL.Append("\n"); strSignedHeader = signedHeaders.TrimEnd(';'); canonicalURL.Append(strSignedHeader).Append("\n"); canonicalURL.Append(GenerateHex(payload)); return canonicalURL.ToString(); } private string PrepareStringToSign(string canonicalURL) { StringBuilder stringToSign = new StringBuilder(); stringToSign.Append(HMACAlgorithm).Append("\n"); stringToSign.Append(xAmzDate).Append("\n"); stringToSign.Append(currentDate).Append("/").Append(regionName).Append("/").Append(serviceName).Append("/").Append(aws4Request).Append("\n"); stringToSign.Append(GenerateHex(canonicalURL)); return stringToSign.ToString(); } private string CalculateSignature(string stringToSign) { byte[] signatureKey = GetSignatureKey(secretKey, currentDate, regionName, serviceName); byte[] signature = HashHMAC(Encoding.UTF8.GetBytes(stringToSign), signatureKey); string strHexSignature = BitConverter.ToString(signature).Replace("-", "").ToLower(); return strHexSignature; } public Dictionary<string, string> GetHeaders() { awsHeaders["x-amz-date"] = xAmzDate; SortedDictionary<string, string> sortedHeaders = new SortedDictionary<string, string>(awsHeaders); StringBuilder canonicalURL = new StringBuilder(PrepareCanonicalRequest()); string stringToSign = PrepareStringToSign(canonicalURL.ToString()); string signature = CalculateSignature(stringToSign); awsHeaders["Authorization"] = BuildAuthorizationString(signature); return awsHeaders; } private string BuildAuthorizationString(string strSignature) { return HMACAlgorithm + " " + "Credential=" + accessKey + "/" + GetDate() + "/" + regionName + "/" + serviceName + "/" + aws4Request + "," + "SignedHeaders=" + strSignedHeader + "," + "Signature=" + strSignature; } private string GenerateHex(string data) { using (SHA256 sha256Hash = SHA256.Create()) { byte[] bytes = sha256Hash.ComputeHash(Encoding.UTF8.GetBytes(data)); StringBuilder builder = new StringBuilder(); for (int i = 0; i < bytes.Length; i++) { builder.Append(bytes[i].ToString("x2")); } return builder.ToString(); } } private byte[] GetSignatureKey(string key, string date, string regionName, string serviceName) { byte[] kSecret = Encoding.UTF8.GetBytes("AWS4" + key); byte[] kDate = HashHMAC(Encoding.UTF8.GetBytes(date), kSecret); byte[] kRegion = HashHMAC(Encoding.UTF8.GetBytes(regionName), kDate); byte[] kService = HashHMAC(Encoding.UTF8.GetBytes(serviceName), kRegion); byte[] kSigning = HashHMAC(Encoding.UTF8.GetBytes(aws4Request), kService); return kSigning; } private byte[] HashHMAC(byte[] data, byte[] key) { using (HMACSHA256 hmac = new HMACSHA256(key)) { return hmac.ComputeHash(data); } } private string GetTimeStamp() { return DateTime.UtcNow.ToString("yyyyMMdd\\THHmmss\\Z"); } private string GetDate() { return DateTime.UtcNow.ToString("yyyyMMdd"); } } }
问题分析与修复方案
1. Header重复与格式错误
你在AwsV4类中手动添加了content-type、host等header,之后又通过StringContent再次设置content-type,同时host会由HttpClient自动处理,重复设置会导致签名验证失败。
- 修复:移除
AwsV4中添加的content-encoding、content-type、hostheader,只保留x-amz-target。
2. Canonical Request的Header排序问题
AWS V4签名要求Canonical Request中的header必须按小写字母顺序排序,且header名称需转为小写。你的代码直接遍历未排序的原字典,且未转小写,会导致签名计算错误。
- 修复:在
PrepareCanonicalRequest中使用SortedDictionary排序header,并将header名称转为小写:
private string PrepareCanonicalRequest() { StringBuilder canonicalURL = new StringBuilder(); canonicalURL.Append(httpMethodName).Append("\n"); canonicalURL.Append(path).Append("\n").Append("\n"); // 按小写排序header var sortedHeaders = new SortedDictionary<string, string>(awsHeaders, StringComparer.OrdinalIgnoreCase); string signedHeaders = ""; foreach (var header in sortedHeaders) { string lowerKey = header.Key.ToLowerInvariant(); signedHeaders += lowerKey + ";"; canonicalURL.Append(lowerKey).Append(":").Append(header.Value.Trim()).Append("\n"); } canonicalURL.Append("\n"); strSignedHeader = signedHeaders.TrimEnd(';'); canonicalURL.Append(strSignedHeader).Append("\n"); canonicalURL.Append(GenerateHex(payload)); return canonicalURL.ToString(); }
3. 时间戳一致性问题
BuildAuthorizationString中重新调用GetDate(),可能和构造函数中生成的currentDate时间不一致(跨秒场景),导致签名验证失败。
- 修复:直接使用构造函数中已保存的
currentDate变量:
private string BuildAuthorizationString(string strSignature) { return $"{HMACAlgorithm} Credential={accessKey}/{currentDate}/{regionName}/{serviceName}/{aws4Request},SignedHeaders={strSignedHeader},Signature={strSignature}"; }
4. Payload手动拼接风险
手动拼接JSON容易出现格式错误,建议用序列化生成:
// 定义请求实体类 public class SearchItemRequest { public string Keywords { get; set; } public string PartnerTag { get; set; } public string PartnerType { get; set; } public string Marketplace { get; set; } } // 在Main方法中生成Payload var requestBody = new SearchItemRequest { Keywords = "notebook", PartnerTag = "x0d-20", PartnerType = "Associates", Marketplace = "www.amazon.com" }; string payload = System.Text.Json.JsonSerializer.Serialize(requestBody);
额外注意事项
- 确保Access Key、Secret Key拥有Product Advertising API的权限,且PartnerTag(关联ID)有效。
- 确认Region与Marketplace匹配(比如
us-east-1对应www.amazon.com)。 - 控制请求频率,避免触发Amazon限流机制。
内容的提问来源于stack exchange,提问作者QuietWorld
相关产品推荐
相关产品推荐

