前言 上一篇 用 C# .NET Core 爬取每季財報 已經抓取了每季財報 不過由於季報週期太長,因此通常還需要月報來確認每個月的營收是否有符合預期 那這篇就來做每月財報資訊的爬蟲吧!
觀察網站 和季報一樣,我們可以從 公開資訊觀測站 找尋我們要的資料
從 首頁 > 彙總報表 > 營運概況 > 每月營收 > 採用IFRSs後每月營業收入彙總表
選擇時間後按下查詢
接著我們會到財報頁面,如果要爬這一頁也是可以 但馬上發現可以下載CSV,當然馬上選CSV,處理起來會方便一些
打開 Chrome -> F12,按下下載,觀察Request
發現請求相當單純,從fileName
發現 _108_1
就是表示108年1月, 因此我們只要調整這個參數抓取我們要的月份資料就可以了,
觀察完畢,馬上開始動工!
爬取財報資訊 爬蟲部分 爬取CSV,直接讀成string型態回傳
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public async Task<string > ReadMonthReportCsvByTWSEAsync (int year, int month ) { using (var client = new HttpClient()) { client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/x-www-form-urlencoded" )); var response = await client.PostAsync( "https://mops.twse.com.tw/server-java/FileDownLoad" , new StringContent($"step=9&functionName=show_file&filePath=%2Fhome%2Fhtml%2Fnas%2Ft21%2Fsii%2F&fileName=t21sc03_{year} _{month} .csv" ) ); var result = await response.Content.ReadAsStringAsync(); if (response.StatusCode != System.Net.HttpStatusCode.OK) throw new PlatformNotSupportedException($"目前無法爬取財報資料...,{response.StatusCode} ,{result} " ); return result; } }
解析CSV資料 接下來解析 CSV 檔案,這裡我使用 CsvHelper 這個套件 安裝套件:
1 dotnet add package CsvHelper --version 15.0.5
針對資料欄位我們先建立一個 DB Model, 資料庫就自己去建吧!這裡就不多說了[Name]
這個 attribute 是 CsvHelper 會針對欄位名稱來自動轉換所使用的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 [Table("MonthReport" ) ] public class MonthReport { [ExplicitKey ] [Name("公司代號" ) ] public string stock_id { get ; set ; } [ExplicitKey ] public int year { get ; set ; } [ExplicitKey ] public int month { get ; set ; } [Name("營業收入-當月營收" ) ] public string revenue { get ; set ; } [Name("備註" ) ] public string remark { get ; set ; } }
這個 Model 因為 year 跟 month 在 CSV 內並沒有資料,是我們自己傳入的!所以這裡需要排除這個兩個欄位 根據 CsvHelper官方文件 ,我們可以繼承 ClassMap 來將不要的欄位 Ignore 掉!
1 2 3 4 5 6 7 8 9 public class MonthReportMap: ClassMap<MonthReport> { public MonthReportMap ( ) { AutoMap(CultureInfo.InvariantCulture); Map(m => m.year).Ignore(); Map(m => m.month).Ignore(); } }
然後我們將剛剛的string傳入, 並且直接讀成IEnumerable<MonthReport>
, 這樣就可以等等就可以直接存進資料庫!
1 2 3 4 5 6 7 8 9 public IEnumerable<MonthReport> ReadCsv (string data ) { using (var reader = new StringReader(data)) using (var csvReader = new CsvReader(reader, CultureInfo.InvariantCulture)) { csvReader.Configuration.RegisterClassMap<MonthReportMap>(); return csvReader.GetRecords<MonthReport>().ToList(); } }
排程 & 存入資料庫 接著處理資料庫的部分,沒什麼複雜的東西,直接看程式碼吧!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 public class MonthReportRepository { private readonly SqlConnection _conn; private readonly ILogger<MonthReportRepository> _logger; public MonthReportRepository (ILogger<MonthReportRepository> logger, SqlConnection conn ) { _logger = logger; _conn = conn; } public void Insert (IEnumerable<MonthReport> monthReportList ) { try { using (var scope = new TransactionScope()) { foreach (var month in monthReportList) { _conn.Insert(month); } scope.Complete(); } } catch (Exception ex) { _logger.LogError(ex.Message); } } internal bool IsExist (int year, int month ) { return _conn.ExecuteScalar<bool >("select count(1) from MonthReport where year=@year and month=@month" , new { year, month }); } public int GetMaxMonth (int year ) { return _conn.ExecuteScalar<int >("select isnull(max(month), 1) from MonthReport where year=@year" , new { year }); } public int GetMaxYear ( ) { return _conn.ExecuteScalar<int >("select isnull(max(year), 106) from MonthReport" ); } }
最後,整理成一個 method ,方便 schedule 呼叫
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public async Task ExecuteAsync (int year, int month ) { if (_monthReportRepository.IsExist(year, month)) { _logger.LogDebug($"{year} , {month} data is exist" ); return ; } _logger.LogInformation($"MonthReportClawer Running, year = {year} , month = {month} " ); string data = await ReadMonthReportCsvByTWSEAsync(year, month); var reports = ReadCsv(data).Select(report => { report.year = year; report.month = month; return report; }); _monthReportRepository.Insert(reports); }
到這邊爬蟲的部分已經完成,接下來就來設定排程, 一樣使用 Coravel 這個套件,這邊就不多說明了!不懂的朋友可以回去看這篇 => 用 C# .NET Core 自動爬取台股每日股價
這邊從 106年1月 開始爬資料,和季報一樣,在上面SQL中,已經技巧性的處理掉了MaxMonth、MaxYear 接著直接看Code吧!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class MonthReportClawerSchedule : IInvocable { private MonthReportClawer _monthReportClawer; private MonthReportRepository _monthReportRepository; public MonthReportClawerSchedule (MonthReportClawer monthReportClawer, MonthReportRepository monthReportRepository ) { _monthReportClawer = monthReportClawer; _monthReportRepository = monthReportRepository; } public async Task Invoke ( ) { int year = _monthReportRepository.GetMaxYear(); int month = _monthReportRepository.GetMaxMonth(year); int twNowYear = DateTime.Now.Year-1911 ; while (year <= twNowYear) { while ((year < twNowYear && month <= 12 ) || (year == twNowYear && month <= DateTime.Now.Month)) { await _monthReportClawer.ExecuteAsync(year, month); Thread.Sleep(7000 ); month++; } year++; month = 1 ; } } }
最後,註冊我們的排程:
1 2 3 scheduler .Schedule<MonthReportClawerSchedule>() .Cron("30 8 10 * *" );
搞定收工!
心得 這次爬月報還是比較簡單的, 有了月報之後就可以透過程式自己找出月營收成長的股票, 現在已經有了股價,可以做技術分析, 又有了季報、月報,可以做基本面分析, 好像還缺了籌碼分析,那下一篇,就來爬千張大戶的籌碼吧,敬請期待!
↓↓↓ 如果喜歡我的文章,可以幫我按個Like! ↓↓↓
>> 或者,請我喝杯咖啡,這樣我會更有動力唷! <<<
街口支付
街口帳號: 901061546