File Coverage

blib/lib/Finance/Quote/YahooWeb.pm
Criterion Covered Total %
statement 26 91 28.5
branch 0 22 0.0
condition n/a
subroutine 10 11 90.9
pod 0 3 0.0
total 36 127 28.3


line stmt bran cond sub pod time code
1             # vi: set noai ic ts=4 sw=4 showmode showmatch:
2             # This program is free software: you can redistribute it and/or modify
3             # it under the terms of the GNU General Public License as published by
4             # the Free Software Foundation, either version 3 of the License, or
5             # (at your option) any later version.
6             #
7             # This program is distributed in the hope that it will be useful,
8             # but WITHOUT ANY WARRANTY; without even the implied warranty of
9             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10             # GNU General Public License for more details.
11             #
12             # You should have received a copy of the GNU General Public License
13             # along with this program. If not, see <http://www.gnu.org/licenses/>.
14              
15             package Finance::Quote::YahooWeb;
16              
17 5     5   2802 use warnings;
  5         15  
  5         181  
18 5     5   33 use strict;
  5         11  
  5         117  
19              
20 5     5   33 use HTTP::Request::Common;
  5         11  
  5         297  
21 5     5   32 use HTML::TableExtract;
  5         10  
  5         32  
22 5     5   210 use HTML::TreeBuilder::XPath;
  5         21  
  5         72  
23 5     5   126 use Text::Template;
  5         13  
  5         262  
24              
25 5     5   41 use constant DEBUG => $ENV{DEBUG};
  5         17  
  5         421  
26 5     5   36 use if DEBUG, 'Smart::Comments';
  5         10  
  5         46  
