Search code examples
ruby-on-railsmechanize

mechanize with google autocomplete


Mechanize do not find forms on this page. So I am trying to fill in via input. The problem is that the form is Google autocomplete. And firstly I need to fill the input and then select a city from dropdown. So what I have tried is this:

agent = Mechanize.new
page = agent.get("https://www.airbnb.com/host/homes")
location = agent.page.search(".earning-estimation__location-input")
location.at("input")['value'] = 'kiev'
location.at("input")[0].select

And get:

NoMethodError: private method `select' called for nil:NilClass

P.S. Firstly I did not find AirBnB API. So I dive in mechanize. If there is a AirBnB API link is appreciated.


Solution

  • Your question didn't really contain a question, so my best stab at what you're looking to accomplish is the following:

    Given that I don't see much of an action on this page, I assume you are looking to acquire the weekly average rates for various areas.

    You don't actually need to fill out the drop down according to auto-complete. This interaction is being powered by getting the lat/lon from the Google geocoding APIs, and passing this to https://www.airbnb.com/wmpw_data.

    For sites that require javascript to function (like this) you have two options:

    • Reverse engineer the API they use (such as the example below)
    • Use a framework like Selenium / WebDriver to navigate the site with a fully native browser.

    To reverse engineer the API, web debugging proxy tools are invaluable. You can get a lot of information from looking at the browser dev tools "network" tab, but things like "Fiddler", "Charles Proxy", "Burp" etc are invaluable.

    When you inspect the traffic, you'll see the following parameters available to send in your request:

    • page
    • duration
    • person_capacity
    • room_type
    • loading
    • sw_lat
    • sw_lng
    • ne-Lat
    • ne_lng

    You can find some valid values to play with by using things like

    [37] pry(main)> page.css("[data-room-type]").map{|n| n["data-room-type"]}.uniq
    => ["entire_home_apt", "private_room", "shared_room"]
    

    If you set the various lat/lng values to things that suit you, you'll get the weekly average prices back for that area. I notice that "localized_place" is reporting my personal area regardless of the lat/lon change, but the money values are in fact changing, and matching what the site shows. Perhaps that attribute is based on IP location, or there is something amiss.

    While the values do seem to scale with bigger and smaller regions for the sw/ne bounds, you can also use the same lat/lng for both and still get results. It just might not reflect exactly how Google Geocoder references a place -- but it might be enough for your uses.

    Once you have a source for getting your lat/lngs, you can just feed them directly to their API.

    Here's what appears to be a working example:

    require 'mechanize'
    agent = Mechanize.new
    page = agent.get "https://www.airbnb.com/host/homes"
    
    room_types = page.css("[data-room-type]").map{|n| n["data-room-type"]}.uniq
    
    # Values for near Charleston, WV, a random place from Google Maps
    sw_lat = '38.360928'
    sw_lng = '-81.6464767'
    ne_lat = sw_lat
    ne_lng = sw_lng
    duration = '1_week'
    person_capacity = 1
    room_type = room_types.first # => 'entire_home_apt'
    
    url = "https://www.airbnb.com/wmpw_data?page=slash_host&duration=#{duration}&person_capacity=#{person_capacity}&room_type=#{room_type}&loading=false&sw_lat=#{sw_lat}&sw_lng=#{sw_lng}&ne_lat=#{ne_lat}&ne_lng=#{ne_lng}"
    
    money = agent.get(url).body
    
    require 'json'
    JSON.parse(money)["data"]
    # => {"average_income_raw"=>385.0,
    #     "average_income"=>"$385",
    #     "localized_place"=>"xxx",
    #     "list_your_space_link"=>"https://www.airbnb.com/rooms/new",
    #     "earning_estimation_duration"=>"1_week",
    #     "localized_market"=>"Other (International)"}