import sys import os import random import asyncio import uuid from PySide6.QtWidgets import QApplication, QLabel, QMainWindow, QVBoxLayout, QWidget from PySide6.QtGui import QPixmap from playwright.async_api import async_playwright import qasync # ============================================================ # 1. PySide6 GUI: QR 이미지 표시 창 # ============================================================ class QRCodeWindow(QMainWindow): def __init__(self, img_bytes): super().__init__() self.setWindowTitle("타오바오 QR 코드 로그인") central_widget = QWidget() self.setCentralWidget(central_widget) layout = QVBoxLayout(central_widget) # QR 이미지를 표시할 QLabel self.label = QLabel() layout.addWidget(self.label) pixmap = QPixmap() if not pixmap.loadFromData(img_bytes): self.label.setText("이미지를 로드할 수 없습니다.") else: self.label.setPixmap(pixmap) # 이미지 크기에 따라 창 크기 조절 self.resize(pixmap.width(), pixmap.height()) # ============================================================ # 2. 로그인 페이지 접속 함수 (브라우저 실행 및 로그인 페이지 열기) # ============================================================ async def open_login_page(p): """ 제공해주신 브라우저 설정 코드를 사용하여 타오바오 로그인 페이지에 접속합니다. 반환값: browser, page, login_url """ # 브라우저 경로 설정 if getattr(sys, 'frozen', False): browser_path = os.path.join(os.path.dirname(sys.executable), 'src', 'browsers', 'chromium-1112', 'chrome-win', 'chrome.exe') else: browser_path = os.path.join(os.path.dirname(__file__), 'src', 'browsers', 'chromium-1112', 'chrome-win', 'chrome.exe') # 사용자 에이전트 설정 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", ]) browser = await p.chromium.launch( headless=False, # 디버깅을 위해 브라우저 창을 표시 executable_path=browser_path, args=[ '--disable-popup-blocking', '--start-maximized', '--window-size=1920,1080' ] ) # 시크릿 브라우저 컨텍스트 생성 context = await browser.new_context( user_agent=user_agent, geolocation={"latitude": 37.5665, "longitude": 126.9780}, locale="ko-KR", permissions=["geolocation", "notifications"] ) # 페이지 열기 page = await context.new_page() login_url = "https://login.taobao.com/member/login.jhtml" await page.goto(login_url) return browser, page, login_url # ============================================================ # 3. QR 스크린샷 캡쳐 함수 (QR 이미지를 리턴) # ============================================================ async def capture_qr_screenshot(page): """ 페이지에서 'div#qrcode-img canvas' 요소의 스크린샷을 찍어 이미지 바이트 데이터를 반환합니다. """ await page.wait_for_selector("div#qrcode-img canvas", timeout=10000) await asyncio.sleep(2) # QR 코드가 완전히 렌더링될 때까지 딜레이 qr_canvas = await page.query_selector("div#qrcode-img canvas") if not qr_canvas: print("QR 코드 캔버스를 찾을 수 없습니다.") return None img_bytes = await qr_canvas.screenshot() return img_bytes # ============================================================ # 4. 로그인 완료 감시 및 검색 페이지 이동 함수 # ============================================================ async def monitor_login(page, login_url, search_url): """ 1초마다 현재 페이지 URL을 출력하다가 로그인 완료(로그인 페이지 URL과 달라짐)를 감지하면 지정한 검색 URL로 이동합니다. """ print("로그인 완료 감시 시작...") while True: current_url = page.url print("현재 URL:", current_url) if current_url != login_url: print("로그인 완료 감지됨. 현재 URL:", current_url) break await asyncio.sleep(1) # 지정한 검색 페이지로 이동 await page.goto(search_url) print("검색 페이지로 이동했습니다:", search_url) # 상품 카드가 나타날 때까지 대기 try: await page.wait_for_selector(".doubleCard--gO3Bz6bu", timeout=15000) print("상품 카드 요소가 나타났습니다.") except Exception as e: print("상품 카드 요소를 찾지 못했습니다.", e) # ============================================================ # 5. 상품정보 추출 함수 (현재 페이지의 모든 상품 카드 처리) # ============================================================ async def extract_products_from_current_page(page, card_or_img="card"): """ 현재 페이지의 모든 상품 카드(.doubleCard--gO3Bz6bu)를 순회하며, 상품 이미지 URL, 상품명, 가격, 구매량 정보를 추출하고, 각 상품 카드의 스크린샷을 찍어 로컬 파일에 저장한 후, 파일 경로를 함께 반환합니다. """ await page.wait_for_selector(".doubleCard--gO3Bz6bu", timeout=10000) cards = await page.query_selector_all(".doubleCard--gO3Bz6bu") products = [] # 로컬에 상품 이미지 저장 폴더 생성 images_folder = "product_images" os.makedirs(images_folder, exist_ok=True) for card in cards: # 상품 이미지 URL 추출 img_elem = await card.query_selector("img.mainPic--Ds3X7I8z") img_src = await img_elem.get_attribute("src") if img_elem else None # 상품명 추출 name_elem = await card.query_selector("div.title--qJ7Xg_90 span") name_text = (await name_elem.inner_text()).strip() if name_elem else "" # 가격 추출 (정수 부분) price_elem = await card.query_selector("span.priceInt--yqqZMJ5a") price_text = (await price_elem.inner_text()).strip() if price_elem else "" # 구매량 추출 sales_elem = await card.query_selector("span.realSales--XZJiepmt") sales_text = (await sales_elem.inner_text()).strip() if sales_elem else "" # 각 상품 카드의 스크린샷을 찍어서 로컬 파일로 저장 unique_filename = f"product_{uuid.uuid4().hex}.png" local_path = os.path.join(images_folder, unique_filename) try: if card_or_img == "card": await card.screenshot(path=local_path) elif card_or_img == "img": if img_elem: await img_elem.screenshot(path=local_path) else: await card.screenshot(path=local_path) print(f"상품 스크린샷 저장됨: {local_path}") except Exception as e: print("상품 스크린샷 저장 실패:", e) local_path = None product = { "image_url": img_src, "local_image": local_path, "name": name_text, "price": price_text, "sales": sales_text } products.append(product) return products # ============================================================ # 6. 여러 페이지에서 상품정보 수집 함수 # ============================================================ async def collect_products(page, pages_to_collect): """ 사용자가 지정한 페이지 수만큼 각 페이지의 상품 정보를 추출합니다. 각 페이지 당 최대 45개 상품이 있다고 가정하며, 페이지 이동 버튼을 클릭하여 이동합니다. """ all_products = [] for i in range(pages_to_collect): print(f"\n--- Page {i+1} 상품정보 추출 시작 ---") products = await extract_products_from_current_page(page) print(f"페이지 {i+1}에서 {len(products)}개의 상품 정보 추출됨.") all_products.extend(products) # 마지막 페이지가 아니라면 다음 페이지로 이동 if i < pages_to_collect - 1: try: # 페이지 이동 버튼 영역 대기 await page.wait_for_selector("div.next-pagination-list", timeout=10000) next_page = i + 2 # 현재 페이지가 i+1이면, 다음 페이지는 i+2 # 페이지 이동 버튼 클릭 (버튼 내 텍스트가 페이지 번호와 일치) btn_selector = f"button.next-pagination-item:has-text('{next_page}')" await page.click(btn_selector) print(f"페이지 {next_page}로 이동 버튼 클릭됨.") # 페이지 이동 후 로드 대기 await page.wait_for_load_state("networkidle") await asyncio.sleep(2) except Exception as e: print(f"페이지 {next_page}로 이동 실패: {e}") break return all_products # ============================================================ # 7. 전체 동작을 통합하는 메인 함수 (qasync 사용) # ============================================================ async def main(pages_to_collect): async with async_playwright() as p: # 1) 로그인 페이지 접속 browser, page, login_url = await open_login_page(p) # 2) QR 스크린샷 캡쳐 (GUI에 표시할 이미지) img_bytes = await capture_qr_screenshot(page) if img_bytes is None: print("QR 코드 이미지를 가져오지 못했습니다.") await browser.close() return # 3) 검색 페이지 URL (로그인 완료 후 이동) search_url = ("https://s.taobao.com/search?commend=all&ie=utf8page=1&" "q=%E5%A5%B3%E5%A3%AB%E8%A5%BF%E6%9C%8D&search_type=item") # 4) GUI 창 생성 (QR 이미지 표시) app = QApplication.instance() if app is None: app = QApplication(sys.argv) window = QRCodeWindow(img_bytes) window.show() # 5) 로그인 완료 감시 및 검색 페이지 이동 await monitor_login(page, login_url, search_url) # 6) 사용자가 지정한 페이지 수만큼 상품 정보 수집 products = await collect_products(page, pages_to_collect) print(f"\n총 {len(products)}개의 상품 정보를 수집했습니다.") for idx, prod in enumerate(products, start=1): print(f"[{idx}] {prod}") # 7) 디버깅 후 브라우저 종료 및 GUI 종료 처리 await browser.close() await asyncio.sleep(2) app.quit() # ============================================================ # 8. qasync를 통한 이벤트 루프 실행 및 사용자 입력 처리 # ============================================================ if __name__ == "__main__": # 사용자에게 수집할 페이지 수 입력 받기 (기본값 1) page_count_input = input("수집할 페이지 수를 입력하세요 (기본값 1): ") try: pages_to_collect = int(page_count_input) if page_count_input.strip() else 1 except Exception: pages_to_collect = 1 app = QApplication(sys.argv) loop = qasync.QEventLoop(app) asyncio.set_event_loop(loop) with loop: loop.run_until_complete(main(pages_to_collect))