• R/O
  • SSH
  • HTTPS

protra: Commit


Commit MetaInfo

Revision532 (tree)
Time2016-09-06 21:50:06
Authorpanacoran

Log Message

Yahooから株価を取得するときにエラーになりやすいのを直す

* Protra.Lib/Update/YahooFinanceUpdator.cs (YahooFinanceUpdator.FetchResult): 新規。取得状況を表すクラス。
(YahooFinanceUpdator.UpdatePrice): 失敗した銘柄の再取得を一番最後に行う。
(YahooFinanceUpdator.FetchPrices): 取得状況を表すオブジェクトを返す。

Change Summary

Incremental Difference

--- protra/trunk/ChangeLog.txt (revision 531)
+++ protra/trunk/ChangeLog.txt (revision 532)
@@ -1,5 +1,13 @@
11 2016-09-06 panacoran <panacoran@users.osdn.me>
22
3+ Yahooから株価を取得するときにエラーになりやすいのを直す
4+
5+ * Protra.Lib/Update/YahooFinanceUpdator.cs (YahooFinanceUpdator.FetchResult): 新規。取得状況を表すクラス。
6+ (YahooFinanceUpdator.UpdatePrice): 失敗した銘柄の再取得を一番最後に行う。
7+ (YahooFinanceUpdator.FetchPrices): 取得状況を表すオブジェクトを返す。
8+
9+2016-09-06 panacoran <panacoran@users.osdn.me>
10+
311 株価の更新でエラーが生じたときにフリーズしてしまうことがあるのを直す
412
513 * Protra/Dialogs/PriceUpdateDialog.cs (PriceUpdateDialog.backgroundWorkerUpdate_RunWorkerCompleted): 処理の終了で必ずマウスカーソルとボタンを復旧させる。
--- protra/trunk/Protra.Lib/Update/YahooFinanceUpdator.cs (revision 531)
+++ protra/trunk/Protra.Lib/Update/YahooFinanceUpdator.cs (revision 532)
@@ -37,12 +37,27 @@
3737 private readonly Object _syncObject = new object();
3838 private Queue<string> _codeQueue;
3939 private readonly List<DateTime> _series = new List<DateTime>();
40- private readonly Queue<IEnumerable<Price>> _priceQueue = new Queue<IEnumerable<Price>>();
40+ private readonly Queue<FetchResult> _resultQueue = new Queue<FetchResult>();
4141 private bool _terminate;
4242 private Exception _exception;
4343 private readonly Progress _progress = new Progress();
4444 private const int DaysAtOnce = 50; // 一度に取得する時系列の営業日数
4545
46+ private class FetchResult
47+ {
48+ public enum Status
49+ {
50+ Success,
51+ Failure,
52+ Obsolete,
53+ Retry,
54+ }
55+
56+ public string Code;
57+ public List<Price> Prices;
58+ public Status ReturnStatus;
59+ }
60+
4661 /// <summary>
4762 /// データが存在する最初の日付を取得する。
4863 /// </summary>
@@ -86,9 +101,12 @@
86101 // 日経平均の時系列データの存在を確認する。
87102 var n = Math.Min(DaysAtOnce, dates.Count);
88103 var nikkei225 = FetchPrices("1001", dates.GetRange(0, n));
104+ if (nikkei225.ReturnStatus != FetchResult.Status.Success)
105+ throw new Exception(string.Format("株価の取得に失敗しました。時間を置いて再試行してください。: {0:d}~{1:d}",
106+ _series[0], _series[_series.Count - 1]));
89107 dates.RemoveRange(0, n);
90108 _series.Clear();
91- foreach (var price in nikkei225)
109+ foreach (var price in nikkei225.Prices)
92110 {
93111 if (price.Close == 0)
94112 break;
@@ -106,28 +124,48 @@
106124 _codeQueue.Dequeue(); // 日経平均を外す。
107125 Monitor.PulseAll(_syncObject);
108126 }
109- for (var i = 1; i < _progress.RecordsPerDay; i++)
127+ var retry = 0;
128+ while (true)
110129 {
111- if (worker.CancellationPending)
130+ var numCodes = _codeQueue.Count;
131+ for (var i = 0; i < numCodes; i++)
112132 {
113- e.Cancel = true;
114- return;
133+ if (worker.CancellationPending)
134+ {
135+ e.Cancel = true;
136+ return;
137+ }
138+ FetchResult result;
139+ lock (_resultQueue)
140+ {
141+ while (_resultQueue.Count == 0 && _exception == null)
142+ Monitor.Wait(_resultQueue);
143+ if (_exception != null)
144+ throw _exception;
145+ result = _resultQueue.Dequeue();
146+ }
147+ switch (result.ReturnStatus)
148+ {
149+ case FetchResult.Status.Failure:
150+ case FetchResult.Status.Obsolete:
151+ continue;
152+ case FetchResult.Status.Retry:
153+ lock (_codeQueue)
154+ {
155+ _codeQueue.Enqueue(result.Code);
156+ }
157+ continue;
158+ }
159+ foreach (var price in result.Prices)
160+ PriceData.Add(price, false);
161+ _progress.IncrementRecords();
162+ _progress.Show(worker, lastDate);
115163 }
116- IEnumerable<Price> prices;
117- lock (_priceQueue)
118- {
119- while (_priceQueue.Count == 0 && _exception == null)
120- Monitor.Wait(_priceQueue);
121- if (_exception != null)
122- throw _exception;
123- prices = _priceQueue.Dequeue();
124- }
125- if (prices == null) // 上場廃止
126- continue;
127- foreach (var price in prices)
128- PriceData.Add(price, false);
129- _progress.IncrementRecords();
130- _progress.Show(worker, lastDate);
164+ if (_codeQueue.Count == 0)
165+ break;
166+ if (retry++ == 10)
167+ throw new Exception(string.Format("株価の取得に失敗しました。時間を置いて再試行してください。: {0:d}~{1:d}",
168+ _series[0], _series[_series.Count - 1]));
131169 }
132170 PriceData.MaxDate = lastDate;
133171 _progress.IncrementDays();
@@ -162,30 +200,33 @@
162200 code = _codeQueue.Dequeue();
163201 }
164202 var price = FetchPrices(code, _series);
165- lock (_priceQueue)
203+ lock (_resultQueue)
166204 {
167- _priceQueue.Enqueue(price);
168- Monitor.Pulse(_priceQueue);
205+ _resultQueue.Enqueue(price);
206+ Monitor.Pulse(_resultQueue);
169207 }
170208 }
171209 }
172210 catch (Exception e)
173211 {
174- lock (_priceQueue)
212+ lock (_resultQueue)
175213 {
176214 _exception = new Exception(string.Format("{0}: {1} {2:d}", e.Message, code, _series[0]), e);
177- Monitor.Pulse(_priceQueue);
215+ Monitor.Pulse(_resultQueue);
178216 }
179217 }
180218 }
181219
182- private IEnumerable<Price> FetchPrices(string code, IList<DateTime> dates)
220+ private FetchResult FetchPrices(string code, IList<DateTime> dates)
183221 {
184- var page = GetPage(code, dates[0], dates[dates.Count - 1]);
222+ string page;
223+ var status = GetPage(code, dates[0], dates[dates.Count - 1], out page);
224+ if (status == FetchResult.Status.Failure || status == FetchResult.Status.Retry)
225+ return new FetchResult {Code = code, ReturnStatus = status};
185226 return ParsePage(code, page, dates);
186227 }
187228
188- private string GetPage(string code, DateTime begin, DateTime end)
229+ private FetchResult.Status GetPage(string code, DateTime begin, DateTime end, out string page)
189230 {
190231 if (code == "1001")
191232 code = "998407";
@@ -194,35 +235,37 @@
194235 var dl = new DownloadUtil(string.Format(
195236 "http://info.finance.yahoo.co.jp/history/?code={0}&sy={1}&sm={2}&sd={3}&ey={4}&em={5}&ed={6}&tm=d",
196237 code, begin.Year, begin.Month, begin.Day, end.Year, end.Month, end.Day));
197- for (var i = 0; i < 10; i++)
238+ page = null;
239+ try
198240 {
199- try
241+ var stream = dl.GetResponse();
242+ if (stream == null)
243+ return FetchResult.Status.Failure;
244+ using (var reader = new StreamReader(stream))
245+ page = reader.ReadToEnd();
246+ }
247+ catch (WebException e)
248+ {
249+ switch (e.Status)
200250 {
201- var stream = dl.GetResponse();
202- if (stream == null)
203- return null;
204- using (var reader = new StreamReader(stream))
205- return reader.ReadToEnd();
251+ case WebExceptionStatus.ProtocolError:
252+ var c = ((HttpWebResponse)e.Response).StatusCode;
253+ if (c == HttpStatusCode.BadGateway || c == HttpStatusCode.InternalServerError)
254+ goto case WebExceptionStatus.Timeout;
255+ throw;
256+ case WebExceptionStatus.Timeout:
257+ case WebExceptionStatus.ConnectionClosed:
258+ case WebExceptionStatus.ReceiveFailure:
259+ case WebExceptionStatus.ConnectFailure:
260+ return FetchResult.Status.Retry;
261+ default:
262+ throw;
206263 }
207- catch (WebException e)
208- {
209- switch (e.Status)
210- {
211- case WebExceptionStatus.Timeout:
212- case WebExceptionStatus.ConnectionClosed:
213- case WebExceptionStatus.ReceiveFailure:
214- case WebExceptionStatus.ConnectFailure:
215- Thread.Sleep(1000);
216- break;
217- default:
218- throw;
219- }
220- }
221264 }
222- throw new Exception(string.Format("ページの取得に失敗しました。"));
265+ return FetchResult.Status.Success;
223266 }
224267
225- private IEnumerable<Price> ParsePage(string code, string buf, IEnumerable<DateTime> dates)
268+ private FetchResult ParsePage(string code, string buf, IEnumerable<DateTime> dates)
226269 {
227270 var valid = new Regex(
228271 @"<td>(?<year>\d{4})年(?<month>1?\d)月(?<day>\d?\d)日</td>" +
@@ -232,14 +275,12 @@
232275 var obs = new Regex("該当する銘柄はありません。<br>再度銘柄(コード)を入力し、「表示」ボタンを押してください。");
233276 var empty = new Regex("<dl class=\"stocksInfo\">\n<dt></dt><dd class=\"category yjSb\"></dd>");
234277
235- if (buf == null)
236- return null;
237278 var dict = new Dictionary<DateTime, Price>();
238279 var matches = valid.Matches(buf);
239280 if (matches.Count == 0)
240281 {
241282 if (obs.Match(buf).Success || empty.Match(buf).Success) // 上場廃止(銘柄データが空のこともある)
242- return null;
283+ return new FetchResult {ReturnStatus = FetchResult.Status.Obsolete};
243284 if (!invalid.Match(buf).Success)
244285 throw new Exception("ページから株価を取得できません。");
245286 // ここに到達するのは出来高がないか株価が用意されていない場合
@@ -269,13 +310,13 @@
269310 throw new Exception("ページから株価を取得できません。", e);
270311 }
271312 // 出来高がない日の株価データがないので値が0のデータを補う。
272- var result = new List<Price>();
313+ var prices = new List<Price>();
273314 foreach (var date in dates)
274315 {
275316 Price price;
276- result.Add(dict.TryGetValue(date, out price) ? price : new Price { Date = date, Code = code });
317+ prices.Add(dict.TryGetValue(date, out price) ? price : new Price {Date = date, Code = code});
277318 }
278- return result;
319+ return new FetchResult {Code = code, Prices = prices, ReturnStatus = FetchResult.Status.Success};
279320 }
280321
281322 /// <summary>
Show on old repository browser