import os
import sys
import random
import asyncio
import logging
from playwright.async_api import async_playwright
# 로거 설정
logger = logging.getLogger("playwright_scraper")
logger.setLevel(logging.DEBUG)
handler = logging.StreamHandler()
formatter = logging.Formatter("[%(asctime)s] %(levelname)s - %(message)s")
handler.setFormatter(formatter)
logger.addHandler(handler)
# 로컬 이미지 파일 경로 (실제 파일 경로로 변경)
LOCAL_IMAGE_PATH = os.path.join(os.path.dirname(__file__), "o1cn.webp")
async def run():
try:
async with async_playwright() as p:
# 브라우저 실행 파일 경로 설정
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')
logger.debug(f"브라우저 경로: {browser_path}")
# 사용자 에이전트 설정
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",
])
logger.debug(f"user_agent: {user_agent}")
# 브라우저 시작 (headless 모드)
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()
await page.goto("https://itemscout.io/sourcing/1688/search")
logger.info("검색 페이지 접속 완료.")
# 파일 업로드: 파일 입력 요소는 이므로 선택자 사용
file_upload_selector = "input#fileUpload" # 또는 xpath "//*[@id='fileUpload']"
# 파일 업로드
await page.set_input_files(file_upload_selector, LOCAL_IMAGE_PATH)
logger.info(f"로컬 이미지 파일 업로드 완료: {LOCAL_IMAGE_PATH}")
# 파일 업로드 후 검색 결과가 로드될 때까지 적절히 대기 (필요에 따라 wait_for_selector 조정)
results_container_selector = "div.mb-9.flex.w-full.flex-wrap.gap-x-5.gap-y-6.text-base-100"
await page.wait_for_selector(results_container_selector, timeout=10000)
logger.info("검색 결과 로드 완료.")
# 모든 상품 카드 요소 선택 (각 카드가 태그로 되어 있음)
product_cards = await page.query_selector_all(f"{results_container_selector} > a")
num_products = len(product_cards)
logger.info(f"검색 결과 {num_products}개의 상품 발견.")
# 각 상품 카드에서 정보 추출
for idx, card in enumerate(product_cards, start=1):
# 상품명: span 요소 (클래스 'truncate')
product_name_element = await card.query_selector("span.truncate")
product_name = (await product_name_element.text_content()).strip() if product_name_element else "N/A"
# 상품 이미지: 카드 내부 첫 번째 div의 img 태그
image_element = await card.query_selector("div:first-child img")
product_image = await image_element.get_attribute("src") if image_element else "N/A"
# 가격 정보: div.leading-6 내에 두 개의 span 요소
price_container = await card.query_selector("div.leading-6")
price_spans = await price_container.query_selector_all("span") if price_container else []
price_yuan = (await price_spans[0].text_content()).strip() if len(price_spans) >= 1 else "N/A"
price_krw = (await price_spans[1].text_content()).strip() if len(price_spans) >= 2 else "N/A"
# 재구매율: p 요소 내에 텍스트 "재구매율" 포함, 그 안의 span.font-bold
repurchase_element = await card.query_selector("p:has-text('재구매율') span.font-bold")
repurchase_rate = (await repurchase_element.text_content()).strip() if repurchase_element else "N/A"
# 판매량: p 요소 내에 텍스트 "판매량" 포함
sales_element = await card.query_selector("p:has-text('판매량') span.font-bold")
sales_volume = (await sales_element.text_content()).strip() if sales_element else "N/A"
# 평점: p 요소 내에 텍스트 "평점" 포함
rating_element = await card.query_selector("p:has-text('평점') span.font-bold")
rating = (await rating_element.text_content()).strip() if rating_element else "N/A"
# 최소 구매수량: p 요소 내에 텍스트 "최소 구매수량" 포함
min_order_element = await card.query_selector("p:has-text('최소 구매수량') span.font-bold")
min_order = (await min_order_element.text_content()).strip() if min_order_element else "N/A"
logger.info(
f"[상품 {idx}] 상품명: {product_name}\n"
f" 상품 이미지: {product_image}\n"
f" 가격: {price_yuan} / {price_krw}\n"
f" 재구매율: {repurchase_rate}\n"
f" 판매량: {sales_volume}\n"
f" 평점: {rating}\n"
f" 최소 구매수량: {min_order}\n"
)
await browser.close()
except Exception as e:
logger.exception(f"오류 발생: {e}")
if __name__ == "__main__":
asyncio.run(run())