This commit is contained in:
9700X_PC 2025-02-10 17:05:30 +09:00
parent 476765fbc9
commit 66794fc617
48 changed files with 356 additions and 2 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

280
qrtest.py Normal file
View File

@ -0,0 +1,280 @@
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))

70
sms.py Normal file
View File

@ -0,0 +1,70 @@
#!/usr/bin/env python3
import argparse
import asyncio
import getpass
import textwrap
from playwright.async_api import async_playwright
DEFAULT_DELAY = 1
MESSAGES_URL = 'https://messages.google.com/web/conversations/new'
parser = argparse.ArgumentParser(
prog='messages-for-web-playwright',
description='Playwright를 사용하여 Google Messages for Web에서 SMS 전송 자동화',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=textwrap.dedent('''\
예시:
python script.py --to 010-1234-5678 "안녕하세요, 테스트 메시지입니다."
''')
)
parser.add_argument('MESSAGE', type=str, help='전송할 문자 메시지 내용 (따옴표로 감싸서 입력)')
parser.add_argument('--to', type=str, required=True, help='받는 사람의 전화번호 (예: 010-1234-5678)')
parser.add_argument('-d', '--delay', type=int, default=DEFAULT_DELAY, help='동작 사이의 지연 시간 (초)')
parser.add_argument('--dry_run', action='store_true', help='실제 전송 없이 테스트 모드 실행')
args = parser.parse_args()
async def main():
async with async_playwright() as p:
# persistent context를 사용하여 로그인 세션을 재사용 (로그인 되어 있어야 함)
user_data_dir = f"C:\\Users\\{getpass.getuser()}\\AppData\\Local\\ms-playwright"
context = await p.chromium.launch_persistent_context(user_data_dir=user_data_dir, headless=False)
page = await context.new_page()
print("Playwright로 Google Messages for Web에 접속합니다.")
# 1. Google Messages for Web 새 대화 페이지로 이동
await page.goto(MESSAGES_URL)
print("Google Messages for Web 페이지로 이동합니다.")
await page.wait_for_timeout(args.delay * 1000)
print("Google Messages for Web에 접속했습니다.")
# 2. "이름, 전화번호 또는 이메일 입력" input에 받는 사람 정보 입력
recipient_input_selector = 'input[placeholder="이름, 전화번호 또는 이메일 입력"]'
await page.wait_for_selector(recipient_input_selector, timeout=45000)
print("받는 사람 정보 입력란을 찾았습니다.")
await page.fill(recipient_input_selector, args.to)
print(f"받는 사람 정보를 입력했습니다: {args.to}")
# 3. 새 대화 요소
new_conv_selector = "span:has-text('번으로 보내기')"
await page.wait_for_selector(new_conv_selector, timeout=45000)
print("새 대화 버튼을 찾았습니다.")
await page.click(new_conv_selector)
print("새 대화 버튼을 클릭했습니다.")
# 4. "문자메시지" 입력란이 나타날 때까지 기다림
message_input_selector = 'textarea[placeholder="문자메시지"]'
await page.wait_for_selector(message_input_selector, timeout=45000)
await page.fill(message_input_selector, args.MESSAGE)
await page.wait_for_timeout(args.delay * 1000)
if args.dry_run:
print("Dry run: 실제 전송 없이 메시지 입력만 수행했습니다.")
else:
await page.click('mws-message-compose > div > mws-message-send-button > div > mw-message-send-button > button')
print("메시지가 전송되었습니다.")
await page.wait_for_timeout(args.delay * 1000)
await context.close()
if __name__ == '__main__':
asyncio.run(main())

View File

@ -59,11 +59,15 @@ class PlaywrightThread(QThread):
# 페이지 열기 # 페이지 열기
page = await context.new_page() page = await context.new_page()
await page.goto("https://world.taobao.com") # await page.goto("https://world.taobao.com")
await page.goto("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")
self.logger.log(f"접속 완료.", level=logging.INFO) self.logger.log(f"접속 완료.", level=logging.INFO)
# 페이지 로딩 확인 및 pagedown # 페이지 로딩 확인 및 pagedown
await page.wait_for_selector(".tb-pick-content-item") # 상품 카드 로딩 대기 # await page.wait_for_selector(".tb-pick-content-item") # 상품 카드 로딩 대기
await page.wait_for_selector("doubleCard--gO3Bz6bu") # 상품 카드 로딩 대기
self.logger.log(f"페이지 로딩 완료", level=logging.INFO) self.logger.log(f"페이지 로딩 완료", level=logging.INFO)
await page.keyboard.press("PageDown") await page.keyboard.press("PageDown")
await page.wait_for_timeout(1000) # 1초 대기 await page.wait_for_timeout(1000) # 1초 대기