This commit is contained in:
2025-07-06 21:15:19 +09:00
parent d2018c0143
commit eeb3c2f845
6 changed files with 216 additions and 110 deletions

3
BokBonCheck/.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"dotnet.preferCSharpExtension": true
}

View File

@@ -22,6 +22,18 @@ namespace BokBonCheck
_searchService = new BookSearchService();
InitializeUI();
InitializeChromeDriver();
//_searchService.AddSearcher(new KwangjuCityLibrarySearcher_central());
//_searchService.AddSearcher(new KwangjuCityLibrarySearcher_choisangjun());
//_searchService.AddSearcher(new KwangjuCityLibrarySearcher_dagachi());
//_searchService.AddSearcher(new KwangjuCityLibrarySearcher_Indi());
//_searchService.AddSearcher(new KwangjuCityLibrarySearcher_Student());
//_searchService.AddSearcher(new KwangjuCityLibrarySearcher_Kumho());
//_searchService.AddSearcher(new NamguLibrarySearcher_Children());
//_searchService.AddSearcher(new NamguLibrarySearcher_Hyocheon());
//_searchService.AddSearcher(new NamguLibrarySearcher_Munhwa());
_searchService.AddSearcher(new NamguLibrarySearcher_Purungil());
//_searchService.AddSearcher(new NamguLibrarySearcher_Smart());
}
private async void InitializeChromeDriver()

View File

@@ -23,18 +23,6 @@ namespace BokBonCheck
{
_searchers = new List<ILibrarySearcher>
{
new KwangjuCityLibrarySearcher_central(),
new KwangjuCityLibrarySearcher_choisangjun(),
new KwangjuCityLibrarySearcher_dagachi(),
new KwangjuCityLibrarySearcher_Indi(),
new KwangjuCityLibrarySearcher_Student(),
new KwangjuCityLibrarySearcher_Kumho(),
new NamguLibrarySearcher_Children(),
new NamguLibrarySearcher_Hyocheon(),
new NamguLibrarySearcher_Munhwa(),
new NamguLibrarySearcher_Purungil(),
new NamguLibrarySearcher_Smart(),
};
}
@@ -44,8 +32,10 @@ namespace BokBonCheck
foreach (var searcher in _searchers)
{
searcher.StartDriver();
var result = await searcher.SearchAsync(searchTerm);
results.Add(result);
//searcher.StopDriver();
}
return results;

View File

@@ -1,3 +1,4 @@
using OpenQA.Selenium.Support.UI;
using System.Threading.Tasks;
namespace BokBonCheck
@@ -7,5 +8,9 @@ namespace BokBonCheck
string SiteName { get; }
string SiteUrl { get; }
Task<BookSearchResult> SearchAsync(string searchTerm);
void StartDriver();
void StopDriver();
Task WaitForPageChange(WebDriverWait wait);
}
}

View File

