검색결과 없음 HTML 추출 개선 및 도서관별 지연시간 저장 기능 구현

- 모든 도서관 검색기에서 검색결과 없음시 구체적인 HTML 조각 추출
- BookSearchResult에 Resulthtml 속성 추가하여 매칭된 HTML 컨텍스트 저장
- Helper_LibraryDelaySettings.cs 추가로 도서관별 검색 지연시간 XML 저장
- Check_copyWD.cs에 dvc_resulthtml 컬럼 표시 및 지연시간 저장 UI 구현
- 15개 SearchModel 파일에서 htmlContent 1000자 자르기를 의미있는 메시지로 교체
- HTTP 검색기들에 한글 인코딩 문제 해결을 위한 헤더 개선

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-09-15 23:39:04 +09:00
parent c0e6c9039e
commit 216311b558
34 changed files with 2641 additions and 783 deletions

View File

@@ -10,6 +10,9 @@
- **데이터베이스**: MySQL
- **주요기능**: 마크 작성, 복본조사, DLS 연동, 도서 정보 관리
## 데이터베이스 정보
- Server=1.215.250.130;Port=3306;Database=unimarc;uid=root;pwd=Admin21234;
## 코딩 컨벤션
- 파일명: PascalCase (예: DLS_Copy.cs)
- 클래스명: PascalCase

View File

@@ -9,6 +9,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Factory_Client", "Factory_C
EndProject
Project("{54435603-DBB4-11D2-8724-00A0C9A8B90C}") = "UniMarcSetup", "UniMarcSetup\UniMarcSetup.vdproj", "{B0A88F76-DC68-44F9-90B4-CD94625CC1F4}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "솔루션 항목", "솔루션 항목", "{2A3A057F-5D22-31FD-628C-DF5EF75AEF1E}"
ProjectSection(SolutionItems) = preProject
CLAUDE.md = CLAUDE.md
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU

View File

@@ -0,0 +1,170 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using System.Text;
namespace UniMarc
{
/// <summary>
/// 도서관별 지연시간 설정을 관리하는 클래스
/// </summary>
public class Helper_LibraryDelaySettings
{
private static readonly string SettingsFileName = "LibraryDelaySettings.xml";
private static readonly string SettingsFilePath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"UniMarc",
SettingsFileName
);
private Dictionary<string, int> _libraryDelaySettings;
public Helper_LibraryDelaySettings()
{
_libraryDelaySettings = new Dictionary<string, int>();
LoadSettings();
}
/// <summary>
/// 도서관별 지연시간 설정을 로드
/// </summary>
private void LoadSettings()
{
try
{
if (File.Exists(SettingsFilePath))
{
var xmlDoc = new XmlDocument();
xmlDoc.Load(SettingsFilePath);
var libraryNodes = xmlDoc.SelectNodes("//LibraryDelaySettings/Library");
if (libraryNodes != null)
{
foreach (XmlNode node in libraryNodes)
{
var name = node.Attributes?["Name"]?.Value;
var delayStr = node.Attributes?["Delay"]?.Value;
if (!string.IsNullOrEmpty(name) && int.TryParse(delayStr, out int delay))
{
_libraryDelaySettings[name] = delay;
}
}
}
}
}
catch (Exception ex)
{
Console.WriteLine($"도서관 지연시간 설정 로드 오류: {ex.Message}");
_libraryDelaySettings = new Dictionary<string, int>();
}
}
/// <summary>
/// 도서관별 지연시간 설정을 저장
/// </summary>
private void SaveSettings()
{
try
{
// 디렉토리 생성
var directory = Path.GetDirectoryName(SettingsFilePath);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
// XML 파일로 저장
var xmlDoc = new XmlDocument();
var declaration = xmlDoc.CreateXmlDeclaration("1.0", "UTF-8", null);
xmlDoc.AppendChild(declaration);
var root = xmlDoc.CreateElement("LibraryDelaySettings");
xmlDoc.AppendChild(root);
foreach (var kvp in _libraryDelaySettings)
{
var libraryElement = xmlDoc.CreateElement("Library");
libraryElement.SetAttribute("Name", kvp.Key);
libraryElement.SetAttribute("Delay", kvp.Value.ToString());
root.AppendChild(libraryElement);
}
xmlDoc.Save(SettingsFilePath);
}
catch (Exception ex)
{
Console.WriteLine($"도서관 지연시간 설정 저장 오류: {ex.Message}");
}
}
/// <summary>
/// 특정 도서관의 지연시간 설정을 가져옴
/// </summary>
/// <param name="libraryName">도서관 이름</param>
/// <returns>지연시간(초), 설정이 없으면 0</returns>
public int GetDelay(string libraryName)
{
if (string.IsNullOrEmpty(libraryName))
return 0;
return _libraryDelaySettings.TryGetValue(libraryName, out int delay) ? delay : 0;
}
/// <summary>
/// 특정 도서관의 지연시간 설정을 저장
/// </summary>
/// <param name="libraryName">도서관 이름</param>
/// <param name="delaySeconds">지연시간(초)</param>
public void SetDelay(string libraryName, int delaySeconds)
{
if (string.IsNullOrEmpty(libraryName))
return;
if (delaySeconds < 0)
delaySeconds = 0;
_libraryDelaySettings[libraryName] = delaySeconds;
SaveSettings();
Console.WriteLine($"도서관 지연시간 설정 저장: {libraryName} = {delaySeconds}초");
}
/// <summary>
/// 모든 도서관 지연시간 설정을 가져옴
/// </summary>
/// <returns>도서관명-지연시간 딕셔너리</returns>
public Dictionary<string, int> GetAllSettings()
{
return new Dictionary<string, int>(_libraryDelaySettings);
}
/// <summary>
/// 특정 도서관의 지연시간 설정을 제거
/// </summary>
/// <param name="libraryName">도서관 이름</param>
public void RemoveDelay(string libraryName)
{
if (string.IsNullOrEmpty(libraryName))
return;
if (_libraryDelaySettings.ContainsKey(libraryName))
{
_libraryDelaySettings.Remove(libraryName);
SaveSettings();
Console.WriteLine($"도서관 지연시간 설정 제거: {libraryName}");
}
}
/// <summary>
/// 모든 지연시간 설정을 초기화
/// </summary>
public void ClearAllSettings()
{
_libraryDelaySettings.Clear();
SaveSettings();
Console.WriteLine("모든 도서관 지연시간 설정 초기화");
}
}
}

View File

@@ -30,14 +30,9 @@ namespace WindowsFormsApp1
Application.Run(new Main());
}
static void DB_InitSetting()
{
UniMarc.Properties.Settings.Default.IP = ConvertIP(UniMarc.Properties.Settings.Default.IP);
UniMarc.Properties.Settings.Default.IP = ConvertIP(UniMarc.Properties.Settings.Default.IP);
UniMarc.Properties.Settings.Default.Port = Convert2to10(UniMarc.Properties.Settings.Default.Port);
UniMarc.Properties.Settings.Default.Uid = ConvertAscii(UniMarc.Properties.Settings.Default.Uid);
UniMarc.Properties.Settings.Default.pwd = ConvertAscii(UniMarc.Properties.Settings.Default.pwd);

View File

@@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// 모든 값을 지정하거나 아래와 같이 '*'를 사용하여 빌드 번호 및 수정 번호를
// 기본값으로 할 수 있습니다.
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("2025.09.03.2030")]
[assembly: AssemblyFileVersion("2025.09.03.2030")]
[assembly: AssemblyVersion("2025.09.16.0000")]
[assembly: AssemblyFileVersion("2025.09.16.0000")]

View File

@@ -397,8 +397,10 @@ namespace BokBonCheck
// 페이지 변경을 감지하는 메서드
await WaitForPageChange(new WebDriverWait(_driver, TimeSpan.FromSeconds(15)));
// 검색 결과 수 추출
var resultCount = ExtractBookCount(_driver, searchTerm, out string ermsg);
// SearchAsync에서 PageSource 가져오기
var htmlContent = _driver.PageSource;
var resultCount = ExtractBookCount(htmlContent, out string ermsg, out string resultHtml);
result.Resulthtml = resultHtml;
if (resultCount == -1)
{
result.BookCount = 0;
@@ -423,128 +425,40 @@ namespace BokBonCheck
return result;
}
private int ExtractBookCount(IWebDriver driver, string searchTerm, out string errmessage)
private int ExtractBookCount(string htmlContent, out string errmessage, out string resulthtml)
{
errmessage = string.Empty;
if (driver.Url.EndsWith("DetailSearchResult") == false)
{
errmessage = "결과페이지가아님";
return -1;
}
resulthtml = string.Empty;
try
{
// 1. 검색결과가 없는 경우 확인
try
// 검색 결과가 없메시지 확인
var noResultPatterns = new[]
{
var noResultElements = driver.FindElements(By.XPath("//*[contains(text(), '검색결과가 없습니다') or contains(text(), '검색된 자료가 없습니다')]"));
if (noResultElements.Count > 0)
@"검색결과가 없습니다",
@"검색된 자료가 없습니다",
@"자료가 없습니다",
@"총 0 건",
@"총 0건"
};
foreach (var pattern in noResultPatterns)
{
if (htmlContent.Contains(pattern))
{
errmessage = "검색결과없음";
return 0;
}
}
catch
{
// 검색결과가 있는 경우로 진행
}
RetryPoint:
bool retry = false;
// 2. 안산시 도서관 특화: span.count.gothic em 요소에서 직접 추출
try
{
var countElement = driver.FindElement(By.CssSelector("span.count.gothic em"));
if (countElement != null)
{
// Text 대신 InnerHtml이나 GetAttribute 사용해보기
var countText = countElement.Text.Trim();
var innerHTML = countElement.GetAttribute("innerHTML");
var outerHTML = countElement.GetAttribute("outerHTML");
Console.WriteLine($"count 요소 .Text: '{countText}'");
Console.WriteLine($"count 요소 innerHTML: '{innerHTML}'");
Console.WriteLine($"count 요소 outerHTML: '{outerHTML}'");
// innerHTML이나 outerHTML에서 텍스트 추출 시도
string textToUse = !string.IsNullOrEmpty(countText) ? countText : innerHTML;
if (string.IsNullOrEmpty(textToUse))
var match = System.Text.RegularExpressions.Regex.Match(htmlContent, pattern);
if (match.Success)
{
textToUse = outerHTML;
}
Console.WriteLine($"추출할 텍스트: '{textToUse}'");
// "총 0 건 " 형태에서 숫자 추출
var match = Regex.Match(textToUse, @"총\s*(\d+)\s*건", RegexOptions.IgnoreCase);
if (match.Success && int.TryParse(match.Groups[1].Value, out int count))
{
if (count == 0)
{
errmessage = "검색결과없음";
Console.WriteLine("검색 결과: 0건");
return 0;
}
errmessage = $"검색성공({count}권)";
Console.WriteLine($"검색 결과: {count}건");
return count;
resulthtml = ExtractResultContext(htmlContent, match);
}
else
{
Console.WriteLine($"정규식 매칭 실패: '{textToUse}'");
resulthtml = htmlContent.Length > 500 ? htmlContent.Substring(0, 500) : htmlContent;
}
return 0;
}
}
catch (Exception ex1)
{
Console.WriteLine($"span.count.gothic em 요소 추출 실패: {ex1.Message}");
}
// 3. span.count 전체에서 추출 시도
try
{
var countSpan = driver.FindElement(By.CssSelector("span.count"));
if (countSpan != null)
{
var fullText = countSpan.Text.Trim();
Console.WriteLine($"count span 전체 텍스트: '{fullText}'");
var match = Regex.Match(fullText, @"총\s*(\d+)\s*건", RegexOptions.IgnoreCase);
if (match.Success && int.TryParse(match.Groups[1].Value, out int count))
{
if (count == 0)
{
errmessage = "검색결과없음";
return 0;
}
errmessage = $"검색성공({count}권)";
return count;
}
}
}
catch (Exception ex2)
{
Console.WriteLine($"span.count 요소 추출 실패: {ex2.Message}");
}
// 3. 페이지 소스에서 정규식으로 추출 시도
var pageSource = driver.PageSource;
// 검색 결과가 없다는 메시지 확인
if (pageSource.Contains("검색결과가 없습니다") ||
pageSource.Contains("검색된 자료가 없습니다") ||
pageSource.Contains("자료가 없습니다") ||
pageSource.Contains("총 0 건") ||
pageSource.Contains("총 0건"))
{
errmessage = "검색결과없음";
return 0;
}
// HTML에서 다양한 패턴 찾기 (안산시 도서관 특화)
var htmlPatterns = new[]
@@ -560,11 +474,14 @@ namespace BokBonCheck
foreach (var pattern in htmlPatterns)
{
var match = Regex.Match(pageSource, pattern, RegexOptions.IgnoreCase);
var match = Regex.Match(htmlContent, pattern, RegexOptions.IgnoreCase);
if (match.Success)
{
if (int.TryParse(match.Groups[1].Value, out int count))
{
// 매칭된 부분과 상위 태그 추출하여 resulthtml에 저장
resulthtml = ExtractResultContext(htmlContent, match);
if (count == 0)
{
errmessage = "검색결과없음";
@@ -576,28 +493,81 @@ namespace BokBonCheck
}
}
if (retry == false)
{
Console.WriteLine( "결과를 찾을 수 없어 재시도");
retry = true;
Task.Delay(1000);
goto RetryPoint;
}
else
{
errmessage = "결과수량을찾을수없음";
return -1;
}
errmessage = "결과수량을찾을수없음";
resulthtml = htmlContent.Length > 500 ? htmlContent.Substring(0, 500) : htmlContent;
return -1;
}
catch (Exception ex)
{
errmessage = ex.Message;
resulthtml = htmlContent.Length > 500 ? htmlContent.Substring(0, 500) : htmlContent;
return -1;
}
}
/// <summary>
/// 매칭된 결과와 그 상위 태그를 추출
/// </summary>
private string ExtractResultContext(string htmlContent, Match match)
{
try
{
var matchIndex = match.Index;
var matchLength = match.Length;
// 매칭된 위치 앞쪽에서 상위 태그 시작 찾기
var startSearchIndex = Math.Max(0, matchIndex - 200); // 매칭 위치 200자 전부터 검색
var searchText = htmlContent.Substring(startSearchIndex, matchIndex - startSearchIndex + matchLength + Math.Min(200, htmlContent.Length - matchIndex - matchLength));
// 상위 태그 패턴들 (div, p, h1-h6, span 등)
var tagPatterns = new[] { @"<(div|p|h[1-6]|span|section|article)[^>]*>", @"<[^>]+>" };
string resultContext = match.Value; // 기본값은 매칭된 부분만
foreach (var tagPattern in tagPatterns)
{
// 매칭된 부분 앞에서 가장 가까운 태그 시작 찾기
var tagMatches = Regex.Matches(searchText, tagPattern, RegexOptions.IgnoreCase);
for (int i = tagMatches.Count - 1; i >= 0; i--)
{
var tagMatch = tagMatches[i];
if (tagMatch.Index < (matchIndex - startSearchIndex))
{
// 태그 이름 추출
var tagName = Regex.Match(tagMatch.Value, @"<(\w+)", RegexOptions.IgnoreCase).Groups[1].Value;
// 닫는 태그 찾기
var closeTagPattern = $@"</{tagName}[^>]*>";
var closeMatch = Regex.Match(searchText, closeTagPattern, RegexOptions.IgnoreCase);
if (closeMatch.Success && closeMatch.Index > (matchIndex - startSearchIndex))
{
// 상위 태그와 그 내용을 포함하여 반환
var startIdx = tagMatch.Index;
var endIdx = closeMatch.Index + closeMatch.Length;
resultContext = searchText.Substring(startIdx, Math.Min(endIdx - startIdx, 500)); // 최대 500자
return resultContext;
}
}
}
}
// 상위 태그를 찾지 못한 경우, 매칭 전후 50자씩 포함
var contextStart = Math.Max(0, matchIndex - 50);
var contextEnd = Math.Min(htmlContent.Length, matchIndex + matchLength + 50);
resultContext = htmlContent.Substring(contextStart, contextEnd - contextStart);
return resultContext;
}
catch (Exception ex)
{
Console.WriteLine($"ExtractResultContext 오류: {ex.Message}");
return match.Value; // 오류 시 매칭된 부분만 반환
}
}
// 페이지 변경을 감지하는 메서드
public async Task WaitForPageChange(WebDriverWait wait)
{

View File

@@ -13,6 +13,8 @@ namespace BokBonCheck
public DateTime SearchTime { get; set; }
public string ErrorMessage { get; set; }
public bool IsSuccess { get; set; }
public string Resulthtml { get; set; }
}
public class BookSearchService

View File

@@ -244,8 +244,9 @@ namespace BokBonCheck
// 페이지 변경을 감지하는 메서드
await WaitForPageChange(new WebDriverWait(_driver, TimeSpan.FromSeconds(15)));
// 검색 결과 수 추출
var resultCount = ExtractBookCount(_driver, searchTerm, out string ermsg);
var htmlContent = _driver.PageSource;
var resultCount = ExtractBookCount(htmlContent, out string ermsg, out string resultHtml);
result.Resulthtml = resultHtml;
if (resultCount == -1)
{
result.BookCount = 0;
@@ -270,73 +271,39 @@ namespace BokBonCheck
return result;
}
private int ExtractBookCount(IWebDriver driver, string searchTerm, out string errmessage)
private int ExtractBookCount(string htmlContent, out string errmessage, out string resulthtml)
{
errmessage = string.Empty;
resulthtml = string.Empty;
try
{
// 1. totalCount 요소에서 직접 추출 시도
try
// 검색 결과가 없다는 메시지 확인
var noResultPatterns = new[]
{
var totalCountElement = driver.FindElement(By.CssSelector("p.totalCount"));
if (totalCountElement != null)
{
// strong 태그에서 직접 숫자 추출 시도
try
{
var strongElement = totalCountElement.FindElement(By.TagName("strong"));
if (strongElement != null)
{
var countText = strongElement.Text.Trim();
if (int.TryParse(countText, out int strongCount))
{
if (strongCount == 0)
{
errmessage = "검색결과없음";
return 0;
}
errmessage = $"검색성공({strongCount}권)";
return strongCount;
}
}
}
catch { }
@"검색결과가 없습니다",
@"검색된 자료가 없습니다",
@"자료가 없습니다",
@"전체 0 건"
};
// 전체 텍스트에서 정규식으로 추출
var totalCountText = totalCountElement.Text;
var match = Regex.Match(totalCountText, @"전체\s*(\d+)\s*건", RegexOptions.IgnoreCase);
foreach (var pattern in noResultPatterns)
{
if (htmlContent.Contains(pattern))
{
errmessage = "검색결과없음";
var match = System.Text.RegularExpressions.Regex.Match(htmlContent, pattern);
if (match.Success)
{
if (int.TryParse(match.Groups[1].Value, out int count))
{
if (count == 0)
{
errmessage = "검색결과없음";
return 0;
}
errmessage = $"검색성공({count}권)";
return count;
}
resulthtml = ExtractResultContext(htmlContent, match);
}
else
{
resulthtml = htmlContent.Length > 500 ? htmlContent.Substring(0, 500) : htmlContent;
}
return 0;
}
}
catch (Exception ex)
{
Console.WriteLine($"totalCount 요소 검색 중 오류: {ex.Message}");
}
// 2. 페이지 소스에서 정규식으로 추출 시도
var pageSource = driver.PageSource;
// 검색 결과가 없다는 메시지 확인
if (pageSource.Contains("검색결과가 없습니다") ||
pageSource.Contains("검색된 자료가 없습니다") ||
pageSource.Contains("자료가 없습니다") ||
pageSource.Contains("전체 0 건"))
{
errmessage = "검색결과없음";
return 0;
}
// HTML에서 다양한 패턴 찾기
var htmlPatterns = new[]
@@ -350,11 +317,14 @@ namespace BokBonCheck
foreach (var pattern in htmlPatterns)
{
var match = Regex.Match(pageSource, pattern, RegexOptions.IgnoreCase);
var match = Regex.Match(htmlContent, pattern, RegexOptions.IgnoreCase);
if (match.Success)
{
if (int.TryParse(match.Groups[1].Value, out int count))
{
// 매칭된 부분과 상위 태그 추출하여 resulthtml에 저장
resulthtml = ExtractResultContext(htmlContent, match);
if (count == 0)
{
errmessage = "검색결과없음";
@@ -367,16 +337,80 @@ namespace BokBonCheck
}
errmessage = "결과수량을찾을수없음";
resulthtml = htmlContent.Length > 500 ? htmlContent.Substring(0, 500) : htmlContent;
return -1;
}
catch (Exception ex)
{
errmessage = ex.Message;
resulthtml = htmlContent.Length > 500 ? htmlContent.Substring(0, 500) : htmlContent;
return -1;
}
}
/// <summary>
/// 매칭된 결과와 그 상위 태그를 추출
/// </summary>
private string ExtractResultContext(string htmlContent, Match match)
{
try
{
var matchIndex = match.Index;
var matchLength = match.Length;
// 매칭된 위치 앞쪽에서 상위 태그 시작 찾기
var startSearchIndex = Math.Max(0, matchIndex - 200); // 매칭 위치 200자 전부터 검색
var searchText = htmlContent.Substring(startSearchIndex, matchIndex - startSearchIndex + matchLength + Math.Min(200, htmlContent.Length - matchIndex - matchLength));
// 상위 태그 패턴들 (div, p, h1-h6, span 등)
var tagPatterns = new[] { @"<(div|p|h[1-6]|span|section|article)[^>]*>", @"<[^>]+>" };
string resultContext = match.Value; // 기본값은 매칭된 부분만
foreach (var tagPattern in tagPatterns)
{
// 매칭된 부분 앞에서 가장 가까운 태그 시작 찾기
var tagMatches = Regex.Matches(searchText, tagPattern, RegexOptions.IgnoreCase);
for (int i = tagMatches.Count - 1; i >= 0; i--)
{
var tagMatch = tagMatches[i];
if (tagMatch.Index < (matchIndex - startSearchIndex))
{
// 태그 이름 추출
var tagName = Regex.Match(tagMatch.Value, @"<(\w+)", RegexOptions.IgnoreCase).Groups[1].Value;
// 닫는 태그 찾기
var closeTagPattern = $@"</{tagName}[^>]*>";
var closeMatch = Regex.Match(searchText, closeTagPattern, RegexOptions.IgnoreCase);
if (closeMatch.Success && closeMatch.Index > (matchIndex - startSearchIndex))
{
// 상위 태그와 그 내용을 포함하여 반환
var startIdx = tagMatch.Index;
var endIdx = closeMatch.Index + closeMatch.Length;
resultContext = searchText.Substring(startIdx, Math.Min(endIdx - startIdx, 500)); // 최대 500자
return resultContext;
}
}
}
}
// 상위 태그를 찾지 못한 경우, 매칭 전후 50자씩 포함
var contextStart = Math.Max(0, matchIndex - 50);
var contextEnd = Math.Min(htmlContent.Length, matchIndex + matchLength + 50);
resultContext = htmlContent.Substring(contextStart, contextEnd - contextStart);
return resultContext;
}
catch (Exception ex)
{
Console.WriteLine($"ExtractResultContext 오류: {ex.Message}");
return match.Value; // 오류 시 매칭된 부분만 반환
}
}
// 페이지 변경을 감지하는 메서드
public async Task WaitForPageChange(WebDriverWait wait)
{

View File

@@ -69,7 +69,7 @@ namespace BokBonCheck
// 브라우저와 유사한 헤더 추가
request.Headers.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
request.Headers.Add("Accept-Language", "ko-KR,ko;q=0.8,en-US;q=0.5,en;q=0.3");
request.Headers.Add("Accept-Encoding", "gzip, deflate, br");
//request.Headers.Add("Accept-Encoding", "gzip, deflate, br");
request.Headers.Add("Connection", "keep-alive");
request.Headers.Add("Upgrade-Insecure-Requests", "1");
@@ -84,7 +84,8 @@ namespace BokBonCheck
var htmlContent = await response.Content.ReadAsStringAsync();
// 검색 결과 수 추출
var resultCount = ExtractBookCount(htmlContent, out string errorMessage);
var resultCount = ExtractBookCount(htmlContent, out string errorMessage, out string resultHtml);
result.Resulthtml = resultHtml;
if (resultCount == -1)
{
@@ -112,19 +113,21 @@ namespace BokBonCheck
return result;
}
private int ExtractBookCount(string htmlContent, out string errorMessage)
private int ExtractBookCount(string htmlContent, out string errorMessage, out string resulthtml)
{
errorMessage = string.Empty;
resulthtml = string.Empty;
try
{
// 1. 검색 결과가 없는 경우 확인
if (htmlContent.Contains("검색결과가 없습니다") ||
htmlContent.Contains("검색된 자료가 없습니다") ||
htmlContent.Contains("자가 없습니다") ||
htmlContent.Contains("자가 없습니다") ||
htmlContent.Contains("<strong>0</strong> Results:"))
{
errorMessage = "검색결과없음";
resulthtml = "검색결과없음";
return 0;
}
@@ -147,8 +150,11 @@ namespace BokBonCheck
if (count == 0)
{
errorMessage = "검색결과없음";
resulthtml = match.Value;
return 0;
}
// 매칭된 부분과 그 상위 태그를 찾아서 저장
resulthtml = ExtractResultContext(htmlContent, match);
errorMessage = $"검색성공({count}권)";
Console.WriteLine($"조선이공대학교도서관 검색 결과: {count}건");
return count;
@@ -173,22 +179,27 @@ namespace BokBonCheck
if (count == 0)
{
errorMessage = "검색결과없음";
resulthtml = match.Value;
return 0;
}
resulthtml = ExtractResultContext(htmlContent, match);
errorMessage = $"검색성공({count}권)";
Console.WriteLine($"조선이공대학교도서관 검색 결과: {count}건");
return count;
}
}
}
// 패턴을 찾지 못한 경우
errorMessage = "검색결과 패턴을 찾을 수 없음";
resulthtml = "검색결과 패턴을 찾을 수 없음";
Console.WriteLine("조선이공대학교도서관 검색결과 패턴을 찾을 수 없음");
return -1;
}
catch (Exception ex)
{
errorMessage = $"결과 분석 오류: {ex.Message}";
resulthtml = "결과 분석 오류: " + ex.Message;
return -1;
}
}
@@ -197,5 +208,67 @@ namespace BokBonCheck
{
throw new NotImplementedException();
}
/// <summary>
/// 매칭된 결과와 그 상위 태그를 추출
/// </summary>
private string ExtractResultContext(string htmlContent, Match match)
{
try
{
var matchIndex = match.Index;
var matchLength = match.Length;
// 매칭된 위치 앞쪽에서 상위 태그 시작 찾기
var startSearchIndex = Math.Max(0, matchIndex - 200); // 매칭 위치 200자 전부터 검색
var searchText = htmlContent.Substring(startSearchIndex, matchIndex - startSearchIndex + matchLength + Math.Min(200, htmlContent.Length - matchIndex - matchLength));
// 상위 태그 패턴들 (div, p, h1-h6, span 등)
var tagPatterns = new[] { @"<(div|p|h[1-6]|span|section|article)[^>]*>", @"<[^>]+>" };
string resultContext = match.Value; // 기본값은 매칭된 부분만
foreach (var tagPattern in tagPatterns)
{
// 매칭된 부분 앞에서 가장 가까운 태그 시작 찾기
var tagMatches = Regex.Matches(searchText, tagPattern, RegexOptions.IgnoreCase);
for (int i = tagMatches.Count - 1; i >= 0; i--)
{
var tagMatch = tagMatches[i];
if (tagMatch.Index < (matchIndex - startSearchIndex))
{
// 태그 이름 추출
var tagName = Regex.Match(tagMatch.Value, @"<(\w+)", RegexOptions.IgnoreCase).Groups[1].Value;
// 닫는 태그 찾기
var closeTagPattern = $@"</{tagName}[^>]*>";
var closeMatch = Regex.Match(searchText, closeTagPattern, RegexOptions.IgnoreCase);
if (closeMatch.Success && closeMatch.Index > (matchIndex - startSearchIndex))
{
// 상위 태그와 그 내용을 포함하여 반환
var startIdx = tagMatch.Index;
var endIdx = closeMatch.Index + closeMatch.Length;
resultContext = searchText.Substring(startIdx, Math.Min(endIdx - startIdx, 500)); // 최대 500자
return resultContext;
}
}
}
}
// 상위 태그를 찾지 못한 경우, 매칭 전후 50자씩 포함
var contextStart = Math.Max(0, matchIndex - 50);
var contextEnd = Math.Min(htmlContent.Length, matchIndex + matchLength + 50);
resultContext = htmlContent.Substring(contextStart, contextEnd - contextStart);
return resultContext;
}
catch (Exception ex)
{
Console.WriteLine($"ExtractResultContext 오류: {ex.Message}");
return match.Value; // 오류 시 매칭된 부분만 반환
}
}
}
}

View File

@@ -69,7 +69,7 @@ namespace BokBonCheck
// 브라우저와 유사한 헤더 추가
request.Headers.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
request.Headers.Add("Accept-Language", "ko-KR,ko;q=0.8,en-US;q=0.5,en;q=0.3");
request.Headers.Add("Accept-Encoding", "gzip, deflate, br");
//request.Headers.Add("Accept-Encoding", "gzip, deflate, br");
request.Headers.Add("Connection", "keep-alive");
request.Headers.Add("Upgrade-Insecure-Requests", "1");
@@ -84,7 +84,8 @@ namespace BokBonCheck
var htmlContent = await response.Content.ReadAsStringAsync();
// 검색 결과 수 추출
var resultCount = ExtractBookCount(htmlContent, out string errorMessage);
var resultCount = ExtractBookCount(htmlContent, out string errorMessage, out string resultHtml);
result.Resulthtml = resultHtml;
if (resultCount == -1)
{
@@ -112,9 +113,10 @@ namespace BokBonCheck
return result;
}
private int ExtractBookCount(string htmlContent, out string errorMessage)
private int ExtractBookCount(string htmlContent, out string errorMessage, out string resulthtml)
{
errorMessage = string.Empty;
resulthtml = string.Empty;
try
{
@@ -125,6 +127,7 @@ namespace BokBonCheck
htmlContent.Contains("총 <strong>0</strong>건"))
{
errorMessage = "검색결과없음";
resulthtml = "검색결과없음";
return 0;
}
@@ -147,8 +150,11 @@ namespace BokBonCheck
if (count == 0)
{
errorMessage = "검색결과없음";
resulthtml = match.Value;
return 0;
}
// 매칭된 부분과 그 상위 태그를 찾아서 저장
resulthtml = ExtractResultContext(htmlContent, match);
errorMessage = $"검색성공({count}권)";
Console.WriteLine($"조선대학교중앙도서관 검색 결과: {count}건");
return count;
@@ -173,22 +179,28 @@ namespace BokBonCheck
if (count == 0)
{
errorMessage = "검색결과없음";
resulthtml = match.Value;
return 0;
}
// 매칭된 부분과 그 상위 태그를 찾아서 저장
resulthtml = ExtractResultContext(htmlContent, match);
errorMessage = $"검색성공({count}권)";
Console.WriteLine($"조선대학교중앙도서관 검색 결과: {count}건");
return count;
}
}
}
// 패턴을 찾지 못한 경우
errorMessage = "검색결과 패턴을 찾을 수 없음";
resulthtml = "검색결과 패턴을 찾을 수 없음";
Console.WriteLine("조선대학교중앙도서관 검색결과 패턴을 찾을 수 없음");
return -1;
}
catch (Exception ex)
{
errorMessage = $"결과 분석 오류: {ex.Message}";
resulthtml = "결과 분석 오류: " + ex.Message;
return -1;
}
}
@@ -197,5 +209,67 @@ namespace BokBonCheck
{
throw new NotImplementedException();
}
/// <summary>
/// 매칭된 결과와 그 상위 태그를 추출
/// </summary>
private string ExtractResultContext(string htmlContent, Match match)
{
try
{
var matchIndex = match.Index;
var matchLength = match.Length;
// 매칭된 위치 앞쪽에서 상위 태그 시작 찾기
var startSearchIndex = Math.Max(0, matchIndex - 200); // 매칭 위치 200자 전부터 검색
var searchText = htmlContent.Substring(startSearchIndex, matchIndex - startSearchIndex + matchLength + Math.Min(200, htmlContent.Length - matchIndex - matchLength));
// 상위 태그 패턴들 (div, p, h1-h6, span 등)
var tagPatterns = new[] { @"<(div|p|h[1-6]|span|section|article)[^>]*>", @"<[^>]+>" };
string resultContext = match.Value; // 기본값은 매칭된 부분만
foreach (var tagPattern in tagPatterns)
{
// 매칭된 부분 앞에서 가장 가까운 태그 시작 찾기
var tagMatches = Regex.Matches(searchText, tagPattern, RegexOptions.IgnoreCase);
for (int i = tagMatches.Count - 1; i >= 0; i--)
{
var tagMatch = tagMatches[i];
if (tagMatch.Index < (matchIndex - startSearchIndex))
{
// 태그 이름 추출
var tagName = Regex.Match(tagMatch.Value, @"<(\w+)", RegexOptions.IgnoreCase).Groups[1].Value;
// 닫는 태그 찾기
var closeTagPattern = $@"</{tagName}[^>]*>";
var closeMatch = Regex.Match(searchText, closeTagPattern, RegexOptions.IgnoreCase);
if (closeMatch.Success && closeMatch.Index > (matchIndex - startSearchIndex))
{
// 상위 태그와 그 내용을 포함하여 반환
var startIdx = tagMatch.Index;
var endIdx = closeMatch.Index + closeMatch.Length;
resultContext = searchText.Substring(startIdx, Math.Min(endIdx - startIdx, 500)); // 최대 500자
return resultContext;
}
}
}
}
// 상위 태그를 찾지 못한 경우, 매칭 전후 50자씩 포함
var contextStart = Math.Max(0, matchIndex - 50);
var contextEnd = Math.Min(htmlContent.Length, matchIndex + matchLength + 50);
resultContext = htmlContent.Substring(contextStart, contextEnd - contextStart);
return resultContext;
}
catch (Exception ex)
{
Console.WriteLine($"ExtractResultContext 오류: {ex.Message}");
return match.Value; // 오류 시 매칭된 부분만 반환
}
}
}
}

