using System; using System.Threading.Tasks; using OpenQA.Selenium; using OpenQA.Selenium.Chrome; using OpenQA.Selenium.Support.UI; using System.Text.RegularExpressions; using WebDriverManager; using WebDriverManager.DriverConfigs.Impl; using System.IO; using System.Runtime.InteropServices; using System.Threading; using OpenQA.Selenium.Chromium; using UniMarc.SearchModel; using System.Runtime.CompilerServices; using AR; namespace BokBonCheck { public class DLSSearcher : ILibrarySearcher { public string AreaCode { get; set; } = string.Empty; public bool HttpApiMode { get; set; } = false; public string SiteName { get; protected set; } = "DLS"; public string SiteUrl => "https://dls1.edunet.net/DLS/bookMng/bookMain"; public int No { get; set; } private ChromiumDriver _driver; public DLSSearcher(int no) { this.No = no; } public void StopDriver() { if (_driver != null) { _driver.Quit(); _driver.Dispose(); _driver = null; } } public async Task StartDriver(bool showBrowser = false) { if (_driver == null) { try { if (SeleniumHelper.IsReady == false) await SeleniumHelper.Download(); _driver = await SeleniumHelper.CreateDriver(ShowBrowser: showBrowser); Console.WriteLine("DLSSearcher Driver 초기화 완료"); } catch (Exception ex) { Console.WriteLine($"DLSSearcher Driver 초기화 실패: {ex.Message}"); throw new InvalidOperationException($"DLSSearcher Driver 초기화에 실패했습니다: {ex.Message}", ex); } } } virtual protected bool SelectLibrary(WebDriverWait wait) { IWebElement searchBox = null; try { var selector = "#clickAll"; searchBox = wait.Until(d => d.FindElement(By.CssSelector(selector))); if (searchBox == null) return false; if (searchBox.Selected == false) { SafeClick(searchBox); } return true; } catch (Exception ex) { Console.WriteLine(ex.Message); return false; } } protected void SafeClick(IWebElement searchBox) { // 안정적인 클릭을 위한 여러 방법 시도 try { // 1. JavaScript로 클릭 시도 var driver = ((IWrapsDriver)searchBox).WrappedDriver; ((IJavaScriptExecutor)driver).ExecuteScript("arguments[0].click();", searchBox); } catch { try { // 2. 요소가 보이도록 스크롤 후 클릭 var driver = ((IWrapsDriver)searchBox).WrappedDriver; ((IJavaScriptExecutor)driver).ExecuteScript("arguments[0].scrollIntoView(true);", searchBox); Thread.Sleep(500); searchBox.Click(); } catch { try { // 3. Actions 클래스 사용 var driver = ((IWrapsDriver)searchBox).WrappedDriver; var actions = new OpenQA.Selenium.Interactions.Actions(driver); actions.MoveToElement(searchBox).Click().Perform(); } catch { // 4. 마지막 방법: JavaScript로 직접 체크 해제 var driver = ((IWrapsDriver)searchBox).WrappedDriver; ((IJavaScriptExecutor)driver).ExecuteScript("arguments[0].checked = false;", searchBox); } } } } public async Task<(Boolean, string)> Login(string id, string pw, bool showBrowser = false) { // 드라이버가 없으면 자동으로 시작 if (_driver == null) { await StartDriver(showBrowser); } if (_driver.Url.StartsWith("data:")) { _driver.Navigate().GoToUrl(SiteUrl); } var wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(15)); if (_driver.Url.EndsWith("loginMain")) { IWebElement idBox = wait.Until(d => d.FindElement(By.CssSelector("#lgID"))); if (idBox == null) { return (false, "로그인화면(ID찾기실패)"); } idBox.Clear(); idBox.SendKeys(id); IWebElement pwBox = wait.Until(t => t.FindElement(By.CssSelector("#lgPW"))); if (idBox == null) { return (false, "로그인화면(PW찾기실패)"); } pwBox.Clear(); pwBox.SendKeys(pw); //로그인버튼찾기 IWebElement btLogin = wait.Until(t => t.FindElement(By.CssSelector("#loginBtn"))); if (idBox == null) { return (false, "로그인화면(로그인버튼찾기실패)"); } SafeClick(btLogin); } wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(15)); var retrycnt = 0; while (true) { if (_driver.Url.EndsWith("/bookMain") == false) { retrycnt += 1; if (retrycnt > 3) { UTIL.MsgE("3회 연속 로그인 시도로 인해 자동 로그인을 중단합니다\n사용자가 직접 로그인을 하세요"); break; } Console.WriteLine($"도서검색화면으로 이동({retrycnt})"); _driver.Navigate().GoToUrl(this.SiteUrl); wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(15)); await Task.Delay(1000); } else break; } Console.WriteLine("DLS 로그인 완료"); return (true, ""); } string ID, PW, SCHOOL, STYPE; public void SetParameter(string id, string pw, string schoolname, string searchtype) { this.ID = id; this.PW = pw; this.SCHOOL = schoolname; this.STYPE = searchtype; } string BookMainURL = string.Empty; public async Task SearchAsync(string searchTerm) { var result = new BookSearchResult { SiteName = SiteName, SearchTerm = searchTerm, SearchTime = DateTime.Now }; try { // 드라이버가 없으면 자동으로 시작 if (_driver == null) { await StartDriver(); } if (string.IsNullOrEmpty(searchTerm.Trim())) { result.ErrorMessage = "검색어없음"; result.BookCount = -1; result.IsSuccess = false; return result; } //현재url이 로그인 uRL이라면 로그인을 먼저한다 //https://dls1.edunet.net/DLS/loginMain //ID : #lgID , PW: #lgPW, Button : #loginBtn if (_driver.Url.EndsWith("loginMain")) { result.ErrorMessage = "로그인안됨"; result.BookCount = -1; result.IsSuccess = false; return result; } var retcnt = 0; while (_driver.Url.EndsWith("/bookMain") == false) { retcnt += 1; if (retcnt > 3) { //3회이상 실패하면 오류 처리한다. result.ErrorMessage = "검색페이지이동불가"; result.BookCount = -1; result.IsSuccess = false; return result; } else { //검색페이지로 이동시키고 _driver.Navigate().GoToUrl(this.SiteUrl); new WebDriverWait(_driver, TimeSpan.FromSeconds(15)); await Task.Delay(1000); } } if (string.IsNullOrEmpty(BookMainURL)) { BookMainURL = _driver.Url; } else { //확실하게 하기위해 메인페이지로 다시 이동한다. _driver.Navigate().GoToUrl(this.SiteUrl); new WebDriverWait(_driver, TimeSpan.FromSeconds(15)); await Task.Delay(500); } // 페이지 로딩 대기 var wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(15)); //초등학교명 일치 확인 try { var selectSchool = wait.Until(t => t.FindElement(By.CssSelector("#current_school_select"))); if (selectSchool == null) { result.ErrorMessage = "학교선택칸없음"; result.BookCount = -1; result.IsSuccess = false; return result; } else { Console.WriteLine($"학교명:{this.SCHOOL}"); var select = new SelectElement(selectSchool); var curTxt = select.SelectedOption.Text; var curVal = select.SelectedOption.GetAttribute("value"); if (curTxt.Contains(this.SCHOOL) == false && this.SCHOOL.Contains(curTxt) == false) { select.SelectByText(this.SCHOOL); } else { //이름이 포함되어있다. Console.WriteLine($"지정학교:{this.SCHOOL}, 현재선택학교:{curTxt}"); } } } catch (Exception ex) { Console.WriteLine($"학교명일치확인 실패 {ex.Message}"); } //검색방법(ISBN,서명) 확인 #search_field_first, title, ea_isbn var selectType = wait.Until(t => t.FindElement(By.CssSelector("#search_field_first"))); if (selectType == null) { result.ErrorMessage = "검색방법없음"; result.BookCount = -1; result.IsSuccess = false; return result; } else { var select = new SelectElement(selectType); var curTxt = select.SelectedOption.Text; var curVal = select.SelectedOption.GetAttribute("value"); Console.WriteLine($"지정방법:{this.STYPE}, 현재선택방법:{curVal}"); if (this.STYPE.Equals(curVal) == false) select.SelectByValue(this.STYPE); } //검색어입력 var tbSearch = wait.Until(t => t.FindElement(By.CssSelector("#search_first"))); if (tbSearch == null) { result.ErrorMessage = "검색어입력칸없음"; result.BookCount = -1; result.IsSuccess = false; return result; } else { tbSearch.Clear(); tbSearch.SendKeys(searchTerm); } wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(15)); await Task.Delay(200); //검색실행 var butSearch = wait.Until(t => t.FindElement(By.CssSelector("#book_search_btn"))); SafeClick(butSearch); // 페이지 변경을 감지하는 메서드 await WaitForPageChange(new WebDriverWait(_driver, TimeSpan.FromSeconds(15))); // 검색 결과 수 추출 var resultCount = ExtractBookCount(_driver, searchTerm, out string ermsg); if (resultCount == -1) { result.BookCount = 0; result.IsSuccess = false; result.ErrorMessage = ermsg; } else { result.BookCount = resultCount; result.IsSuccess = true; result.ErrorMessage = ermsg; } } catch (Exception ex) { result.IsSuccess = false; result.ErrorMessage = ex.Message; result.BookCount = 0; } return result; } private int ExtractBookCount(IWebDriver driver, string searchTerm, out string errmessage) { errmessage = string.Empty; try { var msgElement = driver.FindElement(By.CssSelector(".modal-body[name='msg_txt']")); if (msgElement != null) { string messageText = msgElement.Text; bool hasNoDataMessage = messageText.Contains("검색된 자료가 없습니다"); if (hasNoDataMessage) { //다이얼로를 닫아준다. //< button type = "button" class="btn" name="msg_btn_1">닫기 try { var buttomelm = driver.FindElement(By.CssSelector("button[name='msg_btn_1']")); if (buttomelm != null) { SafeClick(buttomelm); var wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(15)); Thread.Sleep(500); } } catch (Exception ex) { Console.WriteLine(ex.Message); } errmessage = "검색결과없음"; return 0; } } } catch (Exception ex) { Console.WriteLine(ex.Message); } try { // result 클래스 div에서 텍스트 추출 var resultElement = driver.FindElement(By.CssSelector("div.result")); if (resultElement != null) { string resultText = resultElement.Text; // "1 - 30 of 684" // 정규식으로 마지막 숫자 추출 var match = System.Text.RegularExpressions.Regex.Match(resultText, @"of\s+(\d+)"); if (match.Success) { int totalCount = int.Parse(match.Groups[1].Value); Console.WriteLine($"전체 책 개수: {totalCount}"); errmessage = ""; return totalCount; } else { errmessage = "수량추출실패"; return -1; } } } catch (Exception ex) { Console.WriteLine(ex.Message); } errmessage = "결과확인실패"; return -1; } // 페이지 변경을 감지하는 메서드 public async Task WaitForPageChange(WebDriverWait wait) { try { await Task.Delay(500); // 방법 4: 페이지 로딩 상태 확인 wait.Until(d => { var readyState = ((IJavaScriptExecutor)d).ExecuteScript("return document.readyState"); return readyState.Equals("complete"); }); // 방법 5: 특정 텍스트가 페이지에 나타날 때까지 대기 wait.Until(d => { try { var elm = d.FindElement(By.CssSelector(".modal-body[name='msg_txt']")); if (elm == null) { //모달창이 없을때 //#pagersearch_book_grid > div > div:nth-child(1) > div var byclassnameRlt = By.CssSelector("#pagersearch_book_grid > div > div:nth-child(1) > div > strong:nth-child(2)"); var elmRlt = d.FindElement(byclassnameRlt); if (elmRlt == null) { return false; } else { Console.WriteLine($"찾은갯수:{elmRlt.Text}"); return true; } } else { var pageText = elm.Text; if (pageText.Contains("검색된 자료가 없습니다")) return true; else return false; } //return pageText.Contains("에 대하여") && pageText.Contains("검색되었습니다"); } catch (Exception ex) { //Console.WriteLine(ex.Message); //return false; } try { //모달창이 없을때 //#pagersearch_book_grid > div > div:nth-child(1) > div var byclassnameRlt = By.CssSelector("#pagersearch_book_grid > div > div:nth-child(1) > div > strong:nth-child(2)"); var elmRlt = d.FindElement(byclassnameRlt); if (elmRlt == null) { return false; } else { Console.WriteLine($"찾은갯수:{elmRlt.Text}"); return true; } //return pageText.Contains("에 대하여") && pageText.Contains("검색되었습니다"); } catch (Exception ex) { //Console.WriteLine(ex.Message); //return false; } return false; }); } catch (Exception ex) { // 모든 감지 방법이 실패하면 최소한의 대기 시간 적용 await Task.Delay(2000); throw new Exception($"페이지 변경 감지 실패: {ex.Message}"); } } } }