rakumarket 0.0.3 → 0.1.0
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/Gemfile.lock +2 -2
- data/README.rdoc +8 -2
- data/fixtures/vcr_casettes/genre_search.yml +168 -0
- data/fixtures/vcr_casettes/item_lookup.yml +163 -0
- data/fixtures/vcr_casettes/item_search.yml +7579 -0
- data/lib/rakumarket/client.rb +110 -51
- data/lib/rakumarket/constants.rb +222 -0
- data/lib/rakumarket/models.rb +125 -0
- data/lib/rakumarket/spitter.rb +102 -0
- data/lib/rakumarket/version.rb +1 -1
- data/lib/rakumarket.rb +15 -17
- data/rakumarket.gemspec +1 -1
- data/spec/rakumarket/client_spec.rb +242 -0
- data/spec/rakumarket/models_spec.rb +257 -0
- data/spec/rakumarket_spec.rb +32 -36
- data/spec/spec_helper.rb +1 -0
- data/spec/spitter_spec.rb +121 -0
- metadata +14 -9
- data/lib/rakumarket/genre_search_client.rb +0 -77
- data/lib/rakumarket/item_search_client.rb +0 -449
data/lib/rakumarket/client.rb
CHANGED
@@ -1,89 +1,148 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
1
2
|
module Rakumarket
|
2
|
-
class Client
|
3
|
+
class Client < Spitter
|
3
4
|
attr_accessor :developer_id
|
4
5
|
include HTTParty
|
5
6
|
base_uri "http://api.rakuten.co.jp/rws/3.0"
|
6
7
|
format :json
|
7
8
|
|
8
|
-
|
9
|
-
|
9
|
+
parameter :developer_id => 'developerId'
|
10
|
+
|
11
|
+
def self.request(params) new(params).request end
|
12
|
+
|
13
|
+
def initialize(params={})
|
14
|
+
@developer_id = params[:developer_id] || Rakumarket.developer_id
|
15
|
+
super params.merge(default_options)
|
10
16
|
end
|
11
17
|
|
12
|
-
def request
|
13
|
-
@
|
14
|
-
transform_request!
|
15
|
-
@response = self.class.get("/json",:query => @options)
|
16
|
-
respond
|
18
|
+
def request
|
19
|
+
@response = self.class.get("/json", :query => parse)
|
17
20
|
end
|
18
21
|
|
19
22
|
private
|
20
|
-
def
|
21
|
-
|
22
|
-
|
23
|
+
def default_options
|
24
|
+
{ :developer_id => @developer_id,
|
25
|
+
:operation => nil,
|
26
|
+
:version => nil }
|
23
27
|
end
|
24
28
|
|
25
|
-
def
|
26
|
-
|
27
|
-
end
|
29
|
+
def self.get(*args); handle_response super end
|
30
|
+
def self.post(*args); handle_response super end
|
28
31
|
|
29
|
-
def
|
30
|
-
|
32
|
+
def self.handle_response(response)
|
33
|
+
case response["Header"]["Status"]
|
34
|
+
when "Success"; response
|
35
|
+
else; raise RakumarketError.new({:status => response["Header"]["Status"], :status_msg => response["Header"]["StatusMsg"]})
|
36
|
+
end
|
37
|
+
response
|
31
38
|
end
|
39
|
+
end
|
32
40
|
|
33
|
-
|
34
|
-
|
41
|
+
class TrueToOne
|
42
|
+
def self.parse value
|
43
|
+
value ? "1" : "0"
|
35
44
|
end
|
45
|
+
end
|
36
46
|
|
37
|
-
|
38
|
-
|
39
|
-
|
47
|
+
class TrueToZero
|
48
|
+
def self.parse value
|
49
|
+
value ? "0" : "1"
|
40
50
|
end
|
51
|
+
end
|
41
52
|
|
42
|
-
|
43
|
-
|
44
|
-
|
53
|
+
class ItemSearchClient < Client
|
54
|
+
|
55
|
+
OPERATION = "ItemSearch"
|
56
|
+
VERSION = "2010-09-15"
|
45
57
|
|
46
|
-
|
47
|
-
|
58
|
+
class SortOrder
|
59
|
+
def self.parse(value) ITEM_SEARCH_SORT_ORDERS[value.downcase] end
|
48
60
|
end
|
49
61
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
def response_values_to_transform; {} end
|
62
|
+
class CountryCode
|
63
|
+
def self.parse(value) INTERNATIONAL_DELIVERY_AREA_CODES[value] end
|
64
|
+
end
|
54
65
|
|
55
|
-
|
56
|
-
|
57
|
-
hash[v] = hash.delete(k) if hash.has_key?(k)
|
58
|
-
end
|
59
|
-
hash
|
66
|
+
class NextDayAreaCode
|
67
|
+
def self.parse(value) ASURAKU_DELIVERY_AREA_CODES[value] end
|
60
68
|
end
|
61
69
|
|
62
|
-
|
63
|
-
|
64
|
-
|
70
|
+
class PurchaseType
|
71
|
+
def self.parse(value)
|
72
|
+
{"normal" => 0, "regular" => 1, "distribution" => 2}[value]
|
65
73
|
end
|
66
|
-
hash
|
67
74
|
end
|
68
75
|
|
69
|
-
|
70
|
-
|
76
|
+
parameter :operation, :with => lambda { OPERATION }
|
77
|
+
parameter :version, :with => lambda { VERSION }
|
78
|
+
parameter :keyword
|
79
|
+
parameter :page
|
80
|
+
parameters :price do
|
81
|
+
parameter :minimum => 'minPrice'
|
82
|
+
parameter :maximum => 'maxPrice'
|
71
83
|
end
|
72
|
-
|
73
|
-
|
84
|
+
parameters :shipping do
|
85
|
+
parameter :must_ship_international => 'shipOverseasFlag', :with => TrueToOne
|
86
|
+
parameter :country => 'shipOverseasArea', :with => CountryCode
|
87
|
+
parameter :must_ship_next_day => 'asurakuFlag', :with => TrueToOne
|
88
|
+
parameter :next_day_area => 'asurakuArea', :with => NextDayAreaCode
|
89
|
+
parameter :must_include_cost => 'postageFlag', :with => TrueToOne
|
90
|
+
end
|
91
|
+
parameter :affiliate_id => 'affiliateId'
|
92
|
+
parameter :call_back => 'callBack'
|
93
|
+
parameter :items_per_page => 'hits'
|
94
|
+
parameter :shop_code => 'shopCode'
|
95
|
+
parameter :genre_id => 'genreId'
|
96
|
+
parameter :order => 'sort', :with => SortOrder
|
97
|
+
parameter :must_be_available => 'availability', :with => TrueToOne
|
98
|
+
parameter :deep_search => 'field', :with => TrueToZero
|
99
|
+
parameter :mobile => 'carrier', :with => TrueToOne
|
100
|
+
parameter :must_have_image => 'imageFlag', :with => TrueToOne
|
101
|
+
parameter :or_search => 'orFlag', :with => TrueToOne
|
102
|
+
parameter :exclude_keyword => 'NGKeyword'
|
103
|
+
parameter :include_genre_info => 'genreInformationFlag', :with => TrueToOne
|
104
|
+
parameter :purchase_type => 'purchaseType', :with => PurchaseType
|
105
|
+
parameter :must_have_point_multiplication => 'pointRateFlag', :with => TrueToOne
|
106
|
+
parameter :point_multiplication_factor => 'pointRate'
|
107
|
+
parameter :must_accept_credit_cards => 'creditCardFlag', :with => TrueToOne
|
108
|
+
|
109
|
+
def request
|
110
|
+
response = super
|
111
|
+
ItemList.parse(response)
|
74
112
|
end
|
75
113
|
|
76
|
-
|
77
|
-
def self.post(*args); handle_response super end
|
114
|
+
end
|
78
115
|
|
79
|
-
|
80
|
-
case response["Header"]["Status"]
|
81
|
-
when "Success"; response
|
82
|
-
else; raise RakumarketError.new(Hashie::Mash.new({:status => response["Header"]["Status"], :status_msg => response["Header"]["StatusMsg"]}))
|
83
|
-
end
|
116
|
+
class GenreSearchClient < Client
|
84
117
|
|
85
|
-
|
118
|
+
OPERATION = "GenreSearch"
|
119
|
+
VERSION = "2007-04-11"
|
86
120
|
|
121
|
+
parameter :operation, :with => lambda { OPERATION }
|
122
|
+
parameter :version, :with => lambda { VERSION }
|
123
|
+
parameter :genre_id => 'genreId'
|
124
|
+
parameter :return_immediate_parent => 'genrePath', :with => TrueToZero
|
125
|
+
|
126
|
+
def request
|
127
|
+
response = super
|
128
|
+
Genre.parse(response)
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
|
133
|
+
class ItemLookupClient < Client
|
134
|
+
OPERATION = "ItemCodeSearch"
|
135
|
+
VERSION = "2010-08-05"
|
136
|
+
|
137
|
+
parameter :operation, :with => lambda { OPERATION }
|
138
|
+
parameter :version, :with => lambda { VERSION }
|
139
|
+
parameter :code => 'itemCode'
|
140
|
+
parameter :mobile => 'carrier', :with => TrueToOne
|
141
|
+
|
142
|
+
def request
|
143
|
+
response = super
|
144
|
+
Item.parse(response)
|
87
145
|
end
|
88
146
|
end
|
147
|
+
|
89
148
|
end
|
@@ -0,0 +1,222 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
module Rakumarket
|
3
|
+
|
4
|
+
INTERNATIONAL_DELIVERY_AREA_CODES = {
|
5
|
+
"worldwide" => "ALL",
|
6
|
+
"usa" => "US",
|
7
|
+
"argentina" => "AR",
|
8
|
+
"brazil" => "BR",
|
9
|
+
"canada" => "CA",
|
10
|
+
"mexico" => "MX",
|
11
|
+
"austria" => "AT",
|
12
|
+
"belgium" => "BE",
|
13
|
+
"denmark" => "DK",
|
14
|
+
"france" => "FR",
|
15
|
+
"germany" => "DE",
|
16
|
+
"greece" => "GR",
|
17
|
+
"italy" => "IT",
|
18
|
+
"morocco" => "MA",
|
19
|
+
"netherlands" => "NL",
|
20
|
+
"poland" => "PL",
|
21
|
+
"portugal" => "PT",
|
22
|
+
"russia" => "RU",
|
23
|
+
"spain" => "ES",
|
24
|
+
"sweden" => "SE",
|
25
|
+
"switzerland" => "CH",
|
26
|
+
"turkey" => "TR",
|
27
|
+
"england" => "GB",
|
28
|
+
"australia" => "AU",
|
29
|
+
"china" => "CN",
|
30
|
+
"hong kong" => "HK",
|
31
|
+
"india" => "IN",
|
32
|
+
"indonesia" => "ID",
|
33
|
+
"south korea" => "KR",
|
34
|
+
"malaysia" => "MY",
|
35
|
+
"new zealand" => "NZ",
|
36
|
+
"philipines" => "PH",
|
37
|
+
"singapore" => "SG",
|
38
|
+
"taiwan" => "TW",
|
39
|
+
"thailand" => "TH",
|
40
|
+
"vietnam" => "VN"
|
41
|
+
}
|
42
|
+
|
43
|
+
INTERNATIONAL_DELIVERY_AREA_NAMES = {
|
44
|
+
"ワールドワイド" => "worldwide",
|
45
|
+
"アメリカ" => "usa",
|
46
|
+
"アルゼンチン" => "argentina",
|
47
|
+
"ブラジル" => "brazil",
|
48
|
+
"カナダ" => "canada",
|
49
|
+
"メキシコ" => "mexico",
|
50
|
+
"オーストリア" => "austria",
|
51
|
+
"ベルギー" => "belgium",
|
52
|
+
"デンマーク" => "denmark",
|
53
|
+
"フランス" => "france",
|
54
|
+
"ドイツ" => "germany",
|
55
|
+
"ギリシャ" => "greece",
|
56
|
+
"イタリア" => "italy",
|
57
|
+
"モロッコ" => "morocco",
|
58
|
+
"オランダ" => "netherlands",
|
59
|
+
"ポーランド" => "poland",
|
60
|
+
"ポルトガル" => "portugal",
|
61
|
+
"ロシア" => "russia",
|
62
|
+
"スペイン" => "spain",
|
63
|
+
"スウェーデン" => "sweden",
|
64
|
+
"スイス" => "switzerland",
|
65
|
+
"トルコ" => "turkey",
|
66
|
+
"英国" => "england",
|
67
|
+
"オーストラリア" => "australia",
|
68
|
+
"中国" => "china",
|
69
|
+
"香港" => "hong kong",
|
70
|
+
"インド" => "india",
|
71
|
+
"インドネシア" => "indonesia",
|
72
|
+
"韓国" => "south korea",
|
73
|
+
"マレーシア" => "malaysia",
|
74
|
+
"ニュージーランド" => "new zealand",
|
75
|
+
"フィリピン" => "philipines",
|
76
|
+
"シンガポール" => "singapore",
|
77
|
+
"台湾" => "taiwan",
|
78
|
+
"タイ" => "thailand",
|
79
|
+
"ベトナム" => "vietnam",
|
80
|
+
}
|
81
|
+
|
82
|
+
ASURAKU_DELIVERY_AREA_CODES = {
|
83
|
+
"all" => "0",
|
84
|
+
"hokkaido" => "1",
|
85
|
+
"tohoku" => "101",
|
86
|
+
"aomori" => "2",
|
87
|
+
"iwate" => "3",
|
88
|
+
"miyagi" => "4",
|
89
|
+
"akita" => "5",
|
90
|
+
"yamagata" => "6",
|
91
|
+
"fukushima" => "7",
|
92
|
+
"kanto" => "102",
|
93
|
+
"ibaraki" => "8",
|
94
|
+
"tochigi" => "9",
|
95
|
+
"gunma" => "10",
|
96
|
+
"saitama" => "11",
|
97
|
+
"chiba" => "12",
|
98
|
+
"tokyo" => "13",
|
99
|
+
"kanagawa" => "14",
|
100
|
+
"koshinetsu" => "103",
|
101
|
+
"niigata" => "15",
|
102
|
+
"yamanashi" => "19",
|
103
|
+
"nagano" => "20",
|
104
|
+
"hokuriku" => "104",
|
105
|
+
"toyama" => "16",
|
106
|
+
"ishikawa" => "17",
|
107
|
+
"fukui" => "18",
|
108
|
+
"tokai" => "105",
|
109
|
+
"gifu" => "21",
|
110
|
+
"shizuoka" => "22",
|
111
|
+
"aichi" => "23",
|
112
|
+
"mie" => "24",
|
113
|
+
"kansai" => "106",
|
114
|
+
"shiga" => "25",
|
115
|
+
"kyoto" => "26",
|
116
|
+
"osaka" => "27",
|
117
|
+
"hyogo" => "28",
|
118
|
+
"nara" => "29",
|
119
|
+
"wakayama" => "30",
|
120
|
+
"chugoku" => "107",
|
121
|
+
"tottori" => "31",
|
122
|
+
"shimane" => "32",
|
123
|
+
"okayama" => "33",
|
124
|
+
"hiroshima" => "34",
|
125
|
+
"yamaguchi" => "35",
|
126
|
+
"shikoku" => "108",
|
127
|
+
"tokushima" => "36",
|
128
|
+
"kagawa" => "37",
|
129
|
+
"ehime" => "38",
|
130
|
+
"kochi" => "39",
|
131
|
+
"kyushu" => "109",
|
132
|
+
"fukuoka" => "40",
|
133
|
+
"saga" => "41",
|
134
|
+
"nagasaki" => "42",
|
135
|
+
"kumamoto" => "43",
|
136
|
+
"ooita" => "44",
|
137
|
+
"miyaza" => "45",
|
138
|
+
"kagoshima" => "46",
|
139
|
+
"okinawa" => "47"
|
140
|
+
}
|
141
|
+
|
142
|
+
ASURAKU_DELIVERY_AREA_NAMES = {
|
143
|
+
"全地域" => "all",
|
144
|
+
"北海道エリアのすべて" => "hokkaido",
|
145
|
+
"北海道" => "hokkaido",
|
146
|
+
"東北エリアのすべて" => "tohoku",
|
147
|
+
"青森県" => "aomori",
|
148
|
+
"岩手県" => "iwate",
|
149
|
+
"宮城県" => "miyagi",
|
150
|
+
"秋田県" => "akita",
|
151
|
+
"山形県" => "yamagata",
|
152
|
+
"福島県" => "fukushima",
|
153
|
+
"関東エリアのすべて" => "kanto",
|
154
|
+
"茨城県" => "ibaraki",
|
155
|
+
"栃木県" => "tochigi",
|
156
|
+
"群馬県" => "gunma",
|
157
|
+
"埼玉県" => "saitama",
|
158
|
+
"千葉県" => "chiba",
|
159
|
+
"東京都" => "tokyo",
|
160
|
+
"神奈川県" => "kanagawa",
|
161
|
+
"甲信越エリアのすべて" => "koshinetsu",
|
162
|
+
"新潟県" => "niigata",
|
163
|
+
"山梨県" => "yamanashi",
|
164
|
+
"長野県" => "nagano",
|
165
|
+
"北陸エリアのすべて" => "hokuriku",
|
166
|
+
"富山県" => "toyama",
|
167
|
+
"石川県" => "ishikawa",
|
168
|
+
"福井県" => "fukui",
|
169
|
+
"東海エリアのすべて" => "tokai",
|
170
|
+
"岐阜県" => "gifu",
|
171
|
+
"静岡県" => "shizuoka",
|
172
|
+
"愛知県" => "aichi",
|
173
|
+
"三重県" => "mie",
|
174
|
+
"関西エリアのすべて" => "kansai",
|
175
|
+
"滋賀県" => "shiga",
|
176
|
+
"京都府" => "kyoto",
|
177
|
+
"大阪府" => "osaka",
|
178
|
+
"兵庫県" => "hyogo",
|
179
|
+
"奈良県" => "nara",
|
180
|
+
"和歌山県" => "wakayama",
|
181
|
+
"中国エリアのすべて" => "chugoku",
|
182
|
+
"鳥取県" => "tottori",
|
183
|
+
"島根県" => "shimane",
|
184
|
+
"岡山県" => "okayama",
|
185
|
+
"広島県" => "hiroshima",
|
186
|
+
"山口県" => "yamaguchi",
|
187
|
+
"四国エリアのすべて" => "shikoku",
|
188
|
+
"徳島県" => "tokushima",
|
189
|
+
"香川県" => "kagawa",
|
190
|
+
"愛媛県" => "ehime",
|
191
|
+
"高知県" => "kochi",
|
192
|
+
"九州エリアのすべて" => "kyushu",
|
193
|
+
"福岡県" => "fukuoka",
|
194
|
+
"佐賀県" => "saga",
|
195
|
+
"長崎県" => "nagasaki",
|
196
|
+
"熊本県" => "kumamoto",
|
197
|
+
"大分県" => "ooita",
|
198
|
+
"宮崎県" => "miyaza",
|
199
|
+
"鹿児島県" => "kagoshima",
|
200
|
+
"沖縄エリアのすべて" => "okinawa",
|
201
|
+
"沖縄県" => "okinawa"
|
202
|
+
}
|
203
|
+
|
204
|
+
ITEM_SEARCH_SORT_ORDERS = {
|
205
|
+
"affiliate_rate" => "+affiliateRate",
|
206
|
+
"affiliate_rate asc" => "+affiliateRate",
|
207
|
+
"affiliate_rate desc" => "-affiliateRate",
|
208
|
+
"review_count" => "+reviewCount",
|
209
|
+
"review_count asc" => "+reviewCount",
|
210
|
+
"review_count desc" => "-reviewCount",
|
211
|
+
"review_average" => "+reviewAverage",
|
212
|
+
"review_average asc" => "+reviewAverage",
|
213
|
+
"review_average desc" => "-reviewAverage",
|
214
|
+
"price" => "+itemPrice",
|
215
|
+
"price asc" => "+itemPrice",
|
216
|
+
"price desc" => "-itemPrice",
|
217
|
+
"updated_at" => "+updateTimestamp",
|
218
|
+
"updated_at asc" => "+updateTimestamp",
|
219
|
+
"updated_at desc" => "-updateTimestamp"
|
220
|
+
}
|
221
|
+
|
222
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
module Rakumarket
|
4
|
+
class Base < NibblerJSON
|
5
|
+
end
|
6
|
+
|
7
|
+
class OneToTrue
|
8
|
+
def self.parse(val) {"1" => true, "0" => false}[val.to_s] end
|
9
|
+
end
|
10
|
+
|
11
|
+
class ZeroToTrue
|
12
|
+
def self.parse(val) {"0" => true, "1" => false}[val.to_s] end
|
13
|
+
end
|
14
|
+
|
15
|
+
class ParseTime < Time
|
16
|
+
def self.parse(val) super unless val.blank? end
|
17
|
+
end
|
18
|
+
|
19
|
+
class InternationalDeliveryCountryList
|
20
|
+
def self.parse(val)
|
21
|
+
val.split('/').map{|a| INTERNATIONAL_DELIVERY_AREA_NAMES[a] || a} if val.respond_to?(:split)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class NextDayAreaList
|
26
|
+
def self.parse(val)
|
27
|
+
val.split('/').map{|a| ASURAKU_DELIVERY_AREA_NAMES[a] || a} if val.respond_to?(:split)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class Shipping < Base
|
32
|
+
element 'postageFlag' => :cost_included, :with => ZeroToTrue
|
33
|
+
element 'shipOverseasFlag' => :international, :with => OneToTrue
|
34
|
+
element 'shipOverseasArea' => :countries, :with => InternationalDeliveryCountryList
|
35
|
+
element 'asurakuFlag' => :next_day, :with => OneToTrue
|
36
|
+
element 'asurakuArea' => :next_day_areas, :with => NextDayAreaList
|
37
|
+
|
38
|
+
def cost_included?; cost_included end
|
39
|
+
def international?; international end
|
40
|
+
def next_day?; next_day end
|
41
|
+
end
|
42
|
+
|
43
|
+
class Item < Base
|
44
|
+
element 'itemName' => :name
|
45
|
+
element 'catchcopy' => :catchphrase
|
46
|
+
element 'itemCode' => :code
|
47
|
+
element 'itemPrice' => :price
|
48
|
+
element 'itemCaption' => :caption
|
49
|
+
element 'itemUrl' => :url
|
50
|
+
element 'genreId' => :genre_id
|
51
|
+
element 'affiliateUrl' => :affiliate_url
|
52
|
+
element 'imageFlag' => :has_image, :with => OneToTrue
|
53
|
+
element 'smallImageUrl' => :small_image_url
|
54
|
+
element 'mediumImageUrl' => :medium_image_url
|
55
|
+
element 'availability' => :available, :with => OneToTrue
|
56
|
+
element 'taxFlag' => :tax_included, :with => ZeroToTrue
|
57
|
+
element 'creditCardFlag' => :credit_cards_accepted, :with => OneToTrue
|
58
|
+
element 'shopOfTheYearFlag' => :shop_of_the_year, :with => OneToTrue
|
59
|
+
element 'affiliateRate' => :affiliate_rate
|
60
|
+
element 'startTime' => :start_time, :with => ParseTime
|
61
|
+
element 'endTime' => :end_time, :with => ParseTime
|
62
|
+
element 'reviewCount' => :review_count
|
63
|
+
element 'reviewAverage' => :review_average
|
64
|
+
element 'pointRate' => :point_multiplication_factor
|
65
|
+
element 'pointRateStartTime' => :point_multiplication_start_time, :with => ParseTime
|
66
|
+
element 'pointRateEndTime' => :point_multiplication_end_time, :with => ParseTime
|
67
|
+
element :shop do
|
68
|
+
element 'shopName' => :name
|
69
|
+
element 'shopCode' => :code
|
70
|
+
element 'shopUrl' => :url
|
71
|
+
end
|
72
|
+
element :shipping, :with => Shipping
|
73
|
+
|
74
|
+
def has_image?; has_image end
|
75
|
+
def available?; available end
|
76
|
+
def tax_included?; tax_included end
|
77
|
+
def credit_cards_accepted?; credit_cards_accepted end
|
78
|
+
def shop_of_the_year?; shop_of_the_year end
|
79
|
+
|
80
|
+
def self.parse(response)
|
81
|
+
response = response.dup['Body']['ItemCodeSearch']['Items']['Item'].first if response['Body'] && response['Body']['ItemCodeSearch']
|
82
|
+
response['shop'] = {
|
83
|
+
'shopName' => response.delete('shopName'),
|
84
|
+
'shopCode' => response.delete('shopCode'),
|
85
|
+
'shopUrl' => response.delete('shopUrl')
|
86
|
+
}
|
87
|
+
response['shipping'] = {
|
88
|
+
'postageFlag' => response.delete('postageFlag'),
|
89
|
+
'shipOverseasFlag' => response.delete('shipOverseasFlag'),
|
90
|
+
'shipOverseasArea' => response.delete('shipOverseasArea'),
|
91
|
+
'asurakuFlag' => response.delete('asurakuFlag'),
|
92
|
+
'asurakuArea' => response.delete('asurakuArea')
|
93
|
+
}
|
94
|
+
super(response)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
class ItemList < Base
|
99
|
+
element 'count' => :total_item_count
|
100
|
+
element 'pageCount' => :page_count
|
101
|
+
element :page
|
102
|
+
elements :items, :with => Item
|
103
|
+
|
104
|
+
def self.parse(response)
|
105
|
+
response = response.dup['Body']['ItemSearch']
|
106
|
+
response['items'] = response.delete('Items')['Item']
|
107
|
+
super(response)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
class Genre < Base
|
112
|
+
element 'genreId' => :id
|
113
|
+
element 'genreName' => :name
|
114
|
+
element :parent, :with => Genre
|
115
|
+
elements 'child' => :children, :with => Genre
|
116
|
+
|
117
|
+
def self.parse(response)
|
118
|
+
response = response.dup['Body']['GenreSearch'] if response['Body']
|
119
|
+
response['genreId'] = response['current'].first['genreId'] if response['current'] && response['current'].any?
|
120
|
+
response['genreName'] = response['current'].first['genreName'] if response['current'] && response['current'].any?
|
121
|
+
super(response)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# DSL for defining data extraction rules from an abstract document object
|
2
|
+
module SpitterMethods
|
3
|
+
def self.extended(base)
|
4
|
+
base.send(:include, InstanceMethods) if base.is_a? Class
|
5
|
+
end
|
6
|
+
|
7
|
+
# Declare a singular spitting rule
|
8
|
+
def parameter(*args, &block)
|
9
|
+
key, name, delegate = parse_rule_declaration(*args, &block)
|
10
|
+
rules[key] = [name, delegate]
|
11
|
+
key
|
12
|
+
end
|
13
|
+
|
14
|
+
# Declare a plural spitting rule
|
15
|
+
def parameters(*args, &block)
|
16
|
+
key = parameter(*args, &block)
|
17
|
+
rules[key] << true
|
18
|
+
end
|
19
|
+
|
20
|
+
# Parsing rules declared with `parameter` or `parameters`
|
21
|
+
def rules
|
22
|
+
@rules ||= {}
|
23
|
+
end
|
24
|
+
|
25
|
+
# Process data by creating a new instance
|
26
|
+
def parse(params) new(params).parse end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
# Make subclasses inherit the parsing rules
|
31
|
+
def inherited(subclass)
|
32
|
+
super
|
33
|
+
subclass.rules.update self.rules
|
34
|
+
end
|
35
|
+
|
36
|
+
# Rule declaration forms:
|
37
|
+
#
|
38
|
+
# { :key => 'property', :with => delegate }
|
39
|
+
# #=> [:key, 'property', delegate]
|
40
|
+
#
|
41
|
+
# :title
|
42
|
+
# #=> [:title, 'title', nil]
|
43
|
+
def parse_rule_declaration(*args, &block)
|
44
|
+
options, name = Hash === args.last ? args.pop : {}, args.first
|
45
|
+
delegate = options.delete(:with)
|
46
|
+
key, property = name ? [name.to_sym, name.to_s] : options.to_a.flatten
|
47
|
+
property = property[key.to_s] if property.is_a?(Proc)
|
48
|
+
property = property.method(property.respond_to?(:call) ? :call : :parse)[key.to_s] if property.is_a?(Class)
|
49
|
+
raise ArgumentError, "invalid rule declaration: #{args.inspect}" unless property
|
50
|
+
# eval block in context of a new scraper subclass
|
51
|
+
delegate = Class.new(Spitter, &block) if block_given?
|
52
|
+
return key, property, delegate
|
53
|
+
end
|
54
|
+
|
55
|
+
def base_parser_class
|
56
|
+
klass = self
|
57
|
+
klass = klass.superclass until klass.superclass == Object
|
58
|
+
klass
|
59
|
+
end
|
60
|
+
|
61
|
+
module InstanceMethods
|
62
|
+
|
63
|
+
# Initialize the parser with a parameters
|
64
|
+
def initialize(params)
|
65
|
+
@params = params
|
66
|
+
end
|
67
|
+
|
68
|
+
def params
|
69
|
+
@params ||= {}
|
70
|
+
end
|
71
|
+
|
72
|
+
def parse
|
73
|
+
request_params = {}
|
74
|
+
self.class.rules.each do |target, (key, delegate, plural)|
|
75
|
+
if @params.has_key?(target)
|
76
|
+
if plural
|
77
|
+
request_params.merge!(delegate.parse(@params[target]))
|
78
|
+
else
|
79
|
+
request_params[key] = parse_result(@params[target], delegate)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
request_params
|
84
|
+
end
|
85
|
+
|
86
|
+
protected
|
87
|
+
|
88
|
+
# `delegate` is optional, but should respond to `call` or `parse`
|
89
|
+
def parse_result(node, delegate)
|
90
|
+
if delegate
|
91
|
+
method = delegate.is_a?(Proc) ? delegate : delegate.method(delegate.respond_to?(:call) ? :call : :parse)
|
92
|
+
method.arity == 1 ? method[node] : (method.arity == 2 ? method[node, self] : method[])
|
93
|
+
else
|
94
|
+
node
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
class Spitter
|
101
|
+
extend SpitterMethods
|
102
|
+
end
|
data/lib/rakumarket/version.rb
CHANGED