ruby-aaws-simple 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +17 -0
- data/Gemfile +7 -0
- data/README.md +13 -0
- data/Rakefile +2 -0
- data/examples/test.rb +12 -0
- data/lib/amazon/aws/cache.rb +143 -0
- data/lib/amazon/aws/simple.rb +183 -0
- data/lib/amazon/aws/simple_version.rb +7 -0
- data/ruby-aaws-simple.gemspec +17 -0
- metadata +54 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# ruby-aaws-simple
|
2
|
+
Simple wrapper for ruby-aaws
|
3
|
+
|
4
|
+
## Installation
|
5
|
+
gem install ruby-aaws-simple
|
6
|
+
|
7
|
+
## Usage
|
8
|
+
require 'amazon/aws/simple'
|
9
|
+
aws = Amazon::AWS::Simple::Search.new(key_id, secret_key_id, affiliate_tag, country_code, encoding, cacahe_dir)
|
10
|
+
items = aws.retrieve_by_keyword('Ruby')
|
11
|
+
items.first.title
|
12
|
+
# => 7.50 Carat Ruby & White Sapphire Heart Pendant in Sterling Silver with 18" Chain,
|
13
|
+
|
data/Rakefile
ADDED
data/examples/test.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/bin/env ruby
|
2
|
+
require 'amazon/aws/simple'
|
3
|
+
|
4
|
+
if $0 == __FILE__
|
5
|
+
key_id = ""
|
6
|
+
secret_key_id = ""
|
7
|
+
cache_dir = "/tmp/amazon/"
|
8
|
+
|
9
|
+
Amazon::AWS::Simple::Search.logger = Logger.new(STDERR)
|
10
|
+
aws = Amazon::AWS::Simple::Search.new(key_id, secret_key_id, "hoge-22", "us", "utf-8", cache_dir)
|
11
|
+
puts aws.retrieve_by_keyword('ruby').map(&:title)
|
12
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
# monkey patch
|
2
|
+
#
|
3
|
+
module Amazon
|
4
|
+
|
5
|
+
module AWS
|
6
|
+
|
7
|
+
# This class provides a simple results caching system for operations
|
8
|
+
# performed by AWS.
|
9
|
+
#
|
10
|
+
# To use it, set _cache_ to *true* in either <tt>/etc/amazonrc</tt> or
|
11
|
+
# <tt>~/.amazonrc</tt>.
|
12
|
+
#
|
13
|
+
# By default, the cache directory used is <tt>/tmp/amazon</tt>, but this
|
14
|
+
# can be changed by defining _cache_dir_ in either <tt>/etc/amazonrc</tt>
|
15
|
+
# or <tt>~/.amazonrc</tt>.
|
16
|
+
#
|
17
|
+
# When a cache is used, Ruby/AWS will check the cache directory for a
|
18
|
+
# recent copy of a response to the exact operation that you are
|
19
|
+
# performing. If found, the cached response will be returned instead of
|
20
|
+
# the request being forwarded to the AWS servers for processing. If no
|
21
|
+
# (recent) copy is found, the request will be forwarded to the AWS servers
|
22
|
+
# as usual. Recency is defined here as less than 24 hours old.
|
23
|
+
#
|
24
|
+
class Cache
|
25
|
+
|
26
|
+
require 'fileutils'
|
27
|
+
|
28
|
+
begin
|
29
|
+
require 'md5'
|
30
|
+
rescue LoadError
|
31
|
+
# Ruby 1.9 has moved MD5.
|
32
|
+
#
|
33
|
+
require 'digest/md5'
|
34
|
+
end
|
35
|
+
|
36
|
+
# Exception class for bad cache paths.
|
37
|
+
#
|
38
|
+
class PathError < StandardError; end
|
39
|
+
|
40
|
+
# Length of one day in seconds
|
41
|
+
#
|
42
|
+
ONE_DAY = 86400 # :nodoc:
|
43
|
+
|
44
|
+
# Age in days below which to consider cache files valid.
|
45
|
+
#
|
46
|
+
MAX_AGE = 1.0
|
47
|
+
|
48
|
+
# Default cache location.
|
49
|
+
#
|
50
|
+
DEFAULT_CACHE_DIR = '/tmp/amazon'
|
51
|
+
|
52
|
+
attr_reader :path
|
53
|
+
|
54
|
+
def initialize(path=DEFAULT_CACHE_DIR)
|
55
|
+
path ||= DEFAULT_CACHE_DIR
|
56
|
+
|
57
|
+
::FileUtils::mkdir_p( path ) unless File.exists? path
|
58
|
+
|
59
|
+
unless File.directory? path
|
60
|
+
raise PathError, "cache path #{path} is not a directory"
|
61
|
+
end
|
62
|
+
|
63
|
+
unless File.readable? path
|
64
|
+
raise PathError, "cache path #{path} is not readable"
|
65
|
+
end
|
66
|
+
|
67
|
+
unless File.writable? path
|
68
|
+
raise PathError, "cache path #{path} is not writable"
|
69
|
+
end
|
70
|
+
|
71
|
+
@path = path
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
# Determine whether or not the the response to a given URL is cached.
|
76
|
+
# Returns *true* or *false*.
|
77
|
+
#
|
78
|
+
def cached?(url)
|
79
|
+
digest = Digest::MD5.hexdigest( url )
|
80
|
+
|
81
|
+
cache_files = Dir.glob( File.join( @path, '*' ) ).map do |d|
|
82
|
+
File.basename( d )
|
83
|
+
end
|
84
|
+
|
85
|
+
return cache_files.include?( digest ) &&
|
86
|
+
( Time.now - File.mtime( File.join( @path, digest ) ) ) /
|
87
|
+
ONE_DAY <= MAX_AGE
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
# Retrieve the cached response associated with _url_.
|
92
|
+
#
|
93
|
+
def fetch(url)
|
94
|
+
digest = Digest::MD5.hexdigest( url )
|
95
|
+
cache_file = File.join( @path, digest )
|
96
|
+
|
97
|
+
return nil unless File.exist? cache_file
|
98
|
+
|
99
|
+
Amazon.dprintf( 'Fetching %s from cache...', digest )
|
100
|
+
#File.open( File.join( cache_file ) ).readlines.to_s
|
101
|
+
File.read(cache_file)
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
# Cache the data from _contents_ and associate it with _url_.
|
106
|
+
#
|
107
|
+
def store(url, contents)
|
108
|
+
digest = Digest::MD5.hexdigest( url )
|
109
|
+
cache_file = File.join( @path, digest )
|
110
|
+
|
111
|
+
Amazon.dprintf( 'Caching %s...', digest )
|
112
|
+
content.force_encoding("UTF-8")
|
113
|
+
#File.open( cache_file, 'w' ) { |f| f.puts contents }
|
114
|
+
File.write cache_file, contents
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
# This method flushes all files from the cache directory specified
|
119
|
+
# in the object's <i>@path</i> variable.
|
120
|
+
#
|
121
|
+
def flush_all
|
122
|
+
FileUtils.rm Dir.glob( File.join( @path, '*' ) )
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
# This method flushes expired files from the cache directory specified
|
127
|
+
# in the object's <i>@path</i> variable.
|
128
|
+
#
|
129
|
+
def flush_expired
|
130
|
+
now = Time.now
|
131
|
+
|
132
|
+
expired_files = Dir.glob( File.join( @path, '*' ) ).find_all do |f|
|
133
|
+
( now - File.mtime( f ) ) / ONE_DAY > MAX_AGE
|
134
|
+
end
|
135
|
+
|
136
|
+
FileUtils.rm expired_files
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
require_relative "simple_version"
|
2
|
+
require 'amazon/aws/search'
|
3
|
+
require 'amazon/aws'
|
4
|
+
require 'logger'
|
5
|
+
require 'cgi'
|
6
|
+
|
7
|
+
module Amazon::AWS::Simple
|
8
|
+
class APIWrapper
|
9
|
+
@@logger = Logger.new(nil)
|
10
|
+
def self.logger=(logger)
|
11
|
+
@@logger = logger
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(key_id, secret_key_id, tag, locale, encoding, cache_dir)
|
15
|
+
@key_id = key_id
|
16
|
+
@secret_key_id = secret_key_id
|
17
|
+
@tag = tag
|
18
|
+
@locale = locale
|
19
|
+
@encoding = encoding
|
20
|
+
@cache_dir = cache_dir
|
21
|
+
end
|
22
|
+
|
23
|
+
def request(query)
|
24
|
+
req = Amazon::AWS::Search::Request.new(@key_id, @tag)
|
25
|
+
req.config["cache_dir"] = @cache_dir
|
26
|
+
req.config["secret_key_id"] = @secret_key_id
|
27
|
+
req.locale = @locale
|
28
|
+
req.encoding = @encoding
|
29
|
+
req.cache = Amazon::AWS::Cache.new(@cache_dir)
|
30
|
+
|
31
|
+
rg = Amazon::AWS::ResponseGroup.new(:Large)
|
32
|
+
query.response_group = rg
|
33
|
+
|
34
|
+
req.search(query)
|
35
|
+
end
|
36
|
+
|
37
|
+
def item_lookup(type, params={})
|
38
|
+
request(Amazon::AWS::ItemLookup.new(type, params)).item_lookup_response.items.item
|
39
|
+
end
|
40
|
+
|
41
|
+
def item_search(type, params={})
|
42
|
+
request(Amazon::AWS::ItemSearch.new(type, params)).item_search_response.items.item
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class Search < APIWrapper
|
47
|
+
attr_reader :errors
|
48
|
+
def initialize(*args)
|
49
|
+
super(*args)
|
50
|
+
@errors = []
|
51
|
+
end
|
52
|
+
|
53
|
+
## どんだけたくさんのASINでも全部自動でAPIの最大数にあわせて分割してリクエストしてくれる版
|
54
|
+
## エラーは全部無視しますが、@errorsに記録されます
|
55
|
+
ITEM_LOOKUP_MAX_ITEMS = 10
|
56
|
+
def search_by_asin(*asins)
|
57
|
+
asins.flatten.each_slice(ITEM_LOOKUP_MAX_ITEMS).map{ |asin_codes|
|
58
|
+
@@logger.info "try to fetch: #{asin_codes.inspect}"
|
59
|
+
_search_by_asin(asin_codes)
|
60
|
+
}.flatten
|
61
|
+
end
|
62
|
+
|
63
|
+
def search_by_keyword(keyword)
|
64
|
+
_search_by_keyword(keyword)
|
65
|
+
end
|
66
|
+
|
67
|
+
## 更につかいやすくしたやつ
|
68
|
+
def retrieve_by_asin(*asins)
|
69
|
+
search_by_asin(*asins).map{ |item|
|
70
|
+
Data.new.load_from_aws_item(item)
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
def retrieve_by_keyword(keyword)
|
75
|
+
search_by_keyword(keyword).map{ |item|
|
76
|
+
Data.new.load_from_aws_item(item)
|
77
|
+
}
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
# 10以上は検索できないAPI, 仕様を満たしてる
|
82
|
+
def _search_by_asin(*asins)
|
83
|
+
params = asins.flatten
|
84
|
+
if params.size > ITEM_LOOKUP_MAX_ITEMS
|
85
|
+
raise ArgumentError.new("asin_codes > #{ITEM_LOOKUP_MAX_ITEMS}, #{params.inspect}")
|
86
|
+
end
|
87
|
+
begin
|
88
|
+
results = item_lookup("ASIN", {"ItemId" => params.join(",")})
|
89
|
+
rescue Amazon::AmazonError => ex
|
90
|
+
@@logger.error "Multiple fetch mode error! change to single fetch mode: #{ex}"
|
91
|
+
@@logger.error ex.backtrace.join($/)
|
92
|
+
results = []
|
93
|
+
params.each{ |asin|
|
94
|
+
begin
|
95
|
+
@@logger.info "try to fetch: #{asin.inspect}"
|
96
|
+
results << item_lookup("ASIN", {"ItemId" => asin})
|
97
|
+
rescue Amazon::AmazonError => ex
|
98
|
+
@@logger.error ex
|
99
|
+
@@logger.error ex.backtrace.join($/)
|
100
|
+
@errors << asin
|
101
|
+
end
|
102
|
+
}
|
103
|
+
results
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def _search_by_keyword(keyword)
|
108
|
+
item_search("All", {'Keywords' => keyword})
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
class Search::Data
|
113
|
+
attr_accessor :asin
|
114
|
+
attr_accessor :title
|
115
|
+
attr_accessor :product_group
|
116
|
+
attr_accessor :publisher
|
117
|
+
attr_accessor :published_at
|
118
|
+
attr_accessor :image_url
|
119
|
+
attr_accessor :image_code
|
120
|
+
attr_accessor :alt_image_urls
|
121
|
+
attr_accessor :alt_image_codes
|
122
|
+
attr_accessor :price
|
123
|
+
attr_accessor :discount_price
|
124
|
+
attr_accessor :discount_percentage
|
125
|
+
attr_accessor :detail
|
126
|
+
|
127
|
+
def load_from_aws_item(item)
|
128
|
+
# 値段関係
|
129
|
+
price = item.item_attributes.list_price.amount.first rescue nil
|
130
|
+
discount_price = item.offers.offer.offer_listing.price.amount.first rescue nil
|
131
|
+
discount_percentage = item.offers.offer.offer_listing.percentage_saved.first rescue nil
|
132
|
+
|
133
|
+
# 画像関係
|
134
|
+
image_url = item.large_image.url rescue nil
|
135
|
+
image_code = extract_image_code_from_image_url(image_url)
|
136
|
+
alt_image_urls = item.image_sets.image_set.map{ |set| set.swatch_image.url }.flatten rescue []
|
137
|
+
alt_image_codes = alt_image_urls.map{|url| extract_image_code_from_image_url(url) }
|
138
|
+
|
139
|
+
# 日付関係
|
140
|
+
publication_date = item.item_attributes.publication_date.first rescue nil
|
141
|
+
release_date = item.item_attributes.release_date.first rescue nil
|
142
|
+
|
143
|
+
published_at = publication_date
|
144
|
+
if published_at.nil?
|
145
|
+
published_at = release_date
|
146
|
+
end
|
147
|
+
|
148
|
+
@asin = item.asin.first.to_s rescue nil
|
149
|
+
@title = CGI.unescape_html(item.item_attributes.title.join(",")) rescue nil
|
150
|
+
@product_group = item.item_attributes.product_group.first.to_s
|
151
|
+
@publisher = item.item_attributes.publisher.join(",") rescue nil
|
152
|
+
@published_at = published_at rescue nil
|
153
|
+
@image_url = image_url.first.to_s rescue nil
|
154
|
+
@image_code = image_code
|
155
|
+
@alt_image_urls = alt_image_urls
|
156
|
+
@alt_image_codes = alt_image_codes
|
157
|
+
@price = price
|
158
|
+
@discount_price = discount_price
|
159
|
+
@discount_percentage = discount_percentage
|
160
|
+
@detail = item.detail_page_url rescue nil
|
161
|
+
self
|
162
|
+
end
|
163
|
+
|
164
|
+
def to_s
|
165
|
+
"#<AWS::Simple::Data: #{@title}(#{@asin}) - #{@publisher} $#{@price}>"
|
166
|
+
end
|
167
|
+
|
168
|
+
private
|
169
|
+
def extract_basename_from_image_url(image_url)
|
170
|
+
if /([^\/]+)\.jpg$/.match(image_url)
|
171
|
+
Regexp.last_match(0)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def extract_image_code_from_image_url(image_url)
|
176
|
+
basename = extract_basename_from_image_url(image_url)
|
177
|
+
# 先頭の.までを抽出
|
178
|
+
if /(^[^\.]+)\./.match(basename)
|
179
|
+
Regexp.last_match(1)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/amazon/aws/simple_version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["kimoto"]
|
6
|
+
gem.email = ["sub+peerler@gmail.com"]
|
7
|
+
gem.description = %q{Simple Wrapper for ruby-aaws}
|
8
|
+
gem.summary = %q{Simple Wrapper for ruby-aaws}
|
9
|
+
gem.homepage = ""
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "ruby-aaws-simple"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Amazon::AWS::Simple::VERSION
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ruby-aaws-simple
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- kimoto
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-07-24 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Simple Wrapper for ruby-aaws
|
15
|
+
email:
|
16
|
+
- sub+peerler@gmail.com
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- .gitignore
|
22
|
+
- Gemfile
|
23
|
+
- README.md
|
24
|
+
- Rakefile
|
25
|
+
- examples/test.rb
|
26
|
+
- lib/amazon/aws/cache.rb
|
27
|
+
- lib/amazon/aws/simple.rb
|
28
|
+
- lib/amazon/aws/simple_version.rb
|
29
|
+
- ruby-aaws-simple.gemspec
|
30
|
+
homepage: ''
|
31
|
+
licenses: []
|
32
|
+
post_install_message:
|
33
|
+
rdoc_options: []
|
34
|
+
require_paths:
|
35
|
+
- lib
|
36
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
37
|
+
none: false
|
38
|
+
requirements:
|
39
|
+
- - ! '>='
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
43
|
+
none: false
|
44
|
+
requirements:
|
45
|
+
- - ! '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
requirements: []
|
49
|
+
rubyforge_project:
|
50
|
+
rubygems_version: 1.8.24
|
51
|
+
signing_key:
|
52
|
+
specification_version: 3
|
53
|
+
summary: Simple Wrapper for ruby-aaws
|
54
|
+
test_files: []
|