Merge pull request #19 from j93es/feat/setup

chore: 환경 설정 및 크로스 플랫폼 실행 파일
This commit is contained in:
암냥 2025-06-22 23:45:27 +09:00 committed by GitHub
commit c1ade99b8a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 240 additions and 46 deletions

View file

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

2
.gitignore vendored
View file

@ -12,7 +12,7 @@ oauth_providers.csv
.venv
.env
#.sensitive.json
.sensitive.json
log_*.log
domains.txt

0
run.sh → .legacy/run.sh Executable file → Normal file
View file

View file

@ -20,7 +20,19 @@
> 다른 플렛폼은 수동으로 설정되어야만 합니다.
> https://docs.mitmproxy.org/stable/concepts/certificates/
---
다음과 같은 명령어로 환경을 설정합니다.
설명하는 가이드를 잘 따라가면 설정할 수 있습니다.
```sh
uv run setup.py
```
<details>
<summary>설치 및 설정 (레거시)</summary>
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에게 직접 로그인 요청 (선택)
<details>
위에 쿠키와 로컬스토리지 설정 방법과 혼용해서 사용가능합니다.
@ -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에서 접근이 어려운 경우 사용해주세요.
</details>
</details>
---
# 실행
@ -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
```
# 참고하면 좋을만한 것

45
main.py
View file

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

View file

@ -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",
]

90
run.py Normal file
View file

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

88
setup.py Normal file
View file

@ -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("🎉 초기 설정이 완료되었습니다! 이제 스크립트를 실행할 준비가 되었습니다.")

16
uv.lock generated
View file

@ -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]]