From 017958d2a8bf2a4e791ffb58ac49952e3de1f6eb Mon Sep 17 00:00:00 2001 From: Envy_PC Date: Mon, 9 Dec 2024 16:42:37 +0900 Subject: [PATCH] test_OK --- requirements.txt | Bin 464 -> 468 bytes src/databaseManager.py | 41 +++++++-- src/excel_export.py | 28 +++---- src/gui.py | 4 +- src/playwright_thread.py | 41 +++++---- src/post_processor.py | 49 +++++++++-- src/xlsProcessingThread.py | 7 +- src/xlsSerachThread.py | 168 +++++++++++++++++++++++++++++++++++++ xls_db.db | Bin 12288 -> 12288 bytes 9 files changed, 286 insertions(+), 52 deletions(-) create mode 100644 src/xlsSerachThread.py diff --git a/requirements.txt b/requirements.txt index 5387d35f0ceae7f8e6e8a6f34fdf2651a03bdeee..0d02bd26aed3e60a1c30f0ab8192a067024d5837 100644 GIT binary patch delta 41 tcmcb>e1&;K6r*ekLn;s!FvK&IFcdK)GUPE7Gvom25{7(+qRGXKu>j_A3Vr|p delta 37 rcmcb@e1UmG6r-dvg91YdLlHwFLmophLk^HGVMt{tV5pp2&KL^-vib=z diff --git a/src/databaseManager.py b/src/databaseManager.py index 6ae677c..49847f0 100644 --- a/src/databaseManager.py +++ b/src/databaseManager.py @@ -35,14 +35,21 @@ class DatabaseManager(): except sqlite3.Error as e: self.logger.log(f"테이블 생성 중 오류 발생: {e}", level=logging.ERROR, exc_info=True) - def insert_items(self, items): + def insert_items(self, items, db_name=None): + """ + 데이터를 데이터베이스에 삽입하는 메서드. + :param items: 삽입할 데이터 리스트 [(id, pc_url, name, price, image_url, sales), ...] + :param db_name: 사용할 데이터베이스 경로 (기본값은 self.db_path) + """ + db_path = db_name or self.db_path # db_name이 전달되면 해당 값을 사용, 없으면 self.db_path 사용 try: - with sqlite3.connect(self.db_path, check_same_thread=False) as conn: - conn.executemany('''INSERT OR REPLACE INTO items (id, pc_url, name, price, image_url, sales) + with sqlite3.connect(db_path, check_same_thread=False) as conn: + conn.executemany('''INSERT OR REPLACE INTO items + (id, pc_url, name, price, image_url, sales) VALUES (?, ?, ?, ?, ?, ?)''', items) - self.logger.log(f"{len(items)}개의 항목이 데이터베이스에 저장되었습니다.", level=logging.DEBUG) + self.logger.log(f"{len(items)}개의 항목이 데이터베이스({db_path})에 저장되었습니다.", level=logging.DEBUG) except sqlite3.Error as e: - self.logger.log(f"데이터베이스 저장 중 오류 발생: {e}", level=logging.ERROR, exc_info=True) + self.logger.log(f"데이터베이스({db_path}) 저장 중 오류 발생: {e}", level=logging.ERROR, exc_info=True) # def fetch_all(self): # try: @@ -96,6 +103,30 @@ class DatabaseManager(): except sqlite3.Error as e: self.logger.log(f"상품 {product['id']} 업데이트 중 오류 발생: {e}", level=logging.ERROR, exc_info=True) + def update_item_by_field(self, product: Dict, db_name: str = None): + """ + 처리된 상품 정보를 데이터베이스에 업데이트. + :param product: 업데이트할 상품 정보 딕셔너리 (필드: 값) + """ + try: + # 데이터베이스 경로 설정 + db_path = db_name or self.db_path + + self.logger.log(f"update_item_by_field - db_path : {db_path}", level=logging.DEBUG) + + # 업데이트할 필드와 값 생성 + update_fields = ", ".join([f"{key} = ?" for key in product.keys() if key != "id"]) + update_values = tuple(product[key] for key in product.keys() if key != "id") + update_query = f"UPDATE items SET {update_fields} WHERE id = ?" + + with sqlite3.connect(db_path, check_same_thread=False) as conn: + conn.execute(update_query, update_values + (product["id"],)) + conn.commit() + + self.logger.log(f"상품 {product['id']}이(가) 성공적으로 업데이트되었습니다.", level=logging.DEBUG) + except sqlite3.Error as e: + self.logger.log(f"상품 {product.get('id', 'Unknown')} 업데이트 중 오류 발생: {e}", level=logging.ERROR, exc_info=True) + def close(self): # 별도의 연결을 사용하는 구조에서는 close 메서드가 필요하지 않을 수 있습니다. self.logger.log(f"DBManager 종료", level=logging.DEBUG) diff --git a/src/excel_export.py b/src/excel_export.py index 389b43c..3b53a6b 100644 --- a/src/excel_export.py +++ b/src/excel_export.py @@ -6,10 +6,10 @@ import xlwings as xw import logging from PySide6.QtWidgets import QMessageBox -logger = logging.getLogger(__name__) class ExcelExporter: - def __init__(self, db_manager, base_excel_path="src\\baseXLS_Percenty.xlsx"): + def __init__(self, logger, db_manager, base_excel_path="src\\baseXLS_Percenty.xlsx"): + self.logger =logger self.db_manager = db_manager # DBManager 인스턴스 self.base_excel_path = base_excel_path self.saved_files = [] @@ -17,44 +17,44 @@ class ExcelExporter: def fetch_data_from_db(self): try: df = self.db_manager.fetch_all() # DBManager 인스턴스를 사용하여 데이터 가져오기 - logger.debug("DB에서 데이터 로드 완료") + self.logger.log(f"DB에서 데이터 로드 완료", level=logging.DEBUG) return df except Exception as e: - logger.error(f"DB에서 데이터 로드 중 오류 발생: {e}") + self.logger.log(f"DB에서 데이터 로드 중 오류 발생: {e}", level=logging.ERROR, exc_info=True) return pd.DataFrame() def save_to_excel(self, output_path="output.xlsx"): df = self.fetch_data_from_db() if df.empty: - logger.warning("DB에서 불러온 데이터가 없습니다.") + self.logger.log(f"DB에서 불러온 데이터가 없습니다.", level=logging.WARNING) return False # 성공 여부 반환 # 조건에 맞는 데이터 필터링 filtered_df = df[(df['is_valid'] == 1) & (df['is_export'] == 0)] if filtered_df.empty: - logger.warning("조건에 맞는 데이터가 없습니다.") + self.logger.log(f"조건에 맞는 데이터가 없습니다.", level=logging.WARNING) return False # 성공 여부 반환 app = xw.App(visible=False) - logger.debug("xlwings 시작") + self.logger.log(f"xlwings 시작", level=logging.DEBUG) try: for i in range(0, len(df), 50): df_subset = df.iloc[i:i+50] - logger.debug(f"{i}번째 출력할 데이터:\n{df_subset}") # 데이터 검증 로그 추가 + self.logger.log(f"{i}번째 출력할 데이터:\n{df_subset}", level=logging.DEBUG) # 데이터 검증 로그 추가 part_file_name = output_path.replace('.xlsx', f'_part{i//50 + 1}.xlsx') shutil.copy(self.base_excel_path, part_file_name) - logger.debug(f"기본 엑셀 파일 '{self.base_excel_path}' 복사 완료") + self.logger.log(f"기본 엑셀 파일 '{self.base_excel_path}' 복사 완료", level=logging.DEBUG) wb = xw.Book(part_file_name) ws = wb.sheets['multi_ss'] for index, row in df_subset.iterrows(): row_num = 4 + (index % 50) - logger.debug(f"{index + 1}번째 행 기록 시작: B{row_num}, C{row_num}, D{row_num}, F{row_num}, G{row_num}, H{row_num}") # 셀 위치 로그 추가 + self.logger.log(f"{index + 1}번째 행 기록 시작: B{row_num}, C{row_num}, D{row_num}, F{row_num}, G{row_num}, H{row_num}", level=logging.DEBUG) # 셀 위치 로그 추가 ws.range(f'B{row_num}').value = row['pc_url'] ws.range(f'C{row_num}').value = row['name'] ws.range(f'D{row_num}').value = row['price'] @@ -74,17 +74,17 @@ class ExcelExporter: 'is_export': 1 # is_export를 1로 설정 }) - logger.debug(f"{index + 1}번째 행 기록 완료") + self.logger.log(f"{index + 1}번째 행 기록 완료", level=logging.DEBUG) wb.save(part_file_name) # SaveCopyAs 대신 save 사용 wb.close() self.saved_files.append(part_file_name) - logger.info(f"파일 '{part_file_name}'에 데이터가 저장되었습니다.") + self.logger.log(f"파일 '{part_file_name}'에 데이터가 저장되었습니다.", level=logging.INFO) return True # 성공 여부 반환 except Exception as e: - logger.error(f"엑셀 저장 중 예외 발생: {e}", exc_info=True) + self.logger.log(f"엑셀 저장 중 예외 발생: {e}", level=logging.ERROR, exc_info=True) return False # 실패 시 False 반환 finally: app.quit() - logger.debug("xlwings 종료") + self.logger.log(f"xlwings 종료", level=logging.DEBUG) diff --git a/src/gui.py b/src/gui.py index 4995694..7c543b7 100644 --- a/src/gui.py +++ b/src/gui.py @@ -38,10 +38,10 @@ class TaobaoScraperApp(QWidget): self.setLayout(self.layout) - self.playwright_thread = PlaywrightThread(self.db_manager) + self.playwright_thread = PlaywrightThread(self.logger, self.db_manager) self.playwright_thread.data_collected.connect(self.on_data_collected) - self.excel_exporter = ExcelExporter(self.db_manager) + self.excel_exporter = ExcelExporter(self.logger, self.db_manager) self.postProcessor = PostProcessor(self.logger, self.db_manager) @Slot() diff --git a/src/playwright_thread.py b/src/playwright_thread.py index d484fc7..a56dace 100644 --- a/src/playwright_thread.py +++ b/src/playwright_thread.py @@ -7,13 +7,12 @@ import re from PySide6.QtCore import QThread, Signal from playwright.async_api import async_playwright, Page -logger = logging.getLogger(__name__) - class PlaywrightThread(QThread): data_collected = Signal(bool, str) - def __init__(self, db_manager): + def __init__(self, logger, db_manager): super().__init__() + self.logger = logger self.db_manager = db_manager async def collect_data(self): @@ -26,7 +25,7 @@ class PlaywrightThread(QThread): else: browser_path = os.path.join(os.path.dirname(__file__), 'browsers', 'chromium-1112', 'chrome-win', 'chrome.exe') - logger.debug(f"브라우저 경로: {browser_path}") + self.logger.log(f"브라우저 경로: {browser_path}", level=logging.DEBUG) # 사용자 에이전트 설정 user_agent = random.choice([ @@ -36,7 +35,7 @@ class PlaywrightThread(QThread): "Mozilla/5.0 (Macintosh; Intel Mac OS X 12_0) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Safari/605.1.15", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 OPR/85.0.0.0", ]) - logger.debug(f"user_agent: {user_agent}") + self.logger.log(f"user_agent: {user_agent}", level=logging.DEBUG) # 브라우저 시작 (headless 모드) browser = await p.chromium.launch( @@ -60,11 +59,11 @@ class PlaywrightThread(QThread): # 페이지 열기 page = await context.new_page() await page.goto("https://world.taobao.com") - logger.info("타오바오 사이트에 접속했습니다.") + self.logger.log(f"타오바오 사이트에 접속했습니다.", level=logging.INFO) # 페이지 로딩 확인 및 pagedown await page.wait_for_selector(".tb-pick-content-item") # 상품 카드 로딩 대기 - logger.info("페이지 로딩 완료 - Pagedown을 두 번 누릅니다.") + self.logger.log(f"페이지 로딩 완료 - Pagedown을 두 번 누릅니다.", level=logging.INFO) await page.keyboard.press("PageDown") await page.wait_for_timeout(1000) # 1초 대기 await page.keyboard.press("PageDown") @@ -82,23 +81,23 @@ class PlaywrightThread(QThread): await browser.close() except Exception as e: - logger.error(f"브라우저 작업 중 오류 발생: {e}", exec=True) + self.logger.log(f"브라우저 작업 중 오류 발생: {e}", level=logging.ERROR, exc_info=True) self.data_collected.emit(False, f"오류 발생: {e}", exec=True) async def scrape_items(self, page: Page): try: items = await page.query_selector_all("div#ice-container div.tb-pick-feeds-container > div") - logger.debug(f"총 {len(items)}개의 상품 카드가 발견되었습니다.") + self.logger.log(f"총 {len(items)}개의 상품 카드가 발견되었습니다.", level=logging.DEBUG) items_data = [] for idx, item in enumerate(items): - logger.debug(f"{idx + 1}번째 상품 카드 처리 중 - XPath 확인") + self.logger.log(f"{idx + 1}번째 상품 카드 처리 중 - XPath 확인", level=logging.DEBUG) # a 태그를 명시적으로 선택하여 href 가져오기 link_element = await item.query_selector("a.item-link") if link_element: item_href = await link_element.get_attribute("href") - logger.debug(f"{idx + 1}번째 상품 href: {item_href}") + self.logger.log(f"{idx + 1}번째 상품 href: {item_href}", level=logging.DEBUG) if item_href: # 숫자만 추출하고 9~12자리 필터링 @@ -106,42 +105,42 @@ class PlaywrightThread(QThread): if item_id_match: item_id = item_id_match.group(1) pc_url = f"https://item.taobao.com/item.htm?id={item_id}" - logger.debug(f"{idx + 1}번째 상품 ID: {item_id}, 상품 URL: {pc_url}") + self.logger.log(f"{idx + 1}번째 상품 ID: {item_id}, 상품 URL: {pc_url}", level=logging.DEBUG) else: - logger.warning(f"{idx + 1}번째 상품 ID를 올바르게 추출할 수 없습니다.") + self.logger.log(f"{idx + 1}번째 상품 ID를 올바르게 추출할 수 없습니다.", level=logging.WARNING) continue else: - logger.warning(f"{idx + 1}번째 상품 ID를 가져올 수 없습니다.") + self.logger.log(f"{idx + 1}번째 상품 ID를 가져올 수 없습니다.", level=logging.WARNING) continue else: - logger.warning(f"{idx + 1}번째 상품의 a 태그를 찾을 수 없습니다.") + self.logger.log(f"{idx + 1}번째 상품의 a 태그를 찾을 수 없습니다.", level=logging.WARNING) continue name_element = await item.query_selector(".info-wrapper-title-text") name = await name_element.inner_text() if name_element else "N/A" - logger.debug(f"{idx + 1}번째 상품명: {name}") + self.logger.log(f"{idx + 1}번째 상품명: {name}", level=logging.DEBUG) price_element = await item.query_selector(".price-value") price = await price_element.inner_text() if price_element else "0" - logger.debug(f"{idx + 1}번째 상품 가격: {price}") + self.logger.log(f"{idx + 1}번째 상품 가격: {price}", level=logging.DEBUG) image_element = await item.query_selector(".img-wrapper") image_style = await image_element.get_attribute("style") if image_element else "" # image_url = image_style.split("url(")[-1].strip('")') if image_style else "N/A" image_url = (image_style.split("url(")[-1].strip('");').strip("'").replace("//", "https://", 1) if image_style else "N/A") - logger.debug(f"{idx + 1}번째 상품 이미지 URL: {image_url}") + self.logger.log(f"{idx + 1}번째 상품 이미지 URL: {image_url}", level=logging.DEBUG) sales_element = await item.query_selector(".month-sale") sales = await sales_element.inner_text() if sales_element else "0" - logger.debug(f"{idx + 1}번째 상품 판매량: {sales}") + self.logger.log(f"{idx + 1}번째 상품 판매량: {sales}", level=logging.DEBUG) items_data.append((item_id, pc_url, name, float(price), image_url, sales)) - logger.debug(f"수집된 상품 수 : {len(items_data)}") + self.logger.log(f"수집된 상품 수 : {len(items_data)}", level=logging.DEBUG) return items_data except Exception as e: - logger.error(f"데이터 수집 중 오류 발생: {e}") + self.logger.log(f"데이터 수집 중 오류 발생: {e}", level=logging.ERROR, exc_info=True) return None async def wait_for_user(self): diff --git a/src/post_processor.py b/src/post_processor.py index 5ee5920..860220a 100644 --- a/src/post_processor.py +++ b/src/post_processor.py @@ -12,9 +12,13 @@ from src.titleManager import TitleManager from src.categoryManager import CategoryManager from src.naver_parser import NaverParser from src.gpt_client import GPTClient +from src.xlsSerachThread import XlsSerachThread + import requests from bs4 import BeautifulSoup +import openpyxl + class PostProcessor: def __init__(self, logger, db_manager): self.logger = logger @@ -28,6 +32,8 @@ class PostProcessor: self.title_manager = TitleManager(self.logger, self.gpt) self.categoryManager = CategoryManager(self.logger, base_xls_path) self.naver_parser = NaverParser(self.logger) + + self.xlThread = XlsSerachThread(self.logger, self.db_manager) # 설정 파일 로드 self.config = configparser.ConfigParser() @@ -128,19 +134,21 @@ class PostProcessor: # self.logger.info(f"선택된 폴더: {selected_folder}") # self._post_by_XLS(selected_folder) - def post_by_XLS(self, folder_path): - import openpyxl + + async def post_by_XLS(self, folder_path): """ 주어진 폴더 경로에서 모든 엑셀 파일을 순회하며 데이터를 수집 및 DB에 저장. :param folder_path: 엑셀 파일이 위치한 폴더 경로 """ try: + # PlaywrightThread 초기화 및 브라우저 시작 + page = await self.xlThread.start_br() + # 폴더 내 모든 엑셀 파일 가져오기 excel_files = [f for f in os.listdir(folder_path) if f.endswith('.xls') or f.endswith('.xlsx')] if not excel_files: self.logger.log(f"엑셀 파일이 폴더 '{folder_path}'에 없습니다.", level=logging.WARNING) - return self.logger.log(f"총 {len(excel_files)}개의 엑셀 파일을 발견했습니다.", level=logging.DEBUG) @@ -160,6 +168,9 @@ class PostProcessor: # 데이터 추출 (B4~B53, C4~C53) items = [] + url_list = [] + row_map = {} + for row in range(4, 54): # 4번 행부터 53번 행까지 pc_url = sheet[f"B{row}"].value # PC_URL name = sheet[f"C{row}"].value # 상품명 @@ -169,20 +180,42 @@ class PostProcessor: continue id_value = self.parse_id_from_url(pc_url) # URL에서 ID 추출 - price, image_url = self.fetch_price_and_image(pc_url) # 가격 및 이미지 URL 수집 + if id_value: + url_list.append(pc_url) + row_map[pc_url] = (id_value, name, row) - if id_value and price and image_url: - items.append((id_value, pc_url, name, price, image_url, 0)) # sales는 0으로 설정 + # URL 목록 처리하여 가격 및 이미지 URL 수집 + for url in url_list: + result = await self.xlThread.goto_url_and_parsing(db_name, page, id_value, url) + + price = result.get("price") if result else None + image_url = result.get("image_url") if result else None + + if not price or not image_url: + self.logger.log(f"데이터 수집 실패: URL {url}", level=logging.WARNING) + continue + + self.logger.log(f"url: {url}", level=logging.DEBUG) + self.logger.log(f"price: {price}", level=logging.DEBUG) + self.logger.log(f"image_url: {image_url}", level=logging.DEBUG) + + id_value, name, row = row_map[url] + items.append((id_value, url, name, price, image_url, 0)) # sales는 0으로 설정 + + # 수집된 데이터 DB 저장 + self.logger.log(f"수집된 items: {items}", level=logging.DEBUG) if items: - self.db_manager.insert_items(items) + self.db_manager.insert_items(items, db_name) self.logger.log(f"{file_path}의 데이터를 DB에 저장했습니다.", level=logging.DEBUG) + # 처리 완료 후 브라우저 종료 + await self.xlThread.close_br() + except Exception as e: self.logger.log(f"엑셀 파일 처리 중 오류 발생: {file_path}, 오류: {e}", level=logging.ERROR, exc_info=True) # 처리되지 않은 상품 로드 및 후처리 - # products = self.db_manager.fetch_all().query("is_export == 0").to_dict('records') products = self.db_manager.fetch_all(db_path=db_name).query("is_export == 0").to_dict('records') self.logger.log(f"총 {len(products)}개의 처리되지 않은 상품 로드 완료.", level=logging.DEBUG) self.process_products(products) diff --git a/src/xlsProcessingThread.py b/src/xlsProcessingThread.py index 1ffce64..e07d225 100644 --- a/src/xlsProcessingThread.py +++ b/src/xlsProcessingThread.py @@ -1,5 +1,5 @@ from PySide6.QtCore import QThread, Signal - +import asyncio class XLSProcessingThread(QThread): progress = Signal(str) # 진행 상태를 GUI에 전달할 시그널 @@ -14,7 +14,10 @@ class XLSProcessingThread(QThread): """ try: self.progress.emit(f"'{self.folder_path}'에서 XLS 파일 처리 시작") - self.post_processor.post_by_XLS(self.folder_path) + + asyncio.run(self.post_processor.post_by_XLS(self.folder_path)) + + # self.post_processor.post_by_XLS(self.folder_path) self.progress.emit("XLS 파일 처리 완료") except Exception as e: self.progress.emit(f"XLS 파일 처리 중 오류 발생: {e}") diff --git a/src/xlsSerachThread.py b/src/xlsSerachThread.py new file mode 100644 index 0000000..46a02a4 --- /dev/null +++ b/src/xlsSerachThread.py @@ -0,0 +1,168 @@ +import os +import sys +import random +import logging +import re +from bs4 import BeautifulSoup + +from playwright.async_api import async_playwright + +class XlsSerachThread(): + + def __init__(self, logger, db_manager): + super().__init__() + self.logger = logger + self.db_manager = db_manager + self.page = None + self.browser = None # 브라우저 상태를 유지하기 위한 속성 + + async def start_br(self): + try: + + # Playwright 객체 초기화 + if not hasattr(self, "playwright") or not self.playwright: + self.playwright = await async_playwright().start() + + browser_path = None + if getattr(sys, 'frozen', False): + browser_path = os.path.join(os.path.dirname(sys.executable), 'browsers', 'chromium-1112', 'chrome-win', 'chrome.exe') + else: + browser_path = os.path.join(os.path.dirname(__file__), 'browsers', 'chromium-1112', 'chrome-win', 'chrome.exe') + + self.logger.log(f"브라우저 경로: {browser_path}", level=logging.DEBUG) + + # 사용자 에이전트 설정 + user_agent = random.choice([ + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.0.0", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:108.0) Gecko/20100101 Firefox/108.0", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 12_0) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Safari/605.1.15", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 OPR/85.0.0.0", + ]) + self.logger.log(f"user_agent: {user_agent}", level=logging.DEBUG) + + # 브라우저 시작 (headless 모드) + self.browser = await self.playwright.chromium.launch( + headless=True, # headless 모드로 설정 + executable_path=browser_path, + args=[ + '--disable-popup-blocking', + '--start-maximized', + '--window-size=1920,1080' + ] + ) + + # 시크릿 브라우저 컨텍스트 생성 + self.context = await self.browser.new_context( + user_agent=user_agent, + geolocation={"latitude": 37.5665, "longitude": 126.9780}, + locale="ko-KR", + permissions=["geolocation", "notifications"] + ) + + # 페이지 열기 + self.page = await self.context.new_page() + + return self.page + + except Exception as e: + self.logger.log(f"브라우저 작업 중 오류 발생: {e}", level=logging.ERROR, exc_info=True) + + + async def goto_url_and_parsing(self, db_name, page, id_value, url): + """ + URL로 이동 후 HTML을 파싱하여 데이터를 추출. + """ + try: + # URL로 이동 + await page.goto(url) + self.logger.log(f"{url} 이동 완료", level=logging.INFO) + + # 페이지 로드 완료 대기 + await page.wait_for_load_state("domcontentloaded") + + # # 페이지 HTML 가져오기 + # html = await page.content() + # soup = BeautifulSoup(html, "html.parser") + + await page.evaluate("window.scrollTo(0, document.body.scrollHeight)") + await page.wait_for_timeout(1000) # 스크롤 후 로드 대기 + + + await page.wait_for_selector("div.summaryInfoWrap--Ndc7k4Hv", timeout=7000) + html = await page.content() + soup = BeautifulSoup(html, "html.parser") + + # self.logger.log(f"soup: {soup}", level=logging.DEBUG) + + # summaryInfoWrap--Ndc7k4Hv 요소만 가져오기 + summary_info = soup.select_one("div.summaryInfoWrap--Ndc7k4Hv") + if not summary_info: + self.logger.log(f"요소 누락: summaryInfoWrap--Ndc7k4Hv, URL: {url}", level=logging.WARNING) + return None + + # self.logger.log(f"summary_info: {summary_info}", level=logging.DEBUG) + + # 이미지 URL 추출 + image_tag = summary_info.select_one("div.mainPicWrap--Ns5WQiHr img") + image_url = image_tag["src"] if image_tag else None + + # 가격 추출 + price_tag = summary_info.select_one("span.text--Mdqy24Ex") + fallback_price_tag = summary_info.select_one("span.unit--i1DKXW20 + span") + price = price_tag.text.strip() if price_tag else (fallback_price_tag.text.strip() if fallback_price_tag else None) + + # 상품명(title) 추출 + title_tag = summary_info.select_one("div.ItemTitle--UReZzEW5 h1.mainTitle--O1XCl8e2") + product_name = title_tag["title"].strip() if title_tag and "title" in title_tag.attrs else None + + # 데이터 검증 + if not image_url or not price or not product_name: + self.logger.log( + f"데이터 누락 발생: URL {url}, 이미지 URL: {image_url}, 가격: {price}, 상품명: {product_name}", + level=logging.WARNING, + ) + return None + + self.logger.log( + f"데이터 추출 완료: {url}, 이미지 URL: {image_url}, 가격: {price}, 상품명: {product_name}", + level=logging.INFO, + ) + + # # 필요한 정보만 데이터베이스 업데이트 + # self.db_manager.update_item_by_field({ + # "id": id_value, + # "image_url": image_url, + # "price": float(price.replace(",", "").strip("₩")) if price else None, + # "name": product_name, + # }, db_name) + # self.logger.log(f"데이터베이스 업데이트 완료: {url}", level=logging.INFO) + + + # 필요한 정보 반환 + # return {"image_url": image_url, "price": price, "product_name": product_name} + + return {"price": price, "image_url": image_url} + + except Exception as e: + self.logger.log(f"URL 처리 중 오류 발생: {url}, {e}", level=logging.ERROR, exc_info=True) + return None + + + async def close_br(self): + """ + 브라우저 종료 메서드. + """ + try: + if self.page and not self.page.is_closed(): + await self.page.close() + if self.context: + await self.context.close() + if self.browser: + await self.browser.close() + if self.playwright: + await self.playwright.stop() + + self.logger.log("브라우저와 Playwright 리소스가 성공적으로 종료되었습니다.", level=logging.DEBUG) + except Exception as e: + self.logger.log(f"브라우저 종료 중 오류 발생: {e}", level=logging.ERROR, exc_info=True) diff --git a/xls_db.db b/xls_db.db index f6ff0b4d425bb6ba7ddf88e97fb7f63574839434..6e294340d302602dc22851544a6702d0e9d941b2 100644 GIT binary patch literal 12288 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