View File

@@ -148,7 +148,10 @@ namespace BokBonCheck
await WaitForPageChange(new WebDriverWait(_driver, TimeSpan.FromSeconds(15)));
var resultCount = ExtractBookCount(_driver, searchTerm, out string ermsg);
// SearchAsync에서 PageSource 가져오기
var htmlContent = _driver.PageSource;
var resultCount = ExtractBookCount(htmlContent, out string ermsg, out string resultHtml);
result.Resulthtml = resultHtml;
if (resultCount == -1)
{
result.BookCount = 0;
@@ -173,60 +176,54 @@ namespace BokBonCheck
return result;
}
private int ExtractBookCount(IWebDriver driver, string searchTerm, out string errmessage)
private int ExtractBookCount(string htmlContent, out string errmessage, out string resulthtml)
{
errmessage = string.Empty;
resulthtml = string.Empty;
try
{
try
var noResultPatterns = new[]
{
var noResultElement = driver.FindElement(By.XPath("//*[contains(text(), '검색된 도서가 없습니다') or contains(text(), '검색결과가 없습니다')]"));
if (noResultElement != null)
@"검색된 도서가 없습니다",
@"검색결과가 없습니다",
@"검색된 자료가 없습니다"
};
foreach (var pattern in noResultPatterns)
{
if (htmlContent.Contains(pattern))
{
errmessage = "검색결과없음";
return 0;
}
}
catch
{
}
try
{
var resultElement = driver.FindElement(By.CssSelector(".subTapContWrap.on p"));
if (resultElement != null)
{
var resultText = resultElement.Text;
var match = Regex.Match(resultText, @"총\s*(\d+)건", RegexOptions.IgnoreCase);
if (match.Success)
// 검색결과 없음 메시지를 포함한 HTML 조각 추출
var index = htmlContent.IndexOf(pattern);
if (index >= 0)
{
if (int.TryParse(match.Groups[1].Value, out int count))
var startIndex = Math.Max(0, index - 100);
var endIndex = Math.Min(htmlContent.Length, index + pattern.Length + 100);
resulthtml = htmlContent.Substring(startIndex, endIndex - startIndex);
// 상위 태그 찾기 시도
try
{
if (count == 0)
var match = System.Text.RegularExpressions.Regex.Match(htmlContent, pattern);
if (match.Success)
{
errmessage = "검색결과없음";
return 0;
resulthtml = ExtractResultContext(htmlContent, match);
}
errmessage = $"검색성공({count}권)";
return count;
}
catch
{
// 실패시 기본 추출 결과 사용
}
}
else
{
resulthtml = pattern;
}
return 0;
}
}
catch (Exception ex)
{
Console.WriteLine($"결과 요소 검색 중 오류: {ex.Message}");
}
var pageSource = driver.PageSource;
if (pageSource.Contains("검색된 도서가 없습니다") ||
pageSource.Contains("검색결과가 없습니다") ||
pageSource.Contains("검색된 자료가 없습니다"))
{
errmessage = "검색결과없음";
return 0;
}
var htmlPatterns = new[]
{
@@ -237,11 +234,14 @@ namespace BokBonCheck
foreach (var pattern in htmlPatterns)
{
var match = Regex.Match(pageSource, pattern, RegexOptions.IgnoreCase);
var match = Regex.Match(htmlContent, pattern, RegexOptions.IgnoreCase);
if (match.Success)
{
if (int.TryParse(match.Groups[1].Value, out int count))
{
// 매칭된 부분과 상위 태그 추출하여 resulthtml에 저장
resulthtml = ExtractResultContext(htmlContent, match);
if (count == 0)
{
errmessage = "검색결과없음";
@@ -254,16 +254,80 @@ namespace BokBonCheck
}
errmessage = "결과수량을찾을수없음";
resulthtml = "결과수량을찾을수없음";
return -1;
}
catch (Exception ex)
{
errmessage = ex.Message;
resulthtml = "ExtractBookCount 오류: " + ex.Message;
return -1;
}
}
/// <summary>
/// 매칭된 결과와 그 상위 태그를 추출
/// </summary>
private string ExtractResultContext(string htmlContent, Match match)
{
try
{
var matchIndex = match.Index;
var matchLength = match.Length;
// 매칭된 위치 앞쪽에서 상위 태그 시작 찾기
var startSearchIndex = Math.Max(0, matchIndex - 200); // 매칭 위치 200자 전부터 검색
var searchText = htmlContent.Substring(startSearchIndex, matchIndex - startSearchIndex + matchLength + Math.Min(200, htmlContent.Length - matchIndex - matchLength));
// 상위 태그 패턴들 (div, p, h1-h6, span 등)
var tagPatterns = new[] { @"<(div|p|h[1-6]|span|section|article)[^>]*>", @"<[^>]+>" };
string resultContext = match.Value; // 기본값은 매칭된 부분만
foreach (var tagPattern in tagPatterns)
{
// 매칭된 부분 앞에서 가장 가까운 태그 시작 찾기
var tagMatches = Regex.Matches(searchText, tagPattern, RegexOptions.IgnoreCase);
for (int i = tagMatches.Count - 1; i >= 0; i--)
{
var tagMatch = tagMatches[i];
if (tagMatch.Index < (matchIndex - startSearchIndex))
{
// 태그 이름 추출
var tagName = Regex.Match(tagMatch.Value, @"<(\w+)", RegexOptions.IgnoreCase).Groups[1].Value;
// 닫는 태그 찾기
var closeTagPattern = $@"</{tagName}[^>]*>";
var closeMatch = Regex.Match(searchText, closeTagPattern, RegexOptions.IgnoreCase);
if (closeMatch.Success && closeMatch.Index > (matchIndex - startSearchIndex))
{
// 상위 태그와 그 내용을 포함하여 반환
var startIdx = tagMatch.Index;
var endIdx = closeMatch.Index + closeMatch.Length;
resultContext = searchText.Substring(startIdx, Math.Min(endIdx - startIdx, 500)); // 최대 500자
return resultContext;
}
}
}
}
// 상위 태그를 찾지 못한 경우, 매칭 전후 50자씩 포함
var contextStart = Math.Max(0, matchIndex - 50);
var contextEnd = Math.Min(htmlContent.Length, matchIndex + matchLength + 50);
resultContext = htmlContent.Substring(contextStart, contextEnd - contextStart);
return resultContext;
}
catch (Exception ex)
{
Console.WriteLine($"ExtractResultContext 오류: {ex.Message}");
return match.Value; // 오류 시 매칭된 부분만 반환
}
}
public async Task WaitForPageChange(WebDriverWait wait)
{
try

View File

@@ -218,8 +218,9 @@ namespace BokBonCheck
// 페이지 변경을 감지하는 메서드
await WaitForPageChange(new WebDriverWait(_driver, TimeSpan.FromSeconds(15)));
// 검색 결과 수 추출
var resultCount = ExtractBookCount(_driver, searchTerm, out string ermsg);
var htmlContent = _driver.PageSource;
var resultCount = ExtractBookCount(htmlContent, out string ermsg, out string resultHtml);
result.Resulthtml = resultHtml;
if (resultCount == -1)
{
result.BookCount = 0;
@@ -244,67 +245,21 @@ namespace BokBonCheck
return result;
}
private int ExtractBookCount(IWebDriver driver, string searchTerm, out string errmessage)
private int ExtractBookCount(string htmlContent, out string errmessage, out string resulthtml)
{
errmessage = string.Empty;
resulthtml = string.Empty;
try
{
// 1. 먼저 totalCount 요소에서 직접 추출 시도
try
{
var totalCountElement = driver.FindElement(By.CssSelector("p.totalCount"));
if (totalCountElement != null)
{
var totalCountText = totalCountElement.Text; // 예: "전체 1 건"
var match = Regex.Match(totalCountText, @"전체\s+(\d+)\s+건", RegexOptions.IgnoreCase);
if (match.Success)
{
if (int.TryParse(match.Groups[1].Value, out int count))
{
if (count == 0)
{
errmessage = "검색결과없음";
return 0;
}
errmessage = $"검색성공({count}권)";
return count;
}
}
// strong 태그에서 직접 숫자 추출 시도
try
{
var strongElement = totalCountElement.FindElement(By.TagName("strong"));
if (strongElement != null && int.TryParse(strongElement.Text, out int strongCount))
{
if(strongCount == 0)
{
errmessage = "검색결과없음";
return 0;
}
errmessage = $"검색성공({strongCount}권)";
return strongCount;
}
}
catch { }
}
}
catch (Exception ex)
{
Console.WriteLine($"totalCount 요소 검색 중 오류: {ex.Message}");
}
// 2. 페이지 소스에서 정규식으로 추출 시도
var pageSource = driver.PageSource;
// 검색 결과가 없다는 메시지 확인
if (pageSource.Contains("검색결과가 없습니다") ||
pageSource.Contains("검색된 자료가 없습니다") ||
pageSource.Contains("자료가 없습니다") ||
pageSource.Contains("전체 0 건"))
if (htmlContent.Contains("검색결과가 없습니다") ||
htmlContent.Contains("검색된 자료가 없습니다") ||
htmlContent.Contains("자료가 없습니다") ||
htmlContent.Contains("전체 0 건"))
{
errmessage = "검색결과없음";
resulthtml = "검색결과가 없습니다";
return 0;
}
@@ -319,11 +274,14 @@ namespace BokBonCheck
foreach (var pattern in htmlPatterns)
{
var match = Regex.Match(pageSource, pattern, RegexOptions.IgnoreCase);
var match = Regex.Match(htmlContent, pattern, RegexOptions.IgnoreCase);
if (match.Success)
{
if (int.TryParse(match.Groups[1].Value, out int count))
{
// 매칭된 부분과 상위 태그 추출하여 resulthtml에 저장
resulthtml = ExtractResultContext(htmlContent, match);
if (count == 0)
{
errmessage = "검색결과없음";
@@ -335,49 +293,81 @@ namespace BokBonCheck
}
}
// 3. 결과 테이블이나 리스트가 있는지 확인
try
{
var resultElements = driver.FindElements(By.CssSelector(".bookList, .searchResult, .result-list, table tbody tr"));
if (resultElements.Count > 0)
{
// 테이블 헤더나 빈 행을 제외한 실제 결과 개수 계산
var actualCount = 0;
foreach (var element in resultElements)
{
var text = element.Text?.Trim();
if (!string.IsNullOrEmpty(text) &&
!text.Contains("도서명") &&
!text.Contains("저자") &&
!text.Contains("출판사"))
{
actualCount++;
}
}
if (actualCount > 0)
{
errmessage = $"검색성공({actualCount}권)";
return actualCount;
}
}
}
catch (Exception ex)
{
Console.WriteLine($"결과 요소 검색 중 오류: {ex.Message}");
}
errmessage = "결과수량을찾을수없음";
resulthtml = "검색결과 패턴을 찾을 수 없음";
return -1;
}
catch (Exception ex)
{
errmessage = ex.Message;
resulthtml = "검색결과 패턴을 찾을 수 없음";
return -1;
}
}
/// <summary>
/// 매칭된 결과와 그 상위 태그를 추출
/// </summary>
private string ExtractResultContext(string htmlContent, Match match)
{
try
{
var matchIndex = match.Index;
var matchLength = match.Length;
// 매칭된 위치 앞쪽에서 상위 태그 시작 찾기
var startSearchIndex = Math.Max(0, matchIndex - 200); // 매칭 위치 200자 전부터 검색
var searchText = htmlContent.Substring(startSearchIndex, matchIndex - startSearchIndex + matchLength + Math.Min(200, htmlContent.Length - matchIndex - matchLength));
// 상위 태그 패턴들 (div, p, h1-h6, span 등)
var tagPatterns = new[] { @"<(div|p|h[1-6]|span|section|article)[^>]*>", @"<[^>]+>" };
string resultContext = match.Value; // 기본값은 매칭된 부분만
foreach (var tagPattern in tagPatterns)
{
// 매칭된 부분 앞에서 가장 가까운 태그 시작 찾기
var tagMatches = Regex.Matches(searchText, tagPattern, RegexOptions.IgnoreCase);
for (int i = tagMatches.Count - 1; i >= 0; i--)
{
var tagMatch = tagMatches[i];
if (tagMatch.Index < (matchIndex - startSearchIndex))
{
// 태그 이름 추출
var tagName = Regex.Match(tagMatch.Value, @"<(\w+)", RegexOptions.IgnoreCase).Groups[1].Value;
// 닫는 태그 찾기
var closeTagPattern = $@"</{tagName}[^>]*>";
var closeMatch = Regex.Match(searchText, closeTagPattern, RegexOptions.IgnoreCase);
if (closeMatch.Success && closeMatch.Index > (matchIndex - startSearchIndex))
{
// 상위 태그와 그 내용을 포함하여 반환
var startIdx = tagMatch.Index;
var endIdx = closeMatch.Index + closeMatch.Length;
resultContext = searchText.Substring(startIdx, Math.Min(endIdx - startIdx, 500)); // 최대 500자
return resultContext;
}
}
}
}
// 상위 태그를 찾지 못한 경우, 매칭 전후 50자씩 포함
var contextStart = Math.Max(0, matchIndex - 50);
var contextEnd = Math.Min(htmlContent.Length, matchIndex + matchLength + 50);
resultContext = htmlContent.Substring(contextStart, contextEnd - contextStart);
return resultContext;
}
catch (Exception ex)
{
Console.WriteLine($"ExtractResultContext 오류: {ex.Message}");
return match.Value; // 오류 시 매칭된 부분만 반환
}
}
// 페이지 변경을 감지하는 메서드
public async Task WaitForPageChange(WebDriverWait wait)
{

View File

@@ -155,8 +155,9 @@ namespace BokBonCheck
// 페이지 변경을 감지하는 메서드
await WaitForPageChange(new WebDriverWait(_driver, TimeSpan.FromSeconds(15)));
// 검색 결과 수 추출
var resultCount = ExtractBookCount(_driver, searchTerm, out string ermsg);
var htmlContent = _driver.PageSource;
var resultCount = ExtractBookCount(htmlContent, out string ermsg, out string resultHtml);
result.Resulthtml = resultHtml;
if (resultCount == -1)
{
result.BookCount = 0;
@@ -181,87 +182,38 @@ namespace BokBonCheck
return result;
}
private int ExtractBookCount(IWebDriver driver, string searchTerm, out string errmessage)
private int ExtractBookCount(string htmlContent, out string errmessage, out string resulthtml)
{
errmessage = string.Empty;
resulthtml = string.Empty;
try
{
// 1. 검색결과가 없는 경우 확인
try
// 검색 결과가 없메시지 확인
var noResultPatterns = new[]
{
var noResultElement = driver.FindElement(By.XPath("//p[contains(text(), '조회된 도서가 없습니다')]"));
if (noResultElement != null)
@"조회된 도서가 없습니다",
@"검색결과가 없습니다",
@"검색된 자료가 없습니다"
};
foreach (var pattern in noResultPatterns)
{
if (htmlContent.Contains(pattern))
{
errmessage = "검색결과없음";
return 0;
}
}
catch
{
// 검색결과가 있는 경우로 진행
}
// 2. srch_info div에서 직접 추출 시도
try
{
var srchInfoElement = driver.FindElement(By.CssSelector("div.srch_info"));
if (srchInfoElement != null)
{
// span.heighlight에서 숫자 추출 시도
try
{
var highlightElement = srchInfoElement.FindElement(By.CssSelector("span.heighlight"));
if (highlightElement != null)
{
var countText = highlightElement.Text.Trim();
if (int.TryParse(countText, out int count))
{
if (count == 0)
{
errmessage = "검색결과없음";
return 0;
}
errmessage = $"검색성공({count}권)";
return count;
}
}
}
catch { }
// 전체 텍스트에서 정규식으로 추출
var srchInfoText = srchInfoElement.Text;
var match = Regex.Match(srchInfoText, @"검색결과\s*총\s*(\d+)\s*건", RegexOptions.IgnoreCase);
var match = System.Text.RegularExpressions.Regex.Match(htmlContent, pattern);
if (match.Success)
{
if (int.TryParse(match.Groups[1].Value, out int count))
{
if (count == 0)
{
errmessage = "검색결과없음";
return 0;
}
errmessage = $"검색성공({count}권)";
return count;
}
resulthtml = ExtractResultContext(htmlContent, match);
}
else
{
resulthtml = htmlContent.Length > 500 ? htmlContent.Substring(0, 500) : htmlContent;
}
return 0;
}
}
catch (Exception ex)
{
Console.WriteLine($"srch_info 요소 검색 중 오류: {ex.Message}");
}
// 3. 페이지 소스에서 정규식으로 추출 시도
var pageSource = driver.PageSource;
// 검색 결과가 없다는 메시지 확인
if (pageSource.Contains("조회된 도서가 없습니다") ||
pageSource.Contains("검색결과가 없습니다") ||
pageSource.Contains("검색된 자료가 없습니다"))
{
errmessage = "검색결과없음";
return 0;
}
// HTML에서 다양한 패턴 찾기
var htmlPatterns = new[]
@@ -274,11 +226,14 @@ namespace BokBonCheck
foreach (var pattern in htmlPatterns)
{
var match = Regex.Match(pageSource, pattern, RegexOptions.IgnoreCase);
var match = Regex.Match(htmlContent, pattern, RegexOptions.IgnoreCase);
if (match.Success)
{
if (int.TryParse(match.Groups[1].Value, out int count))
{
// 매칭된 부분과 상위 태그 추출하여 resulthtml에 저장
resulthtml = ExtractResultContext(htmlContent, match);
if (count == 0)
{
errmessage = "검색결과없음";
@@ -291,16 +246,80 @@ namespace BokBonCheck
}
errmessage = "결과수량을찾을수없음";
resulthtml = htmlContent.Length > 500 ? htmlContent.Substring(0, 500) : htmlContent;
return -1;
}
catch (Exception ex)
{
errmessage = ex.Message;
resulthtml = htmlContent.Length > 500 ? htmlContent.Substring(0, 500) : htmlContent;
return -1;
}
}
/// <summary>
/// 매칭된 결과와 그 상위 태그를 추출
/// </summary>
private string ExtractResultContext(string htmlContent, Match match)
{
try
{
var matchIndex = match.Index;
var matchLength = match.Length;
// 매칭된 위치 앞쪽에서 상위 태그 시작 찾기
var startSearchIndex = Math.Max(0, matchIndex - 200); // 매칭 위치 200자 전부터 검색
var searchText = htmlContent.Substring(startSearchIndex, matchIndex - startSearchIndex + matchLength + Math.Min(200, htmlContent.Length - matchIndex - matchLength));
// 상위 태그 패턴들 (div, p, h1-h6, span 등)
var tagPatterns = new[] { @"<(div|p|h[1-6]|span|section|article)[^>]*>", @"<[^>]+>" };
string resultContext = match.Value; // 기본값은 매칭된 부분만
foreach (var tagPattern in tagPatterns)
{
// 매칭된 부분 앞에서 가장 가까운 태그 시작 찾기
var tagMatches = Regex.Matches(searchText, tagPattern, RegexOptions.IgnoreCase);
for (int i = tagMatches.Count - 1; i >= 0; i--)
{
var tagMatch = tagMatches[i];
if (tagMatch.Index < (matchIndex - startSearchIndex))
{
// 태그 이름 추출
var tagName = Regex.Match(tagMatch.Value, @"<(\w+)", RegexOptions.IgnoreCase).Groups[1].Value;
// 닫는 태그 찾기
var closeTagPattern = $@"</{tagName}[^>]*>";
var closeMatch = Regex.Match(searchText, closeTagPattern, RegexOptions.IgnoreCase);
if (closeMatch.Success && closeMatch.Index > (matchIndex - startSearchIndex))
{
// 상위 태그와 그 내용을 포함하여 반환
var startIdx = tagMatch.Index;
var endIdx = closeMatch.Index + closeMatch.Length;
resultContext = searchText.Substring(startIdx, Math.Min(endIdx - startIdx, 500)); // 최대 500자
return resultContext;
}
}
}
}
// 상위 태그를 찾지 못한 경우, 매칭 전후 50자씩 포함
var contextStart = Math.Max(0, matchIndex - 50);
var contextEnd = Math.Min(htmlContent.Length, matchIndex + matchLength + 50);
resultContext = htmlContent.Substring(contextStart, contextEnd - contextStart);
return resultContext;
}
catch (Exception ex)
{
Console.WriteLine($"ExtractResultContext 오류: {ex.Message}");
return match.Value; // 오류 시 매칭된 부분만 반환
}
}
// 페이지 변경을 감지하는 메서드
public async Task WaitForPageChange(WebDriverWait wait)
{

View File

@@ -81,10 +81,11 @@ namespace BokBonCheck
// HTTP GET 요청 실행 (추가 헤더 포함)
using (var request = new HttpRequestMessage(HttpMethod.Get, searchUrl))
{
// 브라우저와 유사한 헤더 추가
request.Headers.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
request.Headers.Add("Accept-Language", "ko-KR,ko;q=0.8,en-US;q=0.5,en;q=0.3");
request.Headers.Add("Accept-Encoding", "gzip, deflate, br");
// 브라우저와 유사한 헤더 추가 (인코딩 문제 방지를 위해 압축 해제)
request.Headers.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
request.Headers.Add("Accept-Language", "ko-KR,ko;q=0.9,en;q=0.1");
request.Headers.Add("Accept-Charset", "UTF-8,*;q=0.1");
// Accept-Encoding 제거 - 압축으로 인한 인코딩 문제 방지
request.Headers.Add("Connection", "keep-alive");
request.Headers.Add("Upgrade-Insecure-Requests", "1");
@@ -96,10 +97,50 @@ namespace BokBonCheck
throw new HttpRequestException($"HTTP {(int)response.StatusCode} {response.StatusCode}: {errorContent}");
}
var htmlContent = await response.Content.ReadAsStringAsync();
// 인코딩 문제 해결: 서버 응답의 Content-Type 확인 후 적절한 인코딩 사용
string htmlContent;
var contentType = response.Content.Headers.ContentType?.ToString() ?? "";
Console.WriteLine($"광주동구 Content-Type: {contentType}");
if (contentType.Contains("charset="))
{
// 서버에서 지정한 charset 사용
htmlContent = await response.Content.ReadAsStringAsync();
}
else
{
// charset이 명시되지 않은 경우 수동 처리
var responseBytes = await response.Content.ReadAsByteArrayAsync();
// UTF-8 먼저 시도
htmlContent = Encoding.UTF8.GetString(responseBytes);
// 한글이 없거나 깨진 경우 EUC-KR 시도
if (!ContainsKorean(htmlContent) || htmlContent.Contains("<22>"))
{
try
{
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
var euckrEncoding = Encoding.GetEncoding("EUC-KR");
var euckrContent = euckrEncoding.GetString(responseBytes);
if (ContainsKorean(euckrContent))
{
htmlContent = euckrContent;
Console.WriteLine("광주동구: EUC-KR 인코딩 사용");
}
}
catch (Exception encEx)
{
Console.WriteLine($"광주동구 EUC-KR 변환 오류: {encEx.Message}");
}
}
}
// 검색 결과 수 추출
var resultCount = ExtractBookCount(htmlContent, out string errorMessage);
var resultCount = ExtractBookCount(htmlContent, out string errorMessage, out string resultHtml);
// ResultHtml에 분석용 HTML 저장 (디버깅용으로 일부만)
result.Resulthtml = resultHtml;
if (resultCount == -1)
{
@@ -126,10 +167,11 @@ namespace BokBonCheck
return result;
}
private int ExtractBookCount(string htmlContent, out string errorMessage)
private int ExtractBookCount(string htmlContent, out string errorMessage,out string resulthtml)
{
errorMessage = string.Empty;
resulthtml=string.Empty;
try
{
// 1. 검색 결과가 없는 경우 확인
@@ -141,18 +183,23 @@ namespace BokBonCheck
return 0;
}
// 2. 검색 결과 수량 추출: <p class="totalCount">전체 <strong>2</strong> 건</p>
// 2. 검색 결과 수량 추출 - 더 포괄적인 패턴 사용
var patterns = new[]
{
@"<p[^>]*class=""totalCount""[^>]*>전체\s*<strong>\s*(\d+)\s*</strong>\s*건</p>",
@"전체\s*<strong>\s*(\d+)\s*</strong>\s*건",
@"<strong>\s*(\d+)\s*</strong>\s*건",
@"총\s*(\d+)\s*건"
// 기본 패턴들
@"전체\s*(?:\*\*)?(\d+)(?:\*\*)?\s*건", // 전체 **N** 건 또는 전체 N 건
@"<h[1-6][^>]*>.*?전체\s*(?:\*\*)?(\d+)(?:\*\*)?\s*건.*?</h[1-6]>", // h태그 안의 전체 N 건
@"<p[^>]*class=""totalCount""[^>]*>전체\s*<strong>\s*(\d+)\s*</strong>\s*건</p>", // 원래 패턴
@"전체\s*<strong>\s*(\d+)\s*</strong>\s*건", // strong 태그
@"<strong>\s*(\d+)\s*</strong>\s*건", // strong만
@"총\s*(\d+)\s*건", // 총 N 건
@"검색결과\s*:\s*(\d+)\s*건", // 검색결과: N 건
@"(\d+)\s*건의\s*검색결과", // N 건의 검색결과
};
foreach (var pattern in patterns)
{
var match = Regex.Match(htmlContent, pattern, RegexOptions.IgnoreCase);
var match = Regex.Match(htmlContent, pattern, RegexOptions.IgnoreCase | RegexOptions.Singleline);
if (match.Success)
{
if (int.TryParse(match.Groups[1].Value, out int count))
@@ -160,25 +207,31 @@ namespace BokBonCheck
if (count == 0)
{
errorMessage = "검색결과없음";
resulthtml = match.Value; // 매칭된 부분만 저장
return 0;
}
// 매칭된 부분과 그 상위 태그를 찾아서 저장
resulthtml = ExtractResultContext(htmlContent, match);
errorMessage = $"검색성공({count}권)";
Console.WriteLine($"광주동구 검색 결과: {count}건");
Console.WriteLine($"광주동구 검색 결과: {count}건 (패턴: {pattern})");
return count;
}
}
}
// 3. 더 자세한 패턴으로 시도 (줄바꿈 포함)
// 3. 멀티라인 패턴으로 시도
var multilinePatterns = new[]
{
@"전체\s*<strong>\s*\r?\n?\s*(\d+)\s*\r?\n?\s*</strong>\s*건",
@"<strong>\s*\r?\n?\s*(\d+)\s*\r?\n?\s*</strong>\s*건"
@"전체\s*(?:\*\*)?[\r\n\s]*(\d+)[\r\n\s]*(?:\*\*)?\s*건",
@"전체\s*<strong>\s*[\r\n]*\s*(\d+)\s*[\r\n]*\s*</strong>\s*건",
@"<strong>\s*[\r\n]*\s*(\d+)\s*[\r\n]*\s*</strong>\s*건"
};
foreach (var pattern in multilinePatterns)
{
var match = Regex.Match(htmlContent, pattern, RegexOptions.IgnoreCase | RegexOptions.Multiline);
var match = Regex.Match(htmlContent, pattern, RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline);
if (match.Success)
{
if (int.TryParse(match.Groups[1].Value, out int count))
@@ -186,15 +239,24 @@ namespace BokBonCheck
if (count == 0)
{
errorMessage = "검색결과없음";
resulthtml = match.Value; // 매칭된 부분만 저장
return 0;
}
// 매칭된 부분과 그 상위 태그를 찾아서 저장
resulthtml = ExtractResultContext(htmlContent, match);
errorMessage = $"검색성공({count}권)";
Console.WriteLine($"광주동구 검색 결과: {count}건");
Console.WriteLine($"광주동구 검색 결과: {count}건 (멀티라인 패턴)");
return count;
}
}
}
// 4. 패턴을 찾지 못한 경우 - 디버깅을 위한 HTML 내용 일부 출력
resulthtml = "검색결과 패턴을 찾을 수 없음";
Console.WriteLine($"광주동구 HTML 샘플: {resulthtml}");
errorMessage = "검색결과 패턴을 찾을 수 없음";
Console.WriteLine("광주동구 검색결과 패턴을 찾을 수 없음");
return -1;
@@ -202,6 +264,8 @@ namespace BokBonCheck
catch (Exception ex)
{
errorMessage = $"결과 분석 오류: {ex.Message}";
resulthtml = "검색결과 패턴을 찾을 수 없음"; // 오류 시 HTML 일부 저장
Console.WriteLine($"광주동구 결과 분석 오류: {ex.Message}");
return -1;
}
}
@@ -210,5 +274,83 @@ namespace BokBonCheck
{
throw new NotImplementedException();
}
/// <summary>
/// 문자열에 한글이 포함되어 있는지 확인
/// </summary>
private bool ContainsKorean(string text)
{
if (string.IsNullOrEmpty(text))
return false;
foreach (char c in text)
{
if (c >= 0xAC00 && c <= 0xD7A3) // 한글 유니코드 범위
return true;
}
return false;
}
/// <summary>
/// 매칭된 결과와 그 상위 태그를 추출
/// </summary>
private string ExtractResultContext(string htmlContent, Match match)
{
try
{
var matchIndex = match.Index;
var matchLength = match.Length;
// 매칭된 위치 앞쪽에서 상위 태그 시작 찾기
var startSearchIndex = Math.Max(0, matchIndex - 200); // 매칭 위치 200자 전부터 검색
var searchText = htmlContent.Substring(startSearchIndex, matchIndex - startSearchIndex + matchLength + Math.Min(200, htmlContent.Length - matchIndex - matchLength));
// 상위 태그 패턴들 (div, p, h1-h6, span 등)
var tagPatterns = new[] { @"<(div|p|h[1-6]|span|section|article)[^>]*>", @"<[^>]+>" };
string resultContext = match.Value; // 기본값은 매칭된 부분만
foreach (var tagPattern in tagPatterns)
{
// 매칭된 부분 앞에서 가장 가까운 태그 시작 찾기
var tagMatches = Regex.Matches(searchText, tagPattern, RegexOptions.IgnoreCase);
for (int i = tagMatches.Count - 1; i >= 0; i--)
{
var tagMatch = tagMatches[i];
if (tagMatch.Index < (matchIndex - startSearchIndex))
{
// 태그 이름 추출
var tagName = Regex.Match(tagMatch.Value, @"<(\w+)", RegexOptions.IgnoreCase).Groups[1].Value;
// 닫는 태그 찾기
var closeTagPattern = $@"</{tagName}[^>]*>";
var closeMatch = Regex.Match(searchText, closeTagPattern, RegexOptions.IgnoreCase);
if (closeMatch.Success && closeMatch.Index > (matchIndex - startSearchIndex))
{
// 상위 태그와 그 내용을 포함하여 반환
var startIdx = tagMatch.Index;
var endIdx = closeMatch.Index + closeMatch.Length;
resultContext = searchText.Substring(startIdx, Math.Min(endIdx - startIdx, 500)); // 최대 500자
return resultContext;
}
}
}
}
// 상위 태그를 찾지 못한 경우, 매칭 전후 50자씩 포함
var contextStart = Math.Max(0, matchIndex - 50);
var contextEnd = Math.Min(htmlContent.Length, matchIndex + matchLength + 50);
resultContext = htmlContent.Substring(contextStart, contextEnd - contextStart);
return resultContext;
}
catch (Exception ex)
{
Console.WriteLine($"ExtractResultContext 오류: {ex.Message}");
return match.Value; // 오류 시 매칭된 부분만 반환
}
}
}
}

View File

@@ -337,7 +337,8 @@ namespace BokBonCheck
await WaitForPageChange(new WebDriverWait(_driver, TimeSpan.FromSeconds(15)));
// 검색 결과 수 추출 (페이징 포함)
var (resultCount, ermsg) = await ExtractBookCountWithPaging(_driver, searchTerm);
var (resultCount, ermsg, resultHtml) = await ExtractBookCountWithPaging(_driver, searchTerm);
result.Resulthtml = resultHtml;
if (resultCount == -1)
{
result.BookCount = 0;
@@ -362,13 +363,15 @@ namespace BokBonCheck
return result;
}
private async Task<(int count, string message)> ExtractBookCountWithPaging(IWebDriver driver, string searchTerm)
private async Task<(int count, string message, string resultHtml)> ExtractBookCountWithPaging(IWebDriver driver, string searchTerm)
{
string errmessage = string.Empty;
int totalCount = 0;
try
{
var htmlContent = driver.PageSource;
string resultHtml = string.Empty;
// 첫 번째 페이지에서 테이블 row 수 확인
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
@@ -381,7 +384,28 @@ namespace BokBonCheck
if (firstPageRows.Count == 0)
{
errmessage = "검색결과없음";
return (0, errmessage);
// "검색결과가 없습니다"와 같은 메시지를 찾아 context 추출 시도
var noResultPatterns = new[]
{
@"검색결과가 없습니다",
@"검색된 자료가 없습니다",
@"자료가 없습니다"
};
foreach (var pattern in noResultPatterns)
{
if (htmlContent.Contains(pattern))
{
var match = System.Text.RegularExpressions.Regex.Match(htmlContent, pattern);
if (match.Success)
{
resultHtml = ExtractResultContext(htmlContent, match);
return (0, errmessage, resultHtml);
}
}
}
resultHtml = htmlContent.Length > 500 ? htmlContent.Substring(0, 500) : htmlContent;
return (0, errmessage, resultHtml);
}
totalCount += firstPageRows.Count;
@@ -390,7 +414,28 @@ namespace BokBonCheck
catch
{
errmessage = "검색결과없음";
return (0, errmessage);
// "검색결과가 없습니다"와 같은 메시지를 찾아 context 추출 시도
var noResultPatterns = new[]
{
@"검색결과가 없습니다",
@"검색된 자료가 없습니다",
@"자료가 없습니다"
};
foreach (var pattern in noResultPatterns)
{
if (htmlContent.Contains(pattern))
{
var match = System.Text.RegularExpressions.Regex.Match(htmlContent, pattern);
if (match.Success)
{
resultHtml = ExtractResultContext(htmlContent, match);
return (0, errmessage, resultHtml);
}
}
}
resultHtml = htmlContent.Length > 500 ? htmlContent.Substring(0, 500) : htmlContent;
return (0, errmessage, resultHtml);
}
// 페이징이 있는지 확인하고 각 페이지 방문
@@ -436,18 +481,42 @@ namespace BokBonCheck
if (totalCount == 0)
{
errmessage = "검색결과없음";
return (0, errmessage);
// "검색결과가 없습니다"와 같은 메시지를 찾아 context 추출 시도
var noResultPatterns = new[]
{
@"검색결과가 없습니다",
@"검색된 자료가 없습니다",
@"자료가 없습니다",
@"전체\s*0\s*건"
};
foreach (var pattern in noResultPatterns)
{
if (htmlContent.Contains(pattern))
{
var match = System.Text.RegularExpressions.Regex.Match(htmlContent, pattern);
if (match.Success)
{
resultHtml = ExtractResultContext(htmlContent, match);
return (0, errmessage, resultHtml);
}
}
}
resultHtml = htmlContent.Length > 500 ? htmlContent.Substring(0, 500) : htmlContent;
return (0, errmessage, resultHtml);
}
resultHtml = $"검색성공 - 총 {totalCount}건";
errmessage = $"검색성공({totalCount}권)";
Console.WriteLine($"전체 검색 결과: {totalCount}건");
return (totalCount, errmessage);
return (totalCount, errmessage, resultHtml);
}
catch (Exception ex)
{
errmessage = ex.Message;
return (-1, errmessage);
string resultHtml = "오류 발생";
return (-1, errmessage, resultHtml);
}
}
@@ -492,5 +561,67 @@ namespace BokBonCheck
Console.WriteLine($"페이지 변경 감지 실패: {ex.Message}");
}
}
/// <summary>
/// 매칭된 결과와 그 상위 태그를 추출
/// </summary>
private string ExtractResultContext(string htmlContent, System.Text.RegularExpressions.Match match)
{
try
{
var matchIndex = match.Index;
var matchLength = match.Length;
// 매칭된 위치 앞쪽에서 상위 태그 시작 찾기
var startSearchIndex = Math.Max(0, matchIndex - 200); // 매칭 위치 200자 전부터 검색
var searchText = htmlContent.Substring(startSearchIndex, matchIndex - startSearchIndex + matchLength + Math.Min(200, htmlContent.Length - matchIndex - matchLength));
// 상위 태그 패턴들 (div, p, h1-h6, span 등)
var tagPatterns = new[] { @"<(div|p|h[1-6]|span|section|article)[^>]*>", @"<[^>]+>" };
string resultContext = match.Value; // 기본값은 매칭된 부분만
foreach (var tagPattern in tagPatterns)
{
// 매칭된 부분 앞에서 가장 가까운 태그 시작 찾기
var tagMatches = System.Text.RegularExpressions.Regex.Matches(searchText, tagPattern, System.Text.RegularExpressions.RegexOptions.IgnoreCase);
for (int i = tagMatches.Count - 1; i >= 0; i--)
{
var tagMatch = tagMatches[i];
if (tagMatch.Index < (matchIndex - startSearchIndex))
{
// 태그 이름 추출
var tagName = System.Text.RegularExpressions.Regex.Match(tagMatch.Value, @"<(\w+)", System.Text.RegularExpressions.RegexOptions.IgnoreCase).Groups[1].Value;
// 닫는 태그 찾기
var closeTagPattern = $@"</{tagName}[^>]*>";
var closeMatch = System.Text.RegularExpressions.Regex.Match(searchText, closeTagPattern, System.Text.RegularExpressions.RegexOptions.IgnoreCase);
if (closeMatch.Success && closeMatch.Index > (matchIndex - startSearchIndex))
{
// 상위 태그와 그 내용을 포함하여 반환
var startIdx = tagMatch.Index;
var endIdx = closeMatch.Index + closeMatch.Length;
resultContext = searchText.Substring(startIdx, Math.Min(endIdx - startIdx, 500)); // 최대 500자
return resultContext;
}
}
}
}
// 상위 태그를 찾지 못한 경우, 매칭 전후 50자씩 포함
var contextStart = Math.Max(0, matchIndex - 50);
var contextEnd = Math.Min(htmlContent.Length, matchIndex + matchLength + 50);
resultContext = htmlContent.Substring(contextStart, contextEnd - contextStart);
return resultContext;
}
catch (Exception ex)
{
Console.WriteLine($"ExtractResultContext 오류: {ex.Message}");
return match.Value; // 오류 시 매칭된 부분만 반환
}
}
}
}

View File

@@ -74,7 +74,7 @@ namespace BokBonCheck
// 브라우저와 유사한 헤더 추가
request.Headers.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
request.Headers.Add("Accept-Language", "ko-KR,ko;q=0.8,en-US;q=0.5,en;q=0.3");
request.Headers.Add("Accept-Encoding", "gzip, deflate, br");
//request.Headers.Add("Accept-Encoding", "gzip, deflate, br");
request.Headers.Add("Connection", "keep-alive");
request.Headers.Add("Upgrade-Insecure-Requests", "1");
@@ -84,7 +84,8 @@ namespace BokBonCheck
var htmlContent = await response.Content.ReadAsStringAsync();
// 검색 결과 수 추출
var resultCount = ExtractBookCount(htmlContent, out string errorMessage);
var resultCount = ExtractBookCount(htmlContent, out string errorMessage, out string resultHtml);
result.Resulthtml = resultHtml;
if (resultCount == -1)
{
@@ -112,9 +113,10 @@ namespace BokBonCheck
return result;
}
private int ExtractBookCount(string htmlContent, out string errorMessage)
private int ExtractBookCount(string htmlContent, out string errorMessage, out string resulthtml)
{
errorMessage = string.Empty;
resulthtml = string.Empty;
try
{
@@ -124,6 +126,7 @@ namespace BokBonCheck
htmlContent.Contains("자료가 없습니다"))
{
errorMessage = "검색결과없음";
resulthtml = "검색결과없음";
return 0;
}
@@ -146,8 +149,11 @@ namespace BokBonCheck
if (count == 0)
{
errorMessage = "검색결과없음";
resulthtml = match.Value;
return 0;
}
// 매칭된 부분과 그 상위 태그를 찾아서 저장
resulthtml = ExtractResultContext(htmlContent, match);
errorMessage = $"검색성공({count}권)";
return count;
}
@@ -171,20 +177,25 @@ namespace BokBonCheck
if (count == 0)
{
errorMessage = "검색결과없음";
resulthtml = match.Value;
return 0;
}
resulthtml = ExtractResultContext(htmlContent, match);
errorMessage = $"검색성공({count}권)";
return count;
}
}
}
// 패턴을 찾지 못한 경우
resulthtml = "검색결과 패턴을 찾을 수 없음";
errorMessage = "검색결과 패턴을 찾을 수 없음";
return -1;
}
catch (Exception ex)
{
errorMessage = $"결과 분석 오류: {ex.Message}";
resulthtml = "검색결과 패턴을 찾을 수 없음";
return -1;
}
}
@@ -193,5 +204,67 @@ namespace BokBonCheck
{
throw new NotImplementedException();
}
/// <summary>
/// 매칭된 결과와 그 상위 태그를 추출
/// </summary>
private string ExtractResultContext(string htmlContent, Match match)
{
try
{
var matchIndex = match.Index;
var matchLength = match.Length;
// 매칭된 위치 앞쪽에서 상위 태그 시작 찾기
var startSearchIndex = Math.Max(0, matchIndex - 200); // 매칭 위치 200자 전부터 검색
var searchText = htmlContent.Substring(startSearchIndex, matchIndex - startSearchIndex + matchLength + Math.Min(200, htmlContent.Length - matchIndex - matchLength));
// 상위 태그 패턴들 (div, p, h1-h6, span 등)
var tagPatterns = new[] { @"<(div|p|h[1-6]|span|section|article)[^>]*>", @"<[^>]+>" };
string resultContext = match.Value; // 기본값은 매칭된 부분만
foreach (var tagPattern in tagPatterns)
{
// 매칭된 부분 앞에서 가장 가까운 태그 시작 찾기
var tagMatches = Regex.Matches(searchText, tagPattern, RegexOptions.IgnoreCase);
for (int i = tagMatches.Count - 1; i >= 0; i--)
{
var tagMatch = tagMatches[i];
if (tagMatch.Index < (matchIndex - startSearchIndex))
{
// 태그 이름 추출
var tagName = Regex.Match(tagMatch.Value, @"<(\w+)", RegexOptions.IgnoreCase).Groups[1].Value;
// 닫는 태그 찾기
var closeTagPattern = $@"</{tagName}[^>]*>";
var closeMatch = Regex.Match(searchText, closeTagPattern, RegexOptions.IgnoreCase);
if (closeMatch.Success && closeMatch.Index > (matchIndex - startSearchIndex))
{
// 상위 태그와 그 내용을 포함하여 반환
var startIdx = tagMatch.Index;
var endIdx = closeMatch.Index + closeMatch.Length;
resultContext = searchText.Substring(startIdx, Math.Min(endIdx - startIdx, 500)); // 최대 500자
return resultContext;
}
}
}
}
// 상위 태그를 찾지 못한 경우, 매칭 전후 50자씩 포함
var contextStart = Math.Max(0, matchIndex - 50);
var contextEnd = Math.Min(htmlContent.Length, matchIndex + matchLength + 50);
resultContext = htmlContent.Substring(contextStart, contextEnd - contextStart);
return resultContext;
}
catch (Exception ex)
{
Console.WriteLine($"ExtractResultContext 오류: {ex.Message}");
return match.Value; // 오류 시 매칭된 부분만 반환
}
}
}
}

