AutoTao/qrtest.py

281 lines
12 KiB
Python

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))