예전에 작성한 프로그램임
네이버 증권 웹페이지 변경으로 오류 발생하여 약간 수정함
다만 그럼에도 오류 생기는 종목이 있긴 함
이전 버전 : https://diyworld.tistory.com/120
네이버 주식데이터 크롤링 : Web crawler
상폐된 주식의 경우 크롤링중 데이터 미존재 오류로 프로그램 다운됨 이유:예외처리 안했음 수정 : 오류나면 그 종목 무시하고 다음 종목 처리함 (확인 누르면 다음 처리) - 그래프 크기 위치 조
diyworld.tistory.com
수정된 버전 첨부함
// ==========================================================================================
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(" ")) 과거 증시데이터 없음처리 추가
// 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(" ")) 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(" ")) 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);
}
// ===================================================================================
}
}
// ============================================================================================
'C#_.NET' 카테고리의 다른 글
| AI 작성한 네이버 주식데이터 크롤링 : 25.10.07 (1) | 2025.10.07 |
|---|---|
| AI 자율 주행차량 : Altino (C#) (0) | 2023.07.30 |
| 네이버 주식데이터 크롤링 : Web crawler (2) | 2023.05.03 |
| 네이버 증권 데이터 엑셀로 다운 / 그래프 작성 (0) | 2022.07.02 |
| Tesseract OCR 4 한글 학습하기 (0) | 2020.02.16 |