View File

@@ -107,8 +107,9 @@ namespace BokBonCheck
// JavaScript 렌더링 대기
await Task.Delay(3000);
// 검색 결과 수 추출
var resultCount = ExtractBookCount(_driver, searchTerm, out string ermsg);
var htmlContent = _driver.PageSource;
var resultCount = ExtractBookCount(htmlContent, out string ermsg, out string resultHtml);
result.Resulthtml = resultHtml;
if (resultCount == -1)
{
result.BookCount = 0;
@@ -133,71 +134,21 @@ namespace BokBonCheck
return result;
}
private int ExtractBookCount(IWebDriver driver, string searchTerm, out string errmessage)
private int ExtractBookCount(string htmlContent, out string errmessage, out string resulthtml)
{
errmessage = string.Empty;
resulthtml = string.Empty;
try
{
// JavaScript 실행 후 실제 렌더링된 DOM에서 결과 추출
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
// 1. 검색결과가 없는 경우 확인
try
{
var noResultElements = driver.FindElements(By.XPath("//*[contains(text(), '검색결과가 없습니다') or contains(text(), '검색된 자료가 없습니다')]"));
if (noResultElements.Count > 0)
{
errmessage = "검색결과없음";
return 0;
}
}
catch
{
// 검색결과가 있는 경우로 진행
}
// 2. total_area에서 결과 수량 추출 (JavaScript 렌더링 후)
try
{
var totalAreaElement = wait.Until(d => d.FindElement(By.CssSelector("div.total_area p.total span")));
if (totalAreaElement != null)
{
var countText = totalAreaElement.Text.Trim();
Console.WriteLine($"total_area 텍스트: '{countText}'");
// "총 3건" 형태에서 숫자 추출
var match = Regex.Match(countText, @"총\s*(\d+)\s*건", RegexOptions.IgnoreCase);
if (match.Success && int.TryParse(match.Groups[1].Value, out int count))
{
if (count == 0)
{
errmessage = "검색결과없음";
Console.WriteLine("검색 결과: 0건");
return 0;
}
errmessage = $"검색성공({count}권)";
Console.WriteLine($"검색 결과: {count}건");
return count;
}
}
}
catch (Exception ex1)
{
Console.WriteLine($"total_area 요소 추출 실패: {ex1.Message}");
}
// 3. 페이지 소스에서 렌더링된 결과 추출
var pageSource = driver.PageSource;
Console.WriteLine("페이지 소스에서 결과 추출 시도");
// 검색 결과가 없다는 메시지 확인
if (pageSource.Contains("검색결과가 없습니다") ||
pageSource.Contains("검색된 자료가 없습니다") ||
pageSource.Contains("자료가 없습니다") ||
pageSource.Contains("총 0건"))
if (htmlContent.Contains("검색결과가 없습니다") ||
htmlContent.Contains("검색된 자료가 없습니다") ||
htmlContent.Contains("자료가 없습니다") ||
htmlContent.Contains("총 0건"))
{
errmessage = "검색결과없음";
resulthtml = "검색결과 패턴을 찾을 수 없음";
return 0;
}
@@ -212,11 +163,14 @@ namespace BokBonCheck
foreach (var pattern in htmlPatterns)
{
var match = Regex.Match(pageSource, pattern, RegexOptions.IgnoreCase);
var match = Regex.Match(htmlContent, pattern, RegexOptions.IgnoreCase);
if (match.Success)
{
if (int.TryParse(match.Groups[1].Value, out int count))
{
// 매칭된 부분과 상위 태그 추출하여 resulthtml에 저장
resulthtml = ExtractResultContext(htmlContent, match);
if (count == 0)
{
errmessage = "검색결과없음";
@@ -229,16 +183,80 @@ namespace BokBonCheck
}
errmessage = "결과수량을찾을수없음";
resulthtml = "검색결과 패턴을 찾을 수 없음";
return -1;
}
catch (Exception ex)
{
errmessage = ex.Message;
resulthtml = "검색결과 패턴을 찾을 수 없음";
return -1;
}
}
/// <summary>
/// 매칭된 결과와 그 상위 태그를 추출
/// </summary>
private string ExtractResultContext(string htmlContent, Match match)
{
try
{
var matchIndex = match.Index;
var matchLength = match.Length;
// 매칭된 위치 앞쪽에서 상위 태그 시작 찾기
var startSearchIndex = Math.Max(0, matchIndex - 200); // 매칭 위치 200자 전부터 검색
var searchText = htmlContent.Substring(startSearchIndex, matchIndex - startSearchIndex + matchLength + Math.Min(200, htmlContent.Length - matchIndex - matchLength));
// 상위 태그 패턴들 (div, p, h1-h6, span 등)
var tagPatterns = new[] { @"<(div|p|h[1-6]|span|section|article)[^>]*>", @"<[^>]+>" };
string resultContext = match.Value; // 기본값은 매칭된 부분만
foreach (var tagPattern in tagPatterns)
{
// 매칭된 부분 앞에서 가장 가까운 태그 시작 찾기
var tagMatches = Regex.Matches(searchText, tagPattern, RegexOptions.IgnoreCase);
for (int i = tagMatches.Count - 1; i >= 0; i--)
{
var tagMatch = tagMatches[i];
if (tagMatch.Index < (matchIndex - startSearchIndex))
{
// 태그 이름 추출
var tagName = Regex.Match(tagMatch.Value, @"<(\w+)", RegexOptions.IgnoreCase).Groups[1].Value;
// 닫는 태그 찾기
var closeTagPattern = $@"</{tagName}[^>]*>";
var closeMatch = Regex.Match(searchText, closeTagPattern, RegexOptions.IgnoreCase);
if (closeMatch.Success && closeMatch.Index > (matchIndex - startSearchIndex))
{
// 상위 태그와 그 내용을 포함하여 반환
var startIdx = tagMatch.Index;
var endIdx = closeMatch.Index + closeMatch.Length;
resultContext = searchText.Substring(startIdx, Math.Min(endIdx - startIdx, 500)); // 최대 500자
return resultContext;
}
}
}
}
// 상위 태그를 찾지 못한 경우, 매칭 전후 50자씩 포함
var contextStart = Math.Max(0, matchIndex - 50);
var contextEnd = Math.Min(htmlContent.Length, matchIndex + matchLength + 50);
resultContext = htmlContent.Substring(contextStart, contextEnd - contextStart);
return resultContext;
}
catch (Exception ex)
{
Console.WriteLine($"ExtractResultContext 오류: {ex.Message}");
return match.Value; // 오류 시 매칭된 부분만 반환
}
}
// 완전한 페이지 로딩 대기 메서드
private async Task WaitForCompletePageLoad(WebDriverWait wait)
{

View File

@@ -74,7 +74,7 @@ namespace BokBonCheck
// 브라우저와 유사한 헤더 추가
request.Headers.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
request.Headers.Add("Accept-Language", "ko-KR,ko;q=0.8,en-US;q=0.5,en;q=0.3");
request.Headers.Add("Accept-Encoding", "gzip, deflate, br");
//request.Headers.Add("Accept-Encoding", "gzip, deflate, br");
request.Headers.Add("Connection", "keep-alive");
request.Headers.Add("Upgrade-Insecure-Requests", "1");
@@ -84,7 +84,8 @@ namespace BokBonCheck
var htmlContent = await response.Content.ReadAsStringAsync();
// 검색 결과 수 추출
var resultCount = ExtractBookCount(htmlContent, out string errorMessage);
var resultCount = ExtractBookCount(htmlContent, out string errorMessage, out string resultHtml);
result.Resulthtml = resultHtml;
if (resultCount == -1)
{
@@ -110,9 +111,10 @@ namespace BokBonCheck
return result;
}
private int ExtractBookCount(string htmlContent, out string errmessage)
private int ExtractBookCount(string htmlContent, out string errmessage, out string resulthtml)
{
errmessage = string.Empty;
resulthtml = string.Empty;
try
{
// 실제 HTML 구조에 맞는 패턴으로 수정
@@ -139,6 +141,7 @@ namespace BokBonCheck
if (pattern.Contains(@"\s*0\s*"))
{
errmessage = "검색결과없음";
resulthtml = match.Value;
return 0;
}
@@ -148,17 +151,21 @@ namespace BokBonCheck
if (count == 0)
{
errmessage = "검색결과없음";
resulthtml = match.Value;
return 0;
}
// 매칭된 부분과 그 상위 태그를 찾아서 저장
resulthtml = ExtractResultContext(htmlContent, match);
errmessage = $"검색성공({count}권)";
return count;
}
}
}
// 디버깅을 위해 HTML 내용 일부 출력
// 패턴을 찾지 못한 경우
resulthtml = "검색결과 패턴을 찾을 수 없음";
Console.WriteLine($"HTML 내용 일부: {htmlContent.Substring(0, Math.Min(1000, htmlContent.Length))}");
errmessage = "결과수량을찾을수없음";
return -1;
@@ -166,6 +173,7 @@ namespace BokBonCheck
catch (Exception ex)
{
errmessage = ex.Message;
resulthtml = "검색결과 패턴을 찾을 수 없음";
return -1;
}
}
@@ -177,5 +185,66 @@ namespace BokBonCheck
await Task.CompletedTask;
}
/// <summary>
/// 매칭된 결과와 그 상위 태그를 추출
/// </summary>
private string ExtractResultContext(string htmlContent, Match match)
{
try
{
var matchIndex = match.Index;
var matchLength = match.Length;
// 매칭된 위치 앞쪽에서 상위 태그 시작 찾기
var startSearchIndex = Math.Max(0, matchIndex - 200); // 매칭 위치 200자 전부터 검색
var searchText = htmlContent.Substring(startSearchIndex, matchIndex - startSearchIndex + matchLength + Math.Min(200, htmlContent.Length - matchIndex - matchLength));
// 상위 태그 패턴들 (div, p, h1-h6, span 등)
var tagPatterns = new[] { @"<(div|p|h[1-6]|span|section|article)[^>]*>", @"<[^>]+>" };
string resultContext = match.Value; // 기본값은 매칭된 부분만
foreach (var tagPattern in tagPatterns)
{
// 매칭된 부분 앞에서 가장 가까운 태그 시작 찾기
var tagMatches = Regex.Matches(searchText, tagPattern, RegexOptions.IgnoreCase);
for (int i = tagMatches.Count - 1; i >= 0; i--)
{
var tagMatch = tagMatches[i];
if (tagMatch.Index < (matchIndex - startSearchIndex))
{
// 태그 이름 추출
var tagName = Regex.Match(tagMatch.Value, @"<(\w+)", RegexOptions.IgnoreCase).Groups[1].Value;
// 닫는 태그 찾기
var closeTagPattern = $@"</{tagName}[^>]*>";
var closeMatch = Regex.Match(searchText, closeTagPattern, RegexOptions.IgnoreCase);
if (closeMatch.Success && closeMatch.Index > (matchIndex - startSearchIndex))
{
// 상위 태그와 그 내용을 포함하여 반환
var startIdx = tagMatch.Index;
var endIdx = closeMatch.Index + closeMatch.Length;
resultContext = searchText.Substring(startIdx, Math.Min(endIdx - startIdx, 500)); // 최대 500자
return resultContext;
}
}
}
}
// 상위 태그를 찾지 못한 경우, 매칭 전후 50자씩 포함
var contextStart = Math.Max(0, matchIndex - 50);
var contextEnd = Math.Min(htmlContent.Length, matchIndex + matchLength + 50);
resultContext = htmlContent.Substring(contextStart, contextEnd - contextStart);
return resultContext;
}
catch (Exception ex)
{
Console.WriteLine($"ExtractResultContext 오류: {ex.Message}");
return match.Value; // 오류 시 매칭된 부분만 반환
}
}
}
}

