• R/O
  • SSH
  • HTTPS

protra: 提交


Commit MetaInfo

修訂552 (tree)
時間2021-05-01 20:36:01
作者panacoran

Log Message

2021-05-01 panacoran <panacoran@users.osdn.me>

Yahooファイナンスから株価をダウロードできないのを直す

* Protra.Lib/Update/YahooFinanceUpdator.cs (YahooFinanceUpdator): Yahooファイナンスのページ構成の変化に対応

Change Summary

差異

--- protra/trunk/ChangeLog.txt (revision 551)
+++ protra/trunk/ChangeLog.txt (revision 552)
@@ -1,3 +1,9 @@
1+2021-05-01 panacoran <panacoran@users.osdn.me>
2+
3+ Yahooファイナンスから株価をダウロードできないのを直す
4+
5+ * Protra.Lib/Update/YahooFinanceUpdator.cs (YahooFinanceUpdator): Yahooファイナンスのページ構成の変化に対応
6+
17 2021-02-24 darai <darai@users.sourceforge.jp>
28
39 #41090: 2021年の祝日対応
--- protra/trunk/Protra.Lib/Update/YahooFinanceUpdator.cs (revision 551)
+++ protra/trunk/Protra.Lib/Update/YahooFinanceUpdator.cs (revision 552)
@@ -22,9 +22,11 @@
2222 using System.ComponentModel;
2323 using System.Globalization;
2424 using System.IO;
25+using System.Linq;
2526 using System.Net;
2627 using System.Text.RegularExpressions;
2728 using Protra.Lib.Data;
29+using Protra.Lib.Lang;
2830
2931 namespace Protra.Lib.Update
3032 {
@@ -80,10 +82,7 @@
8082 // 新しいデータが置かれるのは早くても午後7時以降
8183 if (end.Hour < 19)
8284 end = end.AddDays(-1);
83- var codes = new List<string>();
84- foreach (var brand in GlobalEnv.BrandData)
85- if ((brand.Flags & Brand.Flag.OBS) == 0)
86- codes.Add(brand.Code);
85+ var codes = (from brand in GlobalEnv.BrandData where (brand.Flags & Brand.Flag.OBS) == 0 select brand.Code).ToList();
8786 try
8887 {
8988 var dates = ListOpenDates(begin, end);
@@ -97,6 +96,10 @@
9796 _progress.Show(worker, dates[0]);
9897 // 日経平均の時系列データの存在を確認する。
9998 var n = Math.Min(DaysAtOnce, dates.Count);
99+ // 日経平均/TOPICに東証トラブル日のデータが存在するため、
100+ // 有効なデータを19日分しか取れない
101+ if (IsContainsJpxTrouble(dates[0], dates[n - 1]))
102+ n--;
100103 var nikkei225 = FetchPrices("1001", dates.GetRange(0, n));
101104 if (nikkei225.ReturnStatus != FetchResult.Status.Success)
102105 throw new Exception($"株価の取得に失敗しました。時間を置いて再試行してください。: {dates[0]:d}~{dates[n - 1]:d}");
@@ -163,6 +166,12 @@
163166 }
164167 }
165168
169+ private static bool IsContainsJpxTrouble(DateTime begin, DateTime end)
170+ {
171+ var theDate = new DateTime(2020, 10, 1);
172+ return theDate.CompareTo(begin) >= 0 && theDate.CompareTo(end) <= 0;
173+ }
174+
166175 private FetchResult FetchPrices(string code, IList<DateTime> dates)
167176 {
168177 var status = GetPage(code, dates[0], dates[dates.Count - 1], out var page);
@@ -173,12 +182,13 @@
173182
174183 private FetchResult.Status GetPage(string code, DateTime begin, DateTime end, out string page)
175184 {
176- if (code == "1001")
177- code = "998407";
178- else if (code == "1002")
179- code = "998405";
180- var dl = new DownloadUtil(
181- $"https://info.finance.yahoo.co.jp/history/?code={code}&sy={begin.Year}&sm={begin.Month}&sd={begin.Day}&ey={end.Year}&em={end.Month}&ed={end.Day}&tm=d");
185+ string codeString = code;
186+ if (code == "1001" || code == "1002")
187+ codeString = code == "1001" ? "998407.O" : "998405.T";
188+ var oldUrl = $"https://info.finance.yahoo.co.jp/history/?code={codeString}&sy={begin.Year}&sm={begin.Month}&sd={begin.Day}&ey={end.Year}&em={end.Month}&ed={end.Day}&tm=d";
189+ var url = $"https://finance.yahoo.co.jp/quote/{codeString}.T/history?from={begin:yyyyMMdd}&to={end:yyyyMMdd}&timeFrame=d&page=1";
190+ retry:
191+ var dl = new DownloadUtil(url);
182192 page = null;
183193 try
184194 {
@@ -193,9 +203,18 @@
193203 switch (e.Status)
194204 {
195205 case WebExceptionStatus.ProtocolError:
196- var c = ((HttpWebResponse)e.Response).StatusCode;
197- if (c == HttpStatusCode.BadGateway || c == HttpStatusCode.InternalServerError)
198- goto case WebExceptionStatus.Timeout;
206+ switch (((HttpWebResponse)e.Response).StatusCode)
207+ {
208+ case (HttpStatusCode)999:
209+ case HttpStatusCode.InternalServerError:
210+ case HttpStatusCode.BadGateway:
211+ return FetchResult.Status.Retry;
212+ case HttpStatusCode.NotFound:
213+ if (url == oldUrl)
214+ return FetchResult.Status.Failure;
215+ url = oldUrl;
216+ goto retry;
217+ }
199218 throw;
200219 case WebExceptionStatus.Timeout:
201220 case WebExceptionStatus.ConnectionClosed:
@@ -210,27 +229,41 @@
210229 return FetchResult.Status.Success;
211230 }
212231
232+ private static readonly Regex Valid = new Regex(
233+ @"<tr[^>]*><th[^>]*>(?<year>\d{4})年(?<month>1?\d)月(?<day>\d?\d)日<\/th><td[^>]+>(?:<span[^>]+>)+(?<open>[0-9,.]+)<\/span>.*?<\/td><td[^>]+>(?:<span[^>]+>)+(?<high>[0-9,.]+)<\/span>.*?<\/td><td[^>]+>(?:<span[^>]+>)+(?<low>[0-9,.]+)<\/span>.+?<\/td><td[^>]+>(?:<span[^>]+>)+(?<close>[0-9,.]+)<\/span>.+?<\/td>(?:<td.*?>(?<volume>[0-9,.]+)<\/span>.+?<\/td>)?<\/tr>",
234+ RegexOptions.Compiled);
235+
236+ private static readonly Regex NoData = new Regex("時系列情報がありません");
237+
238+ private static readonly Regex ValidOld = new Regex(
239+ @"<td>(?<year>\d{4})年(?<month>1?\d)月(?<day>\d?\d)日</td><td>(?<open>[0-9,.]+)</td><td>(?<high>[0-9,.]+)</td><td>(?<low>[0-9,.]+)</td><td>(?<close>[0-9,.]+)</td>(?:<td>(?<volume>[0-9,]+)</td>)?", RegexOptions.Compiled);
240+
241+ private static readonly Regex NoDataOld = new Regex("該当する期間のデータはありません。<br>期間をご確認ください。");
242+
243+ private static readonly Regex Obs =
244+ new Regex("該当する銘柄はありません。<br>再度銘柄(コード)を入力し、「表示」ボタンを押してください。", RegexOptions.Compiled);
245+
246+ private static readonly Regex Empty = new Regex("<dl class=\"stocksInfo\">\n<dt></dt><dd class=\"category yjSb\"></dd>", RegexOptions.Compiled);
247+
213248 private FetchResult ParsePage(string code, string buf, IEnumerable<DateTime> dates)
214249 {
215- var valid = new Regex(
216- @"<td>(?<year>\d{4})年(?<month>1?\d)月(?<day>\d?\d)日</td>" +
217- "<td>(?<open>[0-9,.]+)</td><td>(?<high>[0-9,.]+)</td><td>(?<low>[0-9,.]+)</td>" +
218- "<td>(?<close>[0-9,.]+)</td>(?:<td>(?<volume>[0-9,]+)</td>)?");
219- var invalid = new Regex("該当する期間のデータはありません。<br>期間をご確認ください。");
220- var obs = new Regex("該当する銘柄はありません。<br>再度銘柄(コード)を入力し、「表示」ボタンを押してください。");
221- var empty = new Regex("<dl class=\"stocksInfo\">\n<dt></dt><dd class=\"category yjSb\"></dd>");
222-
223250 var dict = new Dictionary<DateTime, Price>();
224- var matches = valid.Matches(buf);
251+ var matches = Valid.Matches(buf);
225252 if (matches.Count == 0)
226253 {
227- if (obs.Match(buf).Success || empty.Match(buf).Success) // 上場廃止(銘柄データが空のこともある)
228- return new FetchResult {ReturnStatus = FetchResult.Status.Obsolete};
229- if (!invalid.Match(buf).Success)
230- throw new Exception("ページから株価を取得できません。");
231- // ここに到達するのは出来高がないか株価が用意されていない場合
254+ if (!NoData.IsMatch(buf))
255+ {
256+ matches = ValidOld.Matches(buf);
257+ if (matches.Count == 0)
258+ {
259+ if (Obs.Match(buf).Success || Empty.Match(buf).Success) // 上場廃止(銘柄データが空のこともある)
260+ return new FetchResult {ReturnStatus = FetchResult.Status.Obsolete};
261+ if (!NoDataOld.IsMatch(buf))
262+ throw new Exception("ページから株価を取得できません。");
263+ // ここに到達するのは出来高がないか株価が用意されていない場合
264+ }
265+ }
232266 }
233-
234267 try
235268 {
236269 const NumberStyles s = NumberStyles.AllowDecimalPoint | NumberStyles.AllowThousands;
@@ -257,12 +290,9 @@
257290 }
258291
259292 // 出来高がない日の株価データがないので値が0のデータを補う。
260- var prices = new List<Price>();
261- foreach (var date in dates)
262- {
263- prices.Add(dict.TryGetValue(date, out var price) ? price : new Price {Date = date, Code = code});
264- }
265-
293+ var prices = dates
294+ .Select(date => dict.TryGetValue(date, out var price) ? price : new Price {Date = date, Code = code})
295+ .ToList();
266296 return new FetchResult {Code = code, Prices = prices, ReturnStatus = FetchResult.Status.Success};
267297 }
268298
Show on old repository browser