검색결과 없음 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 - **데이터베이스**: MySQL
- **주요기능**: 마크 작성, 복본조사, DLS 연동, 도서 정보 관리 - **주요기능**: 마크 작성, 복본조사, DLS 연동, 도서 정보 관리
## 데이터베이스 정보
- Server=1.215.250.130;Port=3306;Database=unimarc;uid=root;pwd=Admin21234;
## 코딩 컨벤션 ## 코딩 컨벤션
- 파일명: PascalCase (예: DLS_Copy.cs) - 파일명: PascalCase (예: DLS_Copy.cs)
- 클래스명: PascalCase - 클래스명: PascalCase

View File

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

View File

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

View File

@@ -397,8 +397,10 @@ namespace BokBonCheck
// 페이지 변경을 감지하는 메서드 // 페이지 변경을 감지하는 메서드
await WaitForPageChange(new WebDriverWait(_driver, TimeSpan.FromSeconds(15))); await WaitForPageChange(new WebDriverWait(_driver, TimeSpan.FromSeconds(15)));
// 검색 결과 수 추출 // SearchAsync에서 PageSource 가져오기
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) if (resultCount == -1)
{ {
result.BookCount = 0; result.BookCount = 0;
@@ -423,127 +425,39 @@ namespace BokBonCheck
return result; 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; errmessage = string.Empty;
resulthtml = string.Empty;
if (driver.Url.EndsWith("DetailSearchResult") == false)
{
errmessage = "결과페이지가아님";
return -1;
}
try 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 = "검색결과없음"; errmessage = "검색결과없음";
return 0; var match = System.Text.RegularExpressions.Regex.Match(htmlContent, pattern);
} if (match.Success)
}
catch
{ {
// 검색결과가 있는 경우로 진행 resulthtml = ExtractResultContext(htmlContent, match);
}
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))
{
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;
} }
else else
{ {
Console.WriteLine($"정규식 매칭 실패: '{textToUse}'"); resulthtml = htmlContent.Length > 500 ? htmlContent.Substring(0, 500) : htmlContent;
} }
}
}
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; 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에서 다양한 패턴 찾기 (안산시 도서관 특화) // HTML에서 다양한 패턴 찾기 (안산시 도서관 특화)
@@ -560,11 +474,14 @@ namespace BokBonCheck
foreach (var pattern in htmlPatterns) 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 (match.Success)
{ {
if (int.TryParse(match.Groups[1].Value, out int count)) if (int.TryParse(match.Groups[1].Value, out int count))
{ {
// 매칭된 부분과 상위 태그 추출하여 resulthtml에 저장
resulthtml = ExtractResultContext(htmlContent, match);
if (count == 0) if (count == 0)
{ {
errmessage = "검색결과없음"; errmessage = "검색결과없음";
@@ -576,28 +493,81 @@ namespace BokBonCheck
} }
} }
if (retry == false)
{
Console.WriteLine( "결과를 찾을 수 없어 재시도");
retry = true;
Task.Delay(1000);
goto RetryPoint;
}
else
{
errmessage = "결과수량을찾을수없음"; errmessage = "결과수량을찾을수없음";
resulthtml = htmlContent.Length > 500 ? htmlContent.Substring(0, 500) : htmlContent;
return -1; return -1;
}
} }
catch (Exception ex) catch (Exception ex)
{ {
errmessage = ex.Message; errmessage = ex.Message;
resulthtml = htmlContent.Length > 500 ? htmlContent.Substring(0, 500) : htmlContent;
return -1; 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) public async Task WaitForPageChange(WebDriverWait wait)
{ {

View File

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

View File

@@ -244,8 +244,9 @@ namespace BokBonCheck
// 페이지 변경을 감지하는 메서드 // 페이지 변경을 감지하는 메서드
await WaitForPageChange(new WebDriverWait(_driver, TimeSpan.FromSeconds(15))); await WaitForPageChange(new WebDriverWait(_driver, TimeSpan.FromSeconds(15)));
// 검색 결과 수 추출 var htmlContent = _driver.PageSource;
var resultCount = ExtractBookCount(_driver, searchTerm, out string ermsg); var resultCount = ExtractBookCount(htmlContent, out string ermsg, out string resultHtml);
result.Resulthtml = resultHtml;
if (resultCount == -1) if (resultCount == -1)
{ {
result.BookCount = 0; result.BookCount = 0;
@@ -270,72 +271,38 @@ namespace BokBonCheck
return result; 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; errmessage = string.Empty;
resulthtml = string.Empty;
try try
{ {
// 1. totalCount 요소에서 직접 추출 시도 // 검색 결과가 없다는 메시지 확인
try var noResultPatterns = new[]
{ {
var totalCountElement = driver.FindElement(By.CssSelector("p.totalCount")); @"검색결과가 없습니다",
if (totalCountElement != null) @"검색된 자료가 없습니다",
@"자료가 없습니다",
@"전체 0 건"
};
foreach (var pattern in noResultPatterns)
{ {
// strong 태그에서 직접 숫자 추출 시도 if (htmlContent.Contains(pattern))
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 = "검색결과없음"; errmessage = "검색결과없음";
return 0; var match = System.Text.RegularExpressions.Regex.Match(htmlContent, pattern);
}
errmessage = $"검색성공({strongCount}권)";
return strongCount;
}
}
}
catch { }
// 전체 텍스트에서 정규식으로 추출
var totalCountText = totalCountElement.Text;
var match = Regex.Match(totalCountText, @"전체\s*(\d+)\s*건", RegexOptions.IgnoreCase);
if (match.Success) if (match.Success)
{ {
if (int.TryParse(match.Groups[1].Value, out int count)) resulthtml = ExtractResultContext(htmlContent, match);
}
else
{ {
if (count == 0) resulthtml = htmlContent.Length > 500 ? htmlContent.Substring(0, 500) : htmlContent;
{ }
errmessage = "검색결과없음";
return 0; return 0;
} }
errmessage = $"검색성공({count}권)";
return count;
}
}
}
}
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에서 다양한 패턴 찾기 // HTML에서 다양한 패턴 찾기
@@ -350,11 +317,14 @@ namespace BokBonCheck
foreach (var pattern in htmlPatterns) 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 (match.Success)
{ {
if (int.TryParse(match.Groups[1].Value, out int count)) if (int.TryParse(match.Groups[1].Value, out int count))
{ {
// 매칭된 부분과 상위 태그 추출하여 resulthtml에 저장
resulthtml = ExtractResultContext(htmlContent, match);
if (count == 0) if (count == 0)
{ {
errmessage = "검색결과없음"; errmessage = "검색결과없음";
@@ -367,16 +337,80 @@ namespace BokBonCheck
} }
errmessage = "결과수량을찾을수없음"; errmessage = "결과수량을찾을수없음";
resulthtml = htmlContent.Length > 500 ? htmlContent.Substring(0, 500) : htmlContent;
return -1; return -1;
} }
catch (Exception ex) catch (Exception ex)
{ {
errmessage = ex.Message; errmessage = ex.Message;
resulthtml = htmlContent.Length > 500 ? htmlContent.Substring(0, 500) : htmlContent;
return -1; 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) 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", "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-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("Connection", "keep-alive");
request.Headers.Add("Upgrade-Insecure-Requests", "1"); request.Headers.Add("Upgrade-Insecure-Requests", "1");
@@ -84,7 +84,8 @@ namespace BokBonCheck
var htmlContent = await response.Content.ReadAsStringAsync(); 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) if (resultCount == -1)
{ {
@@ -112,19 +113,21 @@ namespace BokBonCheck
return result; return result;
} }
private int ExtractBookCount(string htmlContent, out string errorMessage) private int ExtractBookCount(string htmlContent, out string errorMessage, out string resulthtml)
{ {
errorMessage = string.Empty; errorMessage = string.Empty;
resulthtml = string.Empty;
try try
{ {
// 1. 검색 결과가 없는 경우 확인 // 1. 검색 결과가 없는 경우 확인
if (htmlContent.Contains("검색결과가 없습니다") || if (htmlContent.Contains("검색결과가 없습니다") ||
htmlContent.Contains("검색된 자료가 없습니다") || htmlContent.Contains("검색된 자료가 없습니다") ||
htmlContent.Contains("자가 없습니다") || htmlContent.Contains("자가 없습니다") ||
htmlContent.Contains("<strong>0</strong> Results:")) htmlContent.Contains("<strong>0</strong> Results:"))
{ {
errorMessage = "검색결과없음"; errorMessage = "검색결과없음";
resulthtml = "검색결과없음";
return 0; return 0;
} }
@@ -147,8 +150,11 @@ namespace BokBonCheck
if (count == 0) if (count == 0)
{ {
errorMessage = "검색결과없음"; errorMessage = "검색결과없음";
resulthtml = match.Value;
return 0; return 0;
} }
// 매칭된 부분과 그 상위 태그를 찾아서 저장
resulthtml = ExtractResultContext(htmlContent, match);
errorMessage = $"검색성공({count}권)"; errorMessage = $"검색성공({count}권)";
Console.WriteLine($"조선이공대학교도서관 검색 결과: {count}건"); Console.WriteLine($"조선이공대학교도서관 검색 결과: {count}건");
return count; return count;
@@ -173,8 +179,10 @@ namespace BokBonCheck
if (count == 0) if (count == 0)
{ {
errorMessage = "검색결과없음"; errorMessage = "검색결과없음";
resulthtml = match.Value;
return 0; return 0;
} }
resulthtml = ExtractResultContext(htmlContent, match);
errorMessage = $"검색성공({count}권)"; errorMessage = $"검색성공({count}권)";
Console.WriteLine($"조선이공대학교도서관 검색 결과: {count}건"); Console.WriteLine($"조선이공대학교도서관 검색 결과: {count}건");
return count; return count;
@@ -182,13 +190,16 @@ namespace BokBonCheck
} }
} }
// 패턴을 찾지 못한 경우
errorMessage = "검색결과 패턴을 찾을 수 없음"; errorMessage = "검색결과 패턴을 찾을 수 없음";
resulthtml = "검색결과 패턴을 찾을 수 없음";
Console.WriteLine("조선이공대학교도서관 검색결과 패턴을 찾을 수 없음"); Console.WriteLine("조선이공대학교도서관 검색결과 패턴을 찾을 수 없음");
return -1; return -1;
} }
catch (Exception ex) catch (Exception ex)
{ {
errorMessage = $"결과 분석 오류: {ex.Message}"; errorMessage = $"결과 분석 오류: {ex.Message}";
resulthtml = "결과 분석 오류: " + ex.Message;
return -1; return -1;
} }
} }
@@ -197,5 +208,67 @@ namespace BokBonCheck
{ {
throw new NotImplementedException(); 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", "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-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("Connection", "keep-alive");
request.Headers.Add("Upgrade-Insecure-Requests", "1"); request.Headers.Add("Upgrade-Insecure-Requests", "1");
@@ -84,7 +84,8 @@ namespace BokBonCheck
var htmlContent = await response.Content.ReadAsStringAsync(); 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) if (resultCount == -1)
{ {
@@ -112,9 +113,10 @@ namespace BokBonCheck
return result; return result;
} }
private int ExtractBookCount(string htmlContent, out string errorMessage) private int ExtractBookCount(string htmlContent, out string errorMessage, out string resulthtml)
{ {
errorMessage = string.Empty; errorMessage = string.Empty;
resulthtml = string.Empty;
try try
{ {
@@ -125,6 +127,7 @@ namespace BokBonCheck
htmlContent.Contains("총 <strong>0</strong>건")) htmlContent.Contains("총 <strong>0</strong>건"))
{ {
errorMessage = "검색결과없음"; errorMessage = "검색결과없음";
resulthtml = "검색결과없음";
return 0; return 0;
} }
@@ -147,8 +150,11 @@ namespace BokBonCheck
if (count == 0) if (count == 0)
{ {
errorMessage = "검색결과없음"; errorMessage = "검색결과없음";
resulthtml = match.Value;
return 0; return 0;
} }
// 매칭된 부분과 그 상위 태그를 찾아서 저장
resulthtml = ExtractResultContext(htmlContent, match);
errorMessage = $"검색성공({count}권)"; errorMessage = $"검색성공({count}권)";
Console.WriteLine($"조선대학교중앙도서관 검색 결과: {count}건"); Console.WriteLine($"조선대학교중앙도서관 검색 결과: {count}건");
return count; return count;
@@ -173,8 +179,11 @@ namespace BokBonCheck
if (count == 0) if (count == 0)
{ {
errorMessage = "검색결과없음"; errorMessage = "검색결과없음";
resulthtml = match.Value;
return 0; return 0;
} }
// 매칭된 부분과 그 상위 태그를 찾아서 저장
resulthtml = ExtractResultContext(htmlContent, match);
errorMessage = $"검색성공({count}권)"; errorMessage = $"검색성공({count}권)";
Console.WriteLine($"조선대학교중앙도서관 검색 결과: {count}건"); Console.WriteLine($"조선대학교중앙도서관 검색 결과: {count}건");
return count; return count;
@@ -182,13 +191,16 @@ namespace BokBonCheck
} }
} }
// 패턴을 찾지 못한 경우
errorMessage = "검색결과 패턴을 찾을 수 없음"; errorMessage = "검색결과 패턴을 찾을 수 없음";
resulthtml = "검색결과 패턴을 찾을 수 없음";
Console.WriteLine("조선대학교중앙도서관 검색결과 패턴을 찾을 수 없음"); Console.WriteLine("조선대학교중앙도서관 검색결과 패턴을 찾을 수 없음");
return -1; return -1;
} }
catch (Exception ex) catch (Exception ex)
{ {
errorMessage = $"결과 분석 오류: {ex.Message}"; errorMessage = $"결과 분석 오류: {ex.Message}";
resulthtml = "결과 분석 오류: " + ex.Message;
return -1; return -1;
} }
} }
@@ -197,5 +209,67 @@ namespace BokBonCheck
{ {
throw new NotImplementedException(); 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))); 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) if (resultCount == -1)
{ {
result.BookCount = 0; result.BookCount = 0;
@@ -173,59 +176,53 @@ namespace BokBonCheck
return result; 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; errmessage = string.Empty;
resulthtml = string.Empty;
try 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 = "검색결과없음"; errmessage = "검색결과없음";
return 0; // 검색결과 없음 메시지를 포함한 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 catch
{ {
// 실패시 기본 추출 결과 사용
} }
}
try else
{ {
var resultElement = driver.FindElement(By.CssSelector(".subTapContWrap.on p")); resulthtml = pattern;
if (resultElement != null) }
{
var resultText = resultElement.Text;
var match = Regex.Match(resultText, @"총\s*(\d+)건", RegexOptions.IgnoreCase);
if (match.Success)
{
if (int.TryParse(match.Groups[1].Value, out int count))
{
if (count == 0)
{
errmessage = "검색결과없음";
return 0; return 0;
} }
errmessage = $"검색성공({count}권)";
return count;
}
}
}
}
catch (Exception ex)
{
Console.WriteLine($"결과 요소 검색 중 오류: {ex.Message}");
}
var pageSource = driver.PageSource;
if (pageSource.Contains("검색된 도서가 없습니다") ||
pageSource.Contains("검색결과가 없습니다") ||
pageSource.Contains("검색된 자료가 없습니다"))
{
errmessage = "검색결과없음";
return 0;
} }
var htmlPatterns = new[] var htmlPatterns = new[]
@@ -237,11 +234,14 @@ namespace BokBonCheck
foreach (var pattern in htmlPatterns) 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 (match.Success)
{ {
if (int.TryParse(match.Groups[1].Value, out int count)) if (int.TryParse(match.Groups[1].Value, out int count))
{ {
// 매칭된 부분과 상위 태그 추출하여 resulthtml에 저장
resulthtml = ExtractResultContext(htmlContent, match);
if (count == 0) if (count == 0)
{ {
errmessage = "검색결과없음"; errmessage = "검색결과없음";
@@ -254,16 +254,80 @@ namespace BokBonCheck
} }
errmessage = "결과수량을찾을수없음"; errmessage = "결과수량을찾을수없음";
resulthtml = "결과수량을찾을수없음";
return -1; return -1;
} }
catch (Exception ex) catch (Exception ex)
{ {
errmessage = ex.Message; errmessage = ex.Message;
resulthtml = "ExtractBookCount 오류: " + ex.Message;
return -1; 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) public async Task WaitForPageChange(WebDriverWait wait)
{ {
try try

View File

@@ -218,8 +218,9 @@ namespace BokBonCheck
// 페이지 변경을 감지하는 메서드 // 페이지 변경을 감지하는 메서드
await WaitForPageChange(new WebDriverWait(_driver, TimeSpan.FromSeconds(15))); await WaitForPageChange(new WebDriverWait(_driver, TimeSpan.FromSeconds(15)));
// 검색 결과 수 추출 var htmlContent = _driver.PageSource;
var resultCount = ExtractBookCount(_driver, searchTerm, out string ermsg); var resultCount = ExtractBookCount(htmlContent, out string ermsg, out string resultHtml);
result.Resulthtml = resultHtml;
if (resultCount == -1) if (resultCount == -1)
{ {
result.BookCount = 0; result.BookCount = 0;
@@ -244,67 +245,21 @@ namespace BokBonCheck
return result; 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; errmessage = string.Empty;
resulthtml = string.Empty;
try 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("검색결과가 없습니다") || if (htmlContent.Contains("검색결과가 없습니다") ||
pageSource.Contains("검색된 자료가 없습니다") || htmlContent.Contains("검색된 자료가 없습니다") ||
pageSource.Contains("자료가 없습니다") || htmlContent.Contains("자료가 없습니다") ||
pageSource.Contains("전체 0 건")) htmlContent.Contains("전체 0 건"))
{ {
errmessage = "검색결과없음"; errmessage = "검색결과없음";
resulthtml = "검색결과가 없습니다";
return 0; return 0;
} }
@@ -319,11 +274,14 @@ namespace BokBonCheck
foreach (var pattern in htmlPatterns) 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 (match.Success)
{ {
if (int.TryParse(match.Groups[1].Value, out int count)) if (int.TryParse(match.Groups[1].Value, out int count))
{ {
// 매칭된 부분과 상위 태그 추출하여 resulthtml에 저장
resulthtml = ExtractResultContext(htmlContent, match);
if (count == 0) if (count == 0)
{ {
errmessage = "검색결과없음"; 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 = "결과수량을찾을수없음"; errmessage = "결과수량을찾을수없음";
resulthtml = "검색결과 패턴을 찾을 수 없음";
return -1; return -1;
} }
catch (Exception ex) catch (Exception ex)
{ {
errmessage = ex.Message; errmessage = ex.Message;
resulthtml = "검색결과 패턴을 찾을 수 없음";
return -1; 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) public async Task WaitForPageChange(WebDriverWait wait)
{ {

View File

@@ -155,8 +155,9 @@ namespace BokBonCheck
// 페이지 변경을 감지하는 메서드 // 페이지 변경을 감지하는 메서드
await WaitForPageChange(new WebDriverWait(_driver, TimeSpan.FromSeconds(15))); await WaitForPageChange(new WebDriverWait(_driver, TimeSpan.FromSeconds(15)));
// 검색 결과 수 추출 var htmlContent = _driver.PageSource;
var resultCount = ExtractBookCount(_driver, searchTerm, out string ermsg); var resultCount = ExtractBookCount(htmlContent, out string ermsg, out string resultHtml);
result.Resulthtml = resultHtml;
if (resultCount == -1) if (resultCount == -1)
{ {
result.BookCount = 0; result.BookCount = 0;
@@ -181,86 +182,37 @@ namespace BokBonCheck
return result; 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; errmessage = string.Empty;
resulthtml = string.Empty;
try 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 = "검색결과없음"; errmessage = "검색결과없음";
return 0; var match = System.Text.RegularExpressions.Regex.Match(htmlContent, pattern);
}
}
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);
if (match.Success) if (match.Success)
{ {
if (int.TryParse(match.Groups[1].Value, out int count)) resulthtml = ExtractResultContext(htmlContent, match);
}
else
{ {
if (count == 0) resulthtml = htmlContent.Length > 500 ? htmlContent.Substring(0, 500) : htmlContent;
{ }
errmessage = "검색결과없음";
return 0; return 0;
} }
errmessage = $"검색성공({count}권)";
return count;
}
}
}
}
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에서 다양한 패턴 찾기 // HTML에서 다양한 패턴 찾기
@@ -274,11 +226,14 @@ namespace BokBonCheck
foreach (var pattern in htmlPatterns) 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 (match.Success)
{ {
if (int.TryParse(match.Groups[1].Value, out int count)) if (int.TryParse(match.Groups[1].Value, out int count))
{ {
// 매칭된 부분과 상위 태그 추출하여 resulthtml에 저장
resulthtml = ExtractResultContext(htmlContent, match);
if (count == 0) if (count == 0)
{ {
errmessage = "검색결과없음"; errmessage = "검색결과없음";
@@ -291,16 +246,80 @@ namespace BokBonCheck
} }
errmessage = "결과수량을찾을수없음"; errmessage = "결과수량을찾을수없음";
resulthtml = htmlContent.Length > 500 ? htmlContent.Substring(0, 500) : htmlContent;
return -1; return -1;
} }
catch (Exception ex) catch (Exception ex)
{ {
errmessage = ex.Message; errmessage = ex.Message;
resulthtml = htmlContent.Length > 500 ? htmlContent.Substring(0, 500) : htmlContent;
return -1; 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) public async Task WaitForPageChange(WebDriverWait wait)
{ {

View File

@@ -81,10 +81,11 @@ namespace BokBonCheck
// HTTP GET 요청 실행 (추가 헤더 포함) // HTTP GET 요청 실행 (추가 헤더 포함)
using (var request = new HttpRequestMessage(HttpMethod.Get, searchUrl)) 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", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;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-Language", "ko-KR,ko;q=0.9,en;q=0.1");
request.Headers.Add("Accept-Encoding", "gzip, deflate, br"); request.Headers.Add("Accept-Charset", "UTF-8,*;q=0.1");
// Accept-Encoding 제거 - 압축으로 인한 인코딩 문제 방지
request.Headers.Add("Connection", "keep-alive"); request.Headers.Add("Connection", "keep-alive");
request.Headers.Add("Upgrade-Insecure-Requests", "1"); request.Headers.Add("Upgrade-Insecure-Requests", "1");
@@ -96,10 +97,50 @@ namespace BokBonCheck
throw new HttpRequestException($"HTTP {(int)response.StatusCode} {response.StatusCode}: {errorContent}"); 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) if (resultCount == -1)
{ {
@@ -126,9 +167,10 @@ namespace BokBonCheck
return result; return result;
} }
private int ExtractBookCount(string htmlContent, out string errorMessage) private int ExtractBookCount(string htmlContent, out string errorMessage,out string resulthtml)
{ {
errorMessage = string.Empty; errorMessage = string.Empty;
resulthtml=string.Empty;
try try
{ {
@@ -141,18 +183,23 @@ namespace BokBonCheck
return 0; return 0;
} }
// 2. 검색 결과 수량 추출: <p class="totalCount">전체 <strong>2</strong> 건</p> // 2. 검색 결과 수량 추출 - 더 포괄적인 패턴 사용
var patterns = new[] var patterns = new[]
{ {
@"<p[^>]*class=""totalCount""[^>]*>전체\s*<strong>\s*(\d+)\s*</strong>\s*건</p>", // 기본 패턴들
@"전체\s*<strong>\s*(\d+)\s*</strong>\s*건", @"전체\s*(?:\*\*)?(\d+)(?:\*\*)?\s*건", // 전체 **N** 건 또는 전체 N 건
@"<strong>\s*(\d+)\s*</strong>\s*건", @"<h[1-6][^>]*>.*?전체\s*(?:\*\*)?(\d+)(?:\*\*)?\s*건.*?</h[1-6]>", // h태그 안의 전체 N 건
@"총\s*(\d+)\s*건" @"<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) 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 (match.Success)
{ {
if (int.TryParse(match.Groups[1].Value, out int count)) if (int.TryParse(match.Groups[1].Value, out int count))
@@ -160,25 +207,31 @@ namespace BokBonCheck
if (count == 0) if (count == 0)
{ {
errorMessage = "검색결과없음"; errorMessage = "검색결과없음";
resulthtml = match.Value; // 매칭된 부분만 저장
return 0; return 0;
} }
// 매칭된 부분과 그 상위 태그를 찾아서 저장
resulthtml = ExtractResultContext(htmlContent, match);
errorMessage = $"검색성공({count}권)"; errorMessage = $"검색성공({count}권)";
Console.WriteLine($"광주동구 검색 결과: {count}건"); Console.WriteLine($"광주동구 검색 결과: {count}건 (패턴: {pattern})");
return count; return count;
} }
} }
} }
// 3. 더 자세한 패턴으로 시도 (줄바꿈 포함) // 3. 멀티라인 패턴으로 시도
var multilinePatterns = new[] var multilinePatterns = new[]
{ {
@"전체\s*<strong>\s*\r?\n?\s*(\d+)\s*\r?\n?\s*</strong>\s*건", @"전체\s*(?:\*\*)?[\r\n\s]*(\d+)[\r\n\s]*(?:\*\*)?\s*건",
@"<strong>\s*\r?\n?\s*(\d+)\s*\r?\n?\s*</strong>\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) 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 (match.Success)
{ {
if (int.TryParse(match.Groups[1].Value, out int count)) if (int.TryParse(match.Groups[1].Value, out int count))
@@ -186,15 +239,24 @@ namespace BokBonCheck
if (count == 0) if (count == 0)
{ {
errorMessage = "검색결과없음"; errorMessage = "검색결과없음";
resulthtml = match.Value; // 매칭된 부분만 저장
return 0; return 0;
} }
// 매칭된 부분과 그 상위 태그를 찾아서 저장
resulthtml = ExtractResultContext(htmlContent, match);
errorMessage = $"검색성공({count}권)"; errorMessage = $"검색성공({count}권)";
Console.WriteLine($"광주동구 검색 결과: {count}건"); Console.WriteLine($"광주동구 검색 결과: {count}건 (멀티라인 패턴)");
return count; return count;
} }
} }
} }
// 4. 패턴을 찾지 못한 경우 - 디버깅을 위한 HTML 내용 일부 출력
resulthtml = "검색결과 패턴을 찾을 수 없음";
Console.WriteLine($"광주동구 HTML 샘플: {resulthtml}");
errorMessage = "검색결과 패턴을 찾을 수 없음"; errorMessage = "검색결과 패턴을 찾을 수 없음";
Console.WriteLine("광주동구 검색결과 패턴을 찾을 수 없음"); Console.WriteLine("광주동구 검색결과 패턴을 찾을 수 없음");
return -1; return -1;
@@ -202,6 +264,8 @@ namespace BokBonCheck
catch (Exception ex) catch (Exception ex)
{ {
errorMessage = $"결과 분석 오류: {ex.Message}"; errorMessage = $"결과 분석 오류: {ex.Message}";
resulthtml = "검색결과 패턴을 찾을 수 없음"; // 오류 시 HTML 일부 저장
Console.WriteLine($"광주동구 결과 분석 오류: {ex.Message}");
return -1; return -1;
} }
} }
@@ -210,5 +274,83 @@ namespace BokBonCheck
{ {
throw new NotImplementedException(); 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))); 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) if (resultCount == -1)
{ {
result.BookCount = 0; result.BookCount = 0;
@@ -362,13 +363,15 @@ namespace BokBonCheck
return result; 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; string errmessage = string.Empty;
int totalCount = 0; int totalCount = 0;
try try
{ {
var htmlContent = driver.PageSource;
string resultHtml = string.Empty;
// 첫 번째 페이지에서 테이블 row 수 확인 // 첫 번째 페이지에서 테이블 row 수 확인
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10)); var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
@@ -381,7 +384,28 @@ namespace BokBonCheck
if (firstPageRows.Count == 0) if (firstPageRows.Count == 0)
{ {
errmessage = "검색결과없음"; 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; totalCount += firstPageRows.Count;
@@ -390,7 +414,28 @@ namespace BokBonCheck
catch catch
{ {
errmessage = "검색결과없음"; 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) if (totalCount == 0)
{ {
errmessage = "검색결과없음"; 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}권)"; errmessage = $"검색성공({totalCount}권)";
Console.WriteLine($"전체 검색 결과: {totalCount}건"); Console.WriteLine($"전체 검색 결과: {totalCount}건");
return (totalCount, errmessage); return (totalCount, errmessage, resultHtml);
} }
catch (Exception ex) catch (Exception ex)
{ {
errmessage = ex.Message; errmessage = ex.Message;
return (-1, errmessage); string resultHtml = "오류 발생";
return (-1, errmessage, resultHtml);
} }
} }
@@ -492,5 +561,67 @@ namespace BokBonCheck
Console.WriteLine($"페이지 변경 감지 실패: {ex.Message}"); 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", "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-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("Connection", "keep-alive");
request.Headers.Add("Upgrade-Insecure-Requests", "1"); request.Headers.Add("Upgrade-Insecure-Requests", "1");
@@ -84,7 +84,8 @@ namespace BokBonCheck
var htmlContent = await response.Content.ReadAsStringAsync(); 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) if (resultCount == -1)
{ {
@@ -112,9 +113,10 @@ namespace BokBonCheck
return result; return result;
} }
private int ExtractBookCount(string htmlContent, out string errorMessage) private int ExtractBookCount(string htmlContent, out string errorMessage, out string resulthtml)
{ {
errorMessage = string.Empty; errorMessage = string.Empty;
resulthtml = string.Empty;
try try
{ {
@@ -124,6 +126,7 @@ namespace BokBonCheck
htmlContent.Contains("자료가 없습니다")) htmlContent.Contains("자료가 없습니다"))
{ {
errorMessage = "검색결과없음"; errorMessage = "검색결과없음";
resulthtml = "검색결과없음";
return 0; return 0;
} }
@@ -146,8 +149,11 @@ namespace BokBonCheck
if (count == 0) if (count == 0)
{ {
errorMessage = "검색결과없음"; errorMessage = "검색결과없음";
resulthtml = match.Value;
return 0; return 0;
} }
// 매칭된 부분과 그 상위 태그를 찾아서 저장
resulthtml = ExtractResultContext(htmlContent, match);
errorMessage = $"검색성공({count}권)"; errorMessage = $"검색성공({count}권)";
return count; return count;
} }
@@ -171,20 +177,25 @@ namespace BokBonCheck
if (count == 0) if (count == 0)
{ {
errorMessage = "검색결과없음"; errorMessage = "검색결과없음";
resulthtml = match.Value;
return 0; return 0;
} }
resulthtml = ExtractResultContext(htmlContent, match);
errorMessage = $"검색성공({count}권)"; errorMessage = $"검색성공({count}권)";
return count; return count;
} }
} }
} }
// 패턴을 찾지 못한 경우
resulthtml = "검색결과 패턴을 찾을 수 없음";
errorMessage = "검색결과 패턴을 찾을 수 없음"; errorMessage = "검색결과 패턴을 찾을 수 없음";
return -1; return -1;
} }
catch (Exception ex) catch (Exception ex)
{ {
errorMessage = $"결과 분석 오류: {ex.Message}"; errorMessage = $"결과 분석 오류: {ex.Message}";
resulthtml = "검색결과 패턴을 찾을 수 없음";
return -1; return -1;
} }
} }
@@ -193,5 +204,67 @@ namespace BokBonCheck
{ {
throw new NotImplementedException(); 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 렌더링 대기 // JavaScript 렌더링 대기
await Task.Delay(3000); await Task.Delay(3000);
// 검색 결과 수 추출 var htmlContent = _driver.PageSource;
var resultCount = ExtractBookCount(_driver, searchTerm, out string ermsg); var resultCount = ExtractBookCount(htmlContent, out string ermsg, out string resultHtml);
result.Resulthtml = resultHtml;
if (resultCount == -1) if (resultCount == -1)
{ {
result.BookCount = 0; result.BookCount = 0;
@@ -133,71 +134,21 @@ namespace BokBonCheck
return result; 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; errmessage = string.Empty;
resulthtml = string.Empty;
try 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("검색결과가 없습니다") || if (htmlContent.Contains("검색결과가 없습니다") ||
pageSource.Contains("검색된 자료가 없습니다") || htmlContent.Contains("검색된 자료가 없습니다") ||
pageSource.Contains("자료가 없습니다") || htmlContent.Contains("자료가 없습니다") ||
pageSource.Contains("총 0건")) htmlContent.Contains("총 0건"))
{ {
errmessage = "검색결과없음"; errmessage = "검색결과없음";
resulthtml = "검색결과 패턴을 찾을 수 없음";
return 0; return 0;
} }
@@ -212,11 +163,14 @@ namespace BokBonCheck
foreach (var pattern in htmlPatterns) 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 (match.Success)
{ {
if (int.TryParse(match.Groups[1].Value, out int count)) if (int.TryParse(match.Groups[1].Value, out int count))
{ {
// 매칭된 부분과 상위 태그 추출하여 resulthtml에 저장
resulthtml = ExtractResultContext(htmlContent, match);
if (count == 0) if (count == 0)
{ {
errmessage = "검색결과없음"; errmessage = "검색결과없음";
@@ -229,16 +183,80 @@ namespace BokBonCheck
} }
errmessage = "결과수량을찾을수없음"; errmessage = "결과수량을찾을수없음";
resulthtml = "검색결과 패턴을 찾을 수 없음";
return -1; return -1;
} }
catch (Exception ex) catch (Exception ex)
{ {
errmessage = ex.Message; errmessage = ex.Message;
resulthtml = "검색결과 패턴을 찾을 수 없음";
return -1; 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) 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", "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-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("Connection", "keep-alive");
request.Headers.Add("Upgrade-Insecure-Requests", "1"); request.Headers.Add("Upgrade-Insecure-Requests", "1");
@@ -84,7 +84,8 @@ namespace BokBonCheck
var htmlContent = await response.Content.ReadAsStringAsync(); 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) if (resultCount == -1)
{ {
@@ -110,9 +111,10 @@ namespace BokBonCheck
return result; return result;
} }
private int ExtractBookCount(string htmlContent, out string errmessage) private int ExtractBookCount(string htmlContent, out string errmessage, out string resulthtml)
{ {
errmessage = string.Empty; errmessage = string.Empty;
resulthtml = string.Empty;
try try
{ {
// 실제 HTML 구조에 맞는 패턴으로 수정 // 실제 HTML 구조에 맞는 패턴으로 수정
@@ -139,6 +141,7 @@ namespace BokBonCheck
if (pattern.Contains(@"\s*0\s*")) if (pattern.Contains(@"\s*0\s*"))
{ {
errmessage = "검색결과없음"; errmessage = "검색결과없음";
resulthtml = match.Value;
return 0; return 0;
} }
@@ -148,15 +151,19 @@ namespace BokBonCheck
if (count == 0) if (count == 0)
{ {
errmessage = "검색결과없음"; errmessage = "검색결과없음";
resulthtml = match.Value;
return 0; return 0;
} }
// 매칭된 부분과 그 상위 태그를 찾아서 저장
resulthtml = ExtractResultContext(htmlContent, match);
errmessage = $"검색성공({count}권)"; errmessage = $"검색성공({count}권)";
return count; return count;
} }
} }
} }
// 디버깅을 위해 HTML 내용 일부 출력 // 패턴을 찾지 못한 경우
resulthtml = "검색결과 패턴을 찾을 수 없음";
Console.WriteLine($"HTML 내용 일부: {htmlContent.Substring(0, Math.Min(1000, htmlContent.Length))}"); Console.WriteLine($"HTML 내용 일부: {htmlContent.Substring(0, Math.Min(1000, htmlContent.Length))}");
errmessage = "결과수량을찾을수없음"; errmessage = "결과수량을찾을수없음";
@@ -166,6 +173,7 @@ namespace BokBonCheck
catch (Exception ex) catch (Exception ex)
{ {
errmessage = ex.Message; errmessage = ex.Message;
resulthtml = "검색결과 패턴을 찾을 수 없음";
return -1; return -1;
} }
} }
@@ -177,5 +185,66 @@ namespace BokBonCheck
await Task.CompletedTask; 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))); await WaitForPageChange(new WebDriverWait(_driver, TimeSpan.FromSeconds(15)));
// 검색 결과 수 추출 var htmlContent = _driver.PageSource;
var resultCount = ExtractBookCount(_driver, searchTerm, out string ermsg); var resultCount = ExtractBookCount(htmlContent, out string ermsg, out string resultHtml);
result.Resulthtml = resultHtml;
if (resultCount == -1) if (resultCount == -1)
{ {
result.BookCount = 0; result.BookCount = 0;
@@ -253,71 +254,20 @@ namespace BokBonCheck
return result; 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; errmessage = string.Empty;
resulthtml = string.Empty;
try 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건이 검색되었습니다") || if (htmlContent.Contains("0건이 검색되었습니다") ||
pageSource.Contains("검색결과가 없습니다") || htmlContent.Contains("검색결과가 없습니다") ||
pageSource.Contains("검색된 자료가 없습니다")) htmlContent.Contains("검색된 자료가 없습니다"))
{ {
errmessage = "검색결과없음"; errmessage = "검색결과없음";
resulthtml = "검색결과 패턴을 찾을 수 없음";
return 0; return 0;
} }
@@ -332,11 +282,14 @@ namespace BokBonCheck
foreach (var pattern in htmlPatterns) 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 (match.Success)
{ {
if (int.TryParse(match.Groups[1].Value, out int count)) if (int.TryParse(match.Groups[1].Value, out int count))
{ {
// 매칭된 부분과 상위 태그 추출하여 resulthtml에 저장
resulthtml = ExtractResultContext(htmlContent, match);
if (count == 0) if (count == 0)
{ {
errmessage = "검색결과없음"; errmessage = "검색결과없음";
@@ -349,16 +302,80 @@ namespace BokBonCheck
} }
errmessage = "결과수량을찾을수없음"; errmessage = "결과수량을찾을수없음";
resulthtml = "검색결과 패턴을 찾을 수 없음";
return -1; return -1;
} }
catch (Exception ex) catch (Exception ex)
{ {
errmessage = ex.Message; errmessage = ex.Message;
resulthtml = "검색결과 패턴을 찾을 수 없음";
return -1; 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) public async Task WaitForPageChange(WebDriverWait wait)
{ {

View File

@@ -260,7 +260,8 @@ namespace BokBonCheck
await WaitForPageChange(new WebDriverWait(_driver, TimeSpan.FromSeconds(15))); 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) if (resultCount == -1)
{ {
result.BookCount = 0; result.BookCount = 0;
@@ -285,11 +286,13 @@ namespace BokBonCheck
return result; 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; errmessage = string.Empty;
resulthtml = string.Empty;
try try
{ {
var htmlContent = driver.PageSource;
// 먼저 검색결과가 없는 경우 확인 // 먼저 검색결과가 없는 경우 확인
try try
{ {
@@ -298,6 +301,7 @@ namespace BokBonCheck
if (noResultText.Contains("검색결과가 없습니다")) if (noResultText.Contains("검색결과가 없습니다"))
{ {
errmessage = "검색결과없음"; errmessage = "검색결과없음";
resulthtml = noResultText;
return 0; return 0;
} }
} }
@@ -318,17 +322,36 @@ namespace BokBonCheck
if (int.TryParse(match.Groups[1].Value, out int vqty)) if (int.TryParse(match.Groups[1].Value, out int vqty))
{ {
errmessage = $"검색성공({vqty}건)"; errmessage = $"검색성공({vqty}건)";
resulthtml = ExtractResultContext(htmlContent, match);
return vqty; return vqty;
} }
else else
{ {
errmessage = $"수량값오류({match.Groups[1].Value})"; errmessage = $"수량값오류({match.Groups[1].Value})";
resulthtml = match.Value;
return -1; return -1;
} }
} }
else else
{ {
errmessage = "수량항목없음"; 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; return -1;
} }
} }
@@ -336,6 +359,7 @@ namespace BokBonCheck
{ {
// page_info가 없는 경우 검색결과가 없는 것으로 판단 // page_info가 없는 경우 검색결과가 없는 것으로 판단
errmessage = "검색결과없음"; errmessage = "검색결과없음";
resulthtml = "검색결과 없음: " + ex.Message;
return 0; return 0;
} }
@@ -343,11 +367,72 @@ namespace BokBonCheck
catch (Exception ex) catch (Exception ex)
{ {
errmessage = ex.Message; errmessage = ex.Message;
resulthtml = "ExtractBookCount 오류: " + ex.Message;
return -1; 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) public async Task WaitForPageChange(WebDriverWait wait)

View File

@@ -272,7 +272,8 @@ namespace BokBonCheck
await WaitForPageChange(new WebDriverWait(_driver, TimeSpan.FromSeconds(15))); 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) if (resultCount == -1)
{ {
result.BookCount = 0; result.BookCount = 0;
@@ -297,11 +298,13 @@ namespace BokBonCheck
return result; 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; errmessage = string.Empty;
resulthtml = string.Empty;
try try
{ {
var htmlContent = driver.PageSource;
// 먼저 검색결과가 없는 경우 확인 // 먼저 검색결과가 없는 경우 확인
try try
{ {
@@ -310,6 +313,7 @@ namespace BokBonCheck
if (noResultText.Contains("검색결과가 없습니다")) if (noResultText.Contains("검색결과가 없습니다"))
{ {
errmessage = "검색결과없음"; errmessage = "검색결과없음";
resulthtml = noResultText;
return 0; return 0;
} }
} }
@@ -330,17 +334,36 @@ namespace BokBonCheck
if (int.TryParse(match.Groups[1].Value, out int vqty)) if (int.TryParse(match.Groups[1].Value, out int vqty))
{ {
errmessage = $"검색성공({vqty}건)"; errmessage = $"검색성공({vqty}건)";
resulthtml = ExtractResultContext(htmlContent, match);
return vqty; return vqty;
} }
else else
{ {
errmessage = $"수량값오류({match.Groups[1].Value})"; errmessage = $"수량값오류({match.Groups[1].Value})";
resulthtml = match.Value;
return -1; return -1;
} }
} }
else else
{ {
errmessage = "수량항목없음"; 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; return -1;
} }
} }
@@ -348,6 +371,27 @@ namespace BokBonCheck
{ {
// page_info가 없는 경우 검색결과가 없는 것으로 판단 // page_info가 없는 경우 검색결과가 없는 것으로 판단
errmessage = "검색결과없음"; 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; return 0;
} }
@@ -355,11 +399,72 @@ namespace BokBonCheck
catch (Exception ex) catch (Exception ex)
{ {
errmessage = ex.Message; errmessage = ex.Message;
resulthtml = "ExtractBookCount 오류: " + ex.Message;
return -1; 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) 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", "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-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("Connection", "keep-alive");
request.Headers.Add("Upgrade-Insecure-Requests", "1"); request.Headers.Add("Upgrade-Insecure-Requests", "1");
@@ -100,7 +100,8 @@ namespace BokBonCheck
var htmlContent = await response.Content.ReadAsStringAsync(); 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) if (resultCount == -1)
{ {
@@ -128,9 +129,10 @@ namespace BokBonCheck
return result; return result;
} }
private int ExtractBookCount(string htmlContent, out string errorMessage) private int ExtractBookCount(string htmlContent, out string errorMessage, out string resulthtml)
{ {
errorMessage = string.Empty; errorMessage = string.Empty;
resulthtml = string.Empty;
try try
{ {
@@ -141,6 +143,7 @@ namespace BokBonCheck
htmlContent.Contains("총 0 건이 검색되었습니다")) htmlContent.Contains("총 0 건이 검색되었습니다"))
{ {
errorMessage = "검색결과없음"; errorMessage = "검색결과없음";
resulthtml = "검색결과없음";
return 0; return 0;
} }
@@ -163,8 +166,11 @@ namespace BokBonCheck
if (count == 0) if (count == 0)
{ {
errorMessage = "검색결과없음"; errorMessage = "검색결과없음";
resulthtml = match.Value;
return 0; return 0;
} }
// 매칭된 부분과 그 상위 태그를 찾아서 저장
resulthtml = ExtractResultContext(htmlContent, match);
errorMessage = $"검색성공({count}권)"; errorMessage = $"검색성공({count}권)";
Console.WriteLine($"KCM자료검색시스템 검색 결과: {count}건"); Console.WriteLine($"KCM자료검색시스템 검색 결과: {count}건");
return count; return count;
@@ -189,8 +195,10 @@ namespace BokBonCheck
if (count == 0) if (count == 0)
{ {
errorMessage = "검색결과없음"; errorMessage = "검색결과없음";
resulthtml = match.Value;
return 0; return 0;
} }
resulthtml = ExtractResultContext(htmlContent, match);
errorMessage = $"검색성공({count}권)"; errorMessage = $"검색성공({count}권)";
Console.WriteLine($"KCM자료검색시스템 검색 결과: {count}건"); Console.WriteLine($"KCM자료검색시스템 검색 결과: {count}건");
return count; return count;
@@ -198,6 +206,8 @@ namespace BokBonCheck
} }
} }
// 패턴을 찾지 못한 경우
resulthtml = "검색결과 패턴을 찾을 수 없음";
errorMessage = "검색결과 패턴을 찾을 수 없음"; errorMessage = "검색결과 패턴을 찾을 수 없음";
Console.WriteLine("KCM자료검색시스템 검색결과 패턴을 찾을 수 없음"); Console.WriteLine("KCM자료검색시스템 검색결과 패턴을 찾을 수 없음");
return -1; return -1;
@@ -205,6 +215,7 @@ namespace BokBonCheck
catch (Exception ex) catch (Exception ex)
{ {
errorMessage = $"결과 분석 오류: {ex.Message}"; errorMessage = $"결과 분석 오류: {ex.Message}";
resulthtml = "검색결과 패턴을 찾을 수 없음";
return -1; return -1;
} }
} }
@@ -213,5 +224,67 @@ namespace BokBonCheck
{ {
throw new NotImplementedException(); 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))); 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;
if (resultCount == -1)
{
result.BookCount = 0;
result.IsSuccess = false;
result.ErrorMessage = errorMessage;
}
else
{
result.BookCount = resultCount; result.BookCount = resultCount;
result.IsSuccess = true; result.IsSuccess = true;
result.ErrorMessage = errorMessage;
}
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -211,10 +222,14 @@ namespace BokBonCheck
return result; 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 try
{ {
var htmlContent = driver.PageSource;
// div.search-result 내부의 span에서 '전체 N' 텍스트 추출 // div.search-result 내부의 span에서 '전체 N' 텍스트 추출
var resultDiv = driver.FindElement(By.CssSelector("div.ndls_result")); var resultDiv = driver.FindElement(By.CssSelector("div.ndls_result"));
var span = resultDiv.FindElement(By.XPath(".//span[contains(text(),'전체')]")); 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+)"); var match = System.Text.RegularExpressions.Regex.Match(text, @"전체\s*(\d+)");
if (match.Success) 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; return 0;
} }
errorMessage = $"검색성공({count}건)";
resulthtml = ExtractResultContext(htmlContent, match);
return count;
}
}
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 catch
{ {
return 0; resulthtml = htmlContent.Length > 500 ? htmlContent.Substring(0, 500) : htmlContent;
}
return -1;
}
catch (Exception ex)
{
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", "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-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("Connection", "keep-alive");
request.Headers.Add("Upgrade-Insecure-Requests", "1"); request.Headers.Add("Upgrade-Insecure-Requests", "1");
@@ -92,7 +92,8 @@ namespace BokBonCheck
var htmlContent = await response.Content.ReadAsStringAsync(); 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) if (resultCount == -1)
{ {
@@ -120,9 +121,10 @@ namespace BokBonCheck
return result; return result;
} }
private int ExtractBookCount(string htmlContent, out string errorMessage) private int ExtractBookCount(string htmlContent, out string errorMessage, out string resulthtml)
{ {
errorMessage = string.Empty; errorMessage = string.Empty;
resulthtml = string.Empty;
try try
{ {
@@ -146,8 +148,11 @@ namespace BokBonCheck
if (count == 0) if (count == 0)
{ {
errorMessage = "검색결과없음"; errorMessage = "검색결과없음";
resulthtml = match.Value;
return 0; return 0;
} }
// 매칭된 부분과 그 상위 태그를 찾아서 저장
resulthtml = ExtractResultContext(htmlContent, match);
errorMessage = $"검색성공({count}권)"; errorMessage = $"검색성공({count}권)";
return count; return count;
} }
@@ -161,6 +166,7 @@ namespace BokBonCheck
htmlContent.Contains("총 0권(개)")) htmlContent.Contains("총 0권(개)"))
{ {
errorMessage = "검색결과없음"; errorMessage = "검색결과없음";
resulthtml = "검색결과없음";
return 0; return 0;
} }
@@ -183,20 +189,25 @@ namespace BokBonCheck
if (count == 0) if (count == 0)
{ {
errorMessage = "검색결과없음"; errorMessage = "검색결과없음";
resulthtml = match.Value;
return 0; return 0;
} }
resulthtml = ExtractResultContext(htmlContent, match);
errorMessage = $"검색성공({count}권)"; errorMessage = $"검색성공({count}권)";
return count; return count;
} }
} }
} }
// 패턴을 찾지 못한 경우
resulthtml = "검색결과 패턴을 찾을 수 없음";
errorMessage = "검색결과 패턴을 찾을 수 없음"; errorMessage = "검색결과 패턴을 찾을 수 없음";
return -1; return -1;
} }
catch (Exception ex) catch (Exception ex)
{ {
errorMessage = $"결과 분석 오류: {ex.Message}"; errorMessage = $"결과 분석 오류: {ex.Message}";
resulthtml = "검색결과 패턴을 찾을 수 없음";
return -1; return -1;
} }
} }
@@ -205,5 +216,67 @@ namespace BokBonCheck
{ {
throw new NotImplementedException(); 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))); await WaitForPageChange(new WebDriverWait(_driver, TimeSpan.FromSeconds(15)));
var htmlContent = _driver.PageSource;
// 검색 결과 수 추출 var resultCount = ExtractBookCount(htmlContent, out string ermsg, out string resultHtml);
var resultCount = ExtractBookCount(_driver, searchTerm, out string ermsg); result.Resulthtml = resultHtml;
if (resultCount == -1) if (resultCount == -1)
{ {
result.BookCount = 0; result.BookCount = 0;
@@ -223,70 +223,55 @@ namespace BokBonCheck
return result; 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; errmessage = string.Empty;
resulthtml = string.Empty;
try try
{ {
// 검색결과 페이지 대기 // 검색 결과가 없다는 메시지 확인
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10)); var noResultPatterns = new[]
// 1. 검색결과가 없는 경우 확인
try
{ {
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 = "검색결과없음"; errmessage = "검색결과없음";
return 0; // 검색결과 없음 메시지를 포함한 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 catch
{ {
// 검색결과가 있는 경우로 진행 // 실패시 기본 추출 결과 사용
} }
}
// 2. totalCount에서 결과 수량 추출 else
try
{ {
var totalCountElement = wait.Until(d => d.FindElement(By.CssSelector("p.totalCount strong"))); resulthtml = pattern;
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; 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에서 다양한 패턴 찾기 // HTML에서 다양한 패턴 찾기
@@ -299,11 +284,14 @@ namespace BokBonCheck
foreach (var pattern in htmlPatterns) 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 (match.Success)
{ {
if (int.TryParse(match.Groups[1].Value, out int count)) if (int.TryParse(match.Groups[1].Value, out int count))
{ {
// 매칭된 부분과 상위 태그 추출하여 resulthtml에 저장
resulthtml = ExtractResultContext(htmlContent, match);
if (count == 0) if (count == 0)
{ {
errmessage = "검색결과없음"; errmessage = "검색결과없음";
@@ -316,16 +304,80 @@ namespace BokBonCheck
} }
errmessage = "결과수량을찾을수없음"; errmessage = "결과수량을찾을수없음";
resulthtml = "결과수량을찾을수없음";
return -1; return -1;
} }
catch (Exception ex) catch (Exception ex)
{ {
errmessage = ex.Message; errmessage = ex.Message;
resulthtml = "ExtractBookCount 오류: " + ex.Message;
return -1; 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) private async Task WaitForCompletePageLoad(WebDriverWait wait)
{ {

View File

@@ -269,7 +269,8 @@ namespace BokBonCheck
await WaitForPageChange(new WebDriverWait(_driver, TimeSpan.FromSeconds(15))); 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) if (resultCount == -1)
{ {
result.BookCount = 0; result.BookCount = 0;
@@ -294,11 +295,14 @@ namespace BokBonCheck
return result; 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; errmessage = string.Empty;
resulthtml = string.Empty;
try try
{ {
var htmlContent = driver.PageSource;
// div.search-result 내부의 span에서 '전체 N' 텍스트 추출 // div.search-result 내부의 span에서 '전체 N' 텍스트 추출
var resultDiv = driver.FindElement(By.CssSelector("div.search-result")); var resultDiv = driver.FindElement(By.CssSelector("div.search-result"));
@@ -306,6 +310,7 @@ namespace BokBonCheck
if (bodytext.Contains("검색결과가 없습니다")) if (bodytext.Contains("검색결과가 없습니다"))
{ {
errmessage = "검색결과없음"; errmessage = "검색결과없음";
resulthtml = bodytext;
return 0; return 0;
} }
@@ -315,6 +320,7 @@ namespace BokBonCheck
if (searchTerm.Contains(searchtitle) == false) if (searchTerm.Contains(searchtitle) == false)
{ {
errmessage = $"검색어불일치({searchtitle}/{searchTerm})"; errmessage = $"검색어불일치({searchtitle}/{searchTerm})";
resulthtml = searchtitle;
return -1; return -1;
} }
var span = resultDiv.FindElement(By.XPath(".//span[contains(text(),'전체')]")); 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) if (int.TryParse(match.Groups[1].Value, out int vqty) == false)
{ {
errmessage = $"수량값오류({match.Groups[1].Value})"; errmessage = $"수량값오류({match.Groups[1].Value})";
resulthtml = match.Value;
return -1; return -1;
} }
else else
{ {
errmessage = $"검색성공({vqty}건)";
resulthtml = ExtractResultContext(htmlContent, match);
searchTerm = string.Empty; searchTerm = string.Empty;
return vqty; return vqty;
} }
@@ -336,6 +345,23 @@ namespace BokBonCheck
else else
{ {
errmessage = "수량항목없음"; 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; return -1;
} }
@@ -343,11 +369,72 @@ namespace BokBonCheck
catch (Exception ex) catch (Exception ex)
{ {
errmessage = ex.Message; errmessage = ex.Message;
resulthtml = "ExtractBookCount 오류: " + ex.Message;
return -1; 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) 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", "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-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("Connection", "keep-alive");
request.Headers.Add("Upgrade-Insecure-Requests", "1"); request.Headers.Add("Upgrade-Insecure-Requests", "1");
@@ -97,7 +97,8 @@ namespace BokBonCheck
var htmlContent = await response.Content.ReadAsStringAsync(); 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) if (resultCount == -1)
{ {
@@ -125,9 +126,10 @@ namespace BokBonCheck
return result; return result;
} }
private int ExtractBookCount(string htmlContent, out string errorMessage) private int ExtractBookCount(string htmlContent, out string errorMessage, out string resulthtml)
{ {
errorMessage = string.Empty; errorMessage = string.Empty;
resulthtml = string.Empty;
try try
{ {
@@ -150,8 +152,11 @@ namespace BokBonCheck
if (count == 0) if (count == 0)
{ {
errorMessage = "검색결과없음"; errorMessage = "검색결과없음";
resulthtml = match.Value;
return 0; return 0;
} }
// 매칭된 부분과 그 상위 태그를 찾아서 저장
resulthtml = ExtractResultContext(htmlContent, match);
errorMessage = $"검색성공({count}권)"; errorMessage = $"검색성공({count}권)";
return count; return count;
} }
@@ -165,6 +170,7 @@ namespace BokBonCheck
htmlContent.Contains("총 0건")) htmlContent.Contains("총 0건"))
{ {
errorMessage = "검색결과없음"; errorMessage = "검색결과없음";
resulthtml = "검색결과없음";
return 0; return 0;
} }
@@ -178,8 +184,10 @@ namespace BokBonCheck
if (count == 0) if (count == 0)
{ {
errorMessage = "검색결과없음"; errorMessage = "검색결과없음";
resulthtml = resultConMatch.Value;
return 0; return 0;
} }
resulthtml = ExtractResultContext(htmlContent, resultConMatch);
errorMessage = $"검색성공({count}권)"; errorMessage = $"검색성공({count}권)";
return count; return count;
} }
@@ -203,20 +211,25 @@ namespace BokBonCheck
if (count == 0) if (count == 0)
{ {
errorMessage = "검색결과없음"; errorMessage = "검색결과없음";
resulthtml = match.Value;
return 0; return 0;
} }
resulthtml = ExtractResultContext(htmlContent, match);
errorMessage = $"검색성공({count}권)"; errorMessage = $"검색성공({count}권)";
return count; return count;
} }
} }
} }
// 패턴을 찾지 못한 경우
resulthtml = "검색결과 패턴을 찾을 수 없음";
errorMessage = "검색결과 패턴을 찾을 수 없음"; errorMessage = "검색결과 패턴을 찾을 수 없음";
return -1; return -1;
} }
catch (Exception ex) catch (Exception ex)
{ {
errorMessage = $"결과 분석 오류: {ex.Message}"; errorMessage = $"결과 분석 오류: {ex.Message}";
resulthtml = "검색결과 패턴을 찾을 수 없음";
return -1; return -1;
} }
} }
@@ -225,5 +238,67 @@ namespace BokBonCheck
{ {
throw new NotImplementedException(); 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", "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-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("Connection", "keep-alive");
request.Headers.Add("Upgrade-Insecure-Requests", "1"); request.Headers.Add("Upgrade-Insecure-Requests", "1");
@@ -96,7 +96,8 @@ namespace BokBonCheck
var htmlContent = await response.Content.ReadAsStringAsync(); 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) if (resultCount == -1)
{ {
@@ -122,15 +123,17 @@ namespace BokBonCheck
return result; return result;
} }
private int ExtractBookCount(string htmlContent, out string errmessage) private int ExtractBookCount(string htmlContent, out string errmessage, out string resulthtml)
{ {
errmessage = string.Empty; errmessage = string.Empty;
resulthtml = string.Empty;
try try
{ {
// 검색 결과가 없다는 메시지 확인 // 검색 결과가 없다는 메시지 확인
if (htmlContent.Contains("0권(개)") || htmlContent.Contains("검색결과가 없습니다")) if (htmlContent.Contains("0권(개)") || htmlContent.Contains("검색결과가 없습니다"))
{ {
errmessage = "검색결과없음"; errmessage = "검색결과없음";
resulthtml = "검색결과없음";
return 0; return 0;
} }
@@ -153,14 +156,19 @@ namespace BokBonCheck
if (count == 0) if (count == 0)
{ {
errmessage = "검색결과없음"; errmessage = "검색결과없음";
resulthtml = match.Value;
return 0; return 0;
} }
// 매칭된 부분과 그 상위 태그를 찾아서 저장
resulthtml = ExtractResultContext(htmlContent, match);
errmessage = $"검색성공({count}권)"; errmessage = $"검색성공({count}권)";
return count; return count;
} }
} }
} }
// 패턴을 찾지 못한 경우
resulthtml = "검색결과 패턴을 찾을 수 없음";
errmessage = "결과수량을찾을수없음"; errmessage = "결과수량을찾을수없음";
return -1; return -1;
@@ -168,6 +176,7 @@ namespace BokBonCheck
catch (Exception ex) catch (Exception ex)
{ {
errmessage = ex.Message; errmessage = ex.Message;
resulthtml = "검색결과 패턴을 찾을 수 없음";
return -1; return -1;
} }
} }
@@ -178,5 +187,67 @@ namespace BokBonCheck
// HTTP 방식에서는 즉시 응답이 오므로 대기 불필요 // HTTP 방식에서는 즉시 응답이 오므로 대기 불필요
await Task.CompletedTask; 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", "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-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("Connection", "keep-alive");
request.Headers.Add("Upgrade-Insecure-Requests", "1"); request.Headers.Add("Upgrade-Insecure-Requests", "1");
@@ -92,7 +92,8 @@ namespace BokBonCheck
var htmlContent = await response.Content.ReadAsStringAsync(); 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) if (resultCount == -1)
{ {
@@ -120,9 +121,10 @@ namespace BokBonCheck
return result; return result;
} }
private int ExtractBookCount(string htmlContent, out string errorMessage) private int ExtractBookCount(string htmlContent, out string errorMessage, out string resulthtml)
{ {
errorMessage = string.Empty; errorMessage = string.Empty;
resulthtml = string.Empty;
try try
{ {
@@ -134,12 +136,21 @@ namespace BokBonCheck
{ {
if (int.TryParse(match.Groups[1].Value, out int count)) 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}권)"; errorMessage = $"검색성공({count}권)";
return count; return count;
} }
else else
{ {
errorMessage = $"수량값오류({match.Groups[1].Value})"; errorMessage = $"수량값오류({match.Groups[1].Value})";
resulthtml = match.Value;
return -1; return -1;
} }
} }
@@ -153,11 +164,21 @@ namespace BokBonCheck
{ {
if (int.TryParse(alternateMatch.Groups[1].Value, out int count)) 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}권)"; errorMessage = $"검색성공({count}권)";
return count; return count;
} }
} }
// 패턴을 찾지 못한 경우
resulthtml = "검색결과 패턴을 찾을 수 없음";
errorMessage = "검색결과 패턴을 찾을 수 없음"; errorMessage = "검색결과 패턴을 찾을 수 없음";
return -1; return -1;
} }
@@ -165,6 +186,7 @@ namespace BokBonCheck
catch (Exception ex) catch (Exception ex)
{ {
errorMessage = $"결과 분석 오류: {ex.Message}"; errorMessage = $"결과 분석 오류: {ex.Message}";
resulthtml = "검색결과 패턴을 찾을 수 없음";
return -1; return -1;
} }
} }
@@ -173,5 +195,67 @@ namespace BokBonCheck
{ {
throw new NotImplementedException(); 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

@@ -119,9 +119,16 @@ namespace WindowsFormsApp1
char[] columnSplitter = { '\t' }; char[] columnSplitter = { '\t' };
//get the text from clipboard //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 //split it into lines
//20230209 \r텝 기능과 \n 줄넘김 기능을 같이 공백 제거 처리해버려 공백칸을 활용해야 함에도 제거하는 현상 발생. //20230209 \r텝 기능과 \n 줄넘김 기능을 같이 공백 제거 처리해버려 공백칸을 활용해야 함에도 제거하는 현상 발생.
//텝 공백 문자열 동시에 사용하여 분류 //텝 공백 문자열 동시에 사용하여 분류
@@ -292,7 +299,8 @@ namespace WindowsFormsApp1
string[] db_data = db_res1.Split('|'); string[] db_data = db_res1.Split('|');
string[] db_pur = db_res2.Split('|'); string[] db_pur = db_res2.Split('|');
if (db_res1.Length < 3) { if (db_res1.Length < 3)
{
MessageBox.Show("DB호출 에러!", "Error"); MessageBox.Show("DB호출 에러!", "Error");
return "False"; return "False";
} }
@@ -300,20 +308,26 @@ namespace WindowsFormsApp1
string fax = string.Empty; string fax = string.Empty;
string emchk = string.Empty; string emchk = string.Empty;
if (db_pur.Length > 3) { if (db_pur.Length > 3)
{
for (int a = 0; a < db_pur.Length; a++) for (int a = 0; a < db_pur.Length; a++)
{ {
if (a % 3 == 0) { // 전화번호 if (a % 3 == 0)
if (db_pur[a] != "") { { // 전화번호
if (db_pur[a] != "")
{
tel = db_pur[a]; tel = db_pur[a];
} }
} }
if (a % 3 == 1) { // 팩스 if (a % 3 == 1)
if (db_pur[a] != "") { { // 팩스
if (db_pur[a] != "")
{
fax = db_pur[a]; fax = db_pur[a];
} }
} }
if (a % 3 == 2) { // 팩스 이메일 체크 if (a % 3 == 2)
{ // 팩스 이메일 체크
emchk = db_pur[a]; emchk = db_pur[a];
} }
} }
@@ -1453,9 +1467,11 @@ namespace WindowsFormsApp1
private void checkDir(string localFullPathFile) private void checkDir(string localFullPathFile)
{ {
FileInfo finfo = new FileInfo(localFullPathFile); FileInfo finfo = new FileInfo(localFullPathFile);
if (!finfo.Exists) { if (!finfo.Exists)
{
DirectoryInfo dInfo = new DirectoryInfo(finfo.DirectoryName); DirectoryInfo dInfo = new DirectoryInfo(finfo.DirectoryName);
if (!dInfo.Exists) { if (!dInfo.Exists)
{
dInfo.Create(); dInfo.Create();
} }
} }
@@ -1556,26 +1572,31 @@ namespace WindowsFormsApp1
num.Add(array_text[a].Substring(0, 3)); 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); array_text[a] = array_text[a].Remove(0, 3);
} }
else { else
{
array_text[a] = array_text[a].Remove(0, 5); array_text[a] = array_text[a].Remove(0, 5);
} }
+= array_text[a] + "\n"; += array_text[a] + "\n";
int textLength = 0; int textLength = 0;
if (EncodingType == "UTF-8") { if (EncodingType == "UTF-8")
{
textLength = Encoding.UTF8.GetBytes(array_text[a]).Length textLength = Encoding.UTF8.GetBytes(array_text[a]).Length
- WordCheck(array_text[a], "▲") - WordCheck(array_text[a], "▲")
- WordCheck(array_text[a], "▼"); - WordCheck(array_text[a], "▼");
} }
else if (EncodingType == "UniCode") { else if (EncodingType == "UniCode")
{
textLength = Encoding.Unicode.GetBytes(array_text[a]).Length textLength = Encoding.Unicode.GetBytes(array_text[a]).Length
- WordCheck(array_text[a], "▲") - WordCheck(array_text[a], "▲")
- WordCheck(array_text[a], "▼"); - WordCheck(array_text[a], "▼");
} }
else { // ANSI else
{ // ANSI
textLength = Encoding.Default.GetBytes(array_text[a]).Length textLength = Encoding.Default.GetBytes(array_text[a]).Length
- WordCheck(array_text[a], "▲") - WordCheck(array_text[a], "▲")
- WordCheck(array_text[a], "▼"); - WordCheck(array_text[a], "▼");
@@ -1587,7 +1608,8 @@ namespace WindowsFormsApp1
for (int a = 0; a < array_text.Count; a++) for (int a = 0; a < array_text.Count; a++)
{ {
if (a == 0) { //total.Add("0"); if (a == 0)
{ //total.Add("0");
tTotal.Add(0); tTotal.Add(0);
} }
else else
@@ -2188,7 +2210,8 @@ namespace WindowsFormsApp1
/// <param name="e">EventArgs</param> /// <param name="e">EventArgs</param>
public void Int_Comma(object sender, EventArgs e) public void Int_Comma(object sender, EventArgs e)
{ {
if (((TextBox)sender).Text != "") { if (((TextBox)sender).Text != "")
{
string text; string text;
text = ((TextBox)sender).Text.Replace(",", ""); text = ((TextBox)sender).Text.Replace(",", "");
((TextBox)sender).Text = String.Format("{0:#,###}", Convert.ToInt32(text)); ((TextBox)sender).Text = String.Format("{0:#,###}", Convert.ToInt32(text));
@@ -2326,7 +2349,8 @@ namespace WindowsFormsApp1
xml = CheckString(xml, "〈"); xml = CheckString(xml, "〈");
doc.LoadXml(xml); doc.LoadXml(xml);
} }
catch (Exception ex){ catch (Exception ex)
{
return ""; return "";
} }
var json = JsonConvert.SerializeXmlNode(doc); var json = JsonConvert.SerializeXmlNode(doc);
@@ -2520,19 +2544,23 @@ namespace WindowsFormsApp1
{ {
if (length == 1) if (length == 1)
{ {
try { try
{
tmp_data.Add(docs[Param[b]]["#text"]); tmp_data.Add(docs[Param[b]]["#text"]);
} }
catch (KeyNotFoundException e) { catch (KeyNotFoundException e)
{
tmp_data.Add(""); tmp_data.Add("");
} }
} }
else else
{ {
try { try
{
tmp_data.Add(docs[a][Param[b]]["#text"]); tmp_data.Add(docs[a][Param[b]]["#text"]);
} }
catch (KeyNotFoundException e) { catch (KeyNotFoundException e)
{
tmp_data.Add(""); tmp_data.Add("");
} }
} }

View File

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

View File

@@ -28,7 +28,8 @@
/// </summary> /// </summary>
private void InitializeComponent() 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.panel1 = new System.Windows.Forms.Panel();
this.chkShowBrowser = new System.Windows.Forms.CheckBox(); this.chkShowBrowser = new System.Windows.Forms.CheckBox();
this.chkRetryErrData = 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_PW = new System.Windows.Forms.Label();
this.lbl_ID = new System.Windows.Forms.Label(); this.lbl_ID = new System.Windows.Forms.Label();
this.dv1 = new System.Windows.Forms.DataGridView(); this.dv1 = new System.Windows.Forms.DataGridView();
this.book_name = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.contextMenuStrip1 = new System.Windows.Forms.ContextMenuStrip(this.components);
this.book_comp = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.Count = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.dvc_remark = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.btn_ApplyFilter = new System.Windows.Forms.Button(); this.btn_ApplyFilter = new System.Windows.Forms.Button();
this.panel3 = new System.Windows.Forms.Panel(); this.panel3 = new System.Windows.Forms.Panel();
this.panel6 = 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_GridReset = new System.Windows.Forms.Button();
this.btn_OpenMemo = new System.Windows.Forms.Button(); this.btn_OpenMemo = new System.Windows.Forms.Button();
this.chk_spChar = new System.Windows.Forms.CheckBox(); 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.panel1.SuspendLayout();
this.groupBox1.SuspendLayout(); this.groupBox1.SuspendLayout();
this.panel2.SuspendLayout(); this.panel2.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.dv1)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.dv1)).BeginInit();
this.contextMenuStrip1.SuspendLayout();
this.panel3.SuspendLayout(); this.panel3.SuspendLayout();
this.panel6.SuspendLayout(); this.panel6.SuspendLayout();
this.statusStrip1.SuspendLayout(); this.statusStrip1.SuspendLayout();
this.panel4.SuspendLayout(); this.panel4.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.nudAddDelay)).BeginInit();
this.SuspendLayout(); this.SuspendLayout();
// //
// panel1 // panel1
// //
this.panel1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; 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.chkShowBrowser);
this.panel1.Controls.Add(this.chkRetryErrData); this.panel1.Controls.Add(this.chkRetryErrData);
this.panel1.Controls.Add(this.groupBox1); this.panel1.Controls.Add(this.groupBox1);
@@ -90,7 +101,7 @@
this.panel1.Dock = System.Windows.Forms.DockStyle.Top; this.panel1.Dock = System.Windows.Forms.DockStyle.Top;
this.panel1.Location = new System.Drawing.Point(0, 34); this.panel1.Location = new System.Drawing.Point(0, 34);
this.panel1.Name = "panel1"; 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; this.panel1.TabIndex = 0;
// //
// chkShowBrowser // chkShowBrowser
@@ -98,7 +109,7 @@
this.chkShowBrowser.AutoSize = true; this.chkShowBrowser.AutoSize = true;
this.chkShowBrowser.Checked = true; this.chkShowBrowser.Checked = true;
this.chkShowBrowser.CheckState = System.Windows.Forms.CheckState.Checked; 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.Name = "chkShowBrowser";
this.chkShowBrowser.Size = new System.Drawing.Size(96, 16); this.chkShowBrowser.Size = new System.Drawing.Size(96, 16);
this.chkShowBrowser.TabIndex = 8; this.chkShowBrowser.TabIndex = 8;
@@ -125,7 +136,7 @@
this.groupBox1.Controls.Add(this.radTargetAll); this.groupBox1.Controls.Add(this.radTargetAll);
this.groupBox1.Location = new System.Drawing.Point(318, 34); this.groupBox1.Location = new System.Drawing.Point(318, 34);
this.groupBox1.Name = "groupBox1"; 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.TabIndex = 6;
this.groupBox1.TabStop = false; this.groupBox1.TabStop = false;
this.groupBox1.Text = "검색대상"; this.groupBox1.Text = "검색대상";
@@ -133,7 +144,7 @@
// radTargetErr // radTargetErr
// //
this.radTargetErr.AutoSize = true; 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.Name = "radTargetErr";
this.radTargetErr.Size = new System.Drawing.Size(47, 16); this.radTargetErr.Size = new System.Drawing.Size(47, 16);
this.radTargetErr.TabIndex = 9; this.radTargetErr.TabIndex = 9;
@@ -143,7 +154,7 @@
// radTargetEmpty // radTargetEmpty
// //
this.radTargetEmpty.AutoSize = true; 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.Name = "radTargetEmpty";
this.radTargetEmpty.Size = new System.Drawing.Size(47, 16); this.radTargetEmpty.Size = new System.Drawing.Size(47, 16);
this.radTargetEmpty.TabIndex = 8; this.radTargetEmpty.TabIndex = 8;
@@ -153,7 +164,7 @@
// radTargetErrEmpty // radTargetErrEmpty
// //
this.radTargetErrEmpty.AutoSize = true; 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.Name = "radTargetErrEmpty";
this.radTargetErrEmpty.Size = new System.Drawing.Size(77, 16); this.radTargetErrEmpty.Size = new System.Drawing.Size(77, 16);
this.radTargetErrEmpty.TabIndex = 7; this.radTargetErrEmpty.TabIndex = 7;
@@ -209,7 +220,7 @@
// //
this.btn_Start.Location = new System.Drawing.Point(470, 4); this.btn_Start.Location = new System.Drawing.Point(470, 4);
this.btn_Start.Name = "btn_Start"; 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.TabIndex = 2;
this.btn_Start.Text = "검색시작"; this.btn_Start.Text = "검색시작";
this.btn_Start.UseVisualStyleBackColor = true; this.btn_Start.UseVisualStyleBackColor = true;
@@ -217,7 +228,7 @@
// //
// btn_Stop // 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.Name = "btn_Stop";
this.btn_Stop.Size = new System.Drawing.Size(75, 24); this.btn_Stop.Size = new System.Drawing.Size(75, 24);
this.btn_Stop.TabIndex = 2; this.btn_Stop.TabIndex = 2;
@@ -227,7 +238,7 @@
// //
// btn_Close // 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.Name = "btn_Close";
this.btn_Close.Size = new System.Drawing.Size(75, 24); this.btn_Close.Size = new System.Drawing.Size(75, 24);
this.btn_Close.TabIndex = 2; this.btn_Close.TabIndex = 2;
@@ -270,14 +281,14 @@
this.panel2.Dock = System.Windows.Forms.DockStyle.Top; this.panel2.Dock = System.Windows.Forms.DockStyle.Top;
this.panel2.Location = new System.Drawing.Point(0, 0); this.panel2.Location = new System.Drawing.Point(0, 0);
this.panel2.Name = "panel2"; 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; this.panel2.TabIndex = 0;
// //
// btn_SiteDenote // 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.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.TabIndex = 4;
this.btn_SiteDenote.Text = "사이트 표출"; this.btn_SiteDenote.Text = "사이트 표출";
this.btn_SiteDenote.UseVisualStyleBackColor = true; this.btn_SiteDenote.UseVisualStyleBackColor = true;
@@ -307,55 +318,52 @@
this.dv1.BackgroundColor = System.Drawing.SystemColors.Control; this.dv1.BackgroundColor = System.Drawing.SystemColors.Control;
this.dv1.BorderStyle = System.Windows.Forms.BorderStyle.None; this.dv1.BorderStyle = System.Windows.Forms.BorderStyle.None;
this.dv1.ColumnHeadersBorderStyle = System.Windows.Forms.DataGridViewHeaderBorderStyle.Single; this.dv1.ColumnHeadersBorderStyle = System.Windows.Forms.DataGridViewHeaderBorderStyle.Single;
dataGridViewCellStyle2.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter; dataGridViewCellStyle1.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter;
dataGridViewCellStyle2.BackColor = System.Drawing.SystemColors.Control; dataGridViewCellStyle1.BackColor = System.Drawing.SystemColors.Control;
dataGridViewCellStyle2.Font = new System.Drawing.Font("굴림", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(129))); dataGridViewCellStyle1.Font = new System.Drawing.Font("굴림", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
dataGridViewCellStyle2.ForeColor = System.Drawing.SystemColors.WindowText; dataGridViewCellStyle1.ForeColor = System.Drawing.SystemColors.WindowText;
dataGridViewCellStyle2.SelectionBackColor = System.Drawing.SystemColors.Highlight; dataGridViewCellStyle1.SelectionBackColor = System.Drawing.SystemColors.Highlight;
dataGridViewCellStyle2.SelectionForeColor = System.Drawing.SystemColors.HighlightText; dataGridViewCellStyle1.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
dataGridViewCellStyle2.WrapMode = System.Windows.Forms.DataGridViewTriState.True; dataGridViewCellStyle1.WrapMode = System.Windows.Forms.DataGridViewTriState.True;
this.dv1.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle2; this.dv1.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle1;
this.dv1.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; this.dv1.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.dv1.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { this.dv1.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
this.book_name, this.book_name,
this.book_comp, this.book_comp,
this.Count, 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.Dock = System.Windows.Forms.DockStyle.Fill;
this.dv1.Location = new System.Drawing.Point(0, 0); this.dv1.Location = new System.Drawing.Point(0, 0);
this.dv1.Name = "dv1"; this.dv1.Name = "dv1";
this.dv1.RowTemplate.Height = 23; 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.TabIndex = 1;
this.dv1.RowPostPaint += new System.Windows.Forms.DataGridViewRowPostPaintEventHandler(this.dataGridView1_RowPostPaint); this.dv1.RowPostPaint += new System.Windows.Forms.DataGridViewRowPostPaintEventHandler(this.dataGridView1_RowPostPaint);
this.dv1.KeyDown += new System.Windows.Forms.KeyEventHandler(this.dataGridView1_KeyDown); this.dv1.KeyDown += new System.Windows.Forms.KeyEventHandler(this.dataGridView1_KeyDown);
// //
// book_name // contextMenuStrip1
// //
this.book_name.HeaderText = "도서명(총서명)"; this.contextMenuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.book_name.Name = "book_name"; this.ToolStripMenuItem,
this.book_name.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable; this.ToolStripMenuItem});
this.book_name.Width = 300; this.contextMenuStrip1.Name = "contextMenuStrip1";
this.contextMenuStrip1.Size = new System.Drawing.Size(215, 48);
// //
// book_comp // 유니코드문자로붙여넝ㅎ기ToolStripMenuItem
// //
this.book_comp.HeaderText = "출판사"; this.ToolStripMenuItem.Name = "유니코드문자로붙여넝ㅎ기ToolStripMenuItem";
this.book_comp.Name = "book_comp"; this.ToolStripMenuItem.Size = new System.Drawing.Size(214, 22);
this.book_comp.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable; this.ToolStripMenuItem.Text = "유니코드 문자로 붙여넣기";
this.book_comp.Width = 120; this.ToolStripMenuItem.Click += new System.EventHandler(this.ToolStripMenuItem_Click);
// //
// Count // 일반문자로붙여넣기ToolStripMenuItem
// //
this.Count.HeaderText = "검색 수"; this.ToolStripMenuItem.Name = "일반문자로붙여넣기ToolStripMenuItem";
this.Count.Name = "Count"; this.ToolStripMenuItem.Size = new System.Drawing.Size(214, 22);
this.Count.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable; this.ToolStripMenuItem.Text = "일반 문자로 붙여넣기";
this.Count.Width = 50; this.ToolStripMenuItem.Click += new System.EventHandler(this.ToolStripMenuItem_Click);
//
// dvc_remark
//
this.dvc_remark.HeaderText = "비고";
this.dvc_remark.Name = "dvc_remark";
this.dvc_remark.Width = 110;
// //
// btn_ApplyFilter // btn_ApplyFilter
// //
@@ -376,7 +384,7 @@
this.panel3.Dock = System.Windows.Forms.DockStyle.Fill; this.panel3.Dock = System.Windows.Forms.DockStyle.Fill;
this.panel3.Location = new System.Drawing.Point(0, 0); this.panel3.Location = new System.Drawing.Point(0, 0);
this.panel3.Name = "panel3"; 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; this.panel3.TabIndex = 3;
// //
// panel6 // panel6
@@ -386,7 +394,7 @@
this.panel6.Dock = System.Windows.Forms.DockStyle.Fill; this.panel6.Dock = System.Windows.Forms.DockStyle.Fill;
this.panel6.Location = new System.Drawing.Point(0, 174); this.panel6.Location = new System.Drawing.Point(0, 174);
this.panel6.Name = "panel6"; 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; this.panel6.TabIndex = 3;
// //
// statusStrip1 // statusStrip1
@@ -396,7 +404,7 @@
this.lbSite}); this.lbSite});
this.statusStrip1.Location = new System.Drawing.Point(0, 542); this.statusStrip1.Location = new System.Drawing.Point(0, 542);
this.statusStrip1.Name = "statusStrip1"; 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.TabIndex = 2;
this.statusStrip1.Text = "statusStrip1"; this.statusStrip1.Text = "statusStrip1";
// //
@@ -424,7 +432,7 @@
this.panel4.Dock = System.Windows.Forms.DockStyle.Top; this.panel4.Dock = System.Windows.Forms.DockStyle.Top;
this.panel4.Location = new System.Drawing.Point(0, 140); this.panel4.Location = new System.Drawing.Point(0, 140);
this.panel4.Name = "panel4"; 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; this.panel4.TabIndex = 2;
// //
// chk_RemoveBrit // chk_RemoveBrit
@@ -477,11 +485,60 @@
this.chk_spChar.Text = "특수문자 제거"; this.chk_spChar.Text = "특수문자 제거";
this.chk_spChar.UseVisualStyleBackColor = true; 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 // Check_copyWD
// //
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 12F); this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 12F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 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.Controls.Add(this.panel3);
this.Name = "Check_copyWD"; this.Name = "Check_copyWD";
this.Text = "복본조사(New)"; this.Text = "복본조사(New)";
@@ -494,6 +551,7 @@
this.panel2.ResumeLayout(false); this.panel2.ResumeLayout(false);
this.panel2.PerformLayout(); this.panel2.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.dv1)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.dv1)).EndInit();
this.contextMenuStrip1.ResumeLayout(false);
this.panel3.ResumeLayout(false); this.panel3.ResumeLayout(false);
this.panel6.ResumeLayout(false); this.panel6.ResumeLayout(false);
this.panel6.PerformLayout(); this.panel6.PerformLayout();
@@ -501,6 +559,7 @@
this.statusStrip1.PerformLayout(); this.statusStrip1.PerformLayout();
this.panel4.ResumeLayout(false); this.panel4.ResumeLayout(false);
this.panel4.PerformLayout(); this.panel4.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.nudAddDelay)).EndInit();
this.ResumeLayout(false); this.ResumeLayout(false);
} }
@@ -536,13 +595,19 @@
private System.Windows.Forms.RadioButton radTargetErr; private System.Windows.Forms.RadioButton radTargetErr;
private System.Windows.Forms.RadioButton radTargetEmpty; private System.Windows.Forms.RadioButton radTargetEmpty;
private System.Windows.Forms.RadioButton radTargetErrEmpty; 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.CheckBox chkRetryErrData;
public System.Windows.Forms.GroupBox groupBox1; public System.Windows.Forms.GroupBox groupBox1;
public System.Windows.Forms.CheckBox chkShowBrowser; public System.Windows.Forms.CheckBox chkShowBrowser;
private System.Windows.Forms.ToolStripStatusLabel lbSite; 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 System.Linq;
using UniMarc; using UniMarc;
using System.Globalization; using System.Globalization;
using System.Threading.Tasks;
namespace WindowsFormsApp1.Mac namespace WindowsFormsApp1.Mac
{ {
@@ -39,6 +40,9 @@ namespace WindowsFormsApp1.Mac
main = _main; main = _main;
db.DBcon(); db.DBcon();
// 도서관별 지연시간 설정 초기화
_delaySettings = new Helper_LibraryDelaySettings();
var idx = 1; var idx = 1;
//도서검색(크롤링) //도서검색(크롤링)
_searchService = new BookSearchService(); _searchService = new BookSearchService();
@@ -450,6 +454,7 @@ namespace WindowsFormsApp1.Mac
private readonly BookSearchService _searchService; private readonly BookSearchService _searchService;
private bool _isSearching = false; private bool _isSearching = false;
private readonly Helper_LibraryDelaySettings _delaySettings;
private async void InitializeChromeDriver() private async void InitializeChromeDriver()
{ {
@@ -612,7 +617,10 @@ namespace WindowsFormsApp1.Mac
int cnt_ok = 0; int cnt_ok = 0;
int cnt_ng = 0; int cnt_ng = 0;
int cnt_er = 0; int cnt_er = 0;
int addDelay = (int)nudAddDelay.Value * 1000; //추가 지연시간 : 1개의 레코드 처리 후 추가 지연한다.
// 현재 지연시간을 도서관별로 저장
SaveLibraryDelaySettings(searcher.SiteName, (int)nudAddDelay.Value);
RetrySearch: RetrySearch:
foreach (DataGridViewRow drow in this.dv1.Rows) foreach (DataGridViewRow drow in this.dv1.Rows)
@@ -620,6 +628,10 @@ namespace WindowsFormsApp1.Mac
if (this._isSearching == false) if (this._isSearching == false)
{ {
drow.Cells["dvc_remark"].Value = "Cancel"; drow.Cells["dvc_remark"].Value = "Cancel";
if (dv1.Columns.Contains("dvc_resulthtml"))
{
drow.Cells["dvc_resulthtml"].Value = "";
}
break; break;
} }
@@ -630,6 +642,10 @@ namespace WindowsFormsApp1.Mac
{ {
cnt_skip += 1; cnt_skip += 1;
drow.Cells["dvc_remark"].Value = "No Title"; 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.BackColor = Color.HotPink;
drow.DefaultCellStyle.ForeColor = Color.Blue; drow.DefaultCellStyle.ForeColor = Color.Blue;
continue; continue;
@@ -660,6 +676,13 @@ namespace WindowsFormsApp1.Mac
{ {
drow.Cells["dvc_remark"].Value = rlt.ErrorMessage; drow.Cells["dvc_remark"].Value = rlt.ErrorMessage;
drow.Cells["Count"].Value = $"{rlt.BookCount}"; 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) if (rlt.BookCount == 0)
{ {
cnt_ng += 1; cnt_ng += 1;
@@ -677,9 +700,18 @@ namespace WindowsFormsApp1.Mac
{ {
cnt_er += 1; cnt_er += 1;
drow.Cells["dvc_remark"].Value = $"Error|{rlt.ErrorMessage}"; 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.BackColor = Color.HotPink;
drow.DefaultCellStyle.ForeColor = Color.Blue; drow.DefaultCellStyle.ForeColor = Color.Blue;
} }
if(addDelay > 0)
await Task.Delay(addDelay);
} }
if (_isSearching == true && retry == false && chkRetryErrData.Checked) if (_isSearching == true && retry == false && chkRetryErrData.Checked)
@@ -861,6 +893,12 @@ namespace WindowsFormsApp1.Mac
dv1.Rows[a].DefaultCellStyle.BackColor = Color.White; dv1.Rows[a].DefaultCellStyle.BackColor = Color.White;
dv1.Rows[a].Cells["Count"].Value = ""; dv1.Rows[a].Cells["Count"].Value = "";
dv1.Rows[a].Cells["dvc_remark"].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; chkShowBrowser.Enabled = !searcher.HttpApiMode;
this.lbSite.Text = $"[{searcher.AreaCode}] {searcher.SiteUrl}"; 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"> <metadata name="dvc_remark.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value> <value>True</value>
</metadata> </metadata>
<metadata name="statusStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> <metadata name="dvc_resulthtml.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>17, 17</value> <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>
<metadata name="statusStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> <metadata name="statusStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value> <value>17, 17</value>