View File

@@ -227,8 +227,9 @@ namespace BokBonCheck
// 페이지 변경을 감지하는 메서드
await WaitForPageChange(new WebDriverWait(_driver, TimeSpan.FromSeconds(15)));
// 검색 결과 수 추출
var resultCount = ExtractBookCount(_driver, searchTerm, out string ermsg);
var htmlContent = _driver.PageSource;
var resultCount = ExtractBookCount(htmlContent, out string ermsg, out string resultHtml);
result.Resulthtml = resultHtml;
if (resultCount == -1)
{
result.BookCount = 0;
@@ -253,71 +254,20 @@ namespace BokBonCheck
return result;
}
private int ExtractBookCount(IWebDriver driver, string searchTerm, out string errmessage)
private int ExtractBookCount(string htmlContent, out string errmessage, out string resulthtml)
{
errmessage = string.Empty;
resulthtml = string.Empty;
try
{
// 1. search-info div에서 직접 추출 시도
try
{
var searchInfoElement = driver.FindElement(By.CssSelector("div.search-info"));
if (searchInfoElement != null)
{
var searchInfoText = searchInfoElement.Text;
// "총 N건이 검색되었습니다" 패턴 찾기
var match = Regex.Match(searchInfoText, @"총\s*(\d+)\s*건이\s*검색되었습니다", RegexOptions.IgnoreCase);
if (match.Success)
{
if (int.TryParse(match.Groups[1].Value, out int count))
{
if (count == 0)
{
errmessage = "검색결과없음";
return 0;
}
errmessage = $"검색성공({count}권)";
return count;
}
}
// <b> 태그에서 직접 숫자 추출 시도
try
{
var boldElements = searchInfoElement.FindElements(By.TagName("b"));
foreach (var boldElement in boldElements)
{
var boldText = boldElement.Text.Trim();
if (int.TryParse(boldText, out int boldCount))
{
if (boldCount == 0)
{
errmessage = "검색결과없음";
return 0;
}
errmessage = $"검색성공({boldCount}권)";
return boldCount;
}
}
}
catch { }
}
}
catch (Exception ex)
{
Console.WriteLine($"search-info 요소 검색 중 오류: {ex.Message}");
}
// 2. 페이지 소스에서 정규식으로 추출 시도
var pageSource = driver.PageSource;
// 검색 결과가 없다는 메시지 확인
if (pageSource.Contains("0건이 검색되었습니다") ||
pageSource.Contains("검색결과가 없습니다") ||
pageSource.Contains("검색된 자료가 없습니다"))
if (htmlContent.Contains("0건이 검색되었습니다") ||
htmlContent.Contains("검색결과가 없습니다") ||
htmlContent.Contains("검색된 자료가 없습니다"))
{
errmessage = "검색결과없음";
resulthtml = "검색결과 패턴을 찾을 수 없음";
return 0;
}
@@ -332,11 +282,14 @@ namespace BokBonCheck
foreach (var pattern in htmlPatterns)
{
var match = Regex.Match(pageSource, pattern, RegexOptions.IgnoreCase);
var match = Regex.Match(htmlContent, pattern, RegexOptions.IgnoreCase);
if (match.Success)
{
if (int.TryParse(match.Groups[1].Value, out int count))
{
// 매칭된 부분과 상위 태그 추출하여 resulthtml에 저장
resulthtml = ExtractResultContext(htmlContent, match);
if (count == 0)
{
errmessage = "검색결과없음";
@@ -349,16 +302,80 @@ namespace BokBonCheck
}
errmessage = "결과수량을찾을수없음";
resulthtml = "검색결과 패턴을 찾을 수 없음";
return -1;
}
catch (Exception ex)
{
errmessage = ex.Message;
resulthtml = "검색결과 패턴을 찾을 수 없음";
return -1;
}
}
/// <summary>
/// 매칭된 결과와 그 상위 태그를 추출
/// </summary>
private string ExtractResultContext(string htmlContent, Match match)
{
try
{
var matchIndex = match.Index;
var matchLength = match.Length;
// 매칭된 위치 앞쪽에서 상위 태그 시작 찾기
var startSearchIndex = Math.Max(0, matchIndex - 200); // 매칭 위치 200자 전부터 검색
var searchText = htmlContent.Substring(startSearchIndex, matchIndex - startSearchIndex + matchLength + Math.Min(200, htmlContent.Length - matchIndex - matchLength));
// 상위 태그 패턴들 (div, p, h1-h6, span 등)
var tagPatterns = new[] { @"<(div|p|h[1-6]|span|section|article)[^>]*>", @"<[^>]+>" };
string resultContext = match.Value; // 기본값은 매칭된 부분만
foreach (var tagPattern in tagPatterns)
{
// 매칭된 부분 앞에서 가장 가까운 태그 시작 찾기
var tagMatches = Regex.Matches(searchText, tagPattern, RegexOptions.IgnoreCase);
for (int i = tagMatches.Count - 1; i >= 0; i--)
{
var tagMatch = tagMatches[i];
if (tagMatch.Index < (matchIndex - startSearchIndex))
{
// 태그 이름 추출
var tagName = Regex.Match(tagMatch.Value, @"<(\w+)", RegexOptions.IgnoreCase).Groups[1].Value;
// 닫는 태그 찾기
var closeTagPattern = $@"</{tagName}[^>]*>";
var closeMatch = Regex.Match(searchText, closeTagPattern, RegexOptions.IgnoreCase);
if (closeMatch.Success && closeMatch.Index > (matchIndex - startSearchIndex))
{
// 상위 태그와 그 내용을 포함하여 반환
var startIdx = tagMatch.Index;
var endIdx = closeMatch.Index + closeMatch.Length;
resultContext = searchText.Substring(startIdx, Math.Min(endIdx - startIdx, 500)); // 최대 500자
return resultContext;
}
}
}
}
// 상위 태그를 찾지 못한 경우, 매칭 전후 50자씩 포함
var contextStart = Math.Max(0, matchIndex - 50);
var contextEnd = Math.Min(htmlContent.Length, matchIndex + matchLength + 50);
resultContext = htmlContent.Substring(contextStart, contextEnd - contextStart);
return resultContext;
}
catch (Exception ex)
{
Console.WriteLine($"ExtractResultContext 오류: {ex.Message}");
return match.Value; // 오류 시 매칭된 부분만 반환
}
}
// 페이지 변경을 감지하는 메서드
public async Task WaitForPageChange(WebDriverWait wait)
{

View File

@@ -260,7 +260,8 @@ namespace BokBonCheck
await WaitForPageChange(new WebDriverWait(_driver, TimeSpan.FromSeconds(15)));
// 검색 결과 수 추출
var resultCount = ExtractBookCount(_driver, searchTerm, out string ermsg);
var resultCount = ExtractBookCount(_driver, searchTerm, out string ermsg, out string resultHtml);
result.Resulthtml = resultHtml;
if (resultCount == -1)
{
result.BookCount = 0;
@@ -285,11 +286,13 @@ namespace BokBonCheck
return result;
}
private int ExtractBookCount(IWebDriver driver, string searchTerm, out string errmessage)
private int ExtractBookCount(IWebDriver driver, string searchTerm, out string errmessage, out string resulthtml)
{
errmessage = string.Empty;
resulthtml = string.Empty;
try
{
var htmlContent = driver.PageSource;
// 먼저 검색결과가 없는 경우 확인
try
{
@@ -298,6 +301,7 @@ namespace BokBonCheck
if (noResultText.Contains("검색결과가 없습니다"))
{
errmessage = "검색결과없음";
resulthtml = noResultText;
return 0;
}
}
@@ -318,17 +322,36 @@ namespace BokBonCheck
if (int.TryParse(match.Groups[1].Value, out int vqty))
{
errmessage = $"검색성공({vqty}건)";
resulthtml = ExtractResultContext(htmlContent, match);
return vqty;
}
else
{
errmessage = $"수량값오류({match.Groups[1].Value})";
resulthtml = match.Value;
return -1;
}
}
else
{
errmessage = "수량항목없음";
// 매칭된 부분이 없는 경우, 기본적으로 pageInfoText 부분 추출 시도
try
{
var dummyMatch = System.Text.RegularExpressions.Regex.Match(pageInfoText, @"전체.*");
if (dummyMatch.Success)
{
resulthtml = ExtractResultContext(htmlContent, dummyMatch);
}
else
{
resulthtml = htmlContent.Length > 500 ? htmlContent.Substring(0, 500) : htmlContent;
}
}
catch
{
resulthtml = htmlContent.Length > 500 ? htmlContent.Substring(0, 500) : htmlContent;
}
return -1;
}
}
@@ -336,6 +359,7 @@ namespace BokBonCheck
{
// page_info가 없는 경우 검색결과가 없는 것으로 판단
errmessage = "검색결과없음";
resulthtml = "검색결과 없음: " + ex.Message;
return 0;
}
@@ -343,11 +367,72 @@ namespace BokBonCheck
catch (Exception ex)
{
errmessage = ex.Message;
resulthtml = "ExtractBookCount 오류: " + ex.Message;
return -1;
}
}
/// <summary>
/// 매칭된 결과와 그 상위 태그를 추출
/// </summary>
private string ExtractResultContext(string htmlContent, Match match)
{
try
{
var matchIndex = match.Index;
var matchLength = match.Length;
// 매칭된 위치 앞쪽에서 상위 태그 시작 찾기
var startSearchIndex = Math.Max(0, matchIndex - 200); // 매칭 위치 200자 전부터 검색
var searchText = htmlContent.Substring(startSearchIndex, matchIndex - startSearchIndex + matchLength + Math.Min(200, htmlContent.Length - matchIndex - matchLength));
// 상위 태그 패턴들 (div, p, h1-h6, span 등)
var tagPatterns = new[] { @"<(div|p|h[1-6]|span|section|article)[^>]*>", @"<[^>]+>" };
string resultContext = match.Value; // 기본값은 매칭된 부분만
foreach (var tagPattern in tagPatterns)
{
// 매칭된 부분 앞에서 가장 가까운 태그 시작 찾기
var tagMatches = Regex.Matches(searchText, tagPattern, RegexOptions.IgnoreCase);
for (int i = tagMatches.Count - 1; i >= 0; i--)
{
var tagMatch = tagMatches[i];
if (tagMatch.Index < (matchIndex - startSearchIndex))
{
// 태그 이름 추출
var tagName = Regex.Match(tagMatch.Value, @"<(\w+)", RegexOptions.IgnoreCase).Groups[1].Value;
// 닫는 태그 찾기
var closeTagPattern = $@"</{tagName}[^>]*>";
var closeMatch = Regex.Match(searchText, closeTagPattern, RegexOptions.IgnoreCase);
if (closeMatch.Success && closeMatch.Index > (matchIndex - startSearchIndex))
{
// 상위 태그와 그 내용을 포함하여 반환
var startIdx = tagMatch.Index;
var endIdx = closeMatch.Index + closeMatch.Length;
resultContext = searchText.Substring(startIdx, Math.Min(endIdx - startIdx, 500)); // 최대 500자
return resultContext;
}
}
}
}
// 상위 태그를 찾지 못한 경우, 매칭 전후 50자씩 포함
var contextStart = Math.Max(0, matchIndex - 50);
var contextEnd = Math.Min(htmlContent.Length, matchIndex + matchLength + 50);
resultContext = htmlContent.Substring(contextStart, contextEnd - contextStart);
return resultContext;
}
catch (Exception ex)
{
Console.WriteLine($"ExtractResultContext 오류: {ex.Message}");
return match.Value; // 오류 시 매칭된 부분만 반환
}
}
// 페이지 변경을 감지하는 메서드
public async Task WaitForPageChange(WebDriverWait wait)

View File

@@ -272,7 +272,8 @@ namespace BokBonCheck
await WaitForPageChange(new WebDriverWait(_driver, TimeSpan.FromSeconds(15)));
// 검색 결과 수 추출
var resultCount = ExtractBookCount(_driver, searchTerm, out string ermsg);
var resultCount = ExtractBookCount(_driver, searchTerm, out string ermsg, out string resultHtml);
result.Resulthtml = resultHtml;
if (resultCount == -1)
{
result.BookCount = 0;
@@ -297,11 +298,13 @@ namespace BokBonCheck
return result;
}
private int ExtractBookCount(IWebDriver driver, string searchTerm, out string errmessage)
private int ExtractBookCount(IWebDriver driver, string searchTerm, out string errmessage, out string resulthtml)
{
errmessage = string.Empty;
resulthtml = string.Empty;
try
{
var htmlContent = driver.PageSource;
// 먼저 검색결과가 없는 경우 확인
try
{
@@ -310,6 +313,7 @@ namespace BokBonCheck
if (noResultText.Contains("검색결과가 없습니다"))
{
errmessage = "검색결과없음";
resulthtml = noResultText;
return 0;
}
}
@@ -330,17 +334,36 @@ namespace BokBonCheck
if (int.TryParse(match.Groups[1].Value, out int vqty))
{
errmessage = $"검색성공({vqty}건)";
resulthtml = ExtractResultContext(htmlContent, match);
return vqty;
}
else
{
errmessage = $"수량값오류({match.Groups[1].Value})";
resulthtml = match.Value;
return -1;
}
}
else
{
errmessage = "수량항목없음";
// 매칭된 부분이 없는 경우, 기본적으로 pageInfoText 부분 추출 시도
try
{
var dummyMatch = System.Text.RegularExpressions.Regex.Match(pageInfoText, @"전체.*");
if (dummyMatch.Success)
{
resulthtml = ExtractResultContext(htmlContent, dummyMatch);
}
else
{
resulthtml = htmlContent.Length > 500 ? htmlContent.Substring(0, 500) : htmlContent;
}
}
catch
{
resulthtml = htmlContent.Length > 500 ? htmlContent.Substring(0, 500) : htmlContent;
}
return -1;
}
}
@@ -348,6 +371,27 @@ namespace BokBonCheck
{
// page_info가 없는 경우 검색결과가 없는 것으로 판단
errmessage = "검색결과없음";
// "검색결과가 없습니다"와 같은 메시지를 찾아 context 추출 시도
var noResultPatterns = new[]
{
@"검색결과가 없습니다",
@"검색된 자료가 없습니다",
@"자료가 없습니다"
};
foreach (var pattern in noResultPatterns)
{
if (htmlContent.Contains(pattern))
{
var match = System.Text.RegularExpressions.Regex.Match(htmlContent, pattern);
if (match.Success)
{
resulthtml = ExtractResultContext(htmlContent, match);
return 0;
}
}
}
resulthtml = htmlContent.Length > 500 ? htmlContent.Substring(0, 500) : htmlContent;
return 0;
}
@@ -355,11 +399,72 @@ namespace BokBonCheck
catch (Exception ex)
{
errmessage = ex.Message;
resulthtml = "ExtractBookCount 오류: " + ex.Message;
return -1;
}
}
/// <summary>
/// 매칭된 결과와 그 상위 태그를 추출
/// </summary>
private string ExtractResultContext(string htmlContent, Match match)
{
try
{
var matchIndex = match.Index;
var matchLength = match.Length;
// 매칭된 위치 앞쪽에서 상위 태그 시작 찾기
var startSearchIndex = Math.Max(0, matchIndex - 200); // 매칭 위치 200자 전부터 검색
var searchText = htmlContent.Substring(startSearchIndex, matchIndex - startSearchIndex + matchLength + Math.Min(200, htmlContent.Length - matchIndex - matchLength));
// 상위 태그 패턴들 (div, p, h1-h6, span 등)
var tagPatterns = new[] { @"<(div|p|h[1-6]|span|section|article)[^>]*>", @"<[^>]+>" };
string resultContext = match.Value; // 기본값은 매칭된 부분만
foreach (var tagPattern in tagPatterns)
{
// 매칭된 부분 앞에서 가장 가까운 태그 시작 찾기
var tagMatches = Regex.Matches(searchText, tagPattern, RegexOptions.IgnoreCase);
for (int i = tagMatches.Count - 1; i >= 0; i--)
{
var tagMatch = tagMatches[i];
if (tagMatch.Index < (matchIndex - startSearchIndex))
{
// 태그 이름 추출
var tagName = Regex.Match(tagMatch.Value, @"<(\w+)", RegexOptions.IgnoreCase).Groups[1].Value;
// 닫는 태그 찾기
var closeTagPattern = $@"</{tagName}[^>]*>";
var closeMatch = Regex.Match(searchText, closeTagPattern, RegexOptions.IgnoreCase);
if (closeMatch.Success && closeMatch.Index > (matchIndex - startSearchIndex))
{
// 상위 태그와 그 내용을 포함하여 반환
var startIdx = tagMatch.Index;
var endIdx = closeMatch.Index + closeMatch.Length;
resultContext = searchText.Substring(startIdx, Math.Min(endIdx - startIdx, 500)); // 최대 500자
return resultContext;
}
}
}
}
// 상위 태그를 찾지 못한 경우, 매칭 전후 50자씩 포함
var contextStart = Math.Max(0, matchIndex - 50);
var contextEnd = Math.Min(htmlContent.Length, matchIndex + matchLength + 50);
resultContext = htmlContent.Substring(contextStart, contextEnd - contextStart);
return resultContext;
}
catch (Exception ex)
{
Console.WriteLine($"ExtractResultContext 오류: {ex.Message}");
return match.Value; // 오류 시 매칭된 부분만 반환
}
}
// 페이지 변경을 감지하는 메서드
public async Task WaitForPageChange(WebDriverWait wait)

View File

@@ -85,7 +85,7 @@ namespace BokBonCheck
// 브라우저와 유사한 헤더 추가
request.Headers.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
request.Headers.Add("Accept-Language", "ko-KR,ko;q=0.8,en-US;q=0.5,en;q=0.3");
request.Headers.Add("Accept-Encoding", "gzip, deflate, br");
//request.Headers.Add("Accept-Encoding", "gzip, deflate, br");
request.Headers.Add("Connection", "keep-alive");
request.Headers.Add("Upgrade-Insecure-Requests", "1");
@@ -100,7 +100,8 @@ namespace BokBonCheck
var htmlContent = await response.Content.ReadAsStringAsync();
// 검색 결과 수 추출
var resultCount = ExtractBookCount(htmlContent, out string errorMessage);
var resultCount = ExtractBookCount(htmlContent, out string errorMessage, out string resultHtml);
result.Resulthtml = resultHtml;
if (resultCount == -1)
{
@@ -128,9 +129,10 @@ namespace BokBonCheck
return result;
}
private int ExtractBookCount(string htmlContent, out string errorMessage)
private int ExtractBookCount(string htmlContent, out string errorMessage, out string resulthtml)
{
errorMessage = string.Empty;
resulthtml = string.Empty;
try
{
@@ -141,6 +143,7 @@ namespace BokBonCheck
htmlContent.Contains("총 0 건이 검색되었습니다"))
{
errorMessage = "검색결과없음";
resulthtml = "검색결과없음";
return 0;
}
@@ -163,8 +166,11 @@ namespace BokBonCheck
if (count == 0)
{
errorMessage = "검색결과없음";
resulthtml = match.Value;
return 0;
}
// 매칭된 부분과 그 상위 태그를 찾아서 저장
resulthtml = ExtractResultContext(htmlContent, match);
errorMessage = $"검색성공({count}권)";
Console.WriteLine($"KCM자료검색시스템 검색 결과: {count}건");
return count;
@@ -189,15 +195,19 @@ namespace BokBonCheck
if (count == 0)
{
errorMessage = "검색결과없음";
resulthtml = match.Value;
return 0;
}
resulthtml = ExtractResultContext(htmlContent, match);
errorMessage = $"검색성공({count}권)";
Console.WriteLine($"KCM자료검색시스템 검색 결과: {count}건");
return count;
}
}
}
// 패턴을 찾지 못한 경우
resulthtml = "검색결과 패턴을 찾을 수 없음";
errorMessage = "검색결과 패턴을 찾을 수 없음";
Console.WriteLine("KCM자료검색시스템 검색결과 패턴을 찾을 수 없음");
return -1;
@@ -205,6 +215,7 @@ namespace BokBonCheck
catch (Exception ex)
{
errorMessage = $"결과 분석 오류: {ex.Message}";
resulthtml = "검색결과 패턴을 찾을 수 없음";
return -1;
}
}
@@ -213,5 +224,67 @@ namespace BokBonCheck
{
throw new NotImplementedException();
}
/// <summary>
/// 매칭된 결과와 그 상위 태그를 추출
/// </summary>
private string ExtractResultContext(string htmlContent, Match match)
{
try
{
var matchIndex = match.Index;
var matchLength = match.Length;
// 매칭된 위치 앞쪽에서 상위 태그 시작 찾기
var startSearchIndex = Math.Max(0, matchIndex - 200); // 매칭 위치 200자 전부터 검색
var searchText = htmlContent.Substring(startSearchIndex, matchIndex - startSearchIndex + matchLength + Math.Min(200, htmlContent.Length - matchIndex - matchLength));
// 상위 태그 패턴들 (div, p, h1-h6, span 등)
var tagPatterns = new[] { @"<(div|p|h[1-6]|span|section|article)[^>]*>", @"<[^>]+>" };
string resultContext = match.Value; // 기본값은 매칭된 부분만
foreach (var tagPattern in tagPatterns)
{
// 매칭된 부분 앞에서 가장 가까운 태그 시작 찾기
var tagMatches = Regex.Matches(searchText, tagPattern, RegexOptions.IgnoreCase);
for (int i = tagMatches.Count - 1; i >= 0; i--)
{
var tagMatch = tagMatches[i];
if (tagMatch.Index < (matchIndex - startSearchIndex))
{
// 태그 이름 추출
var tagName = Regex.Match(tagMatch.Value, @"<(\w+)", RegexOptions.IgnoreCase).Groups[1].Value;
// 닫는 태그 찾기
var closeTagPattern = $@"</{tagName}[^>]*>";
var closeMatch = Regex.Match(searchText, closeTagPattern, RegexOptions.IgnoreCase);
if (closeMatch.Success && closeMatch.Index > (matchIndex - startSearchIndex))
{
// 상위 태그와 그 내용을 포함하여 반환
var startIdx = tagMatch.Index;
var endIdx = closeMatch.Index + closeMatch.Length;
resultContext = searchText.Substring(startIdx, Math.Min(endIdx - startIdx, 500)); // 최대 500자
return resultContext;
}
}
}
}
// 상위 태그를 찾지 못한 경우, 매칭 전후 50자씩 포함
var contextStart = Math.Max(0, matchIndex - 50);
var contextEnd = Math.Min(htmlContent.Length, matchIndex + matchLength + 50);
resultContext = htmlContent.Substring(contextStart, contextEnd - contextStart);
return resultContext;
}
catch (Exception ex)
{
Console.WriteLine($"ExtractResultContext 오류: {ex.Message}");
return match.Value; // 오류 시 매칭된 부분만 반환
}
}
}
}

