예전에 작성한 프로그램임

네이버 증권 웹페이지 변경으로 오류 발생하여 약간 수정함

다만 그럼에도 오류 생기는 종목이 있긴 함

이전 버전 : https://diyworld.tistory.com/120

 

네이버 주식데이터 크롤링 : Web crawler

상폐된 주식의 경우 크롤링중 데이터 미존재 오류로 프로그램 다운됨 이유:예외처리 안했음 수정 : 오류나면 그 종목 무시하고 다음 종목 처리함 (확인 누르면 다음 처리) - 그래프 크기 위치 조

diyworld.tistory.com

 

수정된 버전 첨부함

NaverStock2Excel_251007.7z
19.28MB

// ==========================================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Systehttp://m.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using Systehttp://m.Windows.Data;
using System.Windows.Documents;
using Systehttp://m.Windows.Input;
using Systehttp://m.Windows.Media;
using Systehttp://m.Windows.Media.Imaging;
using System.Windows.Navigation;
using Systehttp://m.Windows.Shapes; // Tool - Nuget package 관리자 : "PM> "
using DocumentFormat.OpenXml;               // Install-Package DocumentFormat.OpenXml -Version 2.5.0
using DocumentFormat.OpenXml.Spreadsheet;   // Excel - by 'http://spreadsheetlight.com/'
using SpreadsheetLight;                     // Install-Package SpreadsheetLight -Version 3.4.9
using SpreadsheetLight.Charts;
using HtmlAgilityPack;                      // Install-Package HtmlAgilityPack -Version 1.8.4
using System.Data;
using System.IO;
using System.Net;
using System.Threading;                     /// delay
using Microsoft.Win32;
using FastMember;
/// openDialogue
/// </summary>
// ==========================================================================================
//   PM > Install-Package Fody -Version 3.3.3
//   PM > Install-Package Costura.Fody -Version 3.3.3
// ==========================================================================================
// 2018.07.14 : 초기 버전
// 2018.07.15 : 1차 완성버전
// 2018.09.26 : 수정 (컬러 / 버튼 활성화 / VS2017)
// 2019.01.21 : 그래프 최대 최소 자동 조정 추가
// 2019.10.13 : Fody 설치 (DLL 없는 단일 실행화일 EXE 생성)
//              화면 크기 변경 함수 추가
// 2020.02.04 : 종목코드 오류일때 수정, 장기간 검색시 늦게 상장된 업체 데이터 공백일 때 처리
//              데이터 그리드 선택시 컬러 설정 : 'MainWindow.xaml'에서 설정함
// 2020.03.10 : 'HtmlDoc2Table'에 if (StockData.Contains("&nbsp")) 과거 증시데이터 없음처리 추가
// 2020.07.02 : 'Logxxx' 화일명 개선, 그래프 위치 +1 이동
// 2020.09.15 : 옵션 추가 - 그래프 크기& 위치
// 2020.11.08 : 기관/외인 매매 추이 막대그래프 추가 (SL.InsertColumn - 작동 이상함)
// 2020.11.17 : X축 역순으로 표시 옵션 추가
//              버그 : rowNdx < state.EndRowIndex - 1 ==> rowNdx < state.EndRowIndex 로 수정
//                     최고/최저 구할때 for 루프 index 범위 수정 '<' ==> '<='
// 2020.12.04 : InsertGraphPrice, InsertGraphTradeALL,InsertGraphTradeFor,InsertGraphTradeFor
//              그래프 옵션 레이아웃 수정
// 2021.04.03 : 기관 & 외인 매매량 그래프 Y축 갯수 선택 추가
// 2023.05.03 : "HtmlWeb2Table" 종목코드 없을 때 테이블 노드없어 발생하는 오류처리 => try-catch
// ==========================================================================================
namespace wpfHtml2Grid
{
    /// <summary>
    /// MainWindow.xaml에 대한 상호 작용 논리
    /// </summary>
    public partial class MainWindow : Window
    {
        string LogFileName;
        // ===================================================================================
        public MainWindow()
        {
            InitializeComponent();
        }
        // ===================================================================================
        /// Thread.Sleep 사용시 COM 포트 값 읽기 문제있음
        /// WPF : http://www.codeproject.com/Articles/271598/Application-DoEvents-in-WPF
        /// ------------------------------------------------------------------------------------
        public static DateTime Delay(int MS)
        {
            DateTime ThisMoment = DateTime.Now;
            TimeSpan duration = new TimeSpan(0, 0, 0, 0, MS);
            DateTime AfterWards = ThisMoment.Add(duration);

            while (AfterWards >= ThisMoment)
            {
                Application.Current.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Background,
                    new ThreadStart(delegate { }));
                //Systehttp://m.Windows.Forms.Application.DoEvents();
                ThisMoment = DateTime.Now;
            }
            return DateTime.Now;
        }
        // ===================================================================================
        // 엑셀로 저장 : 시트명  = TestSheet
        // ===================================================================================
        private void btnXlsx_Click(object sender, RoutedEventArgs e)
        {
            LogFileName = "Log" + DateTime.Now.ToString("-yyyyMMdd-HHmmss") + ".txt";
            String btnText = btnXlsx.Content.ToString();
            btnLosdStock.Content = "Wait...";
            tbStatus.Text = "Importing Excel into DataGrid";
            Excel2DataGrid(dgStockList, "Sheet1");
            btnLosdStock.Content = "Load Stock";
            tbStatus.Text = "Stock codes are imported";
            AllStock2Excel(dgHTML, dgStockList);
            btnXlsx.Content = btnText;
        }
        // ===================================================================================
        // web.OverrideEncoding = Encoding.UTF8;
        // webStockURL = "http://finance.daum.net/item/foreign.daum?code=005930";
        // webStockURL = "https://finance.naver.com/item/main.nhn?code=000270";
        // ===================================================================================
        //  https://finance.naver.com/item/frgn.nhn?code=000270&page=1
        // 네이버 = euc-kr, 다음 = utf-8     (Encoding.GetEncoding("euc-kr"); 
        // ===================================================================================
        // 웹페이지 읽어서 데이터 처리 : Web -> DataGrid
        // 2023.05.03 : 종목코드 없을 때 테이블 노드없어 발생하는 오류처리 => try-catch
        // ===================================================================================
        private int HtmlWeb2Table(string webURL, string stockNo, string StockName, int TableNo, int EndPageNo)
        {
            var webStockURL = webURL + stockNo + "&page=" + Convert.ToString(1);
            HtmlWeb web = new HtmlWeb();
            web.AutoDetectEncoding = false;
            web.OverrideEncoding = Encoding.GetEncoding("euc-kr");          // 네이버=euc-kr, 다음=utf-8
            HtmlDocument doc = web.Load(webStockURL);                       // 첫페이지 읽음
            doc.Save("test.html", Encoding.UTF8);
            using (StreamWriter file = new StreamWriter(LogFileName, true))
            {
                file.WriteLine(doc.Text + "\n");
            }
            if (doc.DocumentNode.SelectNodes("//table") == null) return -1; // 20.02.04 - 증권코드 오류
            DataTable dTable = new DataTable();
            dTable.Clear();
            DataRow dRow = dTable.NewRow();
            int tableIndex = 0, HeadIndex = 0;
            // ----------------------------------------------------------------------------------------
            // 헤더 정보 확인 후 datagridview 컬럼 설정
            // ----------------------------------------------------------------------------------------
            try
            {
                foreach (HtmlNode tableBody in doc.DocumentNode.SelectNodes("//table")) // table node 선택
                {
                    tableIndex++;
                    if (tableIndex != TableNo) continue;                // 거래 데이터는 3번째 skip until what I need
                    var headers = tableBody.SelectNodes("tr/th");       // 헤더 모으기
                    HeadIndex = 0;
                    foreach (HtmlNode header in headers)                // 헤더갯수 (2중 헤더 제거)
                    {
                        var headText = header.InnerText;                // Get head columns from th
                        HeadIndex++;                                    // 배열 index + 1
                        if (HeadIndex == 8)                             // 2단 테이블 헤더 처리
                        {
                            dTable.Columns[5].ColumnName = dTable.Columns[5].ColumnName + headText;
                        }
                        else if (HeadIndex == 9)
                        {
                            dTable.Columns[6].ColumnName = dTable.Columns[6].ColumnName + headText;
                        }
                        else
                            dTable.Columns.Add(headText, typeof(string));
                        rtbSource.AppendText(" " + headText);
                    }
                }
            }
            catch(Exception ex)
            {
                MessageBox.Show("Stock = " + StockName + "\n" + "Stock No = " + stockNo + "\n" + ex.Message);
                return -1;
            }
            
            // ----------------------------------------------------------------------------------------
            // 거래정보 업데이트 : https://finance.naver.com/item/frgn.nhn?code=000270&page=1
            // ----------------------------------------------------------------------------------------
            rtbSource.Document.Blocks.Clear();
            rtbSource.AppendText("\n");
            string StatusText = tbStatus.Text;
            for (int index = 1; index <= EndPageNo; index++)
            {
                webStockURL = webURL + stockNo + "&page=" + Convert.ToString(index);
                web.OverrideEncoding = Encoding.GetEncoding("euc-kr");                  // 네이버=euc-kr, 다음=utf-8
                doc = web.Load(webStockURL);
                tbStatus.Text = StatusText + " -> Page = " + index.ToString();
                Delay(20);
                using (StreamWriter file = new StreamWriter(LogFileName, true))
                {
                    file.WriteLine(doc.Text + "\n");
                }
                tableIndex = 0;                                                 // table 인덱스 초기화
                foreach (HtmlNode tableBody in doc.DocumentNode.SelectNodes("//table")) // table node 선택
                {
                    tableIndex++;
                    if (tableIndex != TableNo) continue;                        // skip until what I need
                    List<string> valueList = new List<string>();
                    foreach (HtmlNode rowTable in tableBody.SelectNodes("tr"))  // tr node 선택
                    {
                        if (rowTable.SelectNodes("td") == null) continue;       // td 없으면 무시
                        if (rowTable.InnerText == "") continue;                 // 네이버의 빈줄 무시
                        string StockData = "";
                        valueList.Clear();
                        foreach (HtmlNode cell in rowTable.SelectNodes("td"))   // ("th|td") th 또는 td node 선택
                        {
                            string strValue = cell.InnerText;
                            strValue = strValue.Replace("\t", "");
                            strValue = strValue.Replace("\n", "");
                            strValue = strValue.Replace("\r", "");
                            strValue = strValue.Replace("+", "");               // + 기호 삭제
                            strValue = strValue.Replace(",", "");               // CSV 사용위해 콤마삭제
                            StockData += strValue + ";";
                            valueList.Add(strValue);
                            rtbSource.AppendText(" " + strValue);
                        }
                        if (StockData.Contains("&nbsp")) continue;              // 4 Feb '20 : 과거 증시 데이터 없음
                        //dgHTML.Items.Add(valueList);                          // 작동안됨 ???
                        StockData = StockData.Remove(StockData.Length - 1, 1);  // 맨뒤의 ';' 제거 (칸추가 방지)
                        dTable.Rows.Add(StockData.Split(';'));
                        rtbSource.AppendText("\n");
                    }
                }
                Random rTime = new Random();
                Delay(Convert.ToInt32(tbDelayTime.Text) + rTime.Next(10, 150));
            }
            dgHTML.ItemsSource = dTable.DefaultView;
            return 0;
        }
        // ===================================================================================
        // 주가 및 보유율을 한개의 그래프에 작성
        // chart = sl.CreateChart("A2", "D10", new SLCreateChartOptions() { RowsAsDataSeries = false });
        // SLCreateChartOptions is property RowsAsDataSeries
        // REF : ChartsColumnLineAreaCombination.cs
        // 시트에서 그래프용 데이터 영역설정 : A:x-C:x - 데이터 순서= 축/1번/2번 데이터순
        // 2019.01.21 : 그래프 최대 최소 자동 조정 추가
        // 2020.09.15 : 그래프 위치 & 크기 옵션화
        // ===================================================================================
        private void InsertGraphPrice(SLDocument cSL, string tSheet)
        {
            if (!cSL.SelectWorksheet(tSheet)) return;                       // 시트유무 확인
            SLWorksheetStatistics stats = cSL.GetWorksheetStatistics();
            int GraphTopY = Convert.ToInt32(tbGraphY.Text);                 // 그래프 위치
            int GraphTopX = Convert.ToInt32(tbGraphX.Text);
            double cHeight = Convert.ToInt32(tbChartHeight.Text);           // 챠트 크기
            double cWidth = Convert.ToInt32(tbChartWidth.Text);
            SLChart chart;
            int startRow = stats.StartRowIndex;
            int endRow = stats.EndRowIndex;                                 // 20.11.17 버그수정
            // 시트에서 그래프용 데이터 영역설정 : A:x-C:x - 데이터 순서= 축/1번/2번 데이터순
            chart = cSL.CreateChart("A" + startRow.ToString(), "C" + endRow.ToString());
            chart.SetChartType(SLLineChartType.Line);
            chart.SetChartPosition(GraphTopY, GraphTopX, 1 + cHeight, GraphTopX + cWidth);
            // 그래프 그리기 : A = x 축값, B = 1 번 데이터, C = 2번 데이터임!! 
            // 1번 데이터는 Primary
            chart.PlotDataSeriesAsPrimaryLineChart(1, SLChartDataDisplayType.Normal, true);
            // 2번 데이터는 Secondary
            chart.PlotDataSeriesAsSecondaryLineChart(2, SLChartDataDisplayType.Normal, false);
            SLWorksheetStatistics state = cSL.GetWorksheetStatistics();     // 2019.01.21
            double RangeMin = 1.0, RangeMax = 0.0, RangeTemp = 0.0;         // 보유율
            double RangeMinV = 9990000.0, RangeMaxV = 0.0;                  // 시가
            for (int rowNdx = 2; rowNdx <= state.EndRowIndex; rowNdx++) {   // 첫출과 끝줄 제외
                RangeTemp = cSL.GetCellValueAsDouble(rowNdx, 2);            // 시가 비교
                if (RangeTemp < RangeMinV) RangeMinV = RangeTemp;
                if (RangeTemp > RangeMaxV) RangeMaxV = RangeTemp;
                RangeTemp = cSL.GetCellValueAsDouble(rowNdx, 3);            // 보유율 비교
                if (RangeTemp < RangeMin) RangeMin = RangeTemp;
                if (RangeTemp > RangeMax) RangeMax = RangeTemp;
            }
            chart.PrimaryValueAxis.Minimum = RangeMinV;
            chart.PrimaryValueAxis.Maximum = RangeMaxV;
            chart.SecondaryValueAxis.Minimum = RangeMin;
            chart.SecondaryValueAxis.Maximum = RangeMax;
            //if (chbRev.IsChecked == true)
            //    chart.PrimaryValueAxis.InReverseOrder = true;
            cSL.InsertChart(chart);
        }
        // ===================================================================================
        // 2021.04.03 : 기관,외인매매량 - 2 Y axis 형식
        // 기관 및 외인의 매매량 그래프를 두개 축을 가진 Line 그래프로 작성 (외인 = 좌측)
        // chart = sl.CreateChart("A2", "D10", new SLCreateChartOptions() { RowsAsDataSeries = false });
        // SLCreateChartOptions is property RowsAsDataSeries
        // REF : ChartsColumnLineAreaCombination.cs
        // 시트에서 그래프용 데이터 영역설정 : F = x 축값, G = 기관매매량, H = 외인매매량
        // ===================================================================================
        private void InsertGraphTradeAll2Axis(SLDocument cSL, string tSheet)
        {
            if (!cSL.SelectWorksheet(tSheet)) return;                       // 시트유무 확인
            SLWorksheetStatistics stats = cSL.GetWorksheetStatistics();
            int GraphTopY = Convert.ToInt32(tbGraphY2.Text);                // 그래프 위치
            int GraphTopX = Convert.ToInt32(tbGraphX2.Text);
            double cHeight = Convert.ToInt32(tbChartHeight2.Text);          // 챠트 크기
            double cWidth = Convert.ToInt32(tbChartWidth2.Text);
            SLChart chart;
            int startRow = stats.StartRowIndex;
            int endRow = stats.EndRowIndex;                                 // 20.11.17 버그수정
            // 시트에서 그래프용 데이터 영역설정 : A:x-C:x - 데이터 순서= 축/1번/2번 데이터순
            chart = cSL.CreateChart("F" + startRow.ToString(), "H" + endRow.ToString());
            chart.SetChartType(SLLineChartType.Line);
            chart.SetChartPosition(GraphTopY, GraphTopX, GraphTopY + cHeight, GraphTopX + cWidth);
            // 그래프 그리기 : F = x 축값, G = 기관매매량, H = 외인매매량!! 
            chart.PlotDataSeriesAsPrimaryLineChart(1, SLChartDataDisplayType.Normal, true);
            chart.PlotDataSeriesAsPrimaryLineChart(2, SLChartDataDisplayType.Normal, true);

            SLWorksheetStatistics state = cSL.GetWorksheetStatistics();     // 2021.04.03
            int RangeMinK = 99000000, RangeMaxK = -99000000;                // 거래량 - 기관
            int RangeMinF = 99000000, RangeMaxF = -99000000;                // 거래량 - 외인
            int RangeTempK = 0, RangeTempF = 0;
            for (int rowNdx = 2; rowNdx <= state.EndRowIndex; rowNdx++)
            {   // 첫출과 끝줄 제외
                RangeTempF = cSL.GetCellValueAsInt32(rowNdx, 8);            // 외인거래량 비교
                if (RangeTempF < RangeMinF) RangeMinF = RangeTempF;
                if (RangeTempF > RangeMaxF) RangeMaxF = RangeTempF;
                RangeTempK = cSL.GetCellValueAsInt32(rowNdx, 7);            // 기관거래량 비교
                if (RangeTempK < RangeMinK) RangeMinK = RangeTempK;
                if (RangeTempK > RangeMaxK) RangeMaxK = RangeTempK;
            }
            if (RangeMinF > RangeMinK) RangeMinF = RangeMinK;
            if (RangeMaxK > RangeMaxF) RangeMaxF = RangeMaxK;
            chart.PrimaryValueAxis.Minimum = RangeMinF;
            chart.PrimaryValueAxis.Maximum = RangeMaxF;
            chart.PrimaryValueAxis.SetOtherAxisCrossing(RangeMinF);         // 최소값에 X축 맞춤
            cSL.InsertChart(chart);
        }
        // ===================================================================================
        // 2020.11.08 : 기관,외인매매량 Bar 형식 - 1 axis
        // 2020.12.04 : 기관/외인매매량 분리 -> 이함수는 기관만
        // 기관 및 외인의 매매량 그래프를 한개 축을 가진 Bar 그래프로 작성
        // chart = sl.CreateChart("A2", "D10", new SLCreateChartOptions() { RowsAsDataSeries = false });
        // SLCreateChartOptions is property RowsAsDataSeries
        // REF : ChartsColumnLineAreaCombination.cs
        // 시트에서 그래프용 데이터 영역설정 : F = x 축값, G = 기관매매량, H = 외인매매량
        // ===================================================================================
        private void InsertGraphTradeAllBar(SLDocument cSL, string tSheet) 
        {
            if (!cSL.SelectWorksheet(tSheet)) return;                       // 시트유무 확인
            SLWorksheetStatistics stats = cSL.GetWorksheetStatistics();
            int GraphTopY = Convert.ToInt32(tbGraphY2.Text);                // 그래프 위치
            int GraphTopX = Convert.ToInt32(tbGraphX2.Text);
            double cHeight = Convert.ToInt32(tbChartHeight2.Text);          // 챠트 크기
            double cWidth = Convert.ToInt32(tbChartWidth2.Text);
            SLChart chart;
            int startRow = stats.StartRowIndex;
            int endRow = stats.EndRowIndex;                                 // 20.11.17 버그수정
            // 시트에서 그래프용 데이터 영역설정 : A:x-C:x - 데이터 순서= 축/1번/2번 데이터순
            chart = cSL.CreateChart("F" + startRow.ToString(), "H" + endRow.ToString());
            chart.SetChartType(SLColumnChartType.StackedCylinder);

            chart.SetChartPosition(GraphTopY, GraphTopX, GraphTopY + cHeight, GraphTopX + cWidth);
            // 그래프 그리기 : F = x 축값, G = 기관매매량, H = 외인매매량!! 
            chart.PlotDataSeriesAsPrimaryLineChart(1, SLChartDataDisplayType.Normal, true);
            chart.PlotDataSeriesAsPrimaryLineChart(2, SLChartDataDisplayType.Normal, true);
            SLWorksheetStatistics state = cSL.GetWorksheetStatistics();     // 2019.01.21
            int RangeMin = 99000000, RangeMax = -99000000;                  // 거래량
            int RangeTempA = 0, RangeTempB = 0;
            for (int rowNdx = 2; rowNdx <= state.EndRowIndex; rowNdx++)     // 20.11.17 버그수정
            {  // 첫출과 끝줄 제외
                RangeTempA = cSL.GetCellValueAsInt32(rowNdx, 7);            // 기관 거래량
                RangeTempB = cSL.GetCellValueAsInt32(rowNdx, 8);            // 외인 거래량
                if(RangeTempA > 0 && RangeTempB > 0)                        // + 합산일때
                    RangeTempA = RangeTempA + RangeTempB;
                else if (RangeTempA < 0 && RangeTempB < 0)                  // - 합산일때
                    RangeTempB = RangeTempA + RangeTempB;
                if (RangeTempB < RangeMin) RangeMin = RangeTempB;
                if (RangeTempA < RangeMin) RangeMin = RangeTempA;
                if (RangeTempB > RangeMax) RangeMax = RangeTempB;
                if (RangeTempA > RangeMax) RangeMax = RangeTempA;
            }
            chart.PrimaryValueAxis.Minimum = RangeMin;
            chart.PrimaryValueAxis.Maximum = RangeMax;
            chart.PrimaryValueAxis.SetOtherAxisCrossing(RangeMin);          // 최소값에 X축 맞춤
            cSL.InsertChart(chart);
        }
        // ===================================================================================
        // chart = sl.CreateChart("A2", "D10", new SLCreateChartOptions() { RowsAsDataSeries = false });
        // SLCreateChartOptions is property RowsAsDataSeries
        // REF : ChartsColumnLineAreaCombination.cs
        // 시트에서 그래프용 데이터 영역설정 : F = x 축값, G = 기관매매량, H = 외인매매량
        // 2020.12.04 : 외인매매량
        // ===================================================================================
        private void InsertGraphTradeFor(SLDocument cSL, string tSheet)
        {
            if (!cSL.SelectWorksheet(tSheet)) return;                       // 시트유무 확인
            SLWorksheetStatistics stats = cSL.GetWorksheetStatistics();
            int GraphTopY = Convert.ToInt32(tbGraphY3.Text);                // 그래프 위치
            int GraphTopX = Convert.ToInt32(tbGraphX3.Text);
            double cHeight = Convert.ToInt32(tbChartHeight3.Text);          // 챠트 크기
            double cWidth = Convert.ToInt32(tbChartWidth3.Text);
            SLChart chart;
            int startRow = stats.StartRowIndex;
            int endRow = stats.EndRowIndex;                                 // 20.11.17 버그수정
            // 시트에서 그래프용 데이터 영역설정 : A:x-C:x - 데이터 순서= 축/1번/2번 데이터순
            chart = cSL.CreateChart("K" + startRow.ToString(), "L" + endRow.ToString());
            chart.SetChartType(SLColumnChartType.StackedCylinder);
            chart.SetChartPosition(GraphTopY, GraphTopX, GraphTopY + cHeight, GraphTopX + cWidth);
            // 그래프 그리기 : F = x 축값, G = 기관매매량, H = 외인매매량!! 
            chart.PlotDataSeriesAsPrimaryLineChart(1, SLChartDataDisplayType.Normal, true);
            chart.PlotDataSeriesAsPrimaryLineChart(2, SLChartDataDisplayType.Normal, true);
            SLWorksheetStatistics state = cSL.GetWorksheetStatistics();     // 2019.01.21
            int RangeMin = 99000000, RangeMax = -99000000;                  // 거래량
            int RangeTempA = 0, RangeTempB = 0;
            for (int rowNdx = 2; rowNdx <= state.EndRowIndex; rowNdx++)     // 20.11.17 버그수정
            {  // 첫출과 끝줄 제외
                RangeTempA = cSL.GetCellValueAsInt32(rowNdx, 12);           // 외인 거래량
                if (RangeTempA < RangeMin) RangeMin = RangeTempA;
                if (RangeTempA > RangeMax) RangeMax = RangeTempA;
            }
            chart.PrimaryValueAxis.Minimum = RangeMin;
            chart.PrimaryValueAxis.Maximum = RangeMax;
            chart.PrimaryValueAxis.SetOtherAxisCrossing(RangeMin);          // 최소값에 X축 맞춤
            cSL.InsertChart(chart);
        }
        // ===================================================================================
        // chart = sl.CreateChart("A2", "D10", new SLCreateChartOptions() { RowsAsDataSeries = false });
        // SLCreateChartOptions is property RowsAsDataSeries
        // REF : ChartsColumnLineAreaCombination.cs
        // 시트에서 그래프용 데이터 영역설정 : F = x 축값, G = 기관매매량, H = 외인매매량
        // 2020.12.04 : 외인매매량
        // ===================================================================================
        private void InsertGraphTradeKor(SLDocument cSL, string tSheet)
        {
            if (!cSL.SelectWorksheet(tSheet)) return;                       // 시트유무 확인
            SLWorksheetStatistics stats = cSL.GetWorksheetStatistics();
            int GraphTopY = Convert.ToInt32(tbGraphY4.Text);                // 그래프 위치
            int GraphTopX = Convert.ToInt32(tbGraphX4.Text);
            double cHeight = Convert.ToInt32(tbChartHeight4.Text);          // 챠트 크기
            double cWidth = Convert.ToInt32(tbChartWidth4.Text);
            SLChart chart;
            int startRow = stats.StartRowIndex;
            int endRow = stats.EndRowIndex;                                 // 20.11.17 버그수정
            // 시트에서 그래프용 데이터 영역설정 : A:x-C:x - 데이터 순서= 축/1번/2번 데이터순
            chart = cSL.CreateChart("M" + startRow.ToString(), "N" + endRow.ToString());
            chart.SetChartType(SLColumnChartType.StackedCylinder);
            chart.SetChartPosition(GraphTopY, GraphTopX, GraphTopY + cHeight, GraphTopX + cWidth);
            // 그래프 그리기 : F = x 축값, G = 기관매매량, H = 외인매매량!! 
            chart.PlotDataSeriesAsPrimaryLineChart(1, SLChartDataDisplayType.Normal, true);
            chart.PlotDataSeriesAsPrimaryLineChart(2, SLChartDataDisplayType.Normal, true);
            SLWorksheetStatistics state = cSL.GetWorksheetStatistics();     // 2019.01.21
            int RangeMin = 99000000, RangeMax = -99000000;                  // 거래량
            int RangeTempA = 0, RangeTempB = 0;
            for (int rowNdx = 2; rowNdx <= state.EndRowIndex; rowNdx++)     // 20.11.17 버그수정
            {  // 첫출과 끝줄 제외
                RangeTempA = cSL.GetCellValueAsInt32(rowNdx, 14);           // 외인 거래량
                if (RangeTempA < RangeMin) RangeMin = RangeTempA;
                if (RangeTempA > RangeMax) RangeMax = RangeTempA;
            }
            chart.PrimaryValueAxis.Minimum = RangeMin;
            chart.PrimaryValueAxis.Maximum = RangeMax;
            chart.PrimaryValueAxis.SetOtherAxisCrossing(RangeMin);          // 최소값에 X축 맞춤
            cSL.InsertChart(chart);
        }
        // ===================================================================================
        // 종목화일을 읽어 데이터 그리드에 표시하고 작업준비
        // ===================================================================================
        private void Excel2DataGrid(DataGrid dGrid, string sheetName)
        {
            string XlsFilename;
            OpenFileDialog openfile = new OpenFileDialog();
            openfile.DefaultExt = ".xlsx";
            openfile.Filter = "(.xlsx)|*.xlsx";
            openfile.Title = "처리할 종목코드 화일을 선택하세요";
            openfile.ShowDialog();
            XlsFilename = openfile.FileName;
            if (XlsFilename == "") return;
            SLDocument sl = new SLDocument(XlsFilename, sheetName);
            SLWorksheetStatistics state = sl.GetWorksheetStatistics();
            int noColumn = state.EndColumnIndex;
            DataTable dTable = new DataTable();
            dTable.Clear();
            DataRow dRow = dTable.NewRow();
            rtbSource.Document.Blocks.Clear();
            rtbSource.AppendText("\n");
            // Red 1st row and Insert as header to DatGrid
            for (int colNdx = 1; colNdx <= state.EndColumnIndex; colNdx++)
            {
                string strValue = sl.GetCellValueAsString(1, colNdx);
                dTable.Columns.Add(strValue, typeof(string));
            }
            // red data
            for (int rowNdx = 2; rowNdx <= state.EndRowIndex; rowNdx++)
            {
                List<string> valueList = new List<string>();
                valueList.Clear();
                string ExcelData = "";
                for (int colNdx = 1; colNdx <= state.EndColumnIndex; colNdx++)
                {
                    string strValue = sl.GetCellValueAsString(rowNdx, colNdx);
                    ExcelData += strValue + ";";
                    valueList.Add(strValue);
                    rtbSource.AppendText(strValue);
                }
                ExcelData = ExcelData.Remove(ExcelData.Length - 1, 1);          // 맨뒤의 ';' 제거 (칸추가 방지)
                dTable.Rows.Add(ExcelData.Split(';'));
                rtbSource.AppendText("\n");
            }
            dGrid.ItemsSource = dTable.DefaultView;
        }
        // ===================================================================================
        // 각 종목에 대해 웹데이터 읽고 처리해서 각각 엑셀시트로 저장 처리
        // ===================================================================================
        private void AllStock2Excel(DataGrid dgStockData, DataGrid dgStockCode)
        {
            string XlsFilename;
            btnXlsx.Content = "Wait...";
            SaveFileDialog openfile = new SaveFileDialog();
            openfile.DefaultExt = ".xlsx";
            openfile.Filter = "(.xlsx)|*.xlsx";
            openfile.Title = "종목 거래 데이터를 저장할 화일명을 입력하세요 ";
            if(openfile.ShowDialog() != true)   return;                     // 4 Feb 20
            XlsFilename = openfile.FileName;
            SLDocument sl = new SLDocument();
            // -----------------------------------------------------------------------------------
            dgStockCode.SelectAllCells();                                   // DataGrid -> clipboard
            dgStockCode.ClipboardCopyMode = DataGridClipboardCopyMode.IncludeHeader;
            ApplicationCommands.Copy.Execute(null, dgStockCode);
            dgStockCode.UnselectAllCells();
            String result = (string)Clipboard.GetData(DataFormats.CommaSeparatedValue);
            string[] Lines = result.Split(new string[] { "\r\n", "\n" }, StringSplitOptions.None);
            string[] Fields;
            Fields = Lines[0].Split(new char[] { ',' });                    // 컬럼 갯수확인
            //int ColsNo = Fields.GetLength(0);
            // -----------------------------------------------------------------------------------
            for (int row = 1; row < Lines.GetLength(0); row++)              // 종목코드 = 1st 줄부터
            {
                dgStockCode.SelectedIndex = row - 1;                        // 2020.02.04 추가
                dgStockCode.ScrollIntoView(dgStockCode.SelectedItem, null); //
                Fields = Lines[row].Split(new char[] { ',' });              // 모든 줄 처리 시작
                if (Fields[0].Length < 1) continue;                         // 빈칸이면 무시 
                tbStatus.Text = "Reading : " + Fields[0];
                Delay(20);                     // 웹 크롤링 정보 전달 : URI, Table Number, 마지막 페이지
                int check = HtmlWeb2Table(tbURL.Text, Fields[1], Fields[0], int.Parse(tbTableNo.Text), int.Parse(tbEndPage.Text));
                tbStatus.Text = "Saving Sheet : " + Fields[0];
                if (check == -1) continue;
                DataGrid2Excel(dgStockData, sl, Fields[0]);                 // (그래프포함)
                Random rTime = new Random();
                Delay(Convert.ToInt32(tbDelayTime.Text) + rTime.Next(10, 120));
            }
            // -----------------------------------------------------------------------------------
            sl.SaveAs(XlsFilename);
            tbStatus.Text = "Reading and saving finished";                 
        }
        // ===================================================================================
        // 윈폼과 달리 WPF에서는 DaataGrid.Row[i].Cell[j] 가 안된다
        // https://kidaatlantis.wordpress.com/2013/11/04/data-export-from-datagrid-to-excel-in-wpf/
        // ===================================================================================
        // save datagrid -> excel  (xlsx)
        // SL : Install-Package DocumentFormat.OpenXml -Version 2.5.0 : 다른 버전은 SL과 호환X
        // ===================================================================================
        // 각 종목별 데이터 읽고 -> 데이터 그리드 -> 각각 시트로 저장 (그래프포함) -> 통합화일 저장
        // ===================================================================================
        private void DataGrid2Excel(DataGrid dataGrid, SLDocument sl, string sheetName)
        {
            sl.AddWorksheet(sheetName);                                     // 종목이름 시트 추가
            // -----------------------------------------------------------------------------------
            dataGrid.SelectAllCells();                                      // DataGrid -> clipboard
            dataGrid.ClipboardCopyMode = DataGridClipboardCopyMode.IncludeHeader;
            ApplicationCommands.Copy.Execute(null, dataGrid);
            dataGrid.UnselectAllCells();
            // ------------------------ DataGrid 에 있는 값을 String [] Lines 에 넣음
            String result = (string)Clipboard.GetData(DataFormats.CommaSeparatedValue);
            string[] Lines = result.Split(new string[] { "\r\n", "\n" }, StringSplitOptions.None);
            string[] Fields;                                                // 1줄 데이터 읽음
            Fields = Lines[0].Split(new char[] { ',' });                    // 컬럼 갯수확인
            // -----------------------------------------------------------------------------------
            // Lines는 최근 데이터가 앞에 있음 -> 역순으로 정리 : 20.11.17
            // 제목행을 제외한 데이터 갯수가 홀수인 경우 - 가운데 데이터는 그위치에 둠
            // 제목행을 제외한 데이터 갯수가 짝수인 경우 - 아래 위 대칭이므로 모두 다 이동
            // -----------------------------------------------------------------------------------
            if (chbRev.IsChecked == true)
            {
                String LinesTemp;
                int rowTotalNo = Lines.GetLength(0) - 1;
                int rowNumbers = (rowTotalNo - 1) / 2;
                for (int i = 1; i <= rowNumbers; i++)
                {
                    LinesTemp = Lines[i];
                    Lines[i] = Lines[rowTotalNo-i];
                    Lines[rowTotalNo - i] = LinesTemp;
                }
            }
            // -----------------------------------------------------------------------------------
            int ColsNo = Fields.GetLength(0);
            string temp = Fields[8];                                        // 전일비 - 보유율 교환
            Fields[8] = Fields[2]; Fields[2] = temp;
            for (int col = 0; col < ColsNo; col++)                          // 첫줄 = 헤더
                sl.SetCellValue(1, col + 1, Fields[col]);
            // -----------------------------------------------------------------------------------
            SLStyle style = sl.CreateStyle();
            int rowSheet = 1;           // 실 데이터만 sheet의 row에 넣고 빈데이터가 있는 경우 skip
            for (int row = 1; row < Lines.GetLength(0) - 1; row++)  // 하루 데이터를 Lines에 읽음
            {
                if (Lines[row].Length <= 0) continue;               // '\n' 만 있는 빈줄 ...
                if (Lines[row].Length <= ColsNo) continue;          // ","  만 있는 빈줄 ....
                Fields = Lines[row].Split(new char[] { ',' });      // 각 데이터를 콤마로 분리 
                for (int col = 0; col < ColsNo; col++)                     
                {
                    if (Fields[col].Length < 1) continue;           // '\n' 만 있는 빈줄 ...
                    string tempX = Fields[8]; Fields[8] = Fields[2];// 전일비 - 보유율 교환
                    Fields[2] = tempX;
                    if (col == 0)
                    {
                        sl.SetCellValue(rowSheet + 1, col + 1, Fields[col]);
                    }
                    //else if (col == 2 || col == 3)                // 등락율, 보유율
                    else if (Fields[col].Contains("%"))
                    {
                        double percent = Convert.ToDouble((Fields[col].Replace("%", "").ToString()));
                        sl.SetCellValue(rowSheet + 1, col + 1, percent / 100);
                        style.FormatCode = "0.00%";
                        sl.SetCellStyle(rowSheet + 1, col + 1, style);
                    }
                    else
                    {
                        sl.SetCellValueNumeric(rowSheet + 1, col + 1, Fields[col]);
                        style.FormatCode = "#,##0";
                        sl.SetCellStyle(rowSheet + 1, col + 1, style);
                    }
                }
                rowSheet++;                     // 실데이터가 들어간 sheet의 row 번호 증가
            }
            //sl.InsertColumn(6, 1);            // 20.11.08 : SL.InsertColumn - 작동 이상함
            sl.CopyColumn(6, 9, 7);             // 20.11.08 : ALL - 6-9 데이터를 7번 열에 복사
            sl.CopyColumn(1, 6);                // 20.11.08 :       1번열 날짜를 6번 열에 복사
            sl.CopyColumn(6, 11);               // 20.12.04 : 외인- 6번열 날짜를 11번 열에 복사
            sl.CopyColumn(8, 12);               // 20.12.04 :       8번 외인거래양을 12번 열에 복사
            sl.CopyColumn(6, 13);               // 20.12.04 : 기관- 6번열 날짜를 13번 열에 복사
            sl.CopyColumn(7, 14);               // 20.12.04 :       7번 외인거래양을 14번 열에 복사
            //sl.InsertColumn(9, 1);
            InsertGraphPrice(sl, sheetName);
            if (chb2axis.IsChecked == true)
                InsertGraphTradeAll2Axis(sl, sheetName); // draw 2 axises graph for K & F
            else
                InsertGraphTradeAllBar(sl, sheetName);
            InsertGraphTradeFor(sl, sheetName); // 20.12.04에 외인 분리 - 외인매매량
            InsertGraphTradeKor(sl, sheetName); // 20.12.04에 추가 : 기관매매량 그래프
            sl.AutoFitColumn(1, ColsNo + 5);    // 컬럼 넓이 조정 + 추가된 컬럼 4개
        }
        // ===================================================================================
        // 테스트용 
        // ===================================================================================
        private void btnLoad_Click(object sender, RoutedEventArgs e)
        {
            SaveFileDialog openfile = new SaveFileDialog();
            btnLoad.Content = "Wait...";
            openfile.DefaultExt = ".txt";
            openfile.Filter = "(.txt)|*.txt";
            openfile.Title = "Select HTML file to import";
            openfile.ShowDialog();
            string Filename = openfile.FileName;
            HtmlDoc2Table(Filename, int.Parse(tbTableNoX.Text));
            btnLoad.Content = "Load Html";
        }
        // ===================================================================================
        // 테스트용 -  한 종목 읽어서 datagrid에 표시 : 테스트용임
        // ===================================================================================
        private void btnGo_Click(object sender, RoutedEventArgs e)
        {
            btnGo.Content = "Wait...";
            LogFileName = "Log" + DateTime.Now.ToString("-yyyyMMdd-HHmmss") + ".txt";
            Delay(1);
            HtmlWeb2Table(tbURL.Text, tbStockNo.Text, "test", int.Parse(tbTableNo.Text), int.Parse(tbEndPage.Text));
            btnGo.Content = "Go";
        }
        // ===================================================================================
        // 테스트용 - 엑셀 데이터를 읽음 : 시트명  = Sheet1
        // ===================================================================================
        private void btnImportExcel_Click(object sender, RoutedEventArgs e)
        {
            btnImportExcel.Content = "Wait...";
            tbStatus.Text = "Importing Excel into DataGrid";
            Excel2DataGrid(dgHTML, "TestSheet");
            btnImportExcel.Content = "Import Excel";
            tbStatus.Text = "Excel imported into DataGrid";
        }
        // ===================================================================================
        private void btnLosdStock_Click(object sender, RoutedEventArgs e)
        {
            btnLosdStock.Content = "Wait...";
            tbStatus.Text = "Importing Excel into DataGrid";
            Excel2DataGrid(dgStockList, "Sheet1");
            btnLosdStock.Content = "Load Stock";
            tbStatus.Text = "Stock codes are imported";
        }
        // ===================================================================================
        // 테스트용 
        // ===================================================================================
        // http://coderskey.blogspot.com/2014/08/convert-html-content-values-to-object.html
        // https://www.c-sharpcorner.com/UploadFile/9b86d4/getting-started-with-html-agility-pack/
        // http://davidgiard.com/2018/06/20/UsingHTMLAgilityPackToParseAWebPage.aspx
        // https://stackoverflow.com/questions/19684679/how-to-get-all-html-tags-that-contains-specific-string-in-their-attribute-values
        // var nodes = doc.DocumentNode.Descendants().Where(n => n.Attributes.Any(a => a.Value.Contains("en-us")));
        // var nodes = doc.DocumentNode.SelectNodes("//*[@*[contains(., 'en-us')]]");
        // ===================================================================================
        // 2018.07.04 - 네이버 증권조회 페이지 html 구조 가져오기
        // 투자자별 매매 동향 : http://finance.naver.com/item/frgn.nhn?code=066575
        // TABLE No = 3번째 : <table summary="외국인 기관 순매매
        // 헤더리스트 ("tr/th") : <tr class="title1"><th rowspan = "2" > 날짜 </ th > ...    
        // 날짜별 거래정보 ("tr/td") : <tr><td>2018.07.03</td>  
        // ===================================================================================
        // 테스트용 - HTML 텍스트 읽어서 데이터 처리 : Web -> DataGrid
        // ===================================================================================
        private void HtmlDoc2Table(string dataStream, int TableNo)
        {
            HtmlAgilityPack.HtmlDocument doc = new HtmlAgilityPack.HtmlDocument();  // 시험용
            doc.Load(dataStream);
            if (doc.DocumentNode.SelectNodes("//table") == null) return; // 20.02.04 - 증권코드 오류
            DataTable dTable = new DataTable();
            DataRow dRow = dTable.NewRow();
            int tableIndex = 0, HeadIndex = 0;
            // ----------------------------------------------------------------------------------------
            // 헤더 정보 확인 후 datagridview 컬럼 설정
            // ----------------------------------------------------------------------------------------
            foreach (HtmlNode tableBody in doc.DocumentNode.SelectNodes("//table")) // table node 선택
            {
                tableIndex++;
                if (tableIndex != TableNo) continue;                                // skip until what I need
                var headers = tableBody.SelectNodes("tr/th");                       // 헤더 모으기
                //dgHTML.FrozenColumnCount = headers.Count - 2;                       // 헤더갯수 (2중 헤더 제거)
                HeadIndex = 0;
                foreach (HtmlNode header in headers)
                {
                    var headText = header.InnerText;                                // Get head columns from th
                    HeadIndex++;                                                    // 배열 index + 1
                    if (HeadIndex == 8)                                             // 2단 테이블 헤더 처리
                    {
                        dTable.Columns[5].ColumnName = dTable.Columns[5].ColumnName + headText;
                    }
                    else if (HeadIndex == 9)
                    {
                        dTable.Columns[6].ColumnName = dTable.Columns[6].ColumnName + headText;
                    }
                    else
                        dTable.Columns.Add(headText, typeof(string));
                    rtbSource.AppendText(headText);
                }
            }
            // ----------------------------------------------------------------------------------------
            // 거래정보 업데이트
            // ----------------------------------------------------------------------------------------
            tableIndex = 0;
            rtbSource.Document.Blocks.Clear();
            rtbSource.AppendText("\n");
            foreach (HtmlNode tableBody in doc.DocumentNode.SelectNodes("//table")) // table node 선택
            {
                tableIndex++;
                if (tableIndex != TableNo) continue;                                // skip until what I need
                List<string> valueList = new List<string>();
                foreach (HtmlNode rowTable in tableBody.SelectNodes("tr"))          // tr node 선택
                {
                    if (rowTable.SelectNodes("td") == null) continue;               // td 없으면 무시
                    if (rowTable.InnerText == "") continue;                         // 네이버의 빈줄 무시
                    string StockData = "";
                    valueList.Clear();
                    foreach (HtmlNode cell in rowTable.SelectNodes("td"))           // ("th|td") th 또는 td node 선택
                    {
                        string strValue = cell.InnerText;
                        strValue = strValue.Replace("\t", "");
                        strValue = strValue.Replace("\n", "");
                        strValue = strValue.Replace("\r", "");
                        strValue = strValue.Replace("+", "");                       // + 기호 삭제
                        strValue = strValue.Replace(",", "");                       // CSV 사용위해 콤마삭제
                        StockData += strValue + ";";
                        valueList.Add(strValue);
                        rtbSource.AppendText(" " + strValue);
                    }
                    if (StockData.Contains("&nbsp")) continue;                      // 4 Feb '20 : 과거 증시 데이터 없음
                    //dgHTML.Items.Add(valueList);                                  // 작동안됨 ???
                    StockData = StockData.Remove(StockData.Length - 1, 1);          // 맨뒤의 ';' 제거 (칸추가 방지)
                    dTable.Rows.Add(StockData.Split(';'));
                    rtbSource.AppendText("\n");
                }
            }
            dgHTML.ItemsSource = dTable.DefaultView;
        }
        // ===================================================================================
        private void dgHTML_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {

        }
        // ===================================================================================
        double orginalWidth, originalHeight;
        ScaleTransform scale = new ScaleTransform();
        // ===================================================================================
        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            // 창 사이즈 조절용
            orginalWidth = this.Width;
            originalHeight = this.Height;
            if (this.WindowState == WindowState.Maximized)
            {
                ChangeSize(this.ActualWidth, this.ActualHeight);
            }
            this.SizeChanged += new SizeChangedEventHandler(Window_SizeChanged);
        }
        // -----------------------------------------------------------------------------
        private void ChangeSize(double width, double height)
        {
            scale.ScaleX = width / orginalWidth;
            scale.ScaleY = height / originalHeight;

            FrameworkElement rootElement = this.Content as FrameworkElement;

            rootElement.LayoutTransform = scale;
        }
        // -----------------------------------------------------------------------------
        private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            ChangeSize(e.NewSize.Width, e.NewSize.Height);
        }
        // ===================================================================================
    }
}
// ============================================================================================

블로그 이미지

DIYworld

,