@@ -7,6 +7,7 @@ using System.Text.RegularExpressions;
using WebDriverManager;
using WebDriverManager.DriverConfigs.Impl;
using System.IO;
using System.Runtime.InteropServices;
namespace BokBonCheck
{
@@ -65,6 +66,54 @@ namespace BokBonCheck
protected string SelectorValue = "";
private ChromeDriver _driver;
private ChromeDriverService _service;
[DllImport("user32.dll")]
private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll")]
private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
private const int SW_MINIMIZE = 6;
public void StartDriver()
{
if (_driver == null)
{
var driverPath = ChromeDriverManager.GetDriverPath();
if (string.IsNullOrEmpty(driverPath) || !File.Exists(driverPath))
{
driverPath = ChromeDriverManager.SetupChromeDriverAsync().GetAwaiter().GetResult();
}
_service = ChromeDriverService.CreateDefaultService(Path.GetDirectoryName(driverPath), Path.GetFileName(driverPath));
_service.HideCommandPromptWindow = true;
var options = new ChromeOptions();
options.AddArgument("--no-sandbox");
options.AddArgument("--disable-dev-shm-usage");
options.AddArgument("--disable-gpu");
options.AddArgument("--window-size=1920,1080");
options.AddArgument("--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36");
options.AddArgument("--disable-blink-features=AutomationControlled");
options.AddExcludedArgument("enable-automation");
options.AddAdditionalOption("useAutomationExtension", false);
_driver = new ChromeDriver(_service, options);
((IJavaScriptExecutor)_driver).ExecuteScript("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})");
}
}
public void StopDriver()
{
if (_driver != null)
{
_driver.Quit();
_driver.Dispose();
_driver = null;
}
if (_service != null)
{
_service.Dispose();
_service = null;
}
}
virtual protected void SelectLibrary(WebDriverWait wait)
{
@@ -92,44 +141,22 @@ namespace BokBonCheck
SearchTime = DateTime.Now
};
IWebDriver driver = null;
ChromeDriverService service = null;
try
{
// ChromeDriverManager에서 설정한 드라이버 경로 사용
var driverPath = ChromeDriverManager.GetDriverPath();
if (string.IsNullOrEmpty(driverPath) || !File.Exists(driverPath))
{
// 드라이버가 없으면 다시 설정
driverPath = await ChromeDriverManager.SetupChromeDriverAsync();
}
if (_driver == null)
throw new InvalidOperationException("드라이버가 시작되지 않았습니다. StartDriver()를 먼저 호출하세요.");
// ChromeDriver 서비스 설정
service = ChromeDriverService.CreateDefaultService(Path.GetDirectoryName(driverPath), Path.GetFileName(driverPath));
service.HideCommandPromptWindow = true;
var options = new ChromeOptions();
options.AddArgument("--headless");
options.AddArgument("--no-sandbox");
options.AddArgument("--disable-dev-shm-usage");
options.AddArgument("--disable-gpu");
options.AddArgument("--window-size=1920,1080");
options.AddArgument("--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36");
options.AddArgument("--disable-blink-features=AutomationControlled");
options.AddExcludedArgument("enable-automation");
options.AddAdditionalOption("useAutomationExtension", false);
// 명시적으로 서비스와 옵션을 사용하여 드라이버 생성
driver = new ChromeDriver(service, options);
// 자동화 감지 방지
((IJavaScriptExecutor)driver).ExecuteScript("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})");
driver.Navigate().GoToUrl(SiteUrl);
_driver.Navigate().GoToUrl(SiteUrl);
// 페이지 로딩 대기
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(15));
var wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(15));
// 모든 감지 방법이 끝나면 크롬창 최소화
IntPtr chromeWindow = FindWindow("Chrome_WidgetWin_1", null);
if (chromeWindow != IntPtr.Zero)
{
ShowWindow(chromeWindow, SW_MINIMIZE);
}
//대상도서관 선택
SelectLibrary(wait);
@@ -173,7 +200,7 @@ namespace BokBonCheck
}
// 혹시 readonly라면 속성 제거
((IJavaScriptExecutor)driver).ExecuteScript("arguments[0].removeAttribute('readonly')", searchBox);
((IJavaScriptExecutor)_driver).ExecuteScript("arguments[0].removeAttribute('readonly')", searchBox);
// 입력 전 clear
searchBox.Clear();
@@ -187,12 +214,11 @@ namespace BokBonCheck
{
"input[type='submit']",
};
//#frm_sch > fieldset > div > div.search_style > div > input
foreach (var selector in buttonSelectors)
{
try
{
searchButton = driver.FindElement(By.CssSelector(selector));
searchButton = _driver.FindElement(By.CssSelector(selector));
break;
}
catch
@@ -217,11 +243,11 @@ namespace BokBonCheck
searchBox.SendKeys(Keys.Enter);
}
// 검색 결과 로딩 대기
await Task.Delay(3000);
// 페이지 변경을 감지하는 메서드
await WaitForPageChange(new WebDriverWait(_driver, TimeSpan.FromSeconds(15)));
// 검색 결과 수 추출
var resultCount = ExtractBookCount(driver);
var resultCount = ExtractBookCount(_driver);
result.BookCount = resultCount;
result.IsSuccess = true;
@@ -232,12 +258,6 @@ namespace BokBonCheck
result.ErrorMessage = ex.Message;
result.BookCount = 0;
}
finally
{
driver?.Quit();
driver?.Dispose();
service?.Dispose();
}
return result;
}
@@ -262,6 +282,34 @@ namespace BokBonCheck
return 0;
}
}
}
// 페이지 변경을 감지하는 메서드
public async Task WaitForPageChange(WebDriverWait wait)
{
try
{
// 방법 4: 페이지 로딩 상태 확인
wait.Until(d =>
{
var readyState = ((IJavaScriptExecutor)d).ExecuteScript("return document.readyState");
return readyState.Equals("complete");
});
// 방법 5: 특정 텍스트가 페이지에 나타날 때까지 대기
wait.Until(d =>
{
var pageText = d.FindElement(By.TagName("body")).Text;
return pageText.Contains("전체") || pageText.Contains("건") || pageText.Contains("검색결과");
});
}
catch (Exception ex)
{
// 모든 감지 방법이 실패하면 최소한의 대기 시간 적용
await Task.Delay(2000);
throw new Exception($"페이지 변경 감지 실패: {ex.Message}");
}
}
}
}