View File

@@ -193,10 +193,21 @@ namespace BokBonCheck
await WaitForPageChange(new WebDriverWait(_driver, TimeSpan.FromSeconds(30)));
// 검색 결과 수 추출
var resultCount = ExtractBookCount(_driver);
var resultCount = ExtractBookCount(_driver, out string errorMessage, out string resultHtml);
result.Resulthtml = resultHtml;
result.BookCount = resultCount;
result.IsSuccess = true;
if (resultCount == -1)
{
result.BookCount = 0;
result.IsSuccess = false;
result.ErrorMessage = errorMessage;
}
else
{
result.BookCount = resultCount;
result.IsSuccess = true;
result.ErrorMessage = errorMessage;
}
}
catch (Exception ex)
{
@@ -211,10 +222,14 @@ namespace BokBonCheck
return result;
}
private int ExtractBookCount(IWebDriver driver)
private int ExtractBookCount(IWebDriver driver, out string errorMessage, out string resulthtml)
{
errorMessage = string.Empty;
resulthtml = string.Empty;
try
{
var htmlContent = driver.PageSource;
// div.search-result 내부의 span에서 '전체 N' 텍스트 추출
var resultDiv = driver.FindElement(By.CssSelector("div.ndls_result"));
var span = resultDiv.FindElement(By.XPath(".//span[contains(text(),'전체')]"));
@@ -222,13 +237,106 @@ namespace BokBonCheck
var match = System.Text.RegularExpressions.Regex.Match(text, @"전체\s*(\d+)");
if (match.Success)
{
return int.Parse(match.Groups[1].Value);
if (int.TryParse(match.Groups[1].Value, out int count))
{
if (count == 0)
{
errorMessage = "검색결과없음";
resulthtml = text;
return 0;
}
errorMessage = $"검색성공({count}건)";
resulthtml = ExtractResultContext(htmlContent, match);
return count;
}
}
return 0;
errorMessage = "수량항목없음";
// 매칭된 부분이 없는 경우, 기본적으로 text 부분 추출 시도
try
{
var dummyMatch = System.Text.RegularExpressions.Regex.Match(text, @"전체.*");
if (dummyMatch.Success)
{
resulthtml = ExtractResultContext(htmlContent, dummyMatch);
}
else
{
resulthtml = htmlContent.Length > 500 ? htmlContent.Substring(0, 500) : htmlContent;
}
}
catch
{
resulthtml = htmlContent.Length > 500 ? htmlContent.Substring(0, 500) : htmlContent;
}
return -1;
}
catch
catch (Exception ex)
{
return 0;
errorMessage = ex.Message;
resulthtml = "오류발생";
return -1;
}
}
/// <summary>
/// 매칭된 결과와 그 상위 태그를 추출
/// </summary>
private string ExtractResultContext(string htmlContent, Match match)
{
try
{
var matchIndex = match.Index;
var matchLength = match.Length;
// 매칭된 위치 앞쪽에서 상위 태그 시작 찾기
var startSearchIndex = Math.Max(0, matchIndex - 200); // 매칭 위치 200자 전부터 검색
var searchText = htmlContent.Substring(startSearchIndex, matchIndex - startSearchIndex + matchLength + Math.Min(200, htmlContent.Length - matchIndex - matchLength));
// 상위 태그 패턴들 (div, p, h1-h6, span 등)
var tagPatterns = new[] { @"<(div|p|h[1-6]|span|section|article)[^>]*>", @"<[^>]+>" };
string resultContext = match.Value; // 기본값은 매칭된 부분만
foreach (var tagPattern in tagPatterns)
{
// 매칭된 부분 앞에서 가장 가까운 태그 시작 찾기
var tagMatches = Regex.Matches(searchText, tagPattern, RegexOptions.IgnoreCase);
for (int i = tagMatches.Count - 1; i >= 0; i--)
{
var tagMatch = tagMatches[i];
if (tagMatch.Index < (matchIndex - startSearchIndex))
{
// 태그 이름 추출
var tagName = Regex.Match(tagMatch.Value, @"<(\w+)", RegexOptions.IgnoreCase).Groups[1].Value;
// 닫는 태그 찾기
var closeTagPattern = $@"</{tagName}[^>]*>";
var closeMatch = Regex.Match(searchText, closeTagPattern, RegexOptions.IgnoreCase);
if (closeMatch.Success && closeMatch.Index > (matchIndex - startSearchIndex))
{
// 상위 태그와 그 내용을 포함하여 반환
var startIdx = tagMatch.Index;
var endIdx = closeMatch.Index + closeMatch.Length;
resultContext = searchText.Substring(startIdx, Math.Min(endIdx - startIdx, 500)); // 최대 500자
return resultContext;
}
}
}
}
// 상위 태그를 찾지 못한 경우, 매칭 전후 50자씩 포함
var contextStart = Math.Max(0, matchIndex - 50);
var contextEnd = Math.Min(htmlContent.Length, matchIndex + matchLength + 50);
resultContext = htmlContent.Substring(contextStart, contextEnd - contextStart);
return resultContext;
}
catch (Exception ex)
{
Console.WriteLine($"ExtractResultContext 오류: {ex.Message}");
return match.Value; // 오류 시 매칭된 부분만 반환
}
}

View File

@@ -77,7 +77,7 @@ namespace BokBonCheck
// 브라우저와 유사한 헤더 추가
request.Headers.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
request.Headers.Add("Accept-Language", "ko-KR,ko;q=0.8,en-US;q=0.5,en;q=0.3");
request.Headers.Add("Accept-Encoding", "gzip, deflate, br");
//request.Headers.Add("Accept-Encoding", "gzip, deflate, br");
request.Headers.Add("Connection", "keep-alive");
request.Headers.Add("Upgrade-Insecure-Requests", "1");
@@ -92,7 +92,8 @@ namespace BokBonCheck
var htmlContent = await response.Content.ReadAsStringAsync();
// 검색 결과 수 추출
var resultCount = ExtractBookCount(htmlContent, out string errorMessage);
var resultCount = ExtractBookCount(htmlContent, out string errorMessage, out string resultHtml);
result.Resulthtml = resultHtml;
if (resultCount == -1)
{
@@ -120,9 +121,10 @@ namespace BokBonCheck
return result;
}
private int ExtractBookCount(string htmlContent, out string errorMessage)
private int ExtractBookCount(string htmlContent, out string errorMessage, out string resulthtml)
{
errorMessage = string.Empty;
resulthtml = string.Empty;
try
{
@@ -146,8 +148,11 @@ namespace BokBonCheck
if (count == 0)
{
errorMessage = "검색결과없음";
resulthtml = match.Value;
return 0;
}
// 매칭된 부분과 그 상위 태그를 찾아서 저장
resulthtml = ExtractResultContext(htmlContent, match);
errorMessage = $"검색성공({count}권)";
return count;
}
@@ -161,6 +166,7 @@ namespace BokBonCheck
htmlContent.Contains("총 0권(개)"))
{
errorMessage = "검색결과없음";
resulthtml = "검색결과없음";
return 0;
}
@@ -183,20 +189,25 @@ namespace BokBonCheck
if (count == 0)
{
errorMessage = "검색결과없음";
resulthtml = match.Value;
return 0;
}
resulthtml = ExtractResultContext(htmlContent, match);
errorMessage = $"검색성공({count}권)";
return count;
}
}
}
// 패턴을 찾지 못한 경우
resulthtml = "검색결과 패턴을 찾을 수 없음";
errorMessage = "검색결과 패턴을 찾을 수 없음";
return -1;
}
catch (Exception ex)
{
errorMessage = $"결과 분석 오류: {ex.Message}";
resulthtml = "검색결과 패턴을 찾을 수 없음";
return -1;
}
}
@@ -205,5 +216,67 @@ namespace BokBonCheck
{
throw new NotImplementedException();
}
/// <summary>
/// 매칭된 결과와 그 상위 태그를 추출
/// </summary>
private string ExtractResultContext(string htmlContent, Match match)
{
try
{
var matchIndex = match.Index;
var matchLength = match.Length;
// 매칭된 위치 앞쪽에서 상위 태그 시작 찾기
var startSearchIndex = Math.Max(0, matchIndex - 200); // 매칭 위치 200자 전부터 검색
var searchText = htmlContent.Substring(startSearchIndex, matchIndex - startSearchIndex + matchLength + Math.Min(200, htmlContent.Length - matchIndex - matchLength));
// 상위 태그 패턴들 (div, p, h1-h6, span 등)
var tagPatterns = new[] { @"<(div|p|h[1-6]|span|section|article)[^>]*>", @"<[^>]+>" };
string resultContext = match.Value; // 기본값은 매칭된 부분만
foreach (var tagPattern in tagPatterns)
{
// 매칭된 부분 앞에서 가장 가까운 태그 시작 찾기
var tagMatches = Regex.Matches(searchText, tagPattern, RegexOptions.IgnoreCase);
for (int i = tagMatches.Count - 1; i >= 0; i--)
{
var tagMatch = tagMatches[i];
if (tagMatch.Index < (matchIndex - startSearchIndex))
{
// 태그 이름 추출
var tagName = Regex.Match(tagMatch.Value, @"<(\w+)", RegexOptions.IgnoreCase).Groups[1].Value;
// 닫는 태그 찾기
var closeTagPattern = $@"</{tagName}[^>]*>";
var closeMatch = Regex.Match(searchText, closeTagPattern, RegexOptions.IgnoreCase);
if (closeMatch.Success && closeMatch.Index > (matchIndex - startSearchIndex))
{
// 상위 태그와 그 내용을 포함하여 반환
var startIdx = tagMatch.Index;
var endIdx = closeMatch.Index + closeMatch.Length;
resultContext = searchText.Substring(startIdx, Math.Min(endIdx - startIdx, 500)); // 최대 500자
return resultContext;
}
}
}
}
// 상위 태그를 찾지 못한 경우, 매칭 전후 50자씩 포함
var contextStart = Math.Max(0, matchIndex - 50);
var contextEnd = Math.Min(htmlContent.Length, matchIndex + matchLength + 50);
resultContext = htmlContent.Substring(contextStart, contextEnd - contextStart);
return resultContext;
}
catch (Exception ex)
{
Console.WriteLine($"ExtractResultContext 오류: {ex.Message}");
return match.Value; // 오류 시 매칭된 부분만 반환
}
}
}
}

