user255580
user255580

Reputation: 49

Trying to use open-uri in ruby, some HTML contents are coming in as "Loading..."

I am trying to create a program to compare a specific thing on a webpage, and then compare it another time, I'm currently working on getting the piece of information that will change. But, the text that would change appears if I inspect element in the page, but not if I use open-uri, it comes in as "Loading..." (see picture), is there a way to get all the HTML text?

Picture here.

This is the current code I have

contents  = open('https://www.cargurus.com/Cars/l-Used-Mazda-MAZDASPEED6-d841', &:read)

File.open("testing.txt", "w") do |line|
line.puts "\r" + "#{contents}"
end

Any help to get the Loading... to change to the actual HTML code would be amazing.

Thanks

Upvotes: 1

Views: 1992

Answers (2)

Joshua Cheek
Joshua Cheek

Reputation: 31726

The problem

So, open uri just makes HTTP requests and gives you access to the body. In this case, the body is html. That html has a placeholder for this data, which is what you're seeing. Then that html says to load up some javascript that will make another request to the server to get the data, and when the data comes in, it will replace the placeholder with the real data. So, to handle this, you ultimately need whatever is coming back from that request the javascript is making.

Three solutions

Ordered from my least favourite to my most favourite.

  1. You can try to evaluate the JavaScript to have it operate on the html. This is going to be painful, so I don't recommend it, but if you wanted to go down that path, I think there's a gem called "the ruby racer" or something (IIRC, it wraps v8).
  2. You can launch a web browser, let the browser handle all the cray cray, and then ask the browser for the html after it's been updated. This is what Rahul's solution does, and it's a really nice solution. It's not my favourite because it's pretty heavy and you're relegated to information displayed in the html. This is called "scraping", and it's pretty fragile (some designer moves something around the page and your script breaks), and the information is in human presentation format, which means you usually have to do a lot of little parsing things.
  3. You can open your browser's devtools, go to the network tab, filter to the XHR requests, and reload the page. One of these made the request to get the data that was used to fill in the place holder. Figure out which one it is and then you can make that request yourself. There's ways this can be fragile, too, eg sometimes you have to have the right cookies, and you often have to experiment with what the browser sent to figure out how much of it you need (usually it's way less than was sent, which is true for your case). Protip: When you do this, separate requesting the data from parsing and exploring it (ie save it to a file and then, while looking through the data, get it from the file rather than making a new request every time... this way it won't change on you and you won't get rate limited)

Solution #3

So, I was curious and went ahead and tried solution number 3 myself, and it worked pretty admirably, check it out:

require 'uri'
require 'net/http'

# build a post request to the URL that the page got the data from
uri = URI 'https://www.cargurus.com/Cars/inventorylisting/ajaxFetchSubsetInventoryListing.action?sourceContext=untrackedExternal_true_0'
req = Net::HTTP::Post.new(uri)

# set some headers
req['origin']        = 'https://www.cargurus.com' # for cross origin requests
req['cache-control'] = 'no-cache'                 # no caching, just in case,
req['pragma']        = 'no-cache'                 # we prob don't want stale data

# looks like you can pass it an awful lot of filters to use
req.set_form_data(
  "page"=>"1", "zip"=>"", "address"=>"", "latitude"=>"", "longitude"=>"",
  "distance"=>"100", "selectedEntity"=>"d841", "transmission"=>"ANY",
  "entitySelectingHelper.selectedEntity2"=>"", "minPrice"=>"", "maxPrice"=>"",
  "minMileage"=>"", "maxMileage"=>"", "bodyTypeGroup"=>"", "serviceProvider"=>"",
  "filterBySourcesString"=>"", "filterFeaturedBySourcesString"=>"",
  "displayFeaturedListings"=>"true", "searchSeoPageType"=>"",
  "inventorySearchWidgetType"=>"AUTO", "allYearsForTrimName"=>"false",
  "daysOnMarketMin"=>"", "daysOnMarketMax"=>"", "vehicleDamageCategoriesRaw"=>"",
  "minCo2Emission"=>"", "maxCo2Emission"=>"", "vatOnly"=>"false",
  "minEngineDisplacement"=>"", "maxEngineDisplacement"=>"", "minMpg"=>"",
  "maxMpg"=>"", "minEnginePower"=>"", "maxEnginePower"=>"", "isRecentSearchView"=>"false"
)

# make the request (200 means it worked)
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request req }
res.code # => "200"

# parse the response
require 'json'
json = JSON.parse res.body

# we're on page 1 of 1, and there are 48 results on this page
json['page']                   # => 1
json['listings'].size          # => 48
json['remainingResults']       # => false

# apparently we're looking at some sort of car or smth
json['modelId']                # => "d841"
json['modelName']              # => "Mazda MAZDASPEED6"

# a bunch of places sell this car
json['sellers'].size           # => 47
json['sellers'][0]['location'] # => "Portland OR, 97217"

# the first of our 48 cars seems to be a deal
listing = json['listings'][0]
listing['mainPictureUrl']        # => "https://static.cargurus.com/images/forsale/2018/05/24/02/58/2006_mazda_mazdaspeed6-pic-61663369386257285-152x114.jpeg"
listing['expectedPriceString']   # => "$8,972"
listing['priceString']           # => "$6,890"
listing['daysOnMarket']          # => 61
listing['savingsRecommendation'] # => "Good Deal"
listing['carYear']               # => 2006
listing['mileageString']         # => "81,803"

# none of the 48 are salvaged or lemons
json['listings'].count { |l| l['lemon'] }   # => 0
json['listings'].count { |l| l['salvage'] } # => 0

# the savings recommendations seem reasonably distributed
json['listings'].group_by { |l| l["savingsRecommendation"] }.map { |rec, ls| [rec, ls.size] }
# => [["Good Deal", 4],
#     ["Fair Deal", 11],
#     ["No Price Analysis", 23],
#     ["High Price", 8],
#     ["Overpriced", 2]]

Upvotes: 3

rahul mishra
rahul mishra

Reputation: 1470

Your web page contains ajax request and open-uri only returns server-side page, it not wait for ajax request

You can use the below code which waits for page loading

#load the libraries 
require 'watir'
browser = Watir::Browser.new
browser.goto "https://www.cargurus.com/Cars/l-Used-Mazda-MAZDASPEED6-d841"
# giving some time for website to load
sleep 2
puts browser.html

NOTE: you need chromedriver to use the script http://chromedriver.chromium.org/downloads if you don't want to open url in browser then you can use headless-WebKit

Upvotes: 1

Related Questions