How to view all ratings in Google Play Store

google-play-store

Is there a way to show all ratings in Play?

You can see some ratings ordered descending:

enter image description here

but for hundreds of thousands of reviews it's not that feasible cause you can only read the 5-star ratings this way.

Is there another better way to view the ratings?

maybe this is possible with Greasemonkey?

Best Answer

Equally frustrated by the lack of decent filtering/sorting options in Google Play, and inspired by your suggestion that a Greasemonkey script could solve the problem, I decided to write one, which I have uploaded to https://greasyfork.org/en/scripts/24667-google-play-review-rating-filter. It adds five checkboxes to app pages on play.google.com which allow you to filter for reviews with specific star ratings. I’ve tested it with Greasemonkey and Unified Script Injector in Firefox, and Tampermonkey in Chrome.

Rather than reproduce the entire script here, I’ll describe the approach taken for those who may be interested. TL;DR: If you just want the solution, install the appropriate browser add-on and download the user script from the link above. Note that if you want to use it on your Android device itself, you’ll probably need to use Firefox with the USI add-on (and also select Request Desktop Site from the menu), as most other Android browsers do not support add-ons or user scripts and Greasemonkey currently does not work in Firefox for Android – it will not work in the Google Play app.

As you flick through reviews, GP (Google Play) loads data for more reviews via AJAX requests to the URL /store/getreviews using the HTTP POST method. So by hooking these AJAX calls, it is possible to modify the data returned to GP.

XMLHttpRequest.prototype.open can be replaced with a function which will call the original, but first, if the request is for review data, modify the XHR (XMLHttpRequest) object so that the POST request body can be captured and the response modified. A send property can be assigned to the XHR object as a function which will store the POST data before calling the original. The onreadystatechange property can be assigned as a function which will modify the response before calling the function assigned by GP to this property. As GP will assign onreadystatechange after this, Object.defineProperty would need to be used to redefine the property so that the value GP sets is stored rather than actually being assigned to the internal property. And as the responseText property is read-only, Object.defineProperty would be needed to change its value.

The data returned by GP is in JSON format, though has a few characters of junk at the start which should be faithfully reproduced in any modified data.

The following code demonstrates this, and will output to the browser’s developer console window the request body and response data (though doesn’t actually modify it):

XMLHttpRequest.prototype.open = (function(open) {
  return function(method, url) {
    if (
      method === 'POST' &&
      url &&
      url.replace(/^https?:\/\/play\.google\.com/, '').split('?', 1)[0] ===
        '/store/getreviews'
    ) {
      var requestBody;
      var orgSend = this.send;
      var orgOnReadyStateChange = this.onreadystatechange;
      this.send = function(data) {
        requestBody = data;
        return orgSend.apply(this, arguments);
      };
      this.onreadystatechange = function() {
        if (this.readyState === XMLHttpRequest.DONE && this.status === 200) {
          var responseText = this.responseText;
          var nJunkChars = responseText.indexOf('[');
          try {
            var jsonData = JSON.parse(
              nJunkChars ? responseText.substr(nJunkChars) : responseText
            );
            // TODO: modify jsonData here
            console.log('Request: %o\nResponse: %o', requestBody, jsonData);
            Object.defineProperty(this, 'responseText', {
              value: responseText.substr(0, nJunkChars) +
                JSON.stringify(jsonData),
              configurable: true,
              enumerable: true
            });
          } catch (e) {
            console && console.log && console.log(e);
          }
        }
        if (orgOnReadyStateChange) {
          return orgOnReadyStateChange.apply(this, arguments);
        }
      };
      Object.defineProperty(this, 'onreadystatechange', {
        get: function() { return orgOnReadyStateChange; },
        set: function(v) { orgOnReadyStateChange = v; },
        configurable: true,
        enumerable: true
      });
    }
    return open.apply(this, arguments);
  };
})(XMLHttpRequest.prototype.open);

The data returned by GP comprises an array of one element which is an array of four elements as follows:

  • The string "ecr";
  • 1 if there are more reviews, 2 if this is the last ‘page’ of reviews, 3 if an error occurred;
  • The HTML containing the ‘page’ of reviews (and any developer replies) – currently 40 reviews are returned per page;
  • The page number, corresponding to the pageNum parameter in the POST request body.

The HTML can be modified to remove reviews (and any associated developer reply) with star ratings other than those of interest. The reviews match the selector div.single-review and have a descendant matching div.current-rating with an inline style where the CSS width property is a percentage corresponding to the rating (20% for 1 star, 40% for 2 stars, etc.). Developer replies match the selector div.developer-reply and are siblings immediately following the review.

Adding checkboxes to the UI to allow choosing which star-ratings of reviews to show is fairly straightforward. However, when their selections are changed, the reviews need to be fetched afresh. Changing the sort order causes this to occur, as does even selecting the same sort order as before. So to achieve this automatically, whenever a checkbox is changed, a click event can be triggered on the currently selected sort order element, which can be found with a selector of .id-review-sort-filter .dropdown-child.selected. When an app page on GP is initially loaded, the first page of reviews are already included and are not loaded via AJAX, but as long as the checkboxes are all initially checked, that won’t matter.

Sometimes a page of (40) reviews won’t contain any with a desired rating. If no elements exist in the returned HTML, GP won’t request any more pages. So to cater for this, it would be necessary to fetch additional pages of reviews (via the same AJAX API, but modifying the pageNum parameter) until there are some reviews to return. And for subsequent pages, the pageNum parameter would need translating to account for this.

When the selected sort order is ‘Rating’, there could be many pages of 5 star reviews before any with a desired rating. Repeatedly fetching and discarding pages and pages of reviews would be inefficient (and could trigger a temporary IP block by Google). In this case, when the reviewSortOrder parameter is 1, a binary search can be employed to much more quickly find the next page with reviews to return. A page element matching the selector span.reviews-num can be inspected to find the total number of reviews and thus determine an upper page number bound. Though, as it turns out currently, requests for pages beyond page 111 receive an HTTP 400 response.