View File

@@ -196,9 +196,9 @@ namespace BokBonCheck
// 페이지 변경을 감지하는 메서드
await WaitForPageChange(new WebDriverWait(_driver, TimeSpan.FromSeconds(15)));
// 검색 결과 수 추출
var resultCount = ExtractBookCount(_driver, searchTerm, out string ermsg);
var htmlContent = _driver.PageSource;
var resultCount = ExtractBookCount(htmlContent, out string ermsg, out string resultHtml);
result.Resulthtml = resultHtml;
if (resultCount == -1)
{
result.BookCount = 0;
@@ -223,71 +223,56 @@ namespace BokBonCheck
return result;
}
private int ExtractBookCount(IWebDriver driver, string searchTerm, out string errmessage)
private int ExtractBookCount(string htmlContent, out string errmessage, out string resulthtml)
{
errmessage = string.Empty;
resulthtml = string.Empty;
try
{
// 검색결과 페이지 대기
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
// 1. 검색결과가 없는 경우 확인
try
// 검색 결과가 없다는 메시지 확인
var noResultPatterns = new[]
{
var noResultElements = driver.FindElements(By.XPath("//*[contains(text(), '검색결과가 없습니다') or contains(text(), '검색된 자료가 없습니다')]"));
if (noResultElements.Count > 0)
@"검색결과가 없습니다",
@"검색된 자료가 없습니다",
@"자료가 없습니다",
@"전체 <strong>0</strong> 건"
};
foreach (var pattern in noResultPatterns)
{
if (htmlContent.Contains(pattern))
{
errmessage = "검색결과없음";
// 검색결과 없음 메시지를 포함한 HTML 조각 추출
var index = htmlContent.IndexOf(pattern);
if (index >= 0)
{
var startIndex = Math.Max(0, index - 100);
var endIndex = Math.Min(htmlContent.Length, index + pattern.Length + 100);
resulthtml = htmlContent.Substring(startIndex, endIndex - startIndex);
// 상위 태그 찾기 시도
try
{
var match = System.Text.RegularExpressions.Regex.Match(htmlContent, pattern);
if (match.Success)
{
resulthtml = ExtractResultContext(htmlContent, match);
}
}
catch
{
// 실패시 기본 추출 결과 사용
}
}
else
{
resulthtml = pattern;
}
return 0;
}
}
catch
{
// 검색결과가 있는 경우로 진행
}
// 2. totalCount에서 결과 수량 추출
try
{
var totalCountElement = wait.Until(d => d.FindElement(By.CssSelector("p.totalCount strong")));
if (totalCountElement != null)
{
var countText = totalCountElement.Text.Trim();
Console.WriteLine($"totalCount 텍스트: '{countText}'");
if (int.TryParse(countText, out int count))
{
if (count == 0)
{
errmessage = "검색결과없음";
Console.WriteLine("검색 결과: 0건");
return 0;
}
errmessage = $"검색성공({count}권)";
Console.WriteLine($"검색 결과: {count}건");
return count;
}
}
}
catch (Exception ex1)
{
Console.WriteLine($"totalCount 요소 추출 실패: {ex1.Message}");
}
// 3. 페이지 소스에서 결과 추출
var pageSource = driver.PageSource;
Console.WriteLine("페이지 소스에서 결과 추출 시도");
// 검색 결과가 없다는 메시지 확인
if (pageSource.Contains("검색결과가 없습니다") ||
pageSource.Contains("검색된 자료가 없습니다") ||
pageSource.Contains("자료가 없습니다") ||
pageSource.Contains("전체 <strong>0</strong> 건"))
{
errmessage = "검색결과없음";
return 0;
}
// HTML에서 다양한 패턴 찾기
var htmlPatterns = new[]
@@ -299,11 +284,14 @@ namespace BokBonCheck
foreach (var pattern in htmlPatterns)
{
var match = Regex.Match(pageSource, pattern, RegexOptions.IgnoreCase);
var match = Regex.Match(htmlContent, pattern, RegexOptions.IgnoreCase);
if (match.Success)
{
if (int.TryParse(match.Groups[1].Value, out int count))
{
// 매칭된 부분과 상위 태그 추출하여 resulthtml에 저장
resulthtml = ExtractResultContext(htmlContent, match);
if (count == 0)
{
errmessage = "검색결과없음";
@@ -316,16 +304,80 @@ namespace BokBonCheck
}
errmessage = "결과수량을찾을수없음";
resulthtml = "결과수량을찾을수없음";
return -1;
}
catch (Exception ex)
{
errmessage = ex.Message;
resulthtml = "ExtractBookCount 오류: " + ex.Message;
return -1;
}
}
/// <summary>
/// 매칭된 결과와 그 상위 태그를 추출
/// </summary>
private string ExtractResultContext(string htmlContent, Match match)
{
try
{
var matchIndex = match.Index;
var matchLength = match.Length;
// 매칭된 위치 앞쪽에서 상위 태그 시작 찾기
var startSearchIndex = Math.Max(0, matchIndex - 200); // 매칭 위치 200자 전부터 검색
var searchText = htmlContent.Substring(startSearchIndex, matchIndex - startSearchIndex + matchLength + Math.Min(200, htmlContent.Length - matchIndex - matchLength));
// 상위 태그 패턴들 (div, p, h1-h6, span 등)
var tagPatterns = new[] { @"<(div|p|h[1-6]|span|section|article)[^>]*>", @"<[^>]+>" };
string resultContext = match.Value; // 기본값은 매칭된 부분만
foreach (var tagPattern in tagPatterns)
{
// 매칭된 부분 앞에서 가장 가까운 태그 시작 찾기
var tagMatches = Regex.Matches(searchText, tagPattern, RegexOptions.IgnoreCase);
for (int i = tagMatches.Count - 1; i >= 0; i--)
{
var tagMatch = tagMatches[i];
if (tagMatch.Index < (matchIndex - startSearchIndex))
{
// 태그 이름 추출
var tagName = Regex.Match(tagMatch.Value, @"<(\w+)", RegexOptions.IgnoreCase).Groups[1].Value;
// 닫는 태그 찾기
var closeTagPattern = $@"</{tagName}[^>]*>";
var closeMatch = Regex.Match(searchText, closeTagPattern, RegexOptions.IgnoreCase);
if (closeMatch.Success && closeMatch.Index > (matchIndex - startSearchIndex))
{
// 상위 태그와 그 내용을 포함하여 반환
var startIdx = tagMatch.Index;
var endIdx = closeMatch.Index + closeMatch.Length;
resultContext = searchText.Substring(startIdx, Math.Min(endIdx - startIdx, 500)); // 최대 500자
return resultContext;
}
}
}
}
// 상위 태그를 찾지 못한 경우, 매칭 전후 50자씩 포함
var contextStart = Math.Max(0, matchIndex - 50);
var contextEnd = Math.Min(htmlContent.Length, matchIndex + matchLength + 50);
resultContext = htmlContent.Substring(contextStart, contextEnd - contextStart);
return resultContext;
}
catch (Exception ex)
{
Console.WriteLine($"ExtractResultContext 오류: {ex.Message}");
return match.Value; // 오류 시 매칭된 부분만 반환
}
}
// 완전한 페이지 로딩 대기 메서드
private async Task WaitForCompletePageLoad(WebDriverWait wait)
{

View File

@@ -269,7 +269,8 @@ namespace BokBonCheck
await WaitForPageChange(new WebDriverWait(_driver, TimeSpan.FromSeconds(15)));
// 검색 결과 수 추출
var resultCount = ExtractBookCount(_driver, searchTerm, out string ermsg);
var resultCount = ExtractBookCount(_driver, searchTerm, out string ermsg, out string resultHtml);
result.Resulthtml = resultHtml;
if (resultCount == -1)
{
result.BookCount = 0;
@@ -294,11 +295,14 @@ namespace BokBonCheck
return result;
}
private int ExtractBookCount(IWebDriver driver, string searchTerm, out string errmessage)
private int ExtractBookCount(IWebDriver driver, string searchTerm, out string errmessage, out string resulthtml)
{
errmessage = string.Empty;
resulthtml = string.Empty;
try
{
var htmlContent = driver.PageSource;
// div.search-result 내부의 span에서 '전체 N' 텍스트 추출
var resultDiv = driver.FindElement(By.CssSelector("div.search-result"));
@@ -306,6 +310,7 @@ namespace BokBonCheck
if (bodytext.Contains("검색결과가 없습니다"))
{
errmessage = "검색결과없음";
resulthtml = bodytext;
return 0;
}
@@ -315,6 +320,7 @@ namespace BokBonCheck
if (searchTerm.Contains(searchtitle) == false)
{
errmessage = $"검색어불일치({searchtitle}/{searchTerm})";
resulthtml = searchtitle;
return -1;
}
var span = resultDiv.FindElement(By.XPath(".//span[contains(text(),'전체')]"));
@@ -325,10 +331,13 @@ namespace BokBonCheck
if (int.TryParse(match.Groups[1].Value, out int vqty) == false)
{
errmessage = $"수량값오류({match.Groups[1].Value})";
resulthtml = match.Value;
return -1;
}
else
{
errmessage = $"검색성공({vqty}건)";
resulthtml = ExtractResultContext(htmlContent, match);
searchTerm = string.Empty;
return vqty;
}
@@ -336,6 +345,23 @@ namespace BokBonCheck
else
{
errmessage = "수량항목없음";
// 매칭된 부분이 없는 경우, 기본적으로 text 부분 추출 시도
try
{
var dummyMatch = System.Text.RegularExpressions.Regex.Match(text, @"전체.*");
if (dummyMatch.Success)
{
resulthtml = ExtractResultContext(htmlContent, dummyMatch);
}
else
{
resulthtml = htmlContent.Length > 500 ? htmlContent.Substring(0, 500) : htmlContent;
}
}
catch
{
resulthtml = htmlContent.Length > 500 ? htmlContent.Substring(0, 500) : htmlContent;
}
return -1;
}
@@ -343,11 +369,72 @@ namespace BokBonCheck
catch (Exception ex)
{
errmessage = ex.Message;
resulthtml = "ExtractBookCount 오류: " + ex.Message;
return -1;
}
}
/// <summary>
/// 매칭된 결과와 그 상위 태그를 추출
/// </summary>
private string ExtractResultContext(string htmlContent, Match match)
{
try
{
var matchIndex = match.Index;
var matchLength = match.Length;
// 매칭된 위치 앞쪽에서 상위 태그 시작 찾기
var startSearchIndex = Math.Max(0, matchIndex - 200); // 매칭 위치 200자 전부터 검색
var searchText = htmlContent.Substring(startSearchIndex, matchIndex - startSearchIndex + matchLength + Math.Min(200, htmlContent.Length - matchIndex - matchLength));
// 상위 태그 패턴들 (div, p, h1-h6, span 등)
var tagPatterns = new[] { @"<(div|p|h[1-6]|span|section|article)[^>]*>", @"<[^>]+>" };
string resultContext = match.Value; // 기본값은 매칭된 부분만
foreach (var tagPattern in tagPatterns)
{
// 매칭된 부분 앞에서 가장 가까운 태그 시작 찾기
var tagMatches = Regex.Matches(searchText, tagPattern, RegexOptions.IgnoreCase);
for (int i = tagMatches.Count - 1; i >= 0; i--)
{
var tagMatch = tagMatches[i];
if (tagMatch.Index < (matchIndex - startSearchIndex))
{
// 태그 이름 추출
var tagName = Regex.Match(tagMatch.Value, @"<(\w+)", RegexOptions.IgnoreCase).Groups[1].Value;
// 닫는 태그 찾기
var closeTagPattern = $@"</{tagName}[^>]*>";
var closeMatch = Regex.Match(searchText, closeTagPattern, RegexOptions.IgnoreCase);
if (closeMatch.Success && closeMatch.Index > (matchIndex - startSearchIndex))
{
// 상위 태그와 그 내용을 포함하여 반환
var startIdx = tagMatch.Index;
var endIdx = closeMatch.Index + closeMatch.Length;
resultContext = searchText.Substring(startIdx, Math.Min(endIdx - startIdx, 500)); // 최대 500자
return resultContext;
}
}
}
}
// 상위 태그를 찾지 못한 경우, 매칭 전후 50자씩 포함
var contextStart = Math.Max(0, matchIndex - 50);
var contextEnd = Math.Min(htmlContent.Length, matchIndex + matchLength + 50);
resultContext = htmlContent.Substring(contextStart, contextEnd - contextStart);
return resultContext;
}
catch (Exception ex)
{
Console.WriteLine($"ExtractResultContext 오류: {ex.Message}");
return match.Value; // 오류 시 매칭된 부분만 반환
}
}
// 페이지 변경을 감지하는 메서드
public async Task WaitForPageChange(WebDriverWait wait)

View File

@@ -82,7 +82,7 @@ namespace BokBonCheck
// 브라우저와 유사한 헤더 추가
request.Headers.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
request.Headers.Add("Accept-Language", "ko-KR,ko;q=0.8,en-US;q=0.5,en;q=0.3");
request.Headers.Add("Accept-Encoding", "gzip, deflate, br");
//request.Headers.Add("Accept-Encoding", "gzip, deflate, br");
request.Headers.Add("Connection", "keep-alive");
request.Headers.Add("Upgrade-Insecure-Requests", "1");
@@ -97,7 +97,8 @@ namespace BokBonCheck
var htmlContent = await response.Content.ReadAsStringAsync();
// 검색 결과 수 추출
var resultCount = ExtractBookCount(htmlContent, out string errorMessage);
var resultCount = ExtractBookCount(htmlContent, out string errorMessage, out string resultHtml);
result.Resulthtml = resultHtml;
if (resultCount == -1)
{
@@ -125,9 +126,10 @@ namespace BokBonCheck
return result;
}
private int ExtractBookCount(string htmlContent, out string errorMessage)
private int ExtractBookCount(string htmlContent, out string errorMessage, out string resulthtml)
{
errorMessage = string.Empty;
resulthtml = string.Empty;
try
{
@@ -150,8 +152,11 @@ namespace BokBonCheck
if (count == 0)
{
errorMessage = "검색결과없음";
resulthtml = match.Value;
return 0;
}
// 매칭된 부분과 그 상위 태그를 찾아서 저장
resulthtml = ExtractResultContext(htmlContent, match);
errorMessage = $"검색성공({count}권)";
return count;
}
@@ -165,6 +170,7 @@ namespace BokBonCheck
htmlContent.Contains("총 0건"))
{
errorMessage = "검색결과없음";
resulthtml = "검색결과없음";
return 0;
}
@@ -178,8 +184,10 @@ namespace BokBonCheck
if (count == 0)
{
errorMessage = "검색결과없음";
resulthtml = resultConMatch.Value;
return 0;
}
resulthtml = ExtractResultContext(htmlContent, resultConMatch);
errorMessage = $"검색성공({count}권)";
return count;
}
@@ -203,20 +211,25 @@ namespace BokBonCheck
if (count == 0)
{
errorMessage = "검색결과없음";
resulthtml = match.Value;
return 0;
}
resulthtml = ExtractResultContext(htmlContent, match);
errorMessage = $"검색성공({count}권)";
return count;
}
}
}
// 패턴을 찾지 못한 경우
resulthtml = "검색결과 패턴을 찾을 수 없음";
errorMessage = "검색결과 패턴을 찾을 수 없음";
return -1;
}
catch (Exception ex)
{
errorMessage = $"결과 분석 오류: {ex.Message}";
resulthtml = "검색결과 패턴을 찾을 수 없음";
return -1;
}
}
@@ -225,5 +238,67 @@ namespace BokBonCheck
{
throw new NotImplementedException();
}
/// <summary>
/// 매칭된 결과와 그 상위 태그를 추출
/// </summary>
private string ExtractResultContext(string htmlContent, Match match)
{
try
{
var matchIndex = match.Index;
var matchLength = match.Length;
// 매칭된 위치 앞쪽에서 상위 태그 시작 찾기
var startSearchIndex = Math.Max(0, matchIndex - 200); // 매칭 위치 200자 전부터 검색
var searchText = htmlContent.Substring(startSearchIndex, matchIndex - startSearchIndex + matchLength + Math.Min(200, htmlContent.Length - matchIndex - matchLength));
// 상위 태그 패턴들 (div, p, h1-h6, span 등)
var tagPatterns = new[] { @"<(div|p|h[1-6]|span|section|article)[^>]*>", @"<[^>]+>" };
string resultContext = match.Value; // 기본값은 매칭된 부분만
foreach (var tagPattern in tagPatterns)
{
// 매칭된 부분 앞에서 가장 가까운 태그 시작 찾기
var tagMatches = Regex.Matches(searchText, tagPattern, RegexOptions.IgnoreCase);
for (int i = tagMatches.Count - 1; i >= 0; i--)
{
var tagMatch = tagMatches[i];
if (tagMatch.Index < (matchIndex - startSearchIndex))
{
// 태그 이름 추출
var tagName = Regex.Match(tagMatch.Value, @"<(\w+)", RegexOptions.IgnoreCase).Groups[1].Value;
// 닫는 태그 찾기
var closeTagPattern = $@"</{tagName}[^>]*>";
var closeMatch = Regex.Match(searchText, closeTagPattern, RegexOptions.IgnoreCase);
if (closeMatch.Success && closeMatch.Index > (matchIndex - startSearchIndex))
{
// 상위 태그와 그 내용을 포함하여 반환
var startIdx = tagMatch.Index;
var endIdx = closeMatch.Index + closeMatch.Length;
resultContext = searchText.Substring(startIdx, Math.Min(endIdx - startIdx, 500)); // 최대 500자
return resultContext;
}
}
}
}
// 상위 태그를 찾지 못한 경우, 매칭 전후 50자씩 포함
var contextStart = Math.Max(0, matchIndex - 50);
var contextEnd = Math.Min(htmlContent.Length, matchIndex + matchLength + 50);
resultContext = htmlContent.Substring(contextStart, contextEnd - contextStart);
return resultContext;
}
catch (Exception ex)
{
Console.WriteLine($"ExtractResultContext 오류: {ex.Message}");
return match.Value; // 오류 시 매칭된 부분만 반환
}
}
}
}

View File

@@ -86,7 +86,7 @@ namespace BokBonCheck
// 브라우저와 유사한 헤더 추가
request.Headers.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
request.Headers.Add("Accept-Language", "ko-KR,ko;q=0.8,en-US;q=0.5,en;q=0.3");
request.Headers.Add("Accept-Encoding", "gzip, deflate, br");
//request.Headers.Add("Accept-Encoding", "gzip, deflate, br");
request.Headers.Add("Connection", "keep-alive");
request.Headers.Add("Upgrade-Insecure-Requests", "1");
@@ -96,7 +96,8 @@ namespace BokBonCheck
var htmlContent = await response.Content.ReadAsStringAsync();
// 검색 결과 수 추출
var resultCount = ExtractBookCount(htmlContent, out string errorMessage);
var resultCount = ExtractBookCount(htmlContent, out string errorMessage, out string resultHtml);
result.Resulthtml = resultHtml;
if (resultCount == -1)
{
@@ -122,15 +123,17 @@ namespace BokBonCheck
return result;
}
private int ExtractBookCount(string htmlContent, out string errmessage)
private int ExtractBookCount(string htmlContent, out string errmessage, out string resulthtml)
{
errmessage = string.Empty;
resulthtml = string.Empty;
try
{
// 검색 결과가 없다는 메시지 확인
if (htmlContent.Contains("0권(개)") || htmlContent.Contains("검색결과가 없습니다"))
{
errmessage = "검색결과없음";
resulthtml = "검색결과없음";
return 0;
}
@@ -153,14 +156,19 @@ namespace BokBonCheck
if (count == 0)
{
errmessage = "검색결과없음";
resulthtml = match.Value;
return 0;
}
// 매칭된 부분과 그 상위 태그를 찾아서 저장
resulthtml = ExtractResultContext(htmlContent, match);
errmessage = $"검색성공({count}권)";
return count;
}
}
}
// 패턴을 찾지 못한 경우
resulthtml = "검색결과 패턴을 찾을 수 없음";
errmessage = "결과수량을찾을수없음";
return -1;
@@ -168,6 +176,7 @@ namespace BokBonCheck
catch (Exception ex)
{
errmessage = ex.Message;
resulthtml = "검색결과 패턴을 찾을 수 없음";
return -1;
}
}
@@ -178,5 +187,67 @@ namespace BokBonCheck
// HTTP 방식에서는 즉시 응답이 오므로 대기 불필요
await Task.CompletedTask;
}
/// <summary>
/// 매칭된 결과와 그 상위 태그를 추출
/// </summary>
private string ExtractResultContext(string htmlContent, Match match)
{
try
{
var matchIndex = match.Index;
var matchLength = match.Length;
// 매칭된 위치 앞쪽에서 상위 태그 시작 찾기
var startSearchIndex = Math.Max(0, matchIndex - 200); // 매칭 위치 200자 전부터 검색
var searchText = htmlContent.Substring(startSearchIndex, matchIndex - startSearchIndex + matchLength + Math.Min(200, htmlContent.Length - matchIndex - matchLength));
// 상위 태그 패턴들 (div, p, h1-h6, span 등)
var tagPatterns = new[] { @"<(div|p|h[1-6]|span|section|article)[^>]*>", @"<[^>]+>" };
string resultContext = match.Value; // 기본값은 매칭된 부분만
foreach (var tagPattern in tagPatterns)
{
// 매칭된 부분 앞에서 가장 가까운 태그 시작 찾기
var tagMatches = Regex.Matches(searchText, tagPattern, RegexOptions.IgnoreCase);
for (int i = tagMatches.Count - 1; i >= 0; i--)
{
var tagMatch = tagMatches[i];
if (tagMatch.Index < (matchIndex - startSearchIndex))
{
// 태그 이름 추출
var tagName = Regex.Match(tagMatch.Value, @"<(\w+)", RegexOptions.IgnoreCase).Groups[1].Value;
// 닫는 태그 찾기
var closeTagPattern = $@"</{tagName}[^>]*>";
var closeMatch = Regex.Match(searchText, closeTagPattern, RegexOptions.IgnoreCase);
if (closeMatch.Success && closeMatch.Index > (matchIndex - startSearchIndex))
{
// 상위 태그와 그 내용을 포함하여 반환
var startIdx = tagMatch.Index;
var endIdx = closeMatch.Index + closeMatch.Length;
resultContext = searchText.Substring(startIdx, Math.Min(endIdx - startIdx, 500)); // 최대 500자
return resultContext;
}
}
}
}
// 상위 태그를 찾지 못한 경우, 매칭 전후 50자씩 포함
var contextStart = Math.Max(0, matchIndex - 50);
var contextEnd = Math.Min(htmlContent.Length, matchIndex + matchLength + 50);
resultContext = htmlContent.Substring(contextStart, contextEnd - contextStart);
return resultContext;
}
catch (Exception ex)
{
Console.WriteLine($"ExtractResultContext 오류: {ex.Message}");
return match.Value; // 오류 시 매칭된 부분만 반환
}
}
}
}

