The Amazon Review Tabulator - TART

Lists all of your reviews with vote and comment tallies

目前为 2016-10-30 提交的版本。查看 最新版本

/*=====================================================================================*\
|  The Amazon Review Tabulator - TART                                                   |
|      (c) 2016 by Another Floyd                                                        |
|  From your "Public Reviews Written by You" page on Amazon, this script collects and   |
|  tabulates vote tallies and related information, from all of your Amazon reviews.     |
\*=====================================================================================*/

// ==UserScript==
// @name           The Amazon Review Tabulator - TART
// @namespace      floyd.scripts
// @version        1.0.1
// @author         Another Floyd at Amazon.com
// @description    Lists all of your reviews with vote and comment tallies
// @include        https://www.amazon.com/gp/cdp/member-reviews/*
// @grant          GM_getValue
// @grant          GM_setValue
// @grant          GM_xmlhttpRequest
// @grant          GM_log
// ==/UserScript==

// Start

(function() {

var userId = "";
var reviewCount = 0;
var reviewerRanking = "";
var helpfulVotes = 0;
var urlStart = "";
var urlEnd = "";
var displayBuffer = "";

var oldStoreItemIDs = [];
var oldStoreUpvotes = [];
var oldStoreDownvotes = [];
var oldStoreComments = [];

// if I ever get this to open in a new window, instead of overwriting the Amazon
// window, items below should be cleared at start of tabulate(), so that repeated runs
// will work without error

var newStoreItemIDs = "";
var newStoreUpvotes = "";
var newStoreDownvotes = "";
var newStoreComments = "";

var tallyUpvotes = 0;
var tallyDownvotes = 0;
var tallyStars = 0;
var tallyComments = 0;


function tabulate() {

	// set up top of display page
    displayBuffer += "<style type='text/css'> \
    .tg  {border-collapse:collapse;border-spacing:0} \
    .tg td{font-family:Arial, sans-serif;font-size:12px;padding:10px 5px;border-style:solid;border-width:1px;overflow:hidden;word-break:normal} \
    .tg th{font-family:Arial, sans-serif;font-size:12px;padding:10px 5px;border-style:solid;border-width:1px;overflow:hidden;word-break:normal} \
    .tg .header-left{font-weight:bold;background-color:#010066;color:#ffffff;text-align:left} \
    .tg .header-right{font-weight:bold;background-color:#010066;color:#ffffff;text-align:right} \
    .tg .cell-left{text-align:left} \
    .tg .cell-right{text-align:right} \
    .tg .hilite-left{text-align:left;background-color:#FFFF12} \
    .tg .hilite-right{text-align:right;background-color:#FFFF12} \
    </style> \
	<span style='font-size:16px;font-weight:bold'>Amazon Review Details</span><br> \
	<span style='font-size:8px'>Prepared with The Amazon Review Tabulator - TART</span> \
	<p>Top Reviewer Ranking: " + reviewerRanking + "<br> \
	Reviews Available: " + reviewCount + "<br> \
	Helpful Votes: " + helpfulVotes + "<br> \
	Upvote/Review Ratio: " + (helpfulVotes/reviewCount).toFixed(2) + "<br></p> \
	<table class='tg'> \
    <tr><th class='header-left'>#</th><th class='header-left'>Item</th><th class='header-left'>Date</th><th class='header-right'>Stars<br></th><th class='header-right'>Upvotes</th><th class='header-right'>Downvotes</th><th class='header-right'>% Helpful<br></th><th class='header-right'>Comments</th></tr>";
	
	// read in stored info from past run, for use in change detection
	oldStoreItemIDs = GM_getValue("recentItemIDs", "").split(" ");
	oldStoreUpvotes = GM_getValue("recentUpvotes", "").split(" ");
	oldStoreDownvotes = GM_getValue("recentDownvotes", "").split(" ");
	oldStoreComments = GM_getValue("recentComments", "").split(" ");

	// prepare url with user ID, ready for review page number
    urlStart = "https://www.amazon.com/gp/cdp/member-reviews/" + userID + "?ie=UTF8&display=public&page=";
    urlEnd = "&sort_by=MostRecentReview";

	// lots of page loading and data retrieval
    var perPageResponseDiv = [];
    var pageResponseCount = 0;
	var reviewsProcessed = 0;
    var pageCount = Math.floor(reviewCount / 10) + ((reviewCount % 10 > 0) ? 1 : 0);
    //var pageCount = 2; // for testing
   
    var x = 1;
    while (x <= pageCount) {
       (function(x){
			var urlComplete = urlStart + x + urlEnd;
			perPageResponseDiv[x] = document.createElement('div');

			GM_xmlhttpRequest({
				 method: "GET",
				 url: urlComplete,
				 onload: function(response) {
				   
					perPageResponseDiv[x].innerHTML = response.responseText;					
					pageResponseCount++;

					// see if all data from multiple page loads has arrived
					if(pageResponseCount==pageCount) {
							  
						for(y = 1; y <= pageCount; y++) {
					   
							// get parent of any reviewText DIV
							var findReviews = document.evaluate("//div[@class='reviewText']/..", perPageResponseDiv[y], null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); // evaluating the doc DIV made above

							 for (var j = 0; j < findReviews.snapshotLength; j++) {
						   
								var oneReview = findReviews.snapshotItem(j);
								var reviewChildren = oneReview.children;
								var childCount = reviewChildren.length;

								var commentCount = 0;
								var itemTitle = "";
								var itemLink = "";
								var permaLink = "";
								var starRating = 0;
								var reviewDate = "";
								var upVotes = 0;
								var downVotes = 0;
								var itemID = "";
							   
								// get number of comments, and permalink
								var tempText = reviewChildren[childCount-2].textContent;
								if(tempText.indexOf('Comment (') > -1 || tempText.indexOf('Comments (') > -1) {
									var paren1 = tempText.indexOf('(');
									var paren2 = tempText.indexOf(')');
									commentCount = tempText.substring(paren1+2,paren2-1);
									}
								var lst = reviewChildren[childCount-2].getElementsByTagName('a');
								permaLink = lst[2].getAttribute("href");
							   
								// get item title and item link
								var lst = reviewChildren[childCount-4].getElementsByTagName('a');
								itemLink = lst[0].getAttribute("href");
								itemTitle = lst[0].textContent;
							   
								// the top few items to retrieve are done in a loop with IF checks,
								// because the number of entries above the review varies, depending on
								// whether there have been votes, if it's a verified purchase, etc.
								for (var i = childCount - 5; i > -1; i--) {

									// get star rating AND review date
									var childHTML = reviewChildren[i].innerHTML;
									var ratingClue = childHTML.indexOf('out of 5 stars');
									if(ratingClue > -1) {
										starRating = childHTML.substring(ratingClue-4,ratingClue-1);
										reviewDate = reviewChildren[i].lastElementChild.textContent;
									}               
								   
									// get vote counts
									var childText = reviewChildren[i].textContent;
									var voteClue = childText.indexOf('people found the following review helpful');
									if(voteClue > -1) {
										var list = childText.trim().split(" "); // there were extra, invisible spaces!
									   
										upVotes = list[0];
										var totalVotes = list[2];
										downVotes = totalVotes - upVotes;
									}                           
								}
							   
								// get item ID
								var lst = oneReview.parentNode.getElementsByTagName('a');
								itemID = lst[0].getAttribute("name");

								// get HTML formatted table row
								displayBuffer += prepOneTableRow((j+1+(y-1)*10),itemID,itemTitle,permaLink,reviewDate,starRating,upVotes,downVotes,commentCount);
								
								reviewsProcessed++;

							}
						}
						
						// add footer and complete results page
						displayBuffer += "<tr><td class='header-left'></td><td class='header-left'></td><td class='header-left'></td><td class='header-right'>" + (tallyStars/reviewsProcessed).toFixed(1) + "</td><td class='header-right'>" + tallyUpvotes + "</td><td class='header-right'>" + tallyDownvotes + "</td><td class='header-right'>" + helpfulPercent(tallyUpvotes,tallyDownvotes) + "</td><td class='header-right'>" + tallyComments + "</td></tr></table>";
						
						// store info to be used in subsequent run, for change detection
						GM_setValue("recentItemIDs", newStoreItemIDs.trim());
						GM_setValue("recentUpvotes", newStoreUpvotes.trim());
						GM_setValue("recentDownvotes", newStoreDownvotes.trim());
						GM_setValue("recentComments", newStoreComments.trim());
						
						// display results page
						document.body.innerHTML = displayBuffer;
						}
					}
				});
			})(x);
		x++;
	}   
}

function helpfulPercent(upVotes,downVotes) {
	var helpfulPercent = "";
	upVotes = parseInt(upVotes);
	downVotes = parseInt(downVotes);
	if(upVotes + downVotes > 0) helpfulPercent = (upVotes/(upVotes+downVotes)*100).toFixed(1);

return helpfulPercent;
}

function prepOneTableRow (row,itemID,itemTitle,permaLink,reviewDate,starRating,upVotes,downVotes,commentCount) {
	
	// do these before mangling the values with <b> tags </b>
	var helpfulPct = helpfulPercent(upVotes,downVotes); 
	itemTitle = "<a href='" + permaLink + "' target='_new'>" + itemTitle.substring(0,65) + "</a>";
	
	// keep tallies to use in table footer
	tallyUpvotes += parseInt(upVotes);
	tallyDownvotes += parseInt(downVotes);
	tallyStars += parseInt(starRating);
	tallyComments += parseInt(commentCount);
	
	// assemble storage info, to use in subsequent run, for change detection
	newStoreItemIDs += itemID + " ";
	newStoreUpvotes += upVotes + " ";
	newStoreDownvotes += downVotes + " ";
	newStoreComments += commentCount + " ";
	
	// see if review for this item has previously been examined
	var matchIdx = -1;
	for(var i=0; i<oldStoreItemIDs.length; i++) {
		if(oldStoreItemIDs[i] == itemID) {
			// we have a match, an item that has previously been seen
			matchIdx = i;
			break;
		}
	}
			
	var hiliteRow = false;	
	if(matchIdx > -1) {
		// entry exists; see if any of the numbers have changed
		if(oldStoreUpvotes[matchIdx] != upVotes) {
			// for changed number, make it bold, and hilite row
			upVotes = "<b>" + upVotes + "</b>";
			hiliteRow = true;
		}
		if(oldStoreDownvotes[matchIdx] != downVotes) {
			downVotes = "<b>" + downVotes + "</b>";
			hiliteRow = true;
		}
		if(oldStoreComments[matchIdx] != commentCount) {
			commentCount = "<b>" + commentCount + "</b>";
			hiliteRow = true;
		}
	}
	else {
		// no match, so, it's a new review; bold the title and hilite the row
		itemTitle = "<b>" + itemTitle + "</b>";
		hiliteRow = true;		
	}

	var tdLeft = ((hiliteRow===true)? "<td class='hilite-left'>" : "<td class='cell-left'>");
	var tdRight = ((hiliteRow===true)? "<td class='hilite-right'>" : "<td class='cell-right'>");
	
	var tableRow = "<tr>" + tdLeft + row + "</td>" + tdLeft + itemTitle + "</td>" + tdLeft + reviewDate + "</td>" + tdRight + starRating + "</td>" + tdRight + upVotes + "</td>" + tdRight + downVotes + "</td>" + tdRight + helpfulPct + "</td>" + tdRight + commentCount + "</td></tr>";

return tableRow;
}

//--- global event listeners allow working across scopes

document.addEventListener('click', function(event) {
    var tempstr = new String(event.target);

    if(tempstr.indexOf('tabulate') > -1) {
        tabulate();
        event.stopPropagation();
        event.preventDefault();
    }
}, true);

//--- main script block from this point
   
function Tabulate_Amazon_Reviews_Run() {

    // find profile info panel
    var findDiv = document.evaluate("//div[contains(.,'Helpful Votes')]", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
    var profileDiv = findDiv.snapshotItem(0);
   
    // get reviewer ranking and user ID
    var lst = profileDiv.getElementsByTagName('a');
    reviewerRanking = lst[0].textContent;
    var charIdx = lst[0].getAttribute("href").indexOf('#');
    userID = lst[0].getAttribute("href").substring(charIdx+1);

    // get helpful votes
    charIdx = profileDiv.textContent.lastIndexOf(':');
    helpfulVotes = profileDiv.textContent.substring(charIdx+2);
   
    // get review count
    var prevSibDiv = profileDiv.previousElementSibling;
    charIdx = prevSibDiv.textContent.lastIndexOf(':');
    reviewCount = prevSibDiv.textContent.substring(charIdx+2);
       
    // add Tabulate link
    profileDiv.innerHTML += "<br></br><a href='javascript:tabulate();'>Tabulate</a>";
	
}
   
Tabulate_Amazon_Reviews_Run();

})();
// End

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址