This is the code for my basic implementation of the Amazon Product Advertising API.
Update 9/2016: Switched to using ASIN due to too many conflicts in ISBN/EISBN numbers. Both methods are provided below.
When I finish each book, I add its ASIN or ISBN-10 number to my PHP script. The script then performs an ItemLookup request against Amazon’s Godzilla-sized products database, and Amazon returns each book’s information in XML format, and finally my script iterates over the results and prints them in a simple table.
In order to run an ItemLookup you need to set up an Amazon Associates account and then register to use the Product Advertising API. Requests and results are exchanged here via REST (the onca/xml URI), although Amazon also supports SOAP (onca/soap) for SOAP/WSDL environments.
PHP’s basic built-in SimpleXML library works fine for processing the results, although you can certainly use DOMDocument or an equivalent; both SimpleXML and DOMDocument are based on libxml so their behavior is similar. Finally, it’s worth reading over the documentation regarding request limits to understand how Amazon throttles incoming traffic.
Performance and Accuracy Concerns
Rather than performing a separate request for each item you want to display, it’s better to consolidate your requests into batch jobs or requests for multiple item IDs. Here I’m performing up to 10 (the maximum allowed) ItemLookup operations per request. Since this cuts the number of requests by up to 90%, your product page will load much faster, and you’re much less likely to see Amazon’s 503 errors caused by overrunning the per-second request limit.
Another issue here is that querying by ISBN sometimes returns multiple editions of a given book, including Kindle editions, so that instead of the expected 10 results you may receive dozens. Kindle editions with blank ISBN fields are included by default, so if you don’t want the duplicates you need to exclude them somehow. One workaround for this is to query by Amazon’s unique identifier (ASIN).
Another workaround is to filter the returned XML to eliminate
Here’s some sample XML returned by an ItemLookup:
<?xml version="1.0" ?> <ItemLookupResponse xmlns="http://webservices.amazon.com/AWSECommerceService/2011-08-01"> <OperationRequest> <HTTPHeaders> <Header Name="UserAgent" Value="Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36"></Header> </HTTPHeaders> <RequestId>aff5de19-dbac-44f8-84d9-b901111f7f2f</RequestId> <Arguments> <Argument Name="AWSAccessKeyId" Value="ackThbbbt"></Argument> <Argument Name="AssociateTag" Value="geofstra-20"></Argument> <Argument Name="IdType" Value="ISBN"></Argument> <Argument Name="ItemId" Value="0802779239"></Argument> <Argument Name="Operation" Value="ItemLookup"></Argument> <Argument Name="ResponseGroup" Value="Images,ItemAttributes,Offers"></Argument> <Argument Name="SearchIndex" Value="Books"></Argument> <Argument Name="Service" Value="AWSECommerceService"></Argument> <Argument Name="Timestamp" Value="2016-03-29T23:45:28.000Z"></Argument> <Argument Name="Signature" Value="ackThbbbt"></Argument> </Arguments> <RequestProcessingTime>0.0327330000000000</RequestProcessingTime> </OperationRequest> <Items> <Request> <IsValid>True</IsValid> <ItemLookupRequest> <IdType>ISBN</IdType> <ItemId>0802779239</ItemId> <ResponseGroup>Images</ResponseGroup> <ResponseGroup>ItemAttributes</ResponseGroup> <ResponseGroup>Offers</ResponseGroup> <SearchIndex>Books</SearchIndex> <VariationPage>All</VariationPage> </ItemLookupRequest> </Request> <Item> <ASIN>0802779239</ASIN> <DetailPageURL>http://www.amazon.com/Maos-Great-Famine-Devastating-Catastrophe/dp/0802779239%3FSubscriptionId%3DAKIAIBUBR6FVJSMAIMMA%26tag%3Dgeofstra-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D165953%26creativeASIN%3D0802779239</DetailPageURL> <ItemLinks> <ItemLink> <Description>Technical Details</Description> <URL>http://www.amazon.com/Maos-Great-Famine-Devastating-Catastrophe/dp/tech-data/0802779239%3FSubscriptionId%3DAKIAIBUBR6FVJSMAIMMA%26tag%3Dgeofstra-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D386001%26creativeASIN%3D0802779239</URL> </ItemLink> etc.
Here’s the PHP for querying/rendering by ASIN:
<?php $booksarray = array( array("0375719334","2/2016",4), // Counter-Clock array("0394856228","2/2016",3), // Hitchcock Supernatural array("1857988809","1/2016",3), // Second Variety array("0806511532","1/2016",3), // Short Happy Life Brown Oxford array("0806513284","1/2016",4), // Eye of the Sibyl array("0806512768","1/2016",3), // Collected Stories of PKD Vol 4 array("B001BF44PA","1/2016",3), // PKD Reader array("B008P96TI0","1/2016",4) // Collected Stories of PKD ); $querystrings = array(''); $params = array(); $pairs = array(); $signed_urls = array(); $returned_asins = array(); // Your AWS Access Key ID, as taken from the AWS Your Account page $aws_access_key_id = "ackThbbtt"; // Your AWS Secret Key corresponding to the above ID, as taken from the AWS Your Account page $aws_secret_key = "double-secret-ackThbbtt"; // The region you are interested in $endpoint = "webservices.amazon.com"; $uri = "/onca/xml"; // Build an array of ASINs, in sets of 10, as strings with comma separators $j = 0; for ($i = 0; $i < count($booksarray); $i++) { if ($i > 0 && $i % 10 == 0) { // Element index is divisible by 10, so we need a new array element array_push($querystrings,''); // get rid of undefined offset warning $j++; } $querystrings[$j] .= $booksarray[$i][0].','; } // Build query strings using comma-separated $querystrings for ($k = 0; $k < count($querystrings); $k++) { array_push($params, array( "Service" => "AWSECommerceService", "Operation" => "ItemLookup", "AWSAccessKeyId" => $aws_access_key_id, "AssociateTag" => "geofstra-20", "ItemId" => rtrim($querystrings[$k],','), "IdType" => "ASIN", "ResponseGroup" => "Images,ItemAttributes", ) ); } //print_r($params); // Sign each query string foreach ($params as $param) { // Set current timestamp if not set if (!isset($param["Timestamp"])) { $param["Timestamp"] = gmdate('Y-m-d\TH:i:s\Z'); } // Sort the parameters by key ksort($param); $pairs = array(); foreach ($param as $key => $value) { array_push($pairs, rawurlencode($key)."=".rawurlencode($value)); } // Generate the canonical query $canonical_query_string = join("&", $pairs); // Generate the string to be signed $string_to_sign = "GET\n".$endpoint."\n".$uri."\n".$canonical_query_string; // Generate the signature required by the Product Advertising API $signature = base64_encode(hash_hmac("sha256", $string_to_sign, $aws_secret_key, true)); // Generate the signed URL $request_url = 'http://'.$endpoint.$uri.'?'.$canonical_query_string.'&Signature='.rawurlencode($signature); //echo "<p>Signed URL: \"".$request_url."\"</p>"; array_push($signed_urls, $request_url); } echo '<table class="booklist">'; echo '<tr>'; echo '<th class="bookdetails">Title/Author/Publisher/ISBN</th>'; echo '<th>Buy</th>'; echo '<th>Pages</th>'; echo '<th>Date Completed</th>'; echo '<th>Rating (of 5)</th>'; echo '</tr>'; // Iterate over the <item> tags in each set of 10 results, print non-duplicates $j = 0; // counter to keep track of valid items foreach ($signed_urls as $signed_url) { $xmldoc = file_get_contents("$signed_url"); $xml = new SimpleXMLElement($xmldoc); $count = $xml->Items->Item->count(); for ($i = 0; $i < $count; $i++) { $cur_isbn = ""; // Cast this to string, otherwise you get an array of SimpleXML objects $cur_asin = (string) $xml->Items->Item[$i]->ASIN; //echo $cur_asin . " "; // If ASIN isn't blank or duplicate, print the book's book's details if ($cur_asin && !(in_array($cur_asin, $returned_asins))) { // Determine ISBN, if possible if (empty($xml->Items->Item[$i]->ItemAttributes->ISBN)) { $cur_isbn = $xml->Items->Item[$i]->ItemAttributes->EISBN; } else { $cur_isbn = $xml->Items->Item[$i]->ItemAttributes->ISBN; } echo '<tr>'; echo '<td class="booktitle"><strong>Title: </strong>'.$xml->Items->Item[$i]->ItemAttributes->Title; echo '<br /><strong>Author: </strong>'.$xml->Items->Item[$i]->ItemAttributes->Author; echo '<br /><strong>Publisher: </strong>'.$xml->Items->Item[$i]->ItemAttributes->Publisher; echo '<br /><strong>ISBN: </strong>'.$cur_isbn.'</td>'; echo '<td><a href="'.$xml->Items->Item[$i]->DetailPageURL.'">'. '<img src="'.$xml->Items->Item[$i]->MediumImage->URL.'" /></a></td>'; echo '<td class="pages">'.$xml->Items->Item[$i]->ItemAttributes->NumberOfPages.'</td>'; echo '<td class="date">'.$booksarray[$j][1].'</td>'; echo '<td class="rating">'.$booksarray[$j][2].'</td>'; echo '</tr>'; array_push($returned_asins, $cur_asin); $j++; } } } ?>
And here’s the PHP I use for rendering by ISBN-10:
<?php $booksarray = array( array("014018726X","3/2016",2.5), array("0307450767","2/2016",3), array("1560255021","2/2016",4), array("0375719334","2/2016",4), array("0394826760","2/2016",3), array("1857988809","1/2016",3), array("0806511532","1/2016",3), array("0806513284","1/2016",4), array("0806512768","1/2016",3), array("0806518561","1/2016",3), array("0375421513","1/2016",4) ); $querystrings = array(''); $params = array(); $pairs = array(); $signed_urls = array(); $returned_asins = array(); // Your AWS Access Key ID, as taken from the AWS Your Account page $aws_access_key_id = "ackThbbtt"; // Your AWS Secret Key corresponding to the above ID, as taken from the AWS Your Account page $aws_secret_key = "double-secret-ackThbbtt"; // The region you are interested in $endpoint = "webservices.amazon.com"; $uri = "/onca/xml"; // Build an array of ISBNs, in sets of 10, as strings with comma separators $j = 0; for ($i = 0; $i < count($booksarray); $i++) { if ($i > 0 && $i % 10 == 0) { // Element index is divisible by 10, so we need a new array element array_push($querystrings,''); // get rid of undefined offset warning $j++; } $querystrings[$j] .= $booksarray[$i][0].','; } // Build query strings using comma-separated $querystrings for ($i = 0; $i < count($querystrings); $i++) { array_push($params, array( "Service" => "AWSECommerceService", "Operation" => "ItemLookup", "AWSAccessKeyId" => $aws_access_key_id, "AssociateTag" => "geofstra-20", "ItemId" => rtrim($querystrings[$i],','), "IdType" => "ISBN", "ResponseGroup" => "Images,ItemAttributes", "SearchIndex" => "Books" ) ); } // Sign each query string foreach ($params as $param) { // Set current timestamp if not set if (!isset($param["Timestamp"])) { $param["Timestamp"] = gmdate('Y-m-d\TH:i:s\Z'); } // Sort the parameters by key ksort($param); foreach ($param as $key => $value) { array_push($pairs, rawurlencode($key)."=".rawurlencode($value)); } // Generate the canonical query $canonical_query_string = join("&", $pairs); // Generate the string to be signed $string_to_sign = 'GET\n'.$endpoint.'\n'.$uri.'\n'.$canonical_query_string; // Generate the signature required by the Product Advertising API $signature = base64_encode(hash_hmac('sha256', $string_to_sign, $aws_secret_key, true)); // Generate the signed URL $request_url = 'http://'.$endpoint.$uri.'?'.$canonical_query_string.'&Signature='.rawurlencode($signature); //echo '<p>Signed URL: '.$request_url.'</p>'; array_push($signed_urls, $request_url); } echo '<table class="booklist">'; echo '<tr>'; echo '<th class="bookdetails">Title/Author/Publisher/ISBN</th>'; echo '<th>Buy</th>'; echo '<th>Pages</th>'; echo '<th>Date Completed</th>'; echo '<th>Rating (of 5)</th>'; echo '</tr>'; // Iterate over the <item> tags in each set of 10 results, print non-duplicates $j = 0; // Counter to keep track of valid items foreach ($signed_urls as $signed_url) { $xmldoc = file_get_contents("$signed_url"); $xml = new SimpleXMLElement($xmldoc); $count = $xml->Items->Item->count(); for ($i = 0; $i < $count; $i++) { // Cast this to string, otherwise you get an array of SimpleXML objects $cur_isbn = (string) $xml->Items->Item[$i]->ItemAttributes->ISBN; // If ISBN isn't blank or duplicate, print the book's details if ($cur_isbn && !(in_array($cur_isbn, $returned_isbns))) { echo '<tr>'; echo '<td class="booktitle"><strong>Title: </strong>'.$xml->Items->Item[$i]->ItemAttributes->Title; echo '<br /><strong>Author: </strong>'.$xml->Items->Item[$i]->ItemAttributes->Author; echo '<br /><strong>Publisher: </strong>'.$xml->Items->Item[$i]->ItemAttributes->Publisher; echo '<br /><strong>ISBN: </strong>'.$xml->Items->Item[$i]->ItemAttributes->ISBN.'</td>'; echo '<td><a href="'.$xml->Items->Item[$i]->DetailPageURL.'">'. '<img src="'.$xml->Items->Item[$i]->MediumImage->URL.'" /></a></td>'; echo '<td class="pages">'.$xml->Items->Item[$i]->ItemAttributes->NumberOfPages.'</td>'; echo '<td class="date">'.$booksarray[$j][1].'</td>'; echo '<td class="rating">'.$booksarray[$j][2].'</td>'; echo '</tr>'; array_push($returned_isbns, $cur_isbn); $j++; } } } ?>
Image may be NSFW.
Clik here to view.
The post PHP: Implementing the Amazon Product Advertising API first appeared on GeoffStratton.com.