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 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.2
10
- **Release Date:** Sep 23rd 2011
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
-  例えば"sm1097445"という動画IDから、タイトルや動画の長さ、現在の閲覧数等の詳細な情報を知りたいときは、
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を与えて生成した後、getInfoメソッドを利用します。その結果、
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
-  この例では、'ゆっくり実況プレイpart1リンク'というタグの付く動画を、post_new=投稿日時が新しい順からさかのぼって取得し、取得した動画の日付が前日の0時0分を超えるまでそれを続けます。
132
+  この例では、`ゆっくり実況プレイpart1リンク`というタグの付く動画を、`post_new`=投稿日時が新しい順からさかのぼって取得し、取得した動画の日付が前日の0時0分を超えるまでそれを続けます。
130
133
 
131
-  ブロック内の第1引数には取得結果に基づく動画インスタンスが与えられるのですが、これは32個分の配列です。なぜ32個のセットなのかと言うと、ご存知のようにニコニコ動画の検索画面はページで区切られており、Searcherモジュールの各メソッドはページ毎に情報を取得し、ページ単位でブロックをコールするからです。Htmlから取得するにしろAtomフィードから取得するにしろ、1ページに32個の動画情報が含まれています。そして、第2引数には現在のページ数が与えられます。
134
+  ブロック内の第1引数には取得結果に基づく動画インスタンスが与えられるのですが、これは32個分の配列です。なぜ32個のセットなのかと言うと、ご存知のようにニコニコ動画の検索画面はページで区切られており、Searcherモジュールの各メソッドはページ毎に情報を取得し、ページ単位でブロックをコールするからです。HTMLから取得するにしろAtomフィードから取得するにしろ、1ページに32個の動画情報が含まれています。そして、第2引数には現在のページ数が与えられます。
132
135
 
133
-  そして、ブロック内で"continue"を返すことによりスクレイプが継続します。つまり、"continue"を返し続けるロジックを組み込まないと、1ページ目を読んだ時点で処理が終了します。これは意図せざる過剰アクセスを防ぐための措置です。
136
+  そして、**ブロック内で`continue`の文字列を返すことによりスクレイプが継続します。**つまり、`continue`文字列を返し続けるロジックを組み込まないと、1ページ目を読んだ時点で処理が終了します。これは意図せざる過剰アクセスを防ぐための措置です。
134
137
 
135
-  上の例では、取得した動画の日付を調べ、3日前の0時0分より前の動画に到達すればそこでループを終える設計です。ループを終えるために取得情報を使うかどうかは任意なので、例えば10分間の制限で取得出来るだけ取得するということも可能でしょう。
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
-  動画の説明文中に、'mylist/...'という表記で投稿者がマイリストを提示している事があります。extrMylistはこれを全て取得し、配列として返します。
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
-  それぞれのメソッドは大半がニコニコ動画へのアクセスを伴い、特にSearcherモジュールは継続的かつ無制限なアクセスを可能にするため、使用には十分注意して下さい。その点を考慮し、Searcherモジュールのデフォルトのウェイトはかなり大きめに設定してあります。
268
+  繰り返しになりますが、それぞれのメソッドは大半がニコニコ動画へのアクセスを伴い、特にSearcherモジュールは継続的かつ無制限なアクセスを可能にするため、使用方法によっては開発者である私が通常の使用において想定していない負荷を、ニコニコ動画に対して与える可能性があります。
164
269
 
165
-  使用する際には、ご自分の責任においてウェイトを変更して下さい。**本ライブラリの使用によって発生した損害および法的な責任については、ライブラリのバグに起因するものを含め、一切の責任を負いかねます。**
270
+  その結果、アカウントの停止や法的な責任を追求される可能性も無いとは言えません。その点を考慮し、上で述べた幾つかの制限を行なっています。特にウェイトは大きめに設定してあります。
166
271
 
167
-  なお、Htmlからスクレイプするメソッドよりも、Atomフィードを使うメソッドの方がニコニコ動画側の負荷が(たぶん)軽く、アクセス制限などは起こりにくくなっています。大半の情報はAtomフィードで取得できるため、そうでない情報を取得したい場合に限り、Htmlを利用するメソッドを使うべきでしょう。
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時間になっている。例えば、"【初音ミク】みくみくにしてあげる♪【してやんよ】"の動画IDsm1097445であり、アイテムID1190218917である。このアイテムIDを日時に直すと、日本時間における2007年9月20日 1:21:57となるが、動画に投稿日時として表示されるのは、2007年9月20日 1:22:02である。
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
- **v0.2.4**
331
+ + zeitdiebe@gmail.com
206
332
 
207
- + ドキュメント作成
208
-
209
- + Searcherループのバグ修正。
333
+ + http://twitter.com/h_demon
210
334
 
211
- + Searcherループの継続判定を、ブロック内で"continue"を返す事を要求する方式に変更。
335
+ GitHubを経由して下さってもいいのですが、まだ慣れていないので対応が送れるかもしれません。
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.4
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