diff --git a/.env.example b/.env.example index 04f1dcb..7a97499 100644 --- a/.env.example +++ b/.env.example @@ -4,8 +4,8 @@ ANONYMIZED_TELEMETRY=false GOOGLE_API_KEY= # 권장 (다른 모델로 교체 가능) [다른 모델로 교체시 성능 보장 불가] -GOOGLE_MODEL=gemini-2.5-flash-preview-05-20 -GOOGLE_PLANNER_MODEL=gemini-2.5-flash-preview-05-20 +GOOGLE_MODEL=gemini-2.5-flash +#GOOGLE_PLANNER_MODEL=gemini-2.5-flash # 왜 비활성화 되었나요? // Planner 모델이 오히려 문제를 일으키는 경우가 있어 비활성화했습니다. 필요시 활성화하세요. # min(INITIAL_BACKOFF * (2 ** try_cnt), MAX_BACKOFF)만큼 API가 실패시 대기합니다. INITIAL_BACKOFF=60 diff --git a/.gitignore b/.gitignore index af59832..d53f32f 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,7 @@ oauth_providers.csv .venv .env -#.sensitive.json +.sensitive.json log_*.log domains.txt diff --git a/run.ps1 b/.legacy/run.ps1 similarity index 100% rename from run.ps1 rename to .legacy/run.ps1 diff --git a/run.sh b/.legacy/run.sh old mode 100755 new mode 100644 similarity index 100% rename from run.sh rename to .legacy/run.sh diff --git a/.sensitive.json b/.sensitive.example.json similarity index 100% rename from .sensitive.json rename to .sensitive.example.json diff --git a/README.md b/README.md index 31b70d6..9128804 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,19 @@ > 다른 플렛폼은 수동으로 설정되어야만 합니다. > https://docs.mitmproxy.org/stable/concepts/certificates/ + --- +다음과 같은 명령어로 환경을 설정합니다. + +설명하는 가이드를 잘 따라가면 설정할 수 있습니다. + +```sh +uv run setup.py +``` + + +
+설치 및 설정 (레거시) uv 설치 후 다음과 같은 명령어를 입력합니다. @@ -41,20 +53,13 @@ venv와 패키지가 설치가 됩니다. playwright install chrome ``` ---- -다음과 같은 명령어로 실행합니다. - -```sh -uv run main.py -``` - Environment는 .env.example에 따라 설정되어야합니다. .env.example을 .env로 복사하여서 사용해주세요. -# 로그인 방안 +## 로그인 방안 -## 쿠키와 로컬 스토리지 설정 방법 (추천) +### 쿠키와 로컬 스토리지 설정 방법 (추천) ![1](./docs/image.png) @@ -64,7 +69,7 @@ uv run playwright open https://google.com/ --save-storage=./data/storage_state.j 위 명령어를 실행하면 playwright Browser가 하나 열리는데 여기서 원하는 프로바이더를 모두 로그인 한 후에 브라우저를 정상적으로 닫으면 ./data/storage_state.json 경로에 쿠키, 로컬스토리지를 저장한 파일이 생성됩니다. -## Browser Use에게 직접 로그인 요청 (선택) +### Browser Use에게 직접 로그인 요청 (선택)
위에 쿠키와 로컬스토리지 설정 방법과 혼용해서 사용가능합니다. @@ -77,6 +82,10 @@ uv run playwright open https://google.com/ --save-storage=./data/storage_state.j [Sensitive Data - Browser Use](https://docs.browser-use.com/customize/sensitive-data)에서도 권장하지 않는 방법인만큼 애매하긴 하지만 쿠키와 로컬 스토리지를 저장하기 어려운 경우나 일부 flow에서 접근이 어려운 경우 사용해주세요.
+
+ +--- + # 실행 @@ -87,14 +96,8 @@ curl "https://f.imnya.ng/.whs/tp-domains/data/domains/latest.txt" -o domains.txt ``` ```sh -# ./run.sh {domains.txt 시작 줄} {domains.txt 끝 줄} {HTML 검사 Skip} -./run.sh 12540 13000 False -``` - - -```pwsh -# ./run.ps1 {domains.txt 시작 줄} {domains.txt 끝 줄} {HTML 검사 Skip} -./run.ps1 12540 13000 False +# uv run run.py {domains.txt 시작 줄} {domains.txt 끝 줄} {HTML 검사 Skip} +uv run run.py 12540 13000 False ``` # 참고하면 좋을만한 것 diff --git a/main.py b/main.py index 752095f..70f864e 100644 --- a/main.py +++ b/main.py @@ -50,7 +50,7 @@ if os.getenv("LMNR_PROJECT_API_KEY"): def save_progress(): """현재 진행 상황을 파일에 저장""" - with open(progress_file, 'w', encoding='utf-8') as f: + with open(progress_file, "w", encoding="utf-8") as f: json.dump(current_progress, f, ensure_ascii=False, indent=2) @@ -58,7 +58,7 @@ def load_progress(): """이전 진행 상황을 파일에서 불러오기""" if os.path.exists(progress_file): try: - with open(progress_file, 'r', encoding='utf-8') as f: + with open(progress_file, "r", encoding="utf-8") as f: return json.load(f) except: return None @@ -67,15 +67,19 @@ def load_progress(): def signal_handler(signum, frame): """Ctrl+C 시그널 핸들러""" - print("\n" + "="*60) + print("\n" + "=" * 60) print("🛑 스캔이 중단되었습니다!") print(f"📊 진행 상황:") print(f" - 전체: {current_progress['total']}개 URL") print(f" - 완료: {current_progress['current_index']}개 URL") print(f" - 현재 처리 중: {current_progress['current_url']}") - print(f" - domains.txt의 {current_progress['start_line'] + current_progress['current_index']}번째 줄") - print(f" - 진행률: {current_progress['current_index']}/{current_progress['total']} ({current_progress['current_index']/current_progress['total']*100:.1f}%)") - print("="*60) + print( + f" - domains.txt의 {current_progress['start_line'] + current_progress['current_index']}번째 줄" + ) + print( + f" - 진행률: {current_progress['current_index']}/{current_progress['total']} ({current_progress['current_index']/current_progress['total']*100:.1f}%)" + ) + print("=" * 60) save_progress() print(f"💾 진행 상황이 {progress_file}에 저장되었습니다.") exit(0) @@ -111,7 +115,10 @@ async def scan_one_url(url: str, skip_html_check: bool = False): # Agent 생성 및 실행 (단일 try-except with 백오프) initial_actions = [{"open_tab": {"url": target_url}}] - controller = Controller(output_model=model.BaseModel, exclude_actions=['search_google']) + controller = Controller( + output_model=model.BaseModel, + exclude_actions=["search_google", "unknown_action", "unkown"], + ) print("🤖 LLM 모델 초기화 및 스캔 시작...") print("Available actions:", list(controller.registry.registry.actions.keys())) @@ -130,13 +137,17 @@ async def scan_one_url(url: str, skip_html_check: bool = False): "Always log out before starting the login process, and make sure to attempt the login again from a clean state." ), llm=CreateChatGoogleGenerativeAI(GOOGLE_MODEL), - planner_llm=CreateChatGoogleGenerativeAI(GOOGLE_PLANNER_MODEL), + planner_llm=( + CreateChatGoogleGenerativeAI(GOOGLE_PLANNER_MODEL) + if GOOGLE_PLANNER_MODEL + else None + ), controller=controller, extend_planner_system_message=extend_planner_system_message, ) response = await agent.run() final_result = response.final_result() - + if final_result is None: raise ValueError("final_result()가 None을 반환했습니다.") except Exception as e: @@ -206,18 +217,20 @@ async def loop( current_progress["total"] = len(target_list) current_progress["start_line"] = start_line current_progress["current_index"] = 0 - + # 이전 진행 상황 확인 prev_progress = load_progress() if prev_progress and prev_progress.get("start_line") == start_line: print(f"📋 이전 진행 상황을 발견했습니다:") - print(f" - 이전 완료: {prev_progress['current_index']}/{prev_progress['total']}") + print( + f" - 이전 완료: {prev_progress['current_index']}/{prev_progress['total']}" + ) print(f" - 마지막 처리: {prev_progress.get('current_url', 'N/A')}") - + resume = input("이어서 진행하시겠습니까? (y/n): ").lower().strip() - if resume == 'y': + if resume == "y": current_progress["current_index"] = prev_progress["current_index"] - target_list = target_list[current_progress["current_index"]:] + target_list = target_list[current_progress["current_index"] :] print(f"✅ {current_progress['current_index']}번째부터 재개합니다.") # (필요하다면) 강제 설정이 필요한 경우, 아래 주석을 해제하여 target_list[0] 등을 덮어쓸 수 있습니다. @@ -227,7 +240,7 @@ async def loop( actual_index = current_progress["current_index"] + i current_progress["current_url"] = url current_progress["current_index"] = actual_index - + print(f"\n🔄 Processing {actual_index + 1}/{current_progress['total']}: {url}") print(f"📍 domains.txt의 {start_line + actual_index}번째 줄") @@ -237,7 +250,7 @@ async def loop( await asyncio.sleep(30) await scan_one_url(url, skip_html_check=skip_html_check) - + # 진행 상황 저장 current_progress["current_index"] = actual_index + 1 save_progress() diff --git a/pyproject.toml b/pyproject.toml index 5e8289f..1b2f537 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ description = "Add your description here" readme = "README.md" requires-python = ">=3.13" dependencies = [ - "browser-use[memory]>=0.2.7", + "browser-use[memory]==0.3.2", "lmnr[all]>=0.6.10", "patchright>=1.52.5", ] diff --git a/run.py b/run.py new file mode 100644 index 0000000..30e369e --- /dev/null +++ b/run.py @@ -0,0 +1,90 @@ +import sys +import subprocess +import os +import requests +from datetime import datetime +import argparse + +#!/usr/bin/env python3 + +# ── 설정 부분 ── +PYTHON_SCRIPT = "main.py" +DOMAIN_FILE = "./data/domains.txt" +# ───────────── + +def download_domains(): + """도메인 파일 다운로드""" + try: + print("도메인 파일 다운로드 중...") + response = requests.get("https://f.imnya.ng/.whs/tp-domains/data/domains/latest.txt") + response.raise_for_status() + + # 디렉토리가 없으면 생성 + os.makedirs(os.path.dirname("./data"), exist_ok=True) + + with open(DOMAIN_FILE, 'w', encoding='utf-8') as f: + f.write(response.text) + print("도메인 파일 다운로드 완료") + except requests.RequestException as e: + print(f"도메인 파일 다운로드 실패: {e}") + sys.exit(1) + +def run_script(start_line, end_line, skh_option): + """Python 스크립트 실행""" + current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + print(f"[{current_time}] Processing lines {start_line} to {end_line}...") + + try: + subprocess.run([ + "uv", "run", PYTHON_SCRIPT, + "-f", DOMAIN_FILE, + "-s", str(start_line), + "-e", str(end_line), + "-skh", str(skh_option) + ], check=True) + except subprocess.CalledProcessError: + print("Python 스크립트 실행 실패") + sys.exit(1) + +def main(): + parser = argparse.ArgumentParser( + description="도메인 처리 스크립트 실행기", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +사용 예시: + python run.py 10000 11000 # 10000~11000 라인 처리 + python run.py 10000 11000 --skh # SKH 옵션 활성화 + python run.py 10000 11000 --no-download # 다운로드 생략 + """ + ) + + parser.add_argument("start_line", type=int, help="시작 라인 번호") + parser.add_argument("end_line", type=int, help="종료 라인 번호") + parser.add_argument("--skh", action="store_true", help="SKH 옵션 활성화") + parser.add_argument("--no-download", action="store_true", help="도메인 파일 다운로드 생략") + + args = parser.parse_args() + + # 라인 범위 검증 + if args.start_line < 0 or args.end_line < 0: + print("라인 번호는 0 이상이어야 합니다.") + sys.exit(1) + + if args.start_line >= args.end_line: + print("시작 라인은 종료 라인보다 작아야 합니다.") + sys.exit(1) + + # 도메인 파일 다운로드 + if not args.no_download: + download_domains() + elif not os.path.exists(DOMAIN_FILE): + print(f"도메인 파일({DOMAIN_FILE})이 존재하지 않습니다. --no-download 옵션을 제거하거나 파일을 준비해주세요.") + sys.exit(1) + + # 스크립트 실행 + run_script(args.start_line, args.end_line, args.skh) + + print("처리 완료.") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..5823705 --- /dev/null +++ b/setup.py @@ -0,0 +1,88 @@ +import os +import subprocess + +os.makedirs(os.path.dirname("./data"), exist_ok=True) + +def create_file_from_example(target: str, example: str) -> bool: + if not os.path.exists(target): + if os.path.exists(example): + with open(example, 'r', encoding='utf-8') as example_file, \ + open(target, 'w', encoding='utf-8') as target_file: + target_file.write(example_file.read()) + os.startfile(target) + print(f"✅ {target} 파일이 {example}에서 생성되었습니다.") + return True + else: + print(f"⚠️ {example} 파일이 존재하지 않습니다. {target} 생성에 실패했습니다.") + else: + print(f"ℹ️ {target} 파일이 이미 존재합니다.") + return False + + +def install_playwright_chrome(): + print("\n🛠️ Playwright의 Chrome을 설치 중입니다...") + try: + subprocess.run(['uv', 'run', 'playwright', 'install', 'chrome'], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + print("✅ Playwright Chrome 설치 완료.") + except subprocess.CalledProcessError as e: + if "already" in e.stdout.decode(): + print("ℹ️ Chrome이 이미 설치되어 있습니다.") + else: + print(f"❌ Playwright 설치 실패: {e}") + print("\n") + + +def prompt_yes_no(message: str) -> bool: + print(message, end="") + return input().strip().lower() in ['y', 'yes'] + + +def setup_storage(): + print("\n🔧 쿠키와 로컬 스토리지를 설정하시겠습니까?") + print("👀 다음 단계에서 Senstive Data를 설정할 수 있지만 쿠키와 로컬 스토리지를 더 권장합니다.") + if prompt_yes_no("\033[1m\033[33m선택하시려면 y를 입력하세요 (y/n):\033[0m "): + print("======================================================") + print("👀 원하는 OAuth Providor를 직접 모두 로그인 한 후에 브라우저를 닫으면 설정이 완료됩니다.") + os.system('uv run playwright open https://google.com/ --save-storage=./data/storage_state.json') + print("✅ 쿠키와 로컬 스토리지 설정 완료.") + print("💾 ./data/storage_state.json 파일이 생성되었습니다.") + else: + print("🚫 쿠키와 로컬 스토리지 설정이 취소되었습니다.") + print("======================================================") + print("⚠️ 이후에 쿠키와 로컬 스토리지를 설정하려면, `uv run playwright open https://google.com/ --save-storage=./data/storage_state.json` 명령어를 사용하세요.\n") + + +def setup_sensitive(): + print("\n🔐 Sensitive Data을 설정하시겠습니까?") + print("👉 이미 세션을 설정했다면, 이 작업은 **선택사항**입니다.") + print("⚠️ 민감 정보 파일은 오류를 유발하거나 문제가 될 수 있으므로 가급적 세션 사용을 권장합니다.") + if prompt_yes_no("\033[1m\033[33m선택하시려면 y를 입력하세요 (y/n):\033[0m "): + print("======================================================") + print("👀 .sensitive.json 파일을 생성합니다.") + print("💾 Browser Use의 문서를 참조하여 수정을 수정해주세요.") + print("https://docs.browser-use.com/customize/sensitive-data") + create_file_from_example('.sensitive.json', '.sensitive.example.json') + print("======================================================") + print("✅ .sensitive.json 파일이 생성되었습니다.") + else: + print("🚫 .sensitive.json 생성이 취소되었습니다.") + print("======================================================") + print("⚠️ 이후에 민감 정보 파일을 설정하려면, .sensitive.example.json 파일을 참고하여 .sensitive.json 파일을 생성하세요.\n") + + +if __name__ == "__main__": + # 1. .env 생성 + create_file_from_example('.env', '.env.example') + print("=====================================================") + # 2. Playwright용 Chrome 설치 + install_playwright_chrome() + print("=====================================================") + + # 3. 쿠키와 로컬 스토리지 설정 + setup_storage() + print("=====================================================") + + # 4. .sensitive.json 생성 + setup_sensitive() + print("=====================================================") + print("🎉 초기 설정이 완료되었습니다! 이제 스크립트를 실행할 준비가 되었습니다.") diff --git a/uv.lock b/uv.lock index d6f4e37..c125c7a 100644 --- a/uv.lock +++ b/uv.lock @@ -96,14 +96,13 @@ wheels = [ [[package]] name = "browser-use" -version = "0.3.1" +version = "0.3.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiofiles" }, { name = "anyio" }, { name = "authlib" }, { name = "bubus" }, - { name = "faiss-cpu" }, { name = "google-api-core" }, { name = "httpx" }, { name = "langchain" }, @@ -128,13 +127,14 @@ dependencies = [ { name = "typing-extensions" }, { name = "uuid7" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fd/8f/d12c90ed85f2f6b4248e9183227ca389451a3319bbbd9df6b9b36be69c11/browser_use-0.3.1.tar.gz", hash = "sha256:862cfef984c9af84b3ba4ac13cd23e232bea1f8ec7e83d5afa3e58c8491fb66b", size = 163670, upload-time = "2025-06-20T09:44:58.448Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4a/9b/b0620dea406c878923b38dab4c9391d822a854a7053ec3ba2c831a8f8da1/browser_use-0.3.2.tar.gz", hash = "sha256:600881d087ef246d10505aa133cc18f7ac2f3f8ddcb6210c00a8cabf0a4b9aa1", size = 175299, upload-time = "2025-06-22T05:26:20.979Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/73/81dc17b281858498373662e5034dce316f8ed90b9d9025f7659f49e2d2b7/browser_use-0.3.1-py3-none-any.whl", hash = "sha256:773ace737c93d54ff2d95d734c5bd746e03c10e38d916a89a90effa6d101ec9b", size = 182885, upload-time = "2025-06-20T09:44:57.226Z" }, + { url = "https://files.pythonhosted.org/packages/93/04/0446df95b031362fbbbd20e4b389d4dee98f27d2c40ed38d792842bcc807/browser_use-0.3.2-py3-none-any.whl", hash = "sha256:500340bd3d41440072d9845c640b0cb5decfacc423459ab7a28ce9273b1b1601", size = 195490, upload-time = "2025-06-22T05:26:19.545Z" }, ] [package.optional-dependencies] memory = [ + { name = "faiss-cpu" }, { name = "sentence-transformers" }, ] @@ -150,14 +150,14 @@ dependencies = [ [package.metadata] requires-dist = [ - { name = "browser-use", extras = ["memory"], specifier = ">=0.2.7" }, + { name = "browser-use", extras = ["memory"], specifier = "==0.3.2" }, { name = "lmnr", extras = ["all"], specifier = ">=0.6.10" }, { name = "patchright", specifier = ">=1.52.5" }, ] [[package]] name = "bubus" -version = "1.1.0" +version = "1.1.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiofiles" }, @@ -166,9 +166,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "uuid7" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d2/51/8c953bceebc20314fffa71cbd010601d766ce0e99bcc8133ac5c78085add/bubus-1.1.0.tar.gz", hash = "sha256:4a5125e4bd3868947023ada5d33a6fbeb29ceeb0448999ca50f5dfe4e349a267", size = 19756, upload-time = "2025-06-20T08:51:13.165Z" } +sdist = { url = "https://files.pythonhosted.org/packages/36/d2/1177c30fc3710806b0c57605409511cd6230d21c9c6ed3c8189ee8f579ac/bubus-1.1.2.tar.gz", hash = "sha256:95a90e9b82e397c506ae12558ea538161b04b2b9cee9ce3f585f41177927c373", size = 21001, upload-time = "2025-06-21T09:09:38.616Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/45/7c/bc1eeae426c65b32b3129f31b01a8dc526388e310babebbd1b5b9594666a/bubus-1.1.0-py3-none-any.whl", hash = "sha256:c541fe77bf4444cce724fc49025ea7d996d37346cf708e46974142f9e4eb7577", size = 20715, upload-time = "2025-06-20T08:51:12.053Z" }, + { url = "https://files.pythonhosted.org/packages/f2/f9/e1edf399481a94cbec3016cb4a253df1be0b0631c232d34dca61bb37bfff/bubus-1.1.2-py3-none-any.whl", hash = "sha256:216094a28df3b7869d4e85e5c722b31ba1e44c3bf114aa11b037a76e6fba8225", size = 22083, upload-time = "2025-06-21T09:09:37.477Z" }, ] [[package]]