27              
28             our $VERSION = '1.58'; # VERSION
29              
30             my $URL = Text::Template->new(TYPE => 'STRING', SOURCE => 'https://finance.yahoo.com/quote/{$symbol}/history?p={$symbol}');
31             my $AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36';
32             my $XPATH = Text::Template->new(TYPE => 'STRING', SOURCE => '//*[@data-symbol=~"^{$symbol}$"][@data-field=~"regularMarketPrice"]');
33              
34             sub methods {
35 5     5 0 21 return ( yahooweb => \&yahooweb );
36             }
37              
38             {
39             our @labels =
40             qw/symbol name exchange currency isodate last open high low volume/;
41              
42             sub labels {
43 5     5 0 16 return ( yahooweb => \@labels );
44             }
45             }
46              
47             sub yahooweb {
48 0     0 0   my $quoter = shift;
49              
50 0           my @stocks = @_;
51 0           my ( %info, $url, $reply );
52 0           my $ua = $quoter->user_agent();
53 0           my $agent = $ua->agent();
54 0           $ua->agent($AGENT);
55              
56 0           foreach my $symbol (@stocks) {
57 0           $url = $URL->fill_in(HASH => {symbol => $symbol});
58 0           $reply = $ua->request(GET $url);
59              
60             ### YahooWeb: $url
61 0 0         unless ($reply->is_success) {
62 0           $info{ $symbol, "success" } = 0;
63 0           $info{ $symbol, "errmsg" } = join ' ', $reply->code, $reply->message;
64 0           next;
65             }
66            
67 0           my $tree = HTML::TreeBuilder::XPath->new();
68 0           $tree->ignore_unknown(0);
69 0           $tree->parse($reply->decoded_content);
70              
71 0 0         my ($name, $yahoo_symbol) = map { $_ =~ /^(.+) \(([^)]+)\)/ ? ($1, $2) : () } $tree->findnodes_as_strings('//*[@id="quote-header-info"]//div//h1');
  0            
72            
73 0 0         if (uc($symbol) ne uc($yahoo_symbol)) {
74             ### Error: $symbol, $yahoo_symbol
75 0           $info{ $symbol, "success" } = 0;
76 0           $info{ $symbol, "errmsg" } = 'Unexpected response from Yahoo site';
77 0           next;
78             }
79              
80 0           $info{ $symbol, 'name' } = $name;
81              
82 0 0         my ($exchange, $currency) = map { $_ =~ /^(.+)[.] Currency in (.+)$/ ? ($1, $2) : () } $tree->findnodes_as_strings('//*[@id="quote-header-info"]//div//span');
  0            
83 0           $info{ $symbol, 'exchange' } = $exchange;
84 0 0         if ($currency =~ /^GBp/) {
85 0           $info{ $symbol, 'currency' } = 'GBP';
86             } else {
87 0           $info{ $symbol, 'currency' } = $currency;
88             }
89              
90 0           my $te = HTML::TableExtract->new(
91             headers => ['Date', 'Open', 'High', 'Low', 'Close\*', 'Adj Close\*\*', 'Volume'],
92             attribs => { 'data-test' => "historical-prices" } );
93 0 0         unless ($te->parse($reply->decoded_content)) {
94 0           $info{ $symbol, "success" } = 0;
95 0           $info{ $symbol, "errmsg" } = "YahooWeb - History table not found.";
96 0           next;
97             }
98 0           my $historytable = $te->first_table_found();
99             ### 1st Row: $historytable->row(0)
100 0           my ($month, $day, $year) = $historytable->cell(0,0)
101             =~ m|(\w+) (\d+), (\d{4})|;
102             ### Month: $month
103             ### Day: $day
104             ### Year: $year
105              
106 0           my $last = $historytable->cell(0,4);
107 0           $last =~ s/,//g;
108 0 0         if ($currency =~ /^GBp/) {
109 0           $last = $last / 100;
110             }
111              
112 0           my $open = $historytable->cell(0,1);
113 0           $open =~ s/,//g;
114 0 0         if ($currency =~ /^GBp/) {
115 0           $open = $open / 100;
116             }
117              
118 0           my $high = $historytable->cell(0,2);
119 0           $high =~ s/,//g;
120 0 0         if ($currency =~ /^GBp/) {
121 0           $high = $high / 100;
122             }
123              
124 0           my $low = $historytable->cell(0,3);
125 0           $low =~ s/,//g;
126 0 0         if ($currency =~ /^GBp/) {
127 0           $low = $low / 100;
128             }
129              
130 0           my $volume = $historytable->cell(0,6);
131 0           $volume =~ s/,//g;
132              
133             ### YahooWeb Result: $last
134 0           $info{ $symbol, 'last'} = $last;
135 0           $info{ $symbol, 'open'} = $open;
136 0           $info{ $symbol, 'high'} = $high;
137 0           $info{ $symbol, 'low'} = $low;
138 0           $info{ $symbol, 'volume'} = $volume;
139              
140 0           $quoter->store_date(\%info, $symbol, {month => $month, day => $day, year => $year});
141 0           $info{ $symbol, 'symbol' } = $symbol;
142 0           $info{ $symbol, 'method' } = 'yahooweb';
143 0           $info{ $symbol, 'success' } = 1;
144             }
145 0           $ua->agent($agent);
146 0 0         return wantarray ? %info : \%info;
147             }
148             1;
149              
150             =head1 NAME
151              
152             Finance::Quote::YahooWeb - Obtain quotes from https://finance.yahoo.com/quote
153              
154             =head1 SYNOPSIS
155              
156             use Finance::Quote;
157             $q = Finance::Quote->new('YahooWeb');
158             %info = $q->fetch('yahooweb', "IBM", "AAPL");
159              
160             =head1 DESCRIPTION
161              
162             This module fetches information from https://finance.yahoo.com/quote.
163              
164             This module is loaded by default on a Finance::Quote object. It's
165             also possible to load it explicitly by placing "YahooWeb" in the argument
166             list to Finance::Quote->new().
167              
168             This module provides the "yahooweb" fetch method.
169              
170             =head1 LABELS RETURNED
171              
172             The following labels may be returned by Finance::Quote::YahooWeb :
173             symbol name exchange currency isodate last open high low volume
174              
175             =cut