require 'open-uri' require 'csv' require 'date' require 'nokogiri' module YQuotes COOKIE_URL = 'https://finance.yahoo.com/quote/AAPL/history?p=AAPL' CRUMB_PATTERN = /\"CrumbStore\":{\"crumb\":\"(?[^"]+)/ QUOTE_ENDPOINT = "https://query1.finance.yahoo.com/v7/finance/download/%{symbol}?" class Yahoo # Get cookie and crumb def initialize fetch_credentials end # fetch_csv: fetch historical quotes in csv format def fetch_csv(ticker, start_date=nil, end_date=nil, period='d') connection = nil # retry 3-times in case it sends unauthorized 3.times do |i| begin url = build_url(ticker, start_date, end_date, period) connection = open(url, 'Cookie' => @cookie) break rescue OpenURI::HTTPError => e fetch_credentials end end data = CSV.parse(connection.read, :converters => :numeric) raise "Yahoo.fetch_csv unable to fetch data" unless data.is_a? Array return data end alias_method :get_csv, :fetch_csv alias_method :get_data, :fetch_csv private def fetch_credentials # get cookie page = open(COOKIE_URL) @cookie = page.meta['set-cookie'].split('; ', 2).first # get crumb scripts = Nokogiri::HTML(page).css('script') scripts.each do |s| if s.text.include? 'CrumbStore' pattern = s.text.match(CRUMB_PATTERN) @crumb = pattern['crumb'] break end end end # build_params: build parameters for get query def build_url(ticker, start_date=nil, end_date=nil, period='d') url = QUOTE_ENDPOINT url = url %{:symbol => URI.escape(ticker.upcase)} params = { :crumb => URI.escape(@crumb), :events => 'history', :interval => '1d' } # sanitize date params[:period1] = get_date(start_date).to_i unless start_date.nil? params[:period2] = get_date(end_date).to_i unless end_date.nil? params[:interval] = "1d" if period == "d" params[:interval] = "1mo" if period == "m" params[:interval] = "1wk" if period == "w" url + "#{params.map { |k,v| "#{k}=#{v}" }.join("&")}" end # get_date: get date from String def get_date(d) return nil if d.nil? return d.to_time if d.is_a? DateTime if d.is_a? String begin dt = DateTime.parse(d).to_time rescue Exception => e raise "invalid param #{d} - date should be in yyyy-mm-dd format" end end end end end