View File

@@ -82,7 +82,7 @@ namespace BokBonCheck
// 브라우저와 유사한 헤더 추가
request.Headers.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
request.Headers.Add("Accept-Language", "ko-KR,ko;q=0.8,en-US;q=0.5,en;q=0.3");
request.Headers.Add("Accept-Encoding", "gzip, deflate, br");
//request.Headers.Add("Accept-Encoding", "gzip, deflate, br");
request.Headers.Add("Connection", "keep-alive");
request.Headers.Add("Upgrade-Insecure-Requests", "1");
@@ -92,7 +92,8 @@ namespace BokBonCheck
var htmlContent = await response.Content.ReadAsStringAsync();
// 검색 결과 수 추출
var resultCount = ExtractBookCount(htmlContent, out string errorMessage);
var resultCount = ExtractBookCount(htmlContent, out string errorMessage, out string resultHtml);
result.Resulthtml = resultHtml;
if (resultCount == -1)
{
@@ -120,9 +121,10 @@ namespace BokBonCheck
return result;
}
private int ExtractBookCount(string htmlContent, out string errorMessage)
private int ExtractBookCount(string htmlContent, out string errorMessage, out string resulthtml)
{
errorMessage = string.Empty;
resulthtml = string.Empty;
try
{
@@ -134,12 +136,21 @@ namespace BokBonCheck
{
if (int.TryParse(match.Groups[1].Value, out int count))
{
if (count == 0)
{
errorMessage = "검색결과없음";
resulthtml = match.Value;
return 0;
}
// 매칭된 부분과 그 상위 태그를 찾아서 저장
resulthtml = ExtractResultContext(htmlContent, match);
errorMessage = $"검색성공({count}권)";
return count;
}
else
{
errorMessage = $"수량값오류({match.Groups[1].Value})";
resulthtml = match.Value;
return -1;
}
}
@@ -153,11 +164,21 @@ namespace BokBonCheck
{
if (int.TryParse(alternateMatch.Groups[1].Value, out int count))
{
if (count == 0)
{
errorMessage = "검색결과없음";
resulthtml = alternateMatch.Value;
return 0;
}
// 매칭된 부분과 그 상위 태그를 찾아서 저장
resulthtml = ExtractResultContext(htmlContent, alternateMatch);
errorMessage = $"검색성공({count}권)";
return count;
}
}
// 패턴을 찾지 못한 경우
resulthtml = "검색결과 패턴을 찾을 수 없음";
errorMessage = "검색결과 패턴을 찾을 수 없음";
return -1;
}
@@ -165,6 +186,7 @@ namespace BokBonCheck
catch (Exception ex)
{
errorMessage = $"결과 분석 오류: {ex.Message}";
resulthtml = "검색결과 패턴을 찾을 수 없음";
return -1;
}
}
@@ -173,5 +195,67 @@ namespace BokBonCheck
{
throw new NotImplementedException();
}
/// <summary>
/// 매칭된 결과와 그 상위 태그를 추출
/// </summary>
private string ExtractResultContext(string htmlContent, Match match)
{
try
{
var matchIndex = match.Index;
var matchLength = match.Length;
// 매칭된 위치 앞쪽에서 상위 태그 시작 찾기
var startSearchIndex = Math.Max(0, matchIndex - 200); // 매칭 위치 200자 전부터 검색
var searchText = htmlContent.Substring(startSearchIndex, matchIndex - startSearchIndex + matchLength + Math.Min(200, htmlContent.Length - matchIndex - matchLength));
// 상위 태그 패턴들 (div, p, h1-h6, span 등)
var tagPatterns = new[] { @"<(div|p|h[1-6]|span|section|article)[^>]*>", @"<[^>]+>" };
string resultContext = match.Value; // 기본값은 매칭된 부분만
foreach (var tagPattern in tagPatterns)
{
// 매칭된 부분 앞에서 가장 가까운 태그 시작 찾기
var tagMatches = Regex.Matches(searchText, tagPattern, RegexOptions.IgnoreCase);
for (int i = tagMatches.Count - 1; i >= 0; i--)
{
var tagMatch = tagMatches[i];
if (tagMatch.Index < (matchIndex - startSearchIndex))
{
// 태그 이름 추출
var tagName = Regex.Match(tagMatch.Value, @"<(\w+)", RegexOptions.IgnoreCase).Groups[1].Value;
// 닫는 태그 찾기
var closeTagPattern = $@"</{tagName}[^>]*>";
var closeMatch = Regex.Match(searchText, closeTagPattern, RegexOptions.IgnoreCase);
if (closeMatch.Success && closeMatch.Index > (matchIndex - startSearchIndex))
{
// 상위 태그와 그 내용을 포함하여 반환
var startIdx = tagMatch.Index;
var endIdx = closeMatch.Index + closeMatch.Length;
resultContext = searchText.Substring(startIdx, Math.Min(endIdx - startIdx, 500)); // 최대 500자
return resultContext;
}
}
}
}
// 상위 태그를 찾지 못한 경우, 매칭 전후 50자씩 포함
var contextStart = Math.Max(0, matchIndex - 50);
var contextEnd = Math.Min(htmlContent.Length, matchIndex + matchLength + 50);
resultContext = htmlContent.Substring(contextStart, contextEnd - contextStart);
return resultContext;
}
catch (Exception ex)
{
Console.WriteLine($"ExtractResultContext 오류: {ex.Message}");
return match.Value; // 오류 시 매칭된 부분만 반환
}
}
}
}

View File

