nicoscraper 0.2.4 → 0.2.5
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +2 -1
- data/Gemfile.lock +3 -1
- data/README.md +152 -28
- data/VERSION +1 -1
- data/lib/classes/connector.rb +330 -0
- data/lib/classes/converter.rb +73 -0
- data/lib/classes/header.rb +9 -0
- data/lib/classes/movie.rb +521 -0
- data/lib/classes/mylist.rb +318 -0
- data/lib/classes/parser.rb +235 -0
- data/lib/classes/searcher.rb +248 -0
- data/lib/classes/tools.rb +15 -0
- data/lib/config/wait.rb +63 -0
- data/lib/nicoscraper.rb +31 -0
- data/test/movie_spec.rb +51 -3
- data/test/searcher_spec.rb +207 -0
- metadata +36 -22
- data/lib/connector.rb +0 -364
- data/lib/converter.rb +0 -72
- data/lib/movie.rb +0 -518
- data/lib/mylist.rb +0 -317
- data/lib/namespace.rb +0 -3
- data/lib/parser.rb +0 -234
- data/lib/searcher.rb +0 -243
- data/nicoscraper.gemspec +0 -72
data/Gemfile
CHANGED
@@ -2,7 +2,8 @@ source "http://rubygems.org"
|
|
2
2
|
# Add dependencies required to use your gem here.
|
3
3
|
# Example:
|
4
4
|
# gem "activesupport", ">= 2.3.5"
|
5
|
-
gem "damerau-levenshtein", ">= 0"
|
5
|
+
gem "damerau-levenshtein", ">= 0.5.3"
|
6
|
+
gem "libxml-ruby", ">= 2.2.2"
|
6
7
|
|
7
8
|
# Add dependencies to develop your gem here.
|
8
9
|
# Include everything needed to run rake, tests, features, etc.
|
data/Gemfile.lock
CHANGED
@@ -7,6 +7,7 @@ GEM
|
|
7
7
|
bundler (~> 1.0)
|
8
8
|
git (>= 1.2.5)
|
9
9
|
rake
|
10
|
+
libxml-ruby (2.2.2)
|
10
11
|
rake (0.8.7)
|
11
12
|
rcov (0.9.10)
|
12
13
|
shoulda (2.11.3)
|
@@ -16,8 +17,9 @@ PLATFORMS
|
|
16
17
|
|
17
18
|
DEPENDENCIES
|
18
19
|
bundler (~> 1.0.0)
|
19
|
-
damerau-levenshtein
|
20
|
+
damerau-levenshtein (>= 0.5.3)
|
20
21
|
jeweler (~> 1.6.4)
|
22
|
+
libxml-ruby (>= 2.2.2)
|
21
23
|
rake (= 0.8.7)
|
22
24
|
rcov
|
23
25
|
shoulda
|
data/README.md
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
NicoScraper
|
2
2
|
====================
|
3
3
|
|
4
|
-
**My site:** [http://hdemon.net](http://hdemon.net)
|
5
|
-
**GitHub:** [http://github.com/hdemon/nicoscraper](http://github.com/hdemon/nicoscraper)
|
4
|
+
**My site:** [http://hdemon.net](http://hdemon.net/)
|
5
|
+
**GitHub:** [http://github.com/hdemon/nicoscraper/](http://github.com/hdemon/nicoscraper)
|
6
6
|
**Author:** Masami Yonehara
|
7
7
|
**Copyright:** 2011
|
8
8
|
**License:** MIT License
|
9
|
-
**Latest Version:** 0.2.
|
10
|
-
**Release Date:** Sep
|
9
|
+
**Latest Version:** 0.2.5
|
10
|
+
**Release Date:** Sep 24th 2011
|
11
11
|
|
12
12
|
|
13
13
|
何をするライブラリ?
|
@@ -17,7 +17,7 @@ NicoScraper
|
|
17
17
|
|
18
18
|
インストール
|
19
19
|
------
|
20
|
-
Ruby 1.9.2
|
20
|
+
Ruby 1.9.2において動作を確認しています。インストールには、
|
21
21
|
|
22
22
|
$ gem install nicoscraper
|
23
23
|
|
@@ -25,7 +25,10 @@ NicoScraper
|
|
25
25
|
|
26
26
|
require 'nicoscraper'
|
27
27
|
|
28
|
-
|
28
|
+
で使い始めて下さい。内部的にlibxml-rubyを使っており、インストール時に最新版に更新される可能性があります。
|
29
|
+
|
30
|
+
なお、実行前に[注意点、および免責事項](#___________)をお読み下さい。
|
31
|
+
|
29
32
|
|
30
33
|
基本的な使い方
|
31
34
|
------
|
@@ -34,7 +37,7 @@ NicoScraper
|
|
34
37
|
|
35
38
|
###動画情報の取得
|
36
39
|
|
37
|
-
|
40
|
+
例えば`sm1097445`という動画IDから、タイトルや動画の長さ、現在の閲覧数等の詳細な情報を知りたいときは、
|
38
41
|
|
39
42
|
require 'nicoscraper'
|
40
43
|
|
@@ -43,7 +46,7 @@ NicoScraper
|
|
43
46
|
|
44
47
|
p movie
|
45
48
|
|
46
|
-
Movieクラスのインスタンス(以下「動画インスタンス」)を動画ID
|
49
|
+
Movieクラスのインスタンス(以下「動画インスタンス」)を動画IDを与えて生成した後、`getInfo`メソッドを利用します。その結果、
|
47
50
|
|
48
51
|
<Nicos::Movie:0x00000002537aa8
|
49
52
|
@video_id="sm1097445",
|
@@ -126,13 +129,13 @@ NicoScraper
|
|
126
129
|
}
|
127
130
|
}
|
128
131
|
|
129
|
-
|
132
|
+
この例では、`ゆっくり実況プレイpart1リンク`というタグの付く動画を、`post_new`=投稿日時が新しい順からさかのぼって取得し、取得した動画の日付が前日の0時0分を超えるまでそれを続けます。
|
130
133
|
|
131
|
-
ブロック内の第1引数には取得結果に基づく動画インスタンスが与えられるのですが、これは32個分の配列です。なぜ32個のセットなのかと言うと、ご存知のようにニコニコ動画の検索画面はページで区切られており、Searcherモジュールの各メソッドはページ毎に情報を取得し、ページ単位でブロックをコールするからです。
|
134
|
+
ブロック内の第1引数には取得結果に基づく動画インスタンスが与えられるのですが、これは32個分の配列です。なぜ32個のセットなのかと言うと、ご存知のようにニコニコ動画の検索画面はページで区切られており、Searcherモジュールの各メソッドはページ毎に情報を取得し、ページ単位でブロックをコールするからです。HTMLから取得するにしろAtomフィードから取得するにしろ、1ページに32個の動画情報が含まれています。そして、第2引数には現在のページ数が与えられます。
|
132
135
|
|
133
|
-
|
136
|
+
そして、**ブロック内で`continue`の文字列を返すことによりスクレイプが継続します。**つまり、`continue`文字列を返し続けるロジックを組み込まないと、1ページ目を読んだ時点で処理が終了します。これは意図せざる過剰アクセスを防ぐための措置です。
|
134
137
|
|
135
|
-
上の例では、取得した動画の日付を調べ、3日前の0時0
|
138
|
+
上の例では、取得した動画の日付を調べ、3日前の0時0分より前の動画に到達すればそこでループを終える設計です。ループを継続するために取得情報を使うかどうかは任意なので、例えば10分間の制限で取得出来るだけ取得するということも可能でしょう。
|
136
139
|
|
137
140
|
###取得した情報に対する操作
|
138
141
|
|
@@ -140,36 +143,143 @@ NicoScraper
|
|
140
143
|
|
141
144
|
**動画の説明文からタイトルを取得する。**
|
142
145
|
{Nicos::Movie#extrMylist Nicos::Movie::extrMylist}
|
143
|
-
|
146
|
+
|
147
|
+
動画の説明文中に、`mylist/...`という表記で投稿者がマイリストを提示している事があります。`extrMylist`はこれを全て取得し、配列として返します。
|
144
148
|
|
145
149
|
|
146
150
|
**指定したマイリストに、自分自身が入っているかを調べる。**
|
147
151
|
{Nicos::Movie#isBelongsTo Nicos::Movie::isBelongsTo}
|
148
152
|
|
149
153
|
|
154
|
+
|
150
155
|
**そのマイリスト内に含まれる全ての動画の、タイトルの類似性を調べる。**
|
151
156
|
{Nicos::Mylist#getSimilarity Nicos::Mylist::getSimilarity}
|
157
|
+
|
152
158
|
マイリストのシリーズ性を判定するために、マイリスト内の全ての動画の組み合わせで、タイトルの「編集距離」に基づく類似度を計算します。
|
153
159
|
|
154
160
|
|
155
161
|
**その動画が属する、シリーズとみなせるマイリストのIDを返します。**
|
156
162
|
{Nicos::Movie#isSeriesOf Nicos::Movie::isSeriesOf}
|
157
|
-
isBelongsToとgetSimiralityの組み合わせにより、ある動画の説明文中にマイリストの記載がある場合、そのマイリストがタイトルの類似性によるシリーズとみなせるならば、そのIDを返します。
|
158
163
|
|
164
|
+
`isBelongsTo`と`getSimilarity`の組み合わせにより、ある動画の説明文中にマイリストの記載がある場合、そのマイリストがタイトルの類似性によるシリーズとみなせるならば、そのIDを返します。
|
165
|
+
|
166
|
+
|
167
|
+
ウェイト設定について
|
168
|
+
------
|
169
|
+
|
170
|
+
###ウェイトの役割
|
171
|
+
|
172
|
+
Searcherメソッドは継続的なアクセスを行い、またそれ以外のメソッドも実際の運用目的上ある程度の連続使用が前提になると思います。このライブラリは並列的なリクエストを行いませんが、それでも過剰なアクセスに伴うサーバからの拒絶や、あるいはそれ以上に、アカウントの停止もしくは法的責任を追求されるなどの事があり得ないという保証はできません。
|
173
|
+
|
174
|
+
それを防ぐための措置の一つが、`continue`を明示的に返さないとスクレイピングが継続しない仕様ですが、もう一つ、アクセス中のウェイトを任意に設定できるようにしています。具体的には、連続リクエストの上限回数、連続リクエスト後のウェイト、1リクエスト毎のウェイト、連続アクセス拒絶時やサーバ混雑時の再試行までのウェイトなどです。ウェイトの設定は、以下のように`Nicos::Connector::Config::waitConfig`に与えられています。以下はデフォルトの設定です。
|
175
|
+
|
176
|
+
Nicos::Connector::Config::waitConfig = {
|
177
|
+
|
178
|
+
# module::Searcher用の設定
|
179
|
+
'seqAccLimit' => 10, # 連続してリクエストする回数
|
180
|
+
'afterSeq' => 10, # 連続リクエスト後のウェイト(以下全て単位は秒)
|
181
|
+
'each' => 5, # 連続リクエスト時の、1リクエスト毎のウェイト
|
182
|
+
|
183
|
+
# 全メソッド共通の設定
|
184
|
+
'deniedSeqReq'=> { # 連続アクセス拒絶時
|
185
|
+
'retryLimit' => 3, # 再試行回数の上限
|
186
|
+
'wait' => 120 # 再試行までのウェイト
|
187
|
+
},
|
188
|
+
|
189
|
+
'serverIsBusy'=> { # サーバ混雑時
|
190
|
+
'retryLimit' => 3,
|
191
|
+
'wait' => 120
|
192
|
+
},
|
193
|
+
|
194
|
+
'serviceUnavailable' => { # 503時
|
195
|
+
'retryLimit' => 3,
|
196
|
+
'wait' => 120
|
197
|
+
},
|
198
|
+
|
199
|
+
'timedOut' => { # タイムアウト時
|
200
|
+
'retryLimit' => 3,
|
201
|
+
'wait' => 10
|
202
|
+
},
|
203
|
+
|
204
|
+
'increment' => 1 # 異常ステータス時の、次回以降の1リクエスト毎のウェイトの増加量
|
205
|
+
}
|
206
|
+
|
207
|
+
###連続リクエストとは?
|
208
|
+
|
209
|
+
Searcherメソッドはある一定回数のHTTPリクエストを1つの単位とし、その単位のリクエストが終わるごとに休憩を入れます。この1単位を連続リクエストと言います。上の例では、10のリクエストを1単位とし(`seqAccLimit`)、その連続リクエストが終わった後に10秒の休憩を入れる(`afterSeq`)設定になっています。
|
210
|
+
|
211
|
+
なお、連続リクエスト毎に限らず、1リクエスト毎のウェイトも併せて設定できます。上の例では、1リクエスト毎に1秒のウェイトを入れる設定です(`each`)。
|
212
|
+
|
213
|
+
###レスポンスの種類に対する反応について
|
214
|
+
|
215
|
+
ニコニコ動画のサーバのレスポンスには、正常にデータを返す以外にいくつかの反応があります。この反応に応じて再試行するか、それともそのリクエストをパスするかが決定されます。以下はレスポンスの内容と、それに対応するウェイト設定用ハッシュのキーです。
|
216
|
+
|
217
|
+
**1. 404、削除済み**
|
218
|
+
|
219
|
+
これらの場合、何も情報を取得せずに終えます。
|
220
|
+
|
221
|
+
**2. 連続アクセスの拒絶** :`deniedSeqReq`
|
222
|
+
|
223
|
+
"短時間での連続アクセスはご遠慮ください" と表示される場合です。設定に従って再試行します。
|
224
|
+
|
225
|
+
**3. サーバ混雑時** :`serverIsBusy`
|
226
|
+
|
227
|
+
"大変ご迷惑をおかけいたしますが、しばらく時間をあけてから再度検索いただくようご協力をお願いいたします。" と表示される場合です。再試行します。
|
228
|
+
|
229
|
+
**4. 非公開・権限なし**
|
230
|
+
|
231
|
+
動画がマイリストが非公開設定されている場合、あるいはコミュニティ未加入者には非公開になっている動画があります。後者についてはログイン処理を事前に行うことで技術的には取得可能ですが、v 0.2では未実装です。これらの動画の場合、取得をパスします。なお、この場合は403が返っています。
|
232
|
+
|
233
|
+
**5. 503** :`serviceUnavailable`
|
234
|
+
|
235
|
+
メンテナンス時に限らず、稼働時にも稀に発生します。処理全体を中断することはなく、再試行を行います。
|
236
|
+
|
237
|
+
**6. タイムアウト** :`timedOut`
|
238
|
+
|
239
|
+
再試行します。
|
240
|
+
|
241
|
+
2、3、5に該当した場合には、`increment`を指定することで、次回以降の1リクエスト毎のウェイトを増加させることができます。
|
242
|
+
|
243
|
+
###設定方法
|
244
|
+
|
245
|
+
ウェイトは、全メソッドが共有する設定と、各インスタンスのみに有効な設定の2つを定義できます。
|
246
|
+
|
247
|
+
また、全メソッドの共有設定には初期設定があり、それが上に挙げたハッシュです。これを変更するには、Nicos::Connector::Config::setWait`メソッドを利用します。これにより、その後に生成した各インスタンスにおいて、変更した設定が共有されます。
|
248
|
+
|
249
|
+
もう1つの方法は、動画・マイリスト・Searcherモジュール下クラスの特異メソッドとしての`setWait'メソッドを使う方法です。この方法では、そのインスタンスにおいてのみ変更が有効になります。
|
250
|
+
|
251
|
+
なお、setWaitメソッドは指定したキーの部分のみを上書きするため、設定毎に上記の書式のハッシュオブジェクトを用意する必要はありません。
|
252
|
+
|
253
|
+
wait = {
|
254
|
+
'seqAccLimit' => 100,
|
255
|
+
|
256
|
+
'deniedSeqReq'=> {
|
257
|
+
'wait' => 1200
|
258
|
+
}
|
259
|
+
}
|
260
|
+
|
261
|
+
Nicos::Connector::Config::setWait(wait)
|
262
|
+
|
263
|
+
例えばこのようにすることで、次回以降の`seqAccLimit`と`deniedSeqReq -> wait`のみが前回の設定に上書きされます。
|
159
264
|
|
160
265
|
注意点、および免責事項
|
161
266
|
------
|
162
267
|
|
163
|
-
|
268
|
+
繰り返しになりますが、それぞれのメソッドは大半がニコニコ動画へのアクセスを伴い、特にSearcherモジュールは継続的かつ無制限なアクセスを可能にするため、使用方法によっては開発者である私が通常の使用において想定していない負荷を、ニコニコ動画に対して与える可能性があります。
|
164
269
|
|
165
|
-
|
270
|
+
その結果、アカウントの停止や法的な責任を追求される可能性も無いとは言えません。その点を考慮し、上で述べた幾つかの制限を行なっています。特にウェイトは大きめに設定してあります。
|
166
271
|
|
167
|
-
|
272
|
+
このウェイトは、ご自分の責任において変更して下さい。**本ライブラリの使用によって発生した損害および法的な責任については、開発者が現在認識していない、あるいはそのバージョンの公開時には認識していなかったバグに起因するものを含め、一切の責任を負いかねます。**またこのような事情から、予告なく公開を停止する可能性があります。
|
168
273
|
|
274
|
+
なお、HTMLからスクレイプするメソッドよりも、Atomフィードを使うメソッドの方がニコニコ動画側の負荷が(たぶん)軽く、アクセス制限などは経験上起こりにくくなっています。HTMLメソッドを利用した場合、混雑時には結構な頻度で連続アクセスが拒絶されます。**大半の情報はAtomフィードで取得できるため、そうでない情報を取得したい場合に限り、HTMLを利用するメソッドを使うべきでしょう。**
|
169
275
|
|
170
|
-
|
276
|
+
|
277
|
+
|
278
|
+
その他
|
171
279
|
------
|
172
280
|
|
281
|
+
###文中の用語・用法
|
282
|
+
|
173
283
|
**動画インスタンス**
|
174
284
|
Movieクラスのインスタンス
|
175
285
|
|
@@ -180,11 +290,27 @@ Mylistクラスのインスタンス
|
|
180
290
|
ニコニコ動画の各動画に与えられる、sm|nmで始まる一意のID。
|
181
291
|
|
182
292
|
**アイテムID | item_id**
|
183
|
-
動画に与えられるもう一つの一意なIDであり、投稿日時と同じか非常に近いUNIX時間になっている。例えば、"【初音ミク】みくみくにしてあげる♪【してやんよ】"の動画ID
|
293
|
+
動画に与えられるもう一つの一意なIDであり、投稿日時と同じか非常に近いUNIX時間になっている。例えば、"【初音ミク】みくみくにしてあげる♪【してやんよ】"の動画IDは`sm1097445`であり、アイテムIDは`1190218917`である。このアイテムIDを日時に直すと、日本時間における2007年9月20日 1:21:57となるが、動画に投稿日時として表示されるのは、2007年9月20日 1:22:02である。
|
184
294
|
|
185
295
|
|
186
|
-
|
187
|
-
|
296
|
+
###更新履歴
|
297
|
+
|
298
|
+
**v0.2.5**
|
299
|
+
|
300
|
+
+ ヘッダの追加
|
301
|
+
|
302
|
+
+ コードと設定の分離
|
303
|
+
|
304
|
+
**v0.2.4**
|
305
|
+
|
306
|
+
+ ドキュメント作成
|
307
|
+
|
308
|
+
+ Searcherループのバグ修正。
|
309
|
+
|
310
|
+
+ Searcherループの継続判定を、ブロック内で`"continue"`を返す事を要求する方式に変更。
|
311
|
+
|
312
|
+
|
313
|
+
###今後の予定
|
188
314
|
|
189
315
|
**v0.3**
|
190
316
|
|
@@ -199,13 +325,11 @@ Mylistクラスのインスタンス
|
|
199
325
|
+ コミュニティ動画、限定公開動画・マイリストへの対応。
|
200
326
|
|
201
327
|
|
202
|
-
|
203
|
-
|
328
|
+
###要望、バグ報告について
|
329
|
+
以下のどちらかにお願いします。
|
204
330
|
|
205
|
-
|
331
|
+
+ zeitdiebe@gmail.com
|
206
332
|
|
207
|
-
+
|
208
|
-
|
209
|
-
+ Searcherループのバグ修正。
|
333
|
+
+ http://twitter.com/h_demon
|
210
334
|
|
211
|
-
|
335
|
+
GitHubを経由して下さってもいいのですが、まだ慣れていないので対応が送れるかもしれません。
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.2.
|
1
|
+
0.2.5
|
@@ -0,0 +1,330 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.unshift File.dirname(__FILE__)
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'ruby-debug'
|
6
|
+
require 'net/http'
|
7
|
+
|
8
|
+
module Nicos
|
9
|
+
module Connector
|
10
|
+
class Connector < Config
|
11
|
+
def initialize
|
12
|
+
# デフォルトのウェイト設定
|
13
|
+
@seqTime = 0
|
14
|
+
@result = {}
|
15
|
+
@waitConfig = @@waitConfig
|
16
|
+
end
|
17
|
+
attr_accessor :waitConfig
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def notPublic
|
22
|
+
# マイリスト非公開のときに403になる。後で専用の処理を入れるべき。
|
23
|
+
puts "This movie/mylist is not public."
|
24
|
+
@result = "notPublic"
|
25
|
+
return { "order" => "terminate" }
|
26
|
+
end
|
27
|
+
|
28
|
+
def limInCommunity
|
29
|
+
puts "This movie/mylist is limited in comunity members."
|
30
|
+
# ex. item_id -> 1294702905
|
31
|
+
@result = "limInCommunity"
|
32
|
+
return { "order" => "terminate" }
|
33
|
+
end
|
34
|
+
|
35
|
+
def notFound
|
36
|
+
puts "This movie/mylist is not found."
|
37
|
+
@result = "notFound"
|
38
|
+
return { "order" => "terminate" }
|
39
|
+
end
|
40
|
+
|
41
|
+
def deleted
|
42
|
+
puts "This movie/mylist is deleted."
|
43
|
+
@result = "deleted"
|
44
|
+
return { "order" => "terminate" }
|
45
|
+
end
|
46
|
+
|
47
|
+
def deniedSeqReq
|
48
|
+
puts "Denied sequential requests."
|
49
|
+
sleep @waitConfig["deniedSeqReq"]
|
50
|
+
@result = "deniedSeqReq"
|
51
|
+
return { "order" => "retry" }
|
52
|
+
end
|
53
|
+
|
54
|
+
def serverIsBusy
|
55
|
+
puts "The server is busy."
|
56
|
+
sleep @waitConfig["serverIsBusy"]
|
57
|
+
@result = "serverIsBusy"
|
58
|
+
return { "order" => "retry" }
|
59
|
+
end
|
60
|
+
|
61
|
+
def serviceUnavailable
|
62
|
+
puts "Service unavailable."
|
63
|
+
sleep @waitConfig["serviceUnavailable"]
|
64
|
+
@result = "serviceUnavailable"
|
65
|
+
return { "order" => "retry" }
|
66
|
+
end
|
67
|
+
|
68
|
+
def timedOut
|
69
|
+
puts "Request timed out."
|
70
|
+
sleep @waitConfig["timedOut"]
|
71
|
+
@result = "timedOut"
|
72
|
+
return { "order" => "retry" }
|
73
|
+
end
|
74
|
+
|
75
|
+
def success(resBody)
|
76
|
+
sleep @waitConfig["each"]
|
77
|
+
@seqTime += 1
|
78
|
+
|
79
|
+
if @seqTime >= @waitConfig["seqAccLimit"]
|
80
|
+
sleep @waitConfig["afterSeq"]
|
81
|
+
@seqTime = 0
|
82
|
+
end
|
83
|
+
return { "order" => "success", "body" => resBody }
|
84
|
+
end
|
85
|
+
|
86
|
+
def wait(status)
|
87
|
+
puts "Wait for " + waitTime + " second."
|
88
|
+
sleep @waitConfig[status.to_s]
|
89
|
+
end
|
90
|
+
|
91
|
+
public
|
92
|
+
end
|
93
|
+
|
94
|
+
class Xml < Connector
|
95
|
+
def get (host, entity)
|
96
|
+
response = nil
|
97
|
+
|
98
|
+
begin
|
99
|
+
puts "Request to " + host + entity
|
100
|
+
Net::HTTP.start(host, 80) { |http|
|
101
|
+
response = http.get(entity, HEADER)
|
102
|
+
}
|
103
|
+
|
104
|
+
rescue => e
|
105
|
+
puts e
|
106
|
+
rescue Timeout::Error => e
|
107
|
+
timeOut
|
108
|
+
|
109
|
+
else
|
110
|
+
res = case response
|
111
|
+
when Net::HTTPSuccess
|
112
|
+
reviewRes( response.body.force_encoding("UTF-8") )
|
113
|
+
# return response.body.force_encoding("UTF-8")
|
114
|
+
# when Net::HTTPRedirection
|
115
|
+
# fetch(response['location'], limit - 1)
|
116
|
+
when Net::HTTPForbidden
|
117
|
+
forbidden
|
118
|
+
when Net::HTTPNotFound
|
119
|
+
notFound
|
120
|
+
when Net::HTTPServiceUnavailable
|
121
|
+
serviceUnavailable
|
122
|
+
else
|
123
|
+
unknownError
|
124
|
+
end
|
125
|
+
end until res["order"] == "success" ||
|
126
|
+
res["order"] == "terminate"
|
127
|
+
|
128
|
+
res
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
class MylistAtom < Xml
|
133
|
+
private
|
134
|
+
|
135
|
+
def forbidden
|
136
|
+
# マイリストが非公開の場合、html/Atomのどちらへのリクエストであっても、403が返ってくる。
|
137
|
+
notPublic
|
138
|
+
end
|
139
|
+
|
140
|
+
def reviewRes(resBody)
|
141
|
+
if # アクセス集中時
|
142
|
+
/大変ご迷惑をおかけいたしますが、しばらく時間をあけてから再度検索いただくようご協力をお願いいたします。/ =~
|
143
|
+
resBody.force_encoding("UTF-8")
|
144
|
+
then
|
145
|
+
serverIsBusy
|
146
|
+
else
|
147
|
+
success(resBody)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
class TagAtom < Xml
|
153
|
+
private
|
154
|
+
|
155
|
+
def forbidden
|
156
|
+
# マイリストが非公開の場合、html/Atomのどちらへのリクエストであっても、403が返ってくる。
|
157
|
+
notPublic
|
158
|
+
end
|
159
|
+
|
160
|
+
def reviewRes(resBody)
|
161
|
+
if # アクセス集中時
|
162
|
+
/大変ご迷惑をおかけいたしますが、しばらく時間をあけてから再度検索いただくようご協力をお願いいたします。/ =~
|
163
|
+
resBody.force_encoding("UTF-8")
|
164
|
+
then
|
165
|
+
serverIsBusy
|
166
|
+
else
|
167
|
+
success(resBody)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
class GetThumbInfo < Xml
|
173
|
+
private
|
174
|
+
|
175
|
+
def reviewRes(resBody)
|
176
|
+
r = resBody.force_encoding("UTF-8")
|
177
|
+
|
178
|
+
if # getThumbInfoは、該当する動画がない・削除済み・コミュニティ限定でも200が返ってくる。
|
179
|
+
/<nicovideo_thumb_response\sstatus=\"fail\">/ =~ r
|
180
|
+
if /<code>NOT_FOUND<\/code>/ =~ r
|
181
|
+
notFound
|
182
|
+
elsif /<code>DELETED<\/code>/ =~ r
|
183
|
+
deleted
|
184
|
+
elsif /<code>COMMUNITY<\/code>/ =~ r
|
185
|
+
limInCommunity
|
186
|
+
else
|
187
|
+
serverIsBusy
|
188
|
+
end
|
189
|
+
else
|
190
|
+
success(resBody)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
=begin
|
196
|
+
class HtmlConnector < Connector
|
197
|
+
def initialize(mode)
|
198
|
+
@mode = mode
|
199
|
+
# デフォルトのウェイト設定
|
200
|
+
@@waitConfig = {
|
201
|
+
'consec_count' => 10, # 連続してリクエストする回数
|
202
|
+
'consec_wait' => 10, # 連続リクエスト後のウェイト
|
203
|
+
'each' => 10, # 連続リクエスト時の、1リクエスト毎のウェイト
|
204
|
+
|
205
|
+
'200-abnormal' => 300, # アクセス拒絶時(「短時間での連続アクセスは・・・」)の場合の再試行までの時間
|
206
|
+
'unavailable' => 10,
|
207
|
+
'403' => 300, # "403"時の再試行までのウェイト
|
208
|
+
'404' => 300, # "403"時の再試行までのウェイト
|
209
|
+
'increment' => 1, # アクセス拒絶時の、次回以降の1リクエスト毎のウェイトの増加量
|
210
|
+
|
211
|
+
'timeout' => 10, # タイムアウト時の、再試行までのウェイト
|
212
|
+
'500' => 10, # "500"時の再試行までのウェイト
|
213
|
+
'503' => 10, # "503"時の再試行までのウェイト
|
214
|
+
|
215
|
+
'retryLimit' => 3 # 再試行回数の限度
|
216
|
+
}
|
217
|
+
|
218
|
+
# 1つの検索結果画面に表示される動画の数。現時点では32個がデフォルトの模様。
|
219
|
+
@NumOfSearched = 32
|
220
|
+
|
221
|
+
@mech = Mechanize.new
|
222
|
+
# メモリ節約のため、Mechanizeの履歴機能を切る。
|
223
|
+
@mech.max_history = 1
|
224
|
+
|
225
|
+
@consec_count = 0
|
226
|
+
end
|
227
|
+
|
228
|
+
public
|
229
|
+
|
230
|
+
def errorStatus(ex)
|
231
|
+
# 再試行回数が
|
232
|
+
@retryTime += 1
|
233
|
+
if @retryTime >= @wait['allowance_time']
|
234
|
+
return false
|
235
|
+
end
|
236
|
+
|
237
|
+
case ex.response_code
|
238
|
+
when '403' then
|
239
|
+
sleep @wait['403']
|
240
|
+
warn "403"
|
241
|
+
when '500' then
|
242
|
+
sleep @wait['500']
|
243
|
+
warn "500"
|
244
|
+
when '503' then
|
245
|
+
sleep @wait['503']
|
246
|
+
warn "503"
|
247
|
+
else
|
248
|
+
warn "Server error: #{ex.code}"
|
249
|
+
return false
|
250
|
+
end
|
251
|
+
|
252
|
+
@connection = false
|
253
|
+
@failed += 1
|
254
|
+
end
|
255
|
+
|
256
|
+
def htmlReq (url, request, procedure)
|
257
|
+
@failed = 0
|
258
|
+
|
259
|
+
# 再試行ループ
|
260
|
+
begin
|
261
|
+
eachWait
|
262
|
+
@connection = nil
|
263
|
+
request.call(url)
|
264
|
+
|
265
|
+
# タイムアウト時処理
|
266
|
+
rescue TimeoutError
|
267
|
+
timeOut
|
268
|
+
retry
|
269
|
+
|
270
|
+
# Mechanizeでアクセスし、200以外のステータスが返ってきた時
|
271
|
+
# 実際に該当するコードが返ってきたことがないので、正常に動くか不明
|
272
|
+
rescue Mechanize::ResponseCodeError => ex
|
273
|
+
if errorStatus(ex) then retry
|
274
|
+
else break end
|
275
|
+
|
276
|
+
# HTTP Status:200時の処理
|
277
|
+
else
|
278
|
+
procedure.call
|
279
|
+
|
280
|
+
# 失敗カウントが指定回数を超えたらループを終わる。
|
281
|
+
if @failed >= @wait['allowance_time'] then
|
282
|
+
puts 'Exceeded the limit of retry time.'
|
283
|
+
@connection = false
|
284
|
+
break
|
285
|
+
end
|
286
|
+
end until @connection
|
287
|
+
|
288
|
+
# 連続アクセスカウント+1
|
289
|
+
@consec_count += 1
|
290
|
+
# 成功 = true / 失敗 = false
|
291
|
+
return @connection
|
292
|
+
end
|
293
|
+
|
294
|
+
def htmlGet (host, entity)
|
295
|
+
htmlReq(
|
296
|
+
host + entity,
|
297
|
+
lambda { |url|
|
298
|
+
t = Thread.new do
|
299
|
+
@mech.get(url)
|
300
|
+
puts "Requesting for " + url
|
301
|
+
end
|
302
|
+
t.join
|
303
|
+
},
|
304
|
+
# HTTP Status:200時の処理
|
305
|
+
lambda {
|
306
|
+
# 連続アクセス拒絶メッセージが返ってきた時
|
307
|
+
if /短時間での連続アクセスはご遠慮ください/ =~ @mech.page.search('/html').text then
|
308
|
+
puts 'Access rejected.'
|
309
|
+
@connection = false
|
310
|
+
@failed += 1
|
311
|
+
|
312
|
+
# ウェイトを置いた後、今後のページ毎のウェイトを増やす。
|
313
|
+
puts 'Waiting for ' + @wait['rejected'] + 's.'
|
314
|
+
sleep @wait['rejected']
|
315
|
+
@wait['each'] += @wait['increment']
|
316
|
+
puts 'Increased each @wait by ' + @wait['increment'] + 'sec.'
|
317
|
+
else
|
318
|
+
@connection = true
|
319
|
+
end
|
320
|
+
}
|
321
|
+
)
|
322
|
+
|
323
|
+
return @mech.page
|
324
|
+
end
|
325
|
+
|
326
|
+
attr_reader :mech
|
327
|
+
end
|
328
|
+
=end
|
329
|
+
end
|
330
|
+
end
|