View File

@@ -7,6 +7,7 @@ using System.Text.RegularExpressions;
using WebDriverManager;
using WebDriverManager.DriverConfigs.Impl;
using System.IO;
using System.Runtime.InteropServices;
namespace BokBonCheck
{
@@ -24,7 +25,7 @@ namespace BokBonCheck
{
var selector = "#clickAll";
searchBox = wait.Until(d => d.FindElement(By.CssSelector(selector)));
if (searchBox != null && searchBox.Selected==true)
if (searchBox != null && searchBox.Selected == true)
{
searchBox.Click(); // 전체체크박스가 체크되어 있으면 클릭해서 해제
}
@@ -32,7 +33,7 @@ namespace BokBonCheck
selector = "#libMA";
searchBox = wait.Until(d => d.FindElement(By.CssSelector(selector)));
if (searchBox != null && searchBox.Selected==false)
if (searchBox != null && searchBox.Selected == false)
{
searchBox.Click(); // 문화관 체크박스 선택
}
@@ -47,7 +48,7 @@ namespace BokBonCheck
{
public NamguLibrarySearcher_Purungil()
{
SiteName = "남구통합도서관(푸른길도서관)";
SiteName = "남구통합도서관(푸른길도서관)";
}
protected override void SelectLibrary(WebDriverWait wait)
@@ -76,12 +77,11 @@ namespace BokBonCheck
}
}
}
public class NamguLibrarySearcher_Children : NamguLibrarySearcher
{
public NamguLibrarySearcher_Children()
{
SiteName = "남구통합도서관(청소년도서관)";
SiteName = "남구통합도서관(청소년도서관)";
}
protected override void SelectLibrary(WebDriverWait wait)
@@ -110,7 +110,6 @@ namespace BokBonCheck
}
}
}
public class NamguLibrarySearcher_Hyocheon : NamguLibrarySearcher
{
public NamguLibrarySearcher_Hyocheon()
@@ -148,7 +147,7 @@ namespace BokBonCheck
{
public NamguLibrarySearcher_Smart()
{
SiteName = "남구통합도서관(스마트도서관)";
SiteName = "남구통합도서관(스마트도서관)";
}
protected override void SelectLibrary(WebDriverWait wait)
@@ -177,12 +176,61 @@ namespace BokBonCheck
}
}
}
public class NamguLibrarySearcher : ILibrarySearcher
{
public string SiteName { get; protected set; } = "남구통합도서관(전체)";
public string SiteUrl => "https://lib.namgu.gwangju.kr/main/bookSearch";
private ChromeDriver _driver;
private ChromeDriverService _service;
[DllImport("user32.dll")]
private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll")]
private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
private const int SW_MINIMIZE = 6;
public void StartDriver()
{
if (_driver == null)
{
var driverPath = ChromeDriverManager.GetDriverPath();
if (string.IsNullOrEmpty(driverPath) || !File.Exists(driverPath))
{
driverPath = ChromeDriverManager.SetupChromeDriverAsync().GetAwaiter().GetResult();
}
_service = ChromeDriverService.CreateDefaultService(Path.GetDirectoryName(driverPath), Path.GetFileName(driverPath));
_service.HideCommandPromptWindow = true;
var options = new ChromeOptions();
options.AddArgument("--no-sandbox");
options.AddArgument("--disable-dev-shm-usage");
options.AddArgument("--disable-gpu");
options.AddArgument("--window-size=1920,1080");
options.AddArgument("--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36");
options.AddArgument("--disable-blink-features=AutomationControlled");
options.AddExcludedArgument("enable-automation");
options.AddAdditionalOption("useAutomationExtension", false);
_driver = new ChromeDriver(_service, options);
((IJavaScriptExecutor)_driver).ExecuteScript("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})");
}
}
public void StopDriver()
{
if (_driver != null)
{
_driver.Quit();
_driver.Dispose();
_driver = null;
}
if (_service != null)
{
_service.Dispose();
_service = null;
}
}
virtual protected void SelectLibrary(WebDriverWait wait)
{
IWebElement searchBox = null;
@@ -211,44 +259,22 @@ namespace BokBonCheck
SearchTime = DateTime.Now
};
IWebDriver driver = null;
ChromeDriverService service = null;
try
{
// ChromeDriverManager에서 설정한 드라이버 경로 사용
var driverPath = ChromeDriverManager.GetDriverPath();
if (string.IsNullOrEmpty(driverPath) || !File.Exists(driverPath))
{
// 드라이버가 없으면 다시 설정
driverPath = await ChromeDriverManager.SetupChromeDriverAsync();
}
if (_driver == null)
throw new InvalidOperationException("드라이버가 시작되지 않았습니다. StartDriver()를 먼저 호출하세요.");
// ChromeDriver 서비스 설정
service = ChromeDriverService.CreateDefaultService(Path.GetDirectoryName(driverPath), Path.GetFileName(driverPath));
service.HideCommandPromptWindow = true;
var options = new ChromeOptions();
options.AddArgument("--headless");
options.AddArgument("--no-sandbox");
options.AddArgument("--disable-dev-shm-usage");
options.AddArgument("--disable-gpu");
options.AddArgument("--window-size=1920,1080");
options.AddArgument("--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36");
options.AddArgument("--disable-blink-features=AutomationControlled");
options.AddExcludedArgument("enable-automation");
options.AddAdditionalOption("useAutomationExtension", false);
// 명시적으로 서비스와 옵션을 사용하여 드라이버 생성
driver = new ChromeDriver(service, options);
// 자동화 감지 방지
((IJavaScriptExecutor)driver).ExecuteScript("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})");
driver.Navigate().GoToUrl(SiteUrl);
_driver.Navigate().GoToUrl(SiteUrl);
// 페이지 로딩 대기
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(15));
var wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(15));
// 모든 감지 방법이 끝나면 크롬창 최소화
IntPtr chromeWindow = FindWindow("Chrome_WidgetWin_1", null);
if (chromeWindow != IntPtr.Zero)
{
ShowWindow(chromeWindow, SW_MINIMIZE);
}
//대상도서관 선택
SelectLibrary(wait);
@@ -312,7 +338,7 @@ namespace BokBonCheck
{
try
{
searchButton = driver.FindElement(By.CssSelector(selector));
searchButton = _driver.FindElement(By.CssSelector(selector));
break;
}
catch
@@ -337,11 +363,11 @@ namespace BokBonCheck
searchBox.SendKeys(Keys.Enter);
}
// 검색 결과 로딩 대기
await Task.Delay(3000);
// 페이지 변경을 감지하는 메서드
await WaitForPageChange(new WebDriverWait(_driver, TimeSpan.FromSeconds(15)));
// 검색 결과 수 추출
var resultCount = ExtractBookCount(driver);
var resultCount = ExtractBookCount(_driver);
result.BookCount = resultCount;
result.IsSuccess = true;
@@ -352,13 +378,7 @@ namespace BokBonCheck
result.ErrorMessage = ex.Message;
result.BookCount = 0;
}
finally
{
driver?.Quit();
driver?.Dispose();
service?.Dispose();
}
return result;
}
@@ -382,5 +402,33 @@ namespace BokBonCheck
return 0;
}
}
// 페이지 변경을 감지하는 메서드
public async Task WaitForPageChange(WebDriverWait wait)
{
try
{
// 방법 4: 페이지 로딩 상태 확인
wait.Until(d =>
{
var readyState = ((IJavaScriptExecutor)d).ExecuteScript("return document.readyState");
return readyState.Equals("complete");
});
// 방법 5: 특정 텍스트가 페이지에 나타날 때까지 대기
wait.Until(d =>
{
var pageText = d.FindElement(By.TagName("body")).Text;
return pageText.Contains("전체") || pageText.Contains("건") || pageText.Contains("검색결과");
});
}
catch (Exception ex)
{
// 모든 감지 방법이 실패하면 최소한의 대기 시간 적용
await Task.Delay(2000);
throw new Exception($"페이지 변경 감지 실패: {ex.Message}");
}
}
}
}