@@ -63,7 +63,7 @@ namespace WindowsFormsApp1
else
dataGridView.Sort(dataGridView.Columns[col], System.ComponentModel.ListSortDirection.Ascending);
}
/// <summary>
/// * Row헤더에 체크박스를 넣는 기능*
@@ -99,7 +99,7 @@ namespace WindowsFormsApp1
}
private void datagridview_checkBox_Click(object sender, EventArgs e)
{
foreach(DataGridViewRow r in ((DataGridView)sender).Rows)
foreach (DataGridViewRow r in ((DataGridView)sender).Rows)
{
r.Cells["colCheck"].Value = ((CheckBox)sender).Checked;
}
@@ -119,16 +119,23 @@ namespace WindowsFormsApp1
char[] columnSplitter = { '\t' };
//get the text from clipboard
IDataObject dataInClipboard = Clipboard.GetDataObject();
string stringInClipboard = (string)dataInClipboard.GetData(DataFormats.Text);
if (Clipboard.ContainsText() == false) return;
string stringInClipboard = null;
if (e.Alt)
stringInClipboard = Clipboard.GetText(TextDataFormat.UnicodeText);
else
stringInClipboard = Clipboard.GetText();// (string)objdata;
//split it into lines
//20230209 \r텝 기능과 \n 줄넘김 기능을 같이 공백 제거 처리해버려 공백칸을 활용해야 함에도 제거하는 현상 발생.
//텝 공백 문자열 동시에 사용하여 분류
// stringInClipboard= stringInClipboard.Replace("\r", "");
if (stringInClipboard == null) return;
List<string>rowsInClipboard = stringInClipboard.Split(rowSpliteter, StringSplitOptions.None).ToList();
rowsInClipboard.RemoveAt(rowsInClipboard.Count-1);
List<string> rowsInClipboard = stringInClipboard.Split(rowSpliteter, StringSplitOptions.None).ToList();
rowsInClipboard.RemoveAt(rowsInClipboard.Count - 1);
//get the row and column of selected cell in dataGridView1
int r = ((DataGridView)sender).SelectedCells[0].RowIndex;
int c = ((DataGridView)sender).SelectedCells[0].ColumnIndex;
@@ -209,7 +216,7 @@ namespace WindowsFormsApp1
private Rectangle dragBoxFromMouseDown;
private int rowIndexFromMouseDown;
private int rowIndexOfItemUnderMouseToDrop;
public void MouseMove(object sender, MouseEventArgs e)
{
DataGridView dataGridView = sender as DataGridView;
@@ -292,7 +299,8 @@ namespace WindowsFormsApp1
string[] db_data = db_res1.Split('|');
string[] db_pur = db_res2.Split('|');
if (db_res1.Length < 3) {
if (db_res1.Length < 3)
{
MessageBox.Show("DB호출 에러!", "Error");
return "False";
}
@@ -300,20 +308,26 @@ namespace WindowsFormsApp1
string fax = string.Empty;
string emchk = string.Empty;
if (db_pur.Length > 3) {
for(int a= 0; a < db_pur.Length; a++)
if (db_pur.Length > 3)
{
for (int a = 0; a < db_pur.Length; a++)
{
if (a % 3 == 0) { // 전화번호
if (db_pur[a] != "") {
if (a % 3 == 0)
{ // 전화번호
if (db_pur[a] != "")
{
tel = db_pur[a];
}
}
if (a % 3 == 1) { // 팩스
if (db_pur[a] != "") {
if (a % 3 == 1)
{ // 팩스
if (db_pur[a] != "")
{
fax = db_pur[a];
}
}
if (a % 3 == 2) { // 팩스 이메일 체크
if (a % 3 == 2)
{ // 팩스 이메일 체크
emchk = db_pur[a];
}
}
@@ -401,7 +415,7 @@ namespace WindowsFormsApp1
#region / (4)
rng = ws.Range["A4", "C4"];
rng.MergeCells = true;
rng.Value2 = "주문일자 : "+DateTime.Now.ToString("yyyy-MM-dd H:m:ss");
rng.Value2 = "주문일자 : " + DateTime.Now.ToString("yyyy-MM-dd H:m:ss");
rng.HorizontalAlignment = Excel.XlHAlign.xlHAlignLeft;
rng.Font.Bold = true;
@@ -495,7 +509,7 @@ namespace WindowsFormsApp1
#region
endcount++;
string = "D"+endcount.ToString();
string = "D" + endcount.ToString();
rng = ws.Range["A" + endcount, "C" + endcount];
rng.MergeCells = true;
@@ -551,7 +565,7 @@ namespace WindowsFormsApp1
rng2.Font.Bold = true;
////////
rng = ws.Range[, "D"+endcount];
rng = ws.Range[, "D" + endcount];
rng.MergeCells = true;
rng.Value2 = "발 송 처";
rng.Font.Bold = true;
@@ -580,10 +594,10 @@ namespace WindowsFormsApp1
application.Interactive = true;
application.Quit();
return FileName;
}
catch(Exception e)
catch (Exception e)
{
MessageBox.Show(e.ToString());
return "False";
@@ -630,9 +644,9 @@ namespace WindowsFormsApp1
private string Excel_Sub(string[] data)
{
string[] length = {
"1", "2", "3", "4", "5",
"6", "7", "8", "9", "10",
"11", "12", "13", "14", "15",
"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"
};
@@ -646,8 +660,8 @@ namespace WindowsFormsApp1
string count = data.Length.ToString();
string res = string.Empty;
for(int a = 0; a < length.Length; a++)
for (int a = 0; a < length.Length; a++)
{
if (length[a] == count)
{
@@ -658,7 +672,7 @@ namespace WindowsFormsApp1
}
#endregion
}
public class Helper_Print
public class Helper_Print
{
/// <summary>
/// 행의 갯수
@@ -788,7 +802,7 @@ namespace WindowsFormsApp1
/// <param name="file_name"></param>
/// <param name="fax_param">[0] 발신번호 / [1] 수신번호
/// / [2] 수신자 회사명 / [3 ]수신자명 </param>
public string Send_BaroFax(string file_name, string[] fax_param )
public string Send_BaroFax(string file_name, string[] fax_param)
{
BaroService_FAXSoapClient fAXSoapClient = new BaroService_FAXSoapClient();
@@ -824,7 +838,7 @@ namespace WindowsFormsApp1
BaroService_FAXSoapClient fAXSoapClient = new BaroService_FAXSoapClient();
// 수신자회사명, 수신번호, 전송일시, 전송결과, 전송페이지수, 성공페이지수, 전송파일명
string[] MsgBox_Array = {
string[] MsgBox_Array = {
fAXSoapClient.GetFaxMessage(CERTKEY, CorpNum, sendkey).ReceiveCorp,
fAXSoapClient.GetFaxMessage(CERTKEY, CorpNum, sendkey).ReceiverNum,
fAXSoapClient.GetFaxMessage(CERTKEY, CorpNum, sendkey).SendDT,
@@ -1190,9 +1204,9 @@ namespace WindowsFormsApp1
public bool IsConnected { get; set; }
private string ipAddr = string.Empty;
private string Port = string.Empty;
private string Port = string.Empty;
private string userId = string.Empty;
private string Pwd = string.Empty;
private string Pwd = string.Empty;
public FTP() { }
@@ -1219,7 +1233,7 @@ namespace WindowsFormsApp1
using (ftpRequest.GetResponse()) { }
this.IsConnected = true;
}
catch(Exception ex)
catch (Exception ex)
{
this.LastException = ex;
System.Reflection.MemberInfo info = System.Reflection.MethodInfo.GetCurrentMethod();
@@ -1288,7 +1302,7 @@ namespace WindowsFormsApp1
buff = null;
}
}
catch(Exception ex)
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
this.LastException = ex;
@@ -1392,14 +1406,14 @@ namespace WindowsFormsApp1
if (reader != null) reader.Close();
foreach(string file in result.ToString().Split('\n'))
foreach (string file in result.ToString().Split('\n'))
{
resultList.Add(file);
}
}
return resultList;
}
catch(Exception ex)
catch (Exception ex)
{
this.LastException = ex;
@@ -1420,12 +1434,12 @@ namespace WindowsFormsApp1
try
{
foreach(string tmpFolder in arrDir)
foreach (string tmpFolder in arrDir)
{
try
{
if (tmpFolder == string.Empty) continue;
currentDir += @"/" + tmpFolder;
string url = string.Format(@"FTP://{0}:{1}/{2}", this.ipAddr, this.Port, currentDir);
@@ -1453,9 +1467,11 @@ namespace WindowsFormsApp1
private void checkDir(string localFullPathFile)
{
FileInfo finfo = new FileInfo(localFullPathFile);
if (!finfo.Exists) {
if (!finfo.Exists)
{
DirectoryInfo dInfo = new DirectoryInfo(finfo.DirectoryName);
if (!dInfo.Exists) {
if (!dInfo.Exists)
{
dInfo.Create();
}
}
@@ -1552,30 +1568,35 @@ namespace WindowsFormsApp1
int tDown = 0;
for (int a = 0; a < array_text.Count; a++)
{
// if (array_text[a] == "") continue;
// if (array_text[a] == "") continue;
num.Add(array_text[a].Substring(0, 3));
if (array_text[a][5] == '▼') {
if (array_text[a][5] == '▼')
{
array_text[a] = array_text[a].Remove(0, 3);
}
else {
else
{
array_text[a] = array_text[a].Remove(0, 5);
}
+= array_text[a] + "\n";
int textLength = 0;
if (EncodingType == "UTF-8") {
if (EncodingType == "UTF-8")
{
textLength = Encoding.UTF8.GetBytes(array_text[a]).Length
- WordCheck(array_text[a], "▲")
- WordCheck(array_text[a], "▼");
}
else if (EncodingType == "UniCode") {
else if (EncodingType == "UniCode")
{
textLength = Encoding.Unicode.GetBytes(array_text[a]).Length
- WordCheck(array_text[a], "▲")
- WordCheck(array_text[a], "▼");
}
else { // ANSI
else
{ // ANSI
textLength = Encoding.Default.GetBytes(array_text[a]).Length
- WordCheck(array_text[a], "▲")
- WordCheck(array_text[a], "▼");
@@ -1587,12 +1608,13 @@ namespace WindowsFormsApp1
for (int a = 0; a < array_text.Count; a++)
{
if (a == 0) { //total.Add("0");
if (a == 0)
{ //total.Add("0");
tTotal.Add(0);
}
else
{
// total.Add(total[a - 1] + count[a - 1]);
// total.Add(total[a - 1] + count[a - 1]);
tTotal.Add(tTotal[a - 1] + tCount[a - 1]);
}
//else if (a == 1)
@@ -1609,7 +1631,7 @@ namespace WindowsFormsApp1
// else c = Convert.ToInt32(Encoding.Default.GetBytes(array_text[a - 2]).Length.ToString()) - WordCheck(array_text[a - 2], "▲") - WordCheck(array_text[a - 2], "▼");
// int res = b + c;
// total.Add(res.ToString());
}
string[] str_num = num.ToArray();
@@ -1626,7 +1648,7 @@ namespace WindowsFormsApp1
// else if (total[a].Length == 2) { total[a] = total[a].Insert(0, "000"); }
// else if (total[a].Length == 1) { total[a] = total[a].Insert(0, "0000"); }
// 디렉토리 += str_num[a] + count[a] + total[a] + "\n";
+= str_num[a] + tCount[a].ToString().PadLeft(4, '0') + tTotal[a].ToString().PadLeft(5, '0');
+= str_num[a] + tCount[a].ToString().PadLeft(4, '0') + tTotal[a].ToString().PadLeft(5, '0');
}
string[] = { "00000","n", "a", "m", " ",
@@ -1642,11 +1664,11 @@ namespace WindowsFormsApp1
string dp = + ;
int recode = 0;
if (EncodingType == "UTF-8") recode = Encoding.UTF8.GetBytes(dp).Length- WordCheck(dp, "▲") - WordCheck(dp, "▼") - WordCheck(dp, "↔");
if (EncodingType == "UTF-8") recode = Encoding.UTF8.GetBytes(dp).Length - WordCheck(dp, "▲") - WordCheck(dp, "▼") - WordCheck(dp, "↔");
else if (EncodingType == "UniCode") recode = Encoding.Unicode.GetBytes(dp).Length - WordCheck(dp, "▲") - WordCheck(dp, "▼") - WordCheck(dp, "↔");
else recode = Encoding.Default.GetBytes(dp).Length- WordCheck(dp, "▲") - WordCheck(dp, "▼") - WordCheck(dp, "↔");
else recode = Encoding.Default.GetBytes(dp).Length - WordCheck(dp, "▲") - WordCheck(dp, "▼") - WordCheck(dp, "↔");
[0] = insert_Zero(recode + 24, 5);
int data_addr = 24 + Encoding.Default.GetBytes().Length - WordCheck(, "▲");
@@ -1693,7 +1715,7 @@ namespace WindowsFormsApp1
return result;
}
#endregion
/// <summary>
/// 추가하고 싶은 태그를 뷰형태의 마크에 추가하는 함수.
@@ -1706,7 +1728,7 @@ namespace WindowsFormsApp1
if (Tag.Length < 3) return "";
int TargetTagNum = Convert.ToInt32(Tag.Substring(0, 3));
string[] SplitView = TypeView.Split('\n');
List<string> View = new List<string>(SplitView);
@@ -1731,9 +1753,9 @@ namespace WindowsFormsApp1
/// <param name="pAddTag">추가할 태그 (태그명\t지시기호\t태그내용)</param>
/// <param name="pTargetData">뷰형태의 마크</param>
/// <returns></returns>
public string AddTagInMarc(int pTargetTagNum,string pAddTag, string pTargetData)//TagTarget Num 을 찾아서 있을경우는 해당 Tag 데이터를 전송, 없을경우는 신규로 해야함.
public string AddTagInMarc(int pTargetTagNum, string pAddTag, string pTargetData)//TagTarget Num 을 찾아서 있을경우는 해당 Tag 데이터를 전송, 없을경우는 신규로 해야함.
{
if (pAddTag.Length < 3) return "";
string tRet = pTargetData;
// ex ) 020 : ~~~ 에 XXXX 내용줄 뒤에 추가
@@ -1827,7 +1849,7 @@ namespace WindowsFormsApp1
}
}
}
}
}
else
{// 기존 태그 변경
int endIdx = SplitView[a].IndexOf("▼", startIdx + 1);
@@ -1906,7 +1928,7 @@ namespace WindowsFormsApp1
/// <param name="marc">마크 데이터</param>
/// <param name="search">추출할 함수(배열)</param>
/// <returns></returns>
public string[] Take_Tag(string marc, string[] search,bool pSearchTag = false)
public string[] Take_Tag(string marc, string[] search, bool pSearchTag = false)
{
string[] ary = marc.Split('');
string[] tag = res_dir(ary[0].Substring(24));
@@ -1958,7 +1980,7 @@ namespace WindowsFormsApp1
//memo = result[b];
start += 2;
int end = -1;
if (tmp.Length > 1) end=tmp.IndexOf("", start);
if (tmp.Length > 1) end = tmp.IndexOf("", start);
if (memo == result[b])
break;
@@ -2188,7 +2210,8 @@ namespace WindowsFormsApp1
/// <param name="e">EventArgs</param>
public void Int_Comma(object sender, EventArgs e)
{
if (((TextBox)sender).Text != "") {
if (((TextBox)sender).Text != "")
{
string text;
text = ((TextBox)sender).Text.Replace(",", "");
((TextBox)sender).Text = String.Format("{0:#,###}", Convert.ToInt32(text));
@@ -2204,7 +2227,7 @@ namespace WindowsFormsApp1
public bool isContainHangul(string value)
{
char[] charArr = value.ToCharArray();
foreach(char c in charArr)
foreach (char c in charArr)
{
if (char.GetUnicodeCategory(c) == System.Globalization.UnicodeCategory.OtherLetter)
return true;
@@ -2221,7 +2244,7 @@ namespace WindowsFormsApp1
public bool CheckString(string value, string chkString)
{
int index = value.IndexOf(chkString);
if (index < 0)
if (index < 0)
return false;
return true;
@@ -2264,10 +2287,10 @@ namespace WindowsFormsApp1
public class API
{
public string CheckString(string pText,string pStr)
public string CheckString(string pText, string pStr)
{
string tRet = pText;
Regex reg = new Regex(@"([\"+pStr+"]+)" + @"[가-힣]+");//+ @"([\>]+)");//new Regex(@"([\<]+)"+ @"[ㄱ-ㅎ가-힣]+"+@"([\>]+)");
Regex reg = new Regex(@"([\" + pStr + "]+)" + @"[가-힣]+");//+ @"([\>]+)");//new Regex(@"([\<]+)"+ @"[ㄱ-ㅎ가-힣]+"+@"([\>]+)");
MatchCollection tMatch = reg.Matches(tRet);
for (int i = 0; i < tMatch.Count; i++)
{
@@ -2326,8 +2349,9 @@ namespace WindowsFormsApp1
xml = CheckString(xml, "〈");
doc.LoadXml(xml);
}
catch (Exception ex){
return "";
catch (Exception ex)
{
return "";
}
var json = JsonConvert.SerializeXmlNode(doc);
@@ -2520,19 +2544,23 @@ namespace WindowsFormsApp1
{
if (length == 1)
{
try {
try
{
tmp_data.Add(docs[Param[b]]["#text"]);
}
catch (KeyNotFoundException e) {
catch (KeyNotFoundException e)
{
tmp_data.Add("");
}
}
else
{
try {
try
{
tmp_data.Add(docs[a][Param[b]]["#text"]);
}
catch (KeyNotFoundException e) {
catch (KeyNotFoundException e)
{
tmp_data.Add("");
}
}
@@ -2649,8 +2677,8 @@ namespace WindowsFormsApp1
return dialogResult;
}
}
public class PrintLine
{
public class PrintLine
{
string num { get; set; }
string count { get; set; }
string list_name { get; set; }
@@ -2963,7 +2991,7 @@ namespace WindowsFormsApp1
{
string version = "0";
var fn = Application.StartupPath + "\\update.inf";
if(System.IO.File.Exists(fn))
if (System.IO.File.Exists(fn))
{
StreamReader sr = new StreamReader(fn);
while (!sr.EndOfStream)
@@ -2976,7 +3004,7 @@ namespace WindowsFormsApp1
}
}
}
return version;
}
}

View File

@@ -224,6 +224,7 @@
<DesignTime>True</DesignTime>
<DependentUpon>Reference.svcmap</DependentUpon>
</Compile>
<Compile Include="Helper_LibraryDelaySettings.cs" />
<Compile Include="PUB.cs" />
<Compile Include="SearchModel\AnsanLibSearcher.cs" />
<Compile Include="SearchModel\BookSearchService.cs" />

View File

@@ -28,7 +28,8 @@
/// </summary>
private void InitializeComponent()
{
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle2 = new System.Windows.Forms.DataGridViewCellStyle();
this.components = new System.ComponentModel.Container();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle();
this.panel1 = new System.Windows.Forms.Panel();
this.chkShowBrowser = new System.Windows.Forms.CheckBox();
this.chkRetryErrData = new System.Windows.Forms.CheckBox();
@@ -50,10 +51,9 @@
this.lbl_PW = new System.Windows.Forms.Label();
this.lbl_ID = new System.Windows.Forms.Label();
this.dv1 = new System.Windows.Forms.DataGridView();
this.book_name = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.book_comp = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.Count = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.dvc_remark = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.contextMenuStrip1 = new System.Windows.Forms.ContextMenuStrip(this.components);
this.ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.btn_ApplyFilter = new System.Windows.Forms.Button();
this.panel3 = new System.Windows.Forms.Panel();
this.panel6 = new System.Windows.Forms.Panel();
@@ -66,19 +66,30 @@
this.btn_GridReset = new System.Windows.Forms.Button();
this.btn_OpenMemo = new System.Windows.Forms.Button();
this.chk_spChar = new System.Windows.Forms.CheckBox();
this.book_name = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.book_comp = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.Count = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.dvc_remark = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.dvc_resulthtml = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.nudAddDelay = new System.Windows.Forms.NumericUpDown();
this.label1 = new System.Windows.Forms.Label();
this.panel1.SuspendLayout();
this.groupBox1.SuspendLayout();
this.panel2.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.dv1)).BeginInit();
this.contextMenuStrip1.SuspendLayout();
this.panel3.SuspendLayout();
this.panel6.SuspendLayout();
this.statusStrip1.SuspendLayout();
this.panel4.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.nudAddDelay)).BeginInit();
this.SuspendLayout();
//
// panel1
//
this.panel1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.panel1.Controls.Add(this.label1);
this.panel1.Controls.Add(this.nudAddDelay);
this.panel1.Controls.Add(this.chkShowBrowser);
this.panel1.Controls.Add(this.chkRetryErrData);
this.panel1.Controls.Add(this.groupBox1);
@@ -90,7 +101,7 @@
this.panel1.Dock = System.Windows.Forms.DockStyle.Top;
this.panel1.Location = new System.Drawing.Point(0, 34);
this.panel1.Name = "panel1";
this.panel1.Size = new System.Drawing.Size(637, 106);
this.panel1.Size = new System.Drawing.Size(719, 106);
this.panel1.TabIndex = 0;
//
// chkShowBrowser
@@ -98,7 +109,7 @@
this.chkShowBrowser.AutoSize = true;
this.chkShowBrowser.Checked = true;
this.chkShowBrowser.CheckState = System.Windows.Forms.CheckState.Checked;
this.chkShowBrowser.Location = new System.Drawing.Point(515, 81);
this.chkShowBrowser.Location = new System.Drawing.Point(618, 50);
this.chkShowBrowser.Name = "chkShowBrowser";
this.chkShowBrowser.Size = new System.Drawing.Size(96, 16);
this.chkShowBrowser.TabIndex = 8;
@@ -125,7 +136,7 @@
this.groupBox1.Controls.Add(this.radTargetAll);
this.groupBox1.Location = new System.Drawing.Point(318, 34);
this.groupBox1.Name = "groupBox1";
this.groupBox1.Size = new System.Drawing.Size(304, 39);
this.groupBox1.Size = new System.Drawing.Size(294, 39);
this.groupBox1.TabIndex = 6;
this.groupBox1.TabStop = false;
this.groupBox1.Text = "검색대상";
@@ -133,7 +144,7 @@
// radTargetErr
//
this.radTargetErr.AutoSize = true;
this.radTargetErr.Location = new System.Drawing.Point(197, 16);
this.radTargetErr.Location = new System.Drawing.Point(239, 16);
this.radTargetErr.Name = "radTargetErr";
this.radTargetErr.Size = new System.Drawing.Size(47, 16);
this.radTargetErr.TabIndex = 9;
@@ -143,7 +154,7 @@
// radTargetEmpty
//
this.radTargetEmpty.AutoSize = true;
this.radTargetEmpty.Location = new System.Drawing.Point(144, 16);
this.radTargetEmpty.Location = new System.Drawing.Point(172, 16);
this.radTargetEmpty.Name = "radTargetEmpty";
this.radTargetEmpty.Size = new System.Drawing.Size(47, 16);
this.radTargetEmpty.TabIndex = 8;
@@ -153,7 +164,7 @@
// radTargetErrEmpty
//
this.radTargetErrEmpty.AutoSize = true;
this.radTargetErrEmpty.Location = new System.Drawing.Point(61, 17);
this.radTargetErrEmpty.Location = new System.Drawing.Point(75, 16);
this.radTargetErrEmpty.Name = "radTargetErrEmpty";
this.radTargetErrEmpty.Size = new System.Drawing.Size(77, 16);
this.radTargetErrEmpty.TabIndex = 7;
@@ -209,7 +220,7 @@
//
this.btn_Start.Location = new System.Drawing.Point(470, 4);
this.btn_Start.Name = "btn_Start";
this.btn_Start.Size = new System.Drawing.Size(75, 24);
this.btn_Start.Size = new System.Drawing.Size(153, 24);
this.btn_Start.TabIndex = 2;
this.btn_Start.Text = "검색시작";
this.btn_Start.UseVisualStyleBackColor = true;
@@ -217,7 +228,7 @@
//
// btn_Stop
//
this.btn_Stop.Location = new System.Drawing.Point(548, 4);
this.btn_Stop.Location = new System.Drawing.Point(631, 4);
this.btn_Stop.Name = "btn_Stop";
this.btn_Stop.Size = new System.Drawing.Size(75, 24);
this.btn_Stop.TabIndex = 2;
@@ -227,7 +238,7 @@
//
// btn_Close
//
this.btn_Close.Location = new System.Drawing.Point(548, 4);
this.btn_Close.Location = new System.Drawing.Point(631, 4);
this.btn_Close.Name = "btn_Close";
this.btn_Close.Size = new System.Drawing.Size(75, 24);
this.btn_Close.TabIndex = 2;
@@ -270,14 +281,14 @@
this.panel2.Dock = System.Windows.Forms.DockStyle.Top;
this.panel2.Location = new System.Drawing.Point(0, 0);
this.panel2.Name = "panel2";
this.panel2.Size = new System.Drawing.Size(637, 34);
this.panel2.Size = new System.Drawing.Size(719, 34);
this.panel2.TabIndex = 0;
//
// btn_SiteDenote
//
this.btn_SiteDenote.Location = new System.Drawing.Point(462, 5);
this.btn_SiteDenote.Location = new System.Drawing.Point(470, 5);
this.btn_SiteDenote.Name = "btn_SiteDenote";
this.btn_SiteDenote.Size = new System.Drawing.Size(77, 23);
this.btn_SiteDenote.Size = new System.Drawing.Size(155, 23);
this.btn_SiteDenote.TabIndex = 4;
this.btn_SiteDenote.Text = "사이트 표출";
this.btn_SiteDenote.UseVisualStyleBackColor = true;
@@ -307,55 +318,52 @@
this.dv1.BackgroundColor = System.Drawing.SystemColors.Control;
this.dv1.BorderStyle = System.Windows.Forms.BorderStyle.None;
this.dv1.ColumnHeadersBorderStyle = System.Windows.Forms.DataGridViewHeaderBorderStyle.Single;
dataGridViewCellStyle2.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter;
dataGridViewCellStyle2.BackColor = System.Drawing.SystemColors.Control;
dataGridViewCellStyle2.Font = new System.Drawing.Font("굴림", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
dataGridViewCellStyle2.ForeColor = System.Drawing.SystemColors.WindowText;
dataGridViewCellStyle2.SelectionBackColor = System.Drawing.SystemColors.Highlight;
dataGridViewCellStyle2.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
dataGridViewCellStyle2.WrapMode = System.Windows.Forms.DataGridViewTriState.True;
this.dv1.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle2;
dataGridViewCellStyle1.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter;
dataGridViewCellStyle1.BackColor = System.Drawing.SystemColors.Control;
dataGridViewCellStyle1.Font = new System.Drawing.Font("굴림", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
dataGridViewCellStyle1.ForeColor = System.Drawing.SystemColors.WindowText;
dataGridViewCellStyle1.SelectionBackColor = System.Drawing.SystemColors.Highlight;
dataGridViewCellStyle1.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
dataGridViewCellStyle1.WrapMode = System.Windows.Forms.DataGridViewTriState.True;
this.dv1.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle1;
this.dv1.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.dv1.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
this.book_name,
this.book_comp,
this.Count,
this.dvc_remark});
this.dvc_remark,
this.dvc_resulthtml});
this.dv1.ContextMenuStrip = this.contextMenuStrip1;
this.dv1.Dock = System.Windows.Forms.DockStyle.Fill;
this.dv1.Location = new System.Drawing.Point(0, 0);
this.dv1.Name = "dv1";
this.dv1.RowTemplate.Height = 23;
this.dv1.Size = new System.Drawing.Size(637, 542);
this.dv1.Size = new System.Drawing.Size(719, 542);
this.dv1.TabIndex = 1;
this.dv1.RowPostPaint += new System.Windows.Forms.DataGridViewRowPostPaintEventHandler(this.dataGridView1_RowPostPaint);
this.dv1.KeyDown += new System.Windows.Forms.KeyEventHandler(this.dataGridView1_KeyDown);
//
// book_name
// contextMenuStrip1
//
this.book_name.HeaderText = "도서명(총서명)";
this.book_name.Name = "book_name";
this.book_name.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable;
this.book_name.Width = 300;
this.contextMenuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.ToolStripMenuItem,
this.ToolStripMenuItem});
this.contextMenuStrip1.Name = "contextMenuStrip1";
this.contextMenuStrip1.Size = new System.Drawing.Size(215, 48);
//
// book_comp
// 유니코드문자로붙여넝ㅎ기ToolStripMenuItem
//
this.book_comp.HeaderText = "출판사";
this.book_comp.Name = "book_comp";
this.book_comp.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable;
this.book_comp.Width = 120;
this.ToolStripMenuItem.Name = "유니코드문자로붙여넝ㅎ기ToolStripMenuItem";
this.ToolStripMenuItem.Size = new System.Drawing.Size(214, 22);
this.ToolStripMenuItem.Text = "유니코드 문자로 붙여넣기";
this.ToolStripMenuItem.Click += new System.EventHandler(this.ToolStripMenuItem_Click);
//
// Count
// 일반문자로붙여넣기ToolStripMenuItem
//
this.Count.HeaderText = "검색 수";
this.Count.Name = "Count";
this.Count.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable;
this.Count.Width = 50;
//
// dvc_remark
//
this.dvc_remark.HeaderText = "비고";
this.dvc_remark.Name = "dvc_remark";
this.dvc_remark.Width = 110;
this.ToolStripMenuItem.Name = "일반문자로붙여넣기ToolStripMenuItem";
this.ToolStripMenuItem.Size = new System.Drawing.Size(214, 22);
this.ToolStripMenuItem.Text = "일반 문자로 붙여넣기";
this.ToolStripMenuItem.Click += new System.EventHandler(this.ToolStripMenuItem_Click);
//
// btn_ApplyFilter
//
@@ -376,7 +384,7 @@
this.panel3.Dock = System.Windows.Forms.DockStyle.Fill;
this.panel3.Location = new System.Drawing.Point(0, 0);
this.panel3.Name = "panel3";
this.panel3.Size = new System.Drawing.Size(637, 738);
this.panel3.Size = new System.Drawing.Size(719, 738);
this.panel3.TabIndex = 3;
//
// panel6
@@ -386,7 +394,7 @@
this.panel6.Dock = System.Windows.Forms.DockStyle.Fill;
this.panel6.Location = new System.Drawing.Point(0, 174);
this.panel6.Name = "panel6";
this.panel6.Size = new System.Drawing.Size(637, 564);
this.panel6.Size = new System.Drawing.Size(719, 564);
this.panel6.TabIndex = 3;
//
// statusStrip1
@@ -396,7 +404,7 @@
this.lbSite});
this.statusStrip1.Location = new System.Drawing.Point(0, 542);
this.statusStrip1.Name = "statusStrip1";
this.statusStrip1.Size = new System.Drawing.Size(637, 22);
this.statusStrip1.Size = new System.Drawing.Size(719, 22);
this.statusStrip1.TabIndex = 2;
this.statusStrip1.Text = "statusStrip1";
//
@@ -424,7 +432,7 @@
this.panel4.Dock = System.Windows.Forms.DockStyle.Top;
this.panel4.Location = new System.Drawing.Point(0, 140);
this.panel4.Name = "panel4";
this.panel4.Size = new System.Drawing.Size(637, 34);
this.panel4.Size = new System.Drawing.Size(719, 34);
this.panel4.TabIndex = 2;
//
// chk_RemoveBrit
@@ -477,11 +485,60 @@
this.chk_spChar.Text = "특수문자 제거";
this.chk_spChar.UseVisualStyleBackColor = true;
//
// book_name
//
this.book_name.HeaderText = "도서명(총서명)";
this.book_name.Name = "book_name";
this.book_name.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable;
this.book_name.Width = 300;
//
// book_comp
//
this.book_comp.HeaderText = "출판사";
this.book_comp.Name = "book_comp";
this.book_comp.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable;
this.book_comp.Width = 120;
//
// Count
//
this.Count.HeaderText = "검색 수";
this.Count.Name = "Count";
this.Count.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable;
this.Count.Width = 50;
//
// dvc_remark
//
this.dvc_remark.HeaderText = "비고";
this.dvc_remark.Name = "dvc_remark";
this.dvc_remark.Width = 110;
//
// dvc_resulthtml
//
this.dvc_resulthtml.HeaderText = "결과";
this.dvc_resulthtml.Name = "dvc_resulthtml";
//
// nudAddDelay
//
this.nudAddDelay.Location = new System.Drawing.Point(618, 79);
this.nudAddDelay.Name = "nudAddDelay";
this.nudAddDelay.Size = new System.Drawing.Size(56, 21);
this.nudAddDelay.TabIndex = 9;
this.nudAddDelay.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(559, 83);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(53, 12);
this.label1.TabIndex = 10;
this.label1.Text = "추가지연";
//
// Check_copyWD
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 12F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(637, 738);
this.ClientSize = new System.Drawing.Size(719, 738);
this.Controls.Add(this.panel3);
this.Name = "Check_copyWD";
this.Text = "복본조사(New)";
@@ -494,6 +551,7 @@
this.panel2.ResumeLayout(false);
this.panel2.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.dv1)).EndInit();
this.contextMenuStrip1.ResumeLayout(false);
this.panel3.ResumeLayout(false);
this.panel6.ResumeLayout(false);
this.panel6.PerformLayout();
@@ -501,6 +559,7 @@
this.statusStrip1.PerformLayout();
this.panel4.ResumeLayout(false);
this.panel4.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.nudAddDelay)).EndInit();
this.ResumeLayout(false);
}
@@ -536,13 +595,19 @@
private System.Windows.Forms.RadioButton radTargetErr;
private System.Windows.Forms.RadioButton radTargetEmpty;
private System.Windows.Forms.RadioButton radTargetErrEmpty;
private System.Windows.Forms.DataGridViewTextBoxColumn book_name;
private System.Windows.Forms.DataGridViewTextBoxColumn book_comp;
private System.Windows.Forms.DataGridViewTextBoxColumn Count;
private System.Windows.Forms.DataGridViewTextBoxColumn dvc_remark;
public System.Windows.Forms.CheckBox chkRetryErrData;
public System.Windows.Forms.GroupBox groupBox1;
public System.Windows.Forms.CheckBox chkShowBrowser;
private System.Windows.Forms.ToolStripStatusLabel lbSite;
private System.Windows.Forms.ContextMenuStrip contextMenuStrip1;
private System.Windows.Forms.ToolStripMenuItem ToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem ToolStripMenuItem;
private System.Windows.Forms.DataGridViewTextBoxColumn book_name;
private System.Windows.Forms.DataGridViewTextBoxColumn book_comp;
private System.Windows.Forms.DataGridViewTextBoxColumn Count;
private System.Windows.Forms.DataGridViewTextBoxColumn dvc_remark;
private System.Windows.Forms.DataGridViewTextBoxColumn dvc_resulthtml;
private System.Windows.Forms.NumericUpDown nudAddDelay;
private System.Windows.Forms.Label label1;
}
}

View File

@@ -12,6 +12,7 @@ using UniMarc.SearchModel;
using System.Linq;
using UniMarc;
using System.Globalization;
using System.Threading.Tasks;
namespace WindowsFormsApp1.Mac
{
@@ -39,6 +40,9 @@ namespace WindowsFormsApp1.Mac
main = _main;
db.DBcon();
// 도서관별 지연시간 설정 초기화
_delaySettings = new Helper_LibraryDelaySettings();
var idx = 1;
//도서검색(크롤링)
_searchService = new BookSearchService();
@@ -450,6 +454,7 @@ namespace WindowsFormsApp1.Mac
private readonly BookSearchService _searchService;
private bool _isSearching = false;
private readonly Helper_LibraryDelaySettings _delaySettings;
private async void InitializeChromeDriver()
{
@@ -612,7 +617,10 @@ namespace WindowsFormsApp1.Mac
int cnt_ok = 0;
int cnt_ng = 0;
int cnt_er = 0;
int addDelay = (int)nudAddDelay.Value * 1000; //추가 지연시간 : 1개의 레코드 처리 후 추가 지연한다.
// 현재 지연시간을 도서관별로 저장
SaveLibraryDelaySettings(searcher.SiteName, (int)nudAddDelay.Value);
RetrySearch:
foreach (DataGridViewRow drow in this.dv1.Rows)
@@ -620,6 +628,10 @@ namespace WindowsFormsApp1.Mac
if (this._isSearching == false)
{
drow.Cells["dvc_remark"].Value = "Cancel";
if (dv1.Columns.Contains("dvc_resulthtml"))
{
drow.Cells["dvc_resulthtml"].Value = "";
}
break;
}
@@ -630,6 +642,10 @@ namespace WindowsFormsApp1.Mac
{
cnt_skip += 1;
drow.Cells["dvc_remark"].Value = "No Title";
if (dv1.Columns.Contains("dvc_resulthtml"))
{
drow.Cells["dvc_resulthtml"].Value = "";
}
drow.DefaultCellStyle.BackColor = Color.HotPink;
drow.DefaultCellStyle.ForeColor = Color.Blue;
continue;
@@ -660,6 +676,13 @@ namespace WindowsFormsApp1.Mac
{
drow.Cells["dvc_remark"].Value = rlt.ErrorMessage;
drow.Cells["Count"].Value = $"{rlt.BookCount}";
// ResultHtml을 dvc_resulthtml 컬럼에 저장
if (dv1.Columns.Contains("dvc_resulthtml"))
{
drow.Cells["dvc_resulthtml"].Value = rlt.Resulthtml ?? "";
}
if (rlt.BookCount == 0)
{
cnt_ng += 1;
@@ -677,9 +700,18 @@ namespace WindowsFormsApp1.Mac
{
cnt_er += 1;
drow.Cells["dvc_remark"].Value = $"Error|{rlt.ErrorMessage}";
// 오류시에도 ResultHtml이 있으면 저장 (디버깅용)
if (dv1.Columns.Contains("dvc_resulthtml"))
{
drow.Cells["dvc_resulthtml"].Value = rlt.Resulthtml ?? "";
}
drow.DefaultCellStyle.BackColor = Color.HotPink;
drow.DefaultCellStyle.ForeColor = Color.Blue;
}
if(addDelay > 0)
await Task.Delay(addDelay);
}
if (_isSearching == true && retry == false && chkRetryErrData.Checked)
@@ -861,6 +893,12 @@ namespace WindowsFormsApp1.Mac
dv1.Rows[a].DefaultCellStyle.BackColor = Color.White;
dv1.Rows[a].Cells["Count"].Value = "";
dv1.Rows[a].Cells["dvc_remark"].Value = "";
// dvc_resulthtml 컬럼도 초기화
if (dv1.Columns.Contains("dvc_resulthtml"))
{
dv1.Rows[a].Cells["dvc_resulthtml"].Value = "";
}
}
}
@@ -912,6 +950,67 @@ namespace WindowsFormsApp1.Mac
}
chkShowBrowser.Enabled = !searcher.HttpApiMode;
this.lbSite.Text = $"[{searcher.AreaCode}] {searcher.SiteUrl}";
// 저장된 지연시간 불러오기
LoadLibraryDelaySettings(searcher.SiteName);
}
/// <summary>
/// 선택된 도서관의 저장된 지연시간을 불러와서 UI에 설정
/// </summary>
/// <param name="libraryName">도서관 이름</param>
private void LoadLibraryDelaySettings(string libraryName)
{
try
{
var savedDelay = _delaySettings.GetDelay(libraryName);
if (savedDelay > 0)
{
nudAddDelay.Value = savedDelay;
Console.WriteLine($"도서관 지연시간 불러옴: {libraryName} = {savedDelay}초");
}
else
{
// 저장된 설정이 없으면 기본값 유지
Console.WriteLine($"도서관 지연시간 설정 없음, 기본값 사용: {libraryName}");
}
}
catch (Exception ex)
{
Console.WriteLine($"도서관 지연시간 불러오기 오류: {ex.Message}");
}
}
/// <summary>
/// 검색 시작시 현재 지연시간을 저장
/// </summary>
/// <param name="libraryName">도서관 이름</param>
/// <param name="delaySeconds">지연시간(초)</param>
private void SaveLibraryDelaySettings(string libraryName, int delaySeconds)
{
try
{
_delaySettings.SetDelay(libraryName, delaySeconds);
Console.WriteLine($"도서관 지연시간 저장됨: {libraryName} = {delaySeconds}초");
}
catch (Exception ex)
{
Console.WriteLine($"도서관 지연시간 저장 오류: {ex.Message}");
}
}
private void ToolStripMenuItem_Click(object sender, EventArgs e)
{
Skill_Grid sg = new Skill_Grid();
var newkey = new KeyEventArgs(Keys.Control | Keys.V | Keys.Alt );
sg.Excel_to_DataGridView(this.dv1, newkey);
}
private void ToolStripMenuItem_Click(object sender, EventArgs e)
{
Skill_Grid sg = new Skill_Grid();
var newkey = new KeyEventArgs(Keys.Control | Keys.V);
sg.Excel_to_DataGridView(this.dv1, newkey);
}
}
}

View File

@@ -129,8 +129,11 @@
<metadata name="dvc_remark.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="statusStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
<metadata name="dvc_resulthtml.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="contextMenuStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>134, 17</value>
</metadata>
<metadata name="statusStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>