AppStore::Scraper アップデート #
ちょっと前、 Search API からユニコードエスケープされず生 UTF8 が返ってくるようになった & iTunes Store だけじゃなくて Mac App Store の情報も欲しい
。。。ってことで、仕事としてやる時間をもらって修正したので、こっちのも修正しておく
なんか、ごちゃっとした XML だけで泣きそうだったところに JSON やら HTML やら加わって、カオスだな。この手の (App Store のランキング取得したりする) ライブラリを一時期に比べて見かけなくなったのも納得
カオスになったついでに、レート毎の件数もそのうち再実装するかもしれません
使い方。今までと変わらんけど、ident として ‘mac’ を指定できるようにした
use AppStore::Scraper;
use Data::Dumper;
my $obj = AppStore::Scraper->new(wait => 5);
my $info = $obj->app_info(
app => ['444303913'],
store => ['jp','us'],
lang => 9,
review_number => 1,
review_order => 4,
ident => 'mac',
);
warn Dumper $info;
実行結果
$VAR1 = {
'444303913' => {
'jp' => {
'review_number' => 2,
'total_rank' => undef,
'store_code' => 143462,
'app_name' => 'OS X Lion',
'genre_rank' => undef,
'store_name' => 'Japan',
'artist_id' => 284417353,
'genre_name' => 'Productivity',
'ranks' => {
'total_rank_mac' => 1,
'genre_rank_mac' => 1,
'genre_rank_ipad' => undef,
'total_rank_ipad' => undef,
'total_rank_iphone' => undef,
'genre_rank_iphone' => undef
},
'lang' => 9,
'reviews' => [
{
'date' => '10-Mar-2012',
'message' => " \\x{3084}\\x{3081}\\x{3068}\\x{3051}\\x{3070}\\x{3088}\\x{304b}\\x{3063}\\x{305f}\\x{3002}\\x{305b}\\x{3063}\\x{304b}\\x{304f}\\x{30b5}\\x{30af}\\x{30b5}\\x{30af}\\x{52d5}\\x{3044}\\x{3066}\\x{3044}\\x{305f}\\x{3082}\\x{306e}\\x{3092}icloud\\x{4f7f}\\x{3048}\\x{308b}\\x{304b}\\x{3082}\\x{3068}\\x{601d}\\x{3063}\\x{3066}lion\\x{306b}\\x{3057}\\x{305f}\\x{306e}\\x{304c}\\x{9593}\\x{9055}\\x{3044}\\x{3002}m2tv\\x{306f}\\x{3060}\\x{3081}\\x{306b}\\x{306a}\\x{308b}\\x{3057}\\x{3001}printmusic\\x{3082}\\x{6483}\\x{6c88}\\x{3002}\\x{6642}\\x{9593}\\x{304b}\\x{3051}\\x{308c}\\x{3070}\\x{76f4}\\x{308b}\\x{306e}\\x{304b}\\x{306a}\\x{30fc}\\x{3001}\\x{3084}\\x{3063}\\x{3066}\\x{3089}\\x{308c}\\x{306a}\\x{3044}\\x{3002}iphot\\x{307e}\\x{3067}\\x{3060}\\x{3081}\\x{3063}\\x{3066}\\x{3001}\\x{306a}\\x{3093}\\x{3067}\\x{ff1f}\\x{30b5}\\x{30dd}\\x{30fc}\\x{30c8}\\x{306e}\\x{304a}\\x{59c9}\\x{3055}\\x{3093}\\x{983c}\\x{308a}\\x{306b}\\x{306a}\\x{3089}\\x{305a}\\x{3002}\\x{3068}\\x{307b}\\x{307b}\\x{3001}\\x{81ea}\\x{696d}\\x{81ea}\\x{5f97}\\x{304b}\\x{3002} "
},
{
'date' => '07-Mar-2012',
'message' => " MacBook 2008Early(4GB)\\x{306b}\\x{30a4}\\x{30f3}\\x{30b9}\\x{30c8}\\x{30fc}\\x{30eb}\\x{3057}\\x{305f}\\x{5834}\\x{5408}\\x{306e}\\x{611f}\\x{60f3}\\x{3067}\\x{3059}\\x{3002}\\x{30b9}\\x{30bf}\\x{30c3}\\x{30af}\\x{3084}\\x{30cd}\\x{30c3}\\x{30c8}\\x{30b5}\\x{30fc}\\x{30d5}\\x{30a3}\\x{30f3}\\x{3001}\\x{30c1}\\x{30e3}\\x{30c3}\\x{30c8}\\x{7b49}\\x{3001}\\x{4f55}\\x{6545}\\x{304b}\\x{30ec}\\x{30b9}\\x{30dd}\\x{30f3}\\x{30b9}\\x{306b}\\x{4e00}\\x{547c}\\x{5438}\\x{9593}\\x{304c}\\x{5165}\\x{308a}SnowLeopard\\x{306b}\\x{6bd4}\\x{3079}\\x{82e5}\\x{5e72}\\x{306e}\\x{30b9}\\x{30c8}\\x{30ec}\\x{30b9}\\x{304c}\\x{3042}\\x{308a}\\x{307e}\\x{3057}\\x{305f}\\x{3002}\\x{3059}\\x{3067}\\x{306b}10.7.3\\x{3067}\\x{3059}\\x{304c}\\x{6539}\\x{5584}\\x{3055}\\x{308c}\\x{3066}\\x{3044}\\x{306a}\\x{3044}\\x{3068}\\x{3044}\\x{3046}\\x{306e}\\x{306f}\\x{30cf}\\x{30fc}\\x{30c9}\\x{9762}\\x{3067}\\x{9650}\\x{754c}\\x{304c}\\x{6765}\\x{3066}\\x{3044}\\x{308b}\\x{3068}\\x{3044}\\x{3046}\\x{3053}\\x{3068}\\x{306a}\\x{306e}\\x{3067}\\x{3057}\\x{3087}\\x{3046}\\x{3002}\\x{307e}\\x{305f}\\x{3001}MBP\\x{4ee5}\\x{4e0a}\\x{306e}PC\\x{3067}\\x{306f}\\x{306a}\\x{3044}\\x{305f}\\x{3081}\\x{30d5}\\x{30ea}\\x{30c3}\\x{30af}\\x{3067}\\x{306e}\\x{64cd}\\x{4f5c}\\x{306f}SnowLeopard\\x{304b}\\x{3089}\\x{5909}\\x{5316}\\x{306f}\\x{3042}\\x{308a}\\x{307e}\\x{305b}\\x{3093}\\x{3067}\\x{3057}\\x{305f}\\x{3002}\\x{305d}\\x{3057}\\x{3066}\\x{554f}\\x{984c}\\x{306f}\\x{3001}Lion\\x{306f}\\x{6a19}\\x{6e96}\\x{3067}64BitOS\\x{306b}\\x{5bfe}\\x{5fdc}\\x{3057}\\x{3066}\\x{304a}\\x{308a}Core2Duo\\x{4ee5}\\x{4e0a}\\x{306e}PC\\x{3067}\\x{306a}\\x{3044}\\x{3068}\\x{4f7f}\\x{7528}\\x{3067}\\x{304d}\\x{306a}\\x{3044}\\x{3068}\\x{306a}\\x{3063}\\x{3066}\\x{3044}\\x{308b}\\x{304c}\\x{3001}MacBook 2008Early\\x{306f}EFI\\x{60c5}\\x{5831}\\x{7684}\\x{306b}\\x{3082}64bit\\x{3067}\\x{52d5}\\x{4f5c}\\x{3059}\\x{308b}\\x{306f}\\x{305a}\\x{306a}\\x{306e}\\x{306b}\\x{30ed}\\x{30c3}\\x{30af}\\x{304c}\\x{639b}\\x{3051}\\x{3089}\\x{308c}\\x{3066}\\x{304a}\\x{308a}32bit\\x{304b}\\x{3089}\\x{5909}\\x{66f4}\\x{4e0d}\\x{53ef}\\x{3067}\\x{3042}\\x{308b}\\x{3053}\\x{3068}\\x{3002}\\x{307e}\\x{305f}\\x{3001}\\x{8d77}\\x{52d5}\\x{30ab}\\x{30fc}\\x{30cd}\\x{30eb}\\x{3092}\\x{5909}\\x{66f4}\\x{3059}\\x{308b}App\\x{3067}\\x{3082}\\x{5909}\\x{66f4}\\x{304c}\\x{4e0d}\\x{53ef}\\x{80fd}\\x{3067}\\x{3057}\\x{305f}\\x{3002}\\x{ff08}Air\\x{306f}Lion\\x{5c0e}\\x{5165}\\x{6642}\\x{306b}\\x{30ab}\\x{30fc}\\x{30cd}\\x{30eb}\\x{304c}64bit\\x{306b}\\x{5909}\\x{66f4}\\x{3055}\\x{308c}\\x{3066}\\x{3044}\\x{305f}\\x{3068}\\x{3044}\\x{3046}\\x{4eba}\\x{304c}\\x{304a}\\x{3089}\\x{308c}\\x{307e}\\x{3057}\\x{305f}\\x{306e}\\x{3067}\\x{671f}\\x{5f85}\\x{3057}\\x{3066}\\x{3044}\\x{305f}\\x{306e}\\x{3067}\\x{3059}\\x{304c}\\x{30fb}\\x{30fb}\\x{30fb}\\x{ff09}\\x{73fe}\\x{72b6}\\x{3001}\\x{30d6}\\x{30e9}\\x{30c3}\\x{30af}\\x{30e2}\\x{30c7}\\x{30eb}\\x{6700}\\x{5f8c}\\x{306e}\\x{6a5f}\\x{7a2e}\\x{306a}\\x{306e}\\x{3067}\\x{5927}\\x{4e8b}\\x{306b}\\x{4f7f}\\x{3063}\\x{3066}\\x{3044}\\x{3053}\\x{3046}\\x{3068}\\x{601d}\\x{3044}\\x{307e}\\x{3057}\\x{305f}\\x{304c}\\x{305d}\\x{308d}\\x{305d}\\x{308d}\\x{6f6e}\\x{6642}\\x{306e}\\x{3088}\\x{3046}\\x{3067}\\x{3059}\\x{3002} "
}
],
'ident' => 'mac',
'genre_id' => 12014,
'review_order' => 4,
'ratings' => {
'userRatingCount' => 1697,
'averageUserRatingForCurrentVersion' => '3.5',
'averageUserRating' => '4',
'userRatingCountForCurrentVersion' => 39
},
'price' => '2600'
},
'us' => {
'review_number' => 2,
'total_rank' => undef,
'store_code' => 143441,
'app_name' => 'OS X Lion',
'genre_rank' => undef,
'store_name' => 'United States',
'artist_id' => 284417353,
'genre_name' => 'Productivity',
'ranks' => {
'total_rank_mac' => 1,
'genre_rank_mac' => 1,
'genre_rank_ipad' => undef,
'total_rank_ipad' => undef,
'total_rank_iphone' => undef,
'genre_rank_iphone' => undef
},
'lang' => 9,
'reviews' => [
{
'date' => 'Mar 12, 2012',
'message' => ' The current version (10.7.3) causes my computer to crash sometimes 3 or more times a day while doing simple tasks, E.G. using Safari to watch a movie preview on the Apple Trailer site. Please Fix!iCould synchronizing and new track pad gestures are great. Using a 2.66 GHz Intel Core i7 2010 MacBook Pro with 8 Gigs of ram. '
},
{
'date' => 'Mar 12, 2012',
'message' => ' Not ready for prime time yet. Not sure it is worth upgrading. 1. Pages and Nubmers (Apple Products) do NOT work after the upgrade. The error message is "You can\\'t open the application Pages.app because PowerPC applications are no longer supported? Huh?? This is an Aapple product I purchased.2. My Word and Excel not longer work. They are the Apple version of Work and Excel. Will Apple reimburse me for taking away my purchased programs?If I could undo this I would. Apple is really sticking it to users. You can\\'t move to iCould without upgrading to Lion. And you are forced to change to iCould because MobileMe is being terminated. We\\'re kind of stuck between a rock and a hard place now. :-( And iCloud does not support iWeb. I was such a fan of Apple until now. '
}
],
'ident' => 'mac',
'genre_id' => 12014,
'review_order' => 4,
'ratings' => {
'userRatingCount' => 24648,
'averageUserRatingForCurrentVersion' => '4',
'averageUserRating' => '4',
'userRatingCountForCurrentVersion' => 382
},
'price' => '29.99'
}
}
};
ソースはこちら
package AppStore::Scraper;
use strict;
use utf8;
use warnings;
use Data::Dumper;
use LWP::UserAgent;
use XML::Simple;
use JSON;
use Web::Scraper;
sub new {
my $class = shift;
my @args = @_;
my $args_ref = ref $args[0] eq 'HASH' ? $args[0] : {@args};
my $self = bless{}, ref $class || $class;
$self->{__STORE_CODES} = _init_countries();
$self->{__URL_PREF} = 'http://ax.itunes.apple.com/WebObjects/';
$self->{__SCRAPING_URL_PREF} = $self->{__URL_PREF} . 'MZStore.woa/wa/';
$self->{__SEARCHAPI_URL_PREF} = $self->{__URL_PREF} . 'MZStoreServices.woa/wa/';
#$self->{__URL_PREF} = 'http://itunes.apple.com/WebObjects/MZStore.woa/wa/';
$self->{__UA} = 'iTunes/9.1.1 (Macintosh; Intel Mac OS X 10.6.3';
#$self->{__UA} = 'MacAppStore/1.0.2 (Macintosh; Intel Mac OS X 10.6.8';
$self->{ua} = LWP::UserAgent->new();
$self->{ua}->timeout(30);
$self->{ua}->env_proxy;
$self->{ua}->agent( $self->{__UA} );
$self->{__WAIT} = $args_ref->{wait} || '1';
# $self->{__XML_PREFERRED_PARSER} = 'XML::SAX::PurePerl';
$self->{__XML_PREFERRED_PARSER} = 'XML::Parser';
# $self->{__XML_PREFERRED_PARSER} = 'XML::SAX::Expat';
# $self->{__XML_PREFERRED_PARSER} = 'XML::LibXML::SAX';
$self;
}
sub app_info {
my $self = shift;
my @args = @_;
# get info from app page
my $base = $self->app_base_info( @args );
my $ret = {};
for my $app ( keys %$base ) {
for my $store ( keys %{$base->{$app}} ) {
my $info = $base->{$app}->{$store};
next unless $info->{genre_id};
my $genre_rank_iphone;
my $total_rank_iphone;
my $genre_rank_ipad;
my $total_rank_ipad;
my $genre_rank_mac;
my $total_rank_mac;
if ( $info->{ident} eq 'mac' ) {
$genre_rank_mac = $self->genre_rank(
app => $app,
info => $info
);
$total_rank_mac = $self->total_rank(
app => $app,
info => $info
);
}
else {
if ( $info->{ident} ne 'ipad' ) {
my $tmp = $info->{ident};
$info->{ident} = 'iphone';
$genre_rank_iphone = $self->genre_rank(
app => $app,
info => $info
);
$total_rank_iphone = $self->total_rank(
app => $app,
info => $info
);
$info->{ident} = $tmp;
}
if ( $info->{ident} ne 'iphone' ) {
my $tmp = $info->{ident};
$info->{ident} = 'ipad';
$genre_rank_ipad = $self->genre_rank(
app => $app,
info => $info
);
$total_rank_ipad = $self->total_rank(
app => $app,
info => $info
);
$info->{ident} = $tmp;
}
}
my $reviews = $self->app_reviews(
app => $app,
info => $info
);
$ret->{$app}->{$store} = {
%$info,
genre_rank => $genre_rank_iphone,
total_rank => $total_rank_iphone,
ranks => {
genre_rank_iphone => $genre_rank_iphone,
total_rank_iphone => $total_rank_iphone,
genre_rank_ipad => $genre_rank_ipad,
total_rank_ipad => $total_rank_ipad,
genre_rank_mac => $genre_rank_mac,
total_rank_mac => $total_rank_mac,
},
reviews => $reviews,
store_name => $self->{__STORE_CODES}->{$store}->{name},
};
sleep $self->{__WAIT};
}
}
$ret;
}
sub app_base_info {
my $self = shift;
my @args = @_;
my $args = $self->_validate_args(@args);
my $ret = {};
for my $app ( @{$args->{apps}} ) {
for my $store ( keys %{$args->{stores}} ) {
my $tmp;
$tmp->{store_code} = $args->{stores}->{$store}->{code};
$tmp->{lang} = $args->{lang};
$tmp->{ident} = $args->{ident};
$tmp->{review_number} = $args->{review_number};
$tmp->{review_order} = $args->{review_order};
my $store_tmp = $store eq 'uk' ? 'gb' : $store;
my $uri = $self->{__SEARCHAPI_URL_PREF} . 'wsLookup?id='.$app.'&entity=software&country='.$store_tmp;
my $res = $self->{ua}->get( $uri );
# Error Check
unless ( $res->is_success ) {
warn 'request failed: ', $uri, ': ', $res->status_line, ': ', $store, '-', $args->{lang};
next;
}
my $jsondata = $res->content;
if ( utf8::is_utf8($jsondata) ) {
utf8::encode($jsondata);
}
my $hash;
my $json = JSON->new->utf8;
eval { $hash = $json->decode($jsondata) };
#
# genre_id, artist_id, app_name, genre_name
#
$tmp->{genre_id} = $hash->{results}->[0]->{primaryGenreId};
$tmp->{artist_id} = $hash->{results}->[0]->{artistId};
$tmp->{app_name} = $hash->{results}->[0]->{trackName};
if ( $tmp->{app_name} ) {
$tmp->{app_name} =~ s/^\\s+(.*)\\s+$/$1/;
}
$tmp->{genre_name} = $hash->{results}->[0]->{primaryGenreName};
#
# price
#
$tmp->{price} = $hash->{results}->[0]->{price};
#
# star
#
$tmp->{ratings} = {
averageUserRating => $hash->{results}->[0]->{averageUserRating},
userRatingCount => $hash->{results}->[0]->{userRatingCount},
averageUserRatingForCurrentVersion => $hash->{results}->[0]->{averageUserRatingForCurrentVersion},
userRatingCountForCurrentVersion => $hash->{results}->[0]->{userRatingCountForCurrentVersion},
};
$ret->{$app}->{$store} = $tmp;
}
}
$ret;
}
#
# for rank
#
sub genre_rank {
my $self = shift;
my @args = @_;
$self->_get_rank(@args);
}
sub total_rank {
my $self = shift;
my @args = @_;
$self->_get_rank(@args);
}
sub _rank_uri {
my $self = shift;
my $price = shift;
my $ident = shift;
# iphone 30:27, ipad 47:44, mac 30:27
my $popId = $price ? 30: 27;
$popId += 17 if $ident eq 'ipad';
my $id = 25209;
$id = 29562 if $ident eq 'mac';
my $uri = $self->{__SCRAPING_URL_PREF} . 'viewTop?id=' . $id . '&popId='. $popId;
$uri;
}
sub _get_rank {
my $self = shift;
my @args = @_;
my $args_ref = ref $args[0] eq 'HASH' ? $args[0] : {@args};
my $caller = (caller(1))[3];
my $info;
if ( $args_ref->{info} ) {
$info = $args_ref->{info};
}
else {
my $base_info = $self->app_base_info($args_ref);
$info = $base_info->{ $args_ref->{app} }->{ $args_ref->{store} };
}
my $uri = $self->_rank_uri( $info->{price}, $info->{ident} );
$uri .= '&genreId=' . $info->{genre_id} if $caller =~ /genre_rank$/;
my $ret;
my @arrays;
if ( $info->{ident} ne 'mac' ) {
my $xmlobj = $self->_get_xml($uri, $info->{store_code}, $info->{lang});
@arrays = split /\
+/, Dumper($xmlobj->{View}->{ScrollView}->{VBoxView}->{View});
}
else {
my $html = $self->_get_html($uri, $info->{store_code}, $info->{lang});
my $rule = scraper {
process '//div[@class="lockup small detailed option application mac-application"]','apps[]' => 'HTML';
};
my $apps;
eval { $apps = $rule->scrape($html); };
if ( $@ ) {
warn $info->{store_code},',',$info->{lang},',',$@;
}
else {
@arrays = @{ $apps->{apps} };
}
}
my $i;
for ( @arrays ) {
next unless /salableAdamId=(\\d+)/;
$i++;
next unless $1 == $args_ref->{app};
$ret = $i;
last;
}
$ret;
}
#
# for reviews
#
sub app_reviews {
my $self = shift;
my @args = @_;
my $args_ref = ref $args[0] eq 'HASH' ? $args[0] : {@args};
my $ret = [];
my $info;
if ( $args_ref->{info} ) {
$info = $args_ref->{info};
}
else {
my $base_info = $self->app_base_info($args_ref);
$info = $base_info->{ $args_ref->{app} }->{ $args_ref->{store} };
}
my $order = $info->{review_order};
my $uri = $self->{__SCRAPING_URL_PREF} . 'customerReviews?page=1&id='.$args_ref->{app}.'&displayable-kind=30&appVersion=current&sort='.$order;
$uri =~ s|sort=\\d+|sort=$order|x;
# pagenation
if ( $uri =~ /(?:\\?|&)page=\\d+/ ) {
my $i = 1;
while ( scalar(@$ret) <= $info->{review_number} ) {
$uri =~ s|page=\\d+|page=$i|;
my $tmp = $self->_app_reviews($uri, $info->{store_code}, $info->{lang});
last unless scalar(@$tmp);
$ret = [@$ret, @$tmp];
$i++;
}
}
else {
$ret = $self->_app_reviews($uri, $info->{store_code}, $info->{lang});
}
@$ret = splice @$ret, 0, $info->{review_number};
$ret;
}
sub _app_reviews {
my $self = shift;
my $uri = shift;
my $store_code = shift;
my $lang = shift;
my $ret = [];
my $html = $self->_get_html($uri, $store_code, $lang);
my $rule = scraper {
process '//div[@class="paginate current-reviews"]//div[@class="da-review customer-review"]','reviews[]' => scraper {
process '//span[@class="user-info"]', date => 'HTML';
process '//p[@class="content"]', message => 'TEXT';
};
};
my $reviews;
eval { $reviews = $rule->scrape($html); };
if ( $@ ) {
warn $store_code,',',$lang,',',$@;
}
else {
for my $review ( @{ $reviews->{reviews} } ) {
my @tmps = split / - /, $review->{date};
push @$ret, {
message => $review->{message},
date => pop @tmps,
}
}
}
$ret;
}
#
# common
#
sub _validate_args {
my $self = shift;
my @args = @_;
my $args_ref = ref $args[0] eq 'HASH' ? $args[0] : {@args};
#
# prepare array by target apps
#
die 'app code MUST be needed' unless $args_ref->{app};
my @appcode = ref $args_ref->{app} eq 'ARRAY' ? @{$args_ref->{app}}
: ($args_ref->{app});
for (@appcode) {
die 'app code MUST be numerical: ',$_ unless m|^\\d+$|;
}
my $apps_array = [@appcode];
#
# prepare array by target countries
#
my $stores_hash;
if ( $args_ref->{store} ) {
my @storename = ref $args_ref->{store} eq 'ARRAY' ? @{$args_ref->{store}}
: ($args_ref->{store});
for ( @storename ) {
my $s = lc $_;
if ( exists $self->{__STORE_CODES}->{ $s } ) {
$stores_hash->{ $s } = $self->{__STORE_CODES}->{ $s };
}
else {
die 'cannot found appstore on "', $s, '"';
}
}
}
else {
$stores_hash = $self->{__STORE_CODES};
}
#
# prepare digit by target lang
#
my $lang = ( exists $args_ref->{lang} and $args_ref->{lang} =~ /^\\d+$/ ) ? $args_ref->{lang} : 1;
#
# prepare identifier
#
# my $ident = ( exists $args_ref->{ident} and $args_ref->{ident} eq 'ipad' ) ? 'ipad' : 'iphone';
my $ident = 'both';
if ( exists $args_ref->{ident} ) {
if ( $args_ref->{ident} eq 'ipad' ) {
$ident = 'ipad';
}
elsif ( $args_ref->{ident} eq 'iphone' ) {
$ident = 'iphone';
}
elsif ( $args_ref->{ident} eq 'mac' ) {
$ident = 'mac';
}
}
#
# prepare reviews max number
#
my $review_number = ( exists $args_ref->{review_number} and $args_ref->{review_number} =~ /^\\d+$/ ) ? $args_ref->{review_number} :25;
#
# prepare reviews order
# 1..Most Helpful
# 2..Most Favourable
# 3..Most Critical
# 4..Most Recent
#
my $review_order = ( exists $args_ref->{review_order} and $args_ref->{review_order} =~ /^\\d+$/ ) ? $args_ref->{review_order} :1;
return {
apps => $apps_array,
stores => $stores_hash,
lang => $lang,
ident => $ident,
review_number => $review_number,
review_order => $review_order,
};
}
sub _get_xml {
my $self = shift;
my ($uri,$store,$lang) = @_;
$self->{ua}->default_header('X-Apple-Store-Front' => $store . '-' . $lang);
my $res = $self->{ua}->get( $uri );
# Error Check
unless ( $res->is_success ) {
warn 'request failed: ', $uri, ': ', $res->status_line, ': ', $store, '-', $lang;
return;
}
unless ( $res->headers->header('Content-Type') =~ m|/xml| ) {
warn 'content is not xml: ', $uri, ': ', $res->headers->header('Content-Type'), ': ', $store, '-', $lang;
return;
}
local $XML::Simple::PREFERRED_PARSER = $self->{__XML_PREFERRED_PARSER};
my $xmlobj = XMLin( $res->content );
$xmlobj;
}
sub _get_html {
my $self = shift;
my ($uri,$store,$lang) = @_;
$self->{ua}->default_header('X-Apple-Store-Front' => $store . '-' . $lang . ',13');
my $res = $self->{ua}->get( $uri );
# Error Check
unless ( $res->is_success ) {
warn 'request failed: ', $uri, ': ', $res->status_line, ': ', $store, '-', $lang;
return;
}
unless ( $res->headers->header('Content-Type') =~ m|/html| ) {
warn 'content is not html: ', $uri, ': ', $res->headers->header('Content-Type'), ': ', $store, '-', $lang;
return;
}
$res->decoded_content;
}
sub _init_countries {
my $c = {
jp => {
name => 'Japan',
code => 143462,
},
us => {
name => 'United States',
code => 143441,
},
ar => {
name => 'Argentine',
code => 143505,
},
au => {
name => 'Autstralia',
code => 143460,
},
be => {
name => 'Belgium',
code => 143446,
},
br => {
name => 'Brazil',
code => 143503,
},
ca => {
name => 'Canada',
code => 143455,
},
cl => {
name => 'Chile',
code => 143483,
},
cn => {
name => 'China',
code => 143465,
},
co => {
name => 'Colombia',
code => 143501,
},
cr => {
name => 'Costa Rica',
code => 143495,
},
hr => {
name => 'Croatia',
code => 143494,
},
cz => {
name => 'Czech Republic',
code => 143489,
},
dk => {
name => 'Denmark',
code => 143458,
},
de => {
name => 'Germany',
code => 143443,
},
sv => {
name => 'El Salvador',
code => 143506,
},
es => {
name => 'Spain',
code => 143454,
},
fi => {
name => 'Finland',
code => 143447,
},
fr => {
name => 'France',
code => 143442,
},
gr => {
name => 'Greece',
code => 143448,
},
gt => {
name => 'Guatemala',
code => 143504,
},
hk => {
name => 'Hong Kong',
code => 143463,
},
hu => {
name => 'Hungary',
code => 143482,
},
in => {
name => 'India',
code => 143467,
},
id => {
name => 'Indonesia',
code => 143476,
},
ie => {
name => 'Ireland',
code => 143449,
},
il => {
name => 'Israel',
code => 143491,
},
it => {
name => 'Italia',
code => 143450,
},
kr => {
name => 'Korea',
code => 143466,
},
kw => {
name => 'Kuwait',
code => 143493,
},
lb => {
name => 'Lebanon',
code => 143497,
},
lu => {
name => 'Luxembourg',
code => 143451,
},
my => {
name => 'Malaysia',
code => 143473,
},
mx => {
name => 'Mexico',
code => 143468,
},
nl => {
name => 'Nederland',
code => 143452,
},
nu => {
name => 'New Zealand',
code => 143461,
},
no => {
name => 'Norway',
code => 143457,
},
at => {
name => 'Osterreich',
code => 143445,
},
pk => {
name => 'Pakistan',
code => 143477,
},
pa => {
name => 'Panama',
code => 143485,
},
pe => {
name => 'Peru',
code => 143507,
},
ph => {
name => 'Phillipines',
code => 143474,
},
pl => {
name => 'Poland',
code => 143478,
},
pt => {
name => 'Portugal',
code => 143453,
},
qa => {
name => 'Qatar',
code => 143498,
},
ro => {
name => 'Romania',
code => 143487,
},
ru => {
name => 'Russia',
code => 143469,
},
sa => {
name => 'Saudi Arabia',
code => 143479,
},
ch => {
name => 'Switzerland',
code => 143459,
},
sg => {
name => 'Singapore',
code => 143464,
},
sk => {
name => 'Slovakia',
code => 143496,
},
si => {
name => 'Slovenia',
code => 143499,
},
za => {
name => 'South Africa',
code => 143472,
},
lk => {
name => 'Sri Lanka',
code => 143486,
},
se => {
name => 'Sweden',
code => 143456,
},
tw => {
name => 'Taiwan',
code => 143470,
},
th => {
name => 'Thailand',
code => 143475,
},
tr => {
name => 'Turkey',
code => 143480,
},
ae => {
name => 'United Arab Emirates',
code => 143481,
},
uk => {
name => 'United Kingdom',
code => 143444,
},
ve => {
name => 'Venezuela',
code => 143502,
},
vn => {
name => 'Vietnam',
code => 143471,
},
};
}
1;