웹 자동화 에이전트: Claude와 Computer Use를 활용한 브라우저 제어
웹 자동화 에이전트: Claude와 Computer Use를 활용한 브라우저 제어
회사에서 API가 없는 공급업체 포털의 데이터가 필요합니다. 동적 콘텐츠, 다단계 폼, 그리고 몇 달마다 바뀌는 레이아웃을 가진 웹 앱입니다. Selenium 스크립트는 CSS가 업데이트될 때마다 깨집니다. 레거시 코드처럼 유지보수하고 있죠 — 취약하고, 비용이 들고, 아무도 손대고 싶어 하지 않습니다.
비전 기반 에이전트는 CSS 셀렉터에 신경 쓰지 않습니다. 페이지를 보고, “검색” 버튼을 시각적으로 찾아 클릭하고, 결과 테이블을 읽고, 데이터를 추출합니다. 레이아웃이 변경되어도 에이전트는 적응합니다. DOM을 파싱하는 것이 아니라 사람처럼 페이지를 읽기 때문입니다.
이 글에서는 Claude의 컴퓨터 사용 기능을 활용하여 웹 자동화 에이전트를 구축하는 방법을 알려드립니다. 시각적 자동화의 핵심 메커니즘, 안정적인 내비게이션 패턴, 데이터 추출 기법, 폼 작성 워크플로, 오류 복구 전략을 배우게 됩니다. 이 글을 마치면 간단한 페이지 읽기부터 복잡한 다단계 워크플로까지 모든 웹 인터페이스와 상호작용하는 에이전트를 구축하는 데 필요한 패턴을 갖추게 될 것입니다.
시각적 자동화 vs. 전통적 자동화
비전 기반 에이전트를 구축하기 전에, 그것이 언제 적합한지 — 그리고 언제 적합하지 않은지 이해해야 합니다. Selenium과 Playwright 같은 전통적 자동화 도구가 사라지는 것은 아닙니다. 빠르고, 결정론적이며, 잘 알려져 있습니다. 문제는 각 접근 방식이 어디서 뛰어나고 어디서 한계를 보이는가입니다.
전통적 자동화 (Selenium/Playwright)
전통적 브라우저 자동화는 DOM과 상호작용하는 방식으로 동작합니다. CSS 셀렉터나 XPath 표현식으로 요소를 찾은 다음, 프로그래밍 방식으로 해당 요소에 대해 작업을 수행합니다:
# 전통적 접근 방식 — 빠르지만 취약함search_input = driver.find_element(By.CSS_SELECTOR, "#search-box-v3 > input.query")search_input.send_keys("industrial bearings")driver.find_element(By.CSS_SELECTOR, "button.search-submit-2024").click()이 방식은 빠릅니다. 결정론적입니다. 그리고 누군가 search-box-v3을 search-box-v4로 이름을 바꾸거나 폼 레이아웃을 재구성하는 순간 깨집니다. 사이트의 내부 구조를 미러링하는 셀렉터 매핑을 유지보수하게 됩니다 — 여러분이 통제할 수 없는 구조입니다.
전통적 자동화는 시각적 전용 콘텐츠도 처리할 수 없습니다. 필요한 데이터가 <canvas> 요소에 렌더링되거나, 이미지에 내장되거나, 브라우저 내 PDF로 표시되는 경우 DOM 셀렉터로는 접근할 수 없습니다.
비전 기반 자동화 (Computer Use)
비전 기반 자동화는 사람이 하는 방식과 동일하게 작동합니다. 에이전트가 스크린샷을 받고, 필요한 요소를 시각적으로 식별하고, 특정 좌표에 마우스/키보드 동작을 실행합니다:
# 비전 기반 접근 방식 — 복원력 있지만 느림# 에이전트가 페이지를 보고, 검색 상자를 시각적으로 찾아 입력# 셀렉터 불필요 — 레이아웃 변경에 자동으로 적응트레이드오프: 더 느리고(각 동작마다 이미지가 포함된 API 호출 필요), 더 비싸고(스크린샷에 대한 토큰 비용), 비결정론적(에이전트가 실행마다 스크린샷을 다르게 해석할 수 있음)입니다. 하지만 레이아웃 변경에 대한 복원력이 있고, 모든 시각적 인터페이스에서 작동하며, 페이지 콘텐츠를 의미론적으로 이해할 수 있습니다.
하이브리드 접근 방식
가장 실용적인 전략은 두 가지를 결합하는 것입니다:
- 전통적 자동화를 사용하는 경우: 안정적이고 잘 구조화된 페이지로, 인터페이스를 직접 통제하거나 거의 변경되지 않는 경우
- 비전 기반 자동화를 사용하는 경우: 동적 페이지, 익숙하지 않은 인터페이스, 시각적 콘텐츠, 또는 스크립트 유지보수가 가치 없는 일회성 작업
- 비전을 폴백으로 사용: 셀렉터를 먼저 시도하고, 실패하면 시각적 식별로 전환
비전을 선택해야 하는 경우
비전 기반 자동화가 올바른 선택인 경우:
- API가 없고 웹 인터페이스가 유일한 옵션인 경우
- 페이지 레이아웃이 자주 변경되어 셀렉터 유지보수 비용이 너무 높은 경우
- 콘텐츠가 시각적인 경우 — 차트, 이미지, 캔버스 요소, 내장 PDF
- 다단계 워크플로에 컨텍스트가 필요한 경우 — 다음 동작을 결정하기 위해 화면에 무엇이 있는지 이해해야 하는 경우
- 일회성 자동화로 유지보수된 Selenium 스크립트의 엔지니어링 비용을 정당화할 수 없는 경우
- 익숙하지 않은 인터페이스에서 셀렉터를 매핑하는 대신 자연어로 작업을 설명할 수 있는 경우
Claude Computer Use 기초
Claude의 컴퓨터 사용 기능은 모델이 사람과 동일한 방식으로 컴퓨터 화면과 상호작용할 수 있게 합니다 — 스크린샷을 보고 마우스와 키보드 동작을 실행하는 방식입니다. 안정적인 에이전트를 구축하기 전에 이 메커니즘을 이해하는 것이 필수적입니다.
작동 방식
컴퓨터 사용 루프는 간단합니다:
- 현재 화면(또는 브라우저 창)의 스크린샷을 캡처합니다
- 스크린샷과 작업 설명을 Claude에 전송합니다
- 도구 호출을 수신합니다 — Claude가 수행할 동작(클릭, 입력, 스크롤)을 알려줍니다
- 브라우저에서 해당 동작을 실행합니다
- 새 스크린샷을 캡처합니다
- 작업이 완료되거나 에이전트가 완료를 알릴 때까지 반복합니다
Claude는 브라우저를 직접 제어하지 않습니다. 여러분의 코드가 중간자 역할을 하여 모델로부터 지시를 받고 실제 환경에서 실행합니다.
도구 정의
컴퓨터 사용은 사용 가능한 동작을 설명하는 특정 도구 정의인 computer_20241022에 의존합니다:
computer_tool = { "type": "computer_20241022", "name": "computer", "display_width_px": 1280, "display_height_px": 800, "display_number": 1,}사용 가능한 동작은 다음과 같습니다:
screenshot— 현재 화면 상태 캡처mouse_move— 특정 좌표로 커서 이동left_click/right_click/double_click— 현재 커서 위치에서 클릭left_click_drag— 클릭 후 목표 위치로 드래그type— 텍스트 문자열 입력key— 특정 키 또는 키 조합 누르기 (예:Return,ctrl+a)scroll— 현재 커서 위치에서 위 또는 아래로 스크롤
동작 루프
컴퓨터 사용 핵심 루프의 전체 구현은 다음과 같습니다:
import anthropicimport base64import subprocessimport time
client = anthropic.Anthropic()
def capture_screenshot() -> str: """Capture screen and return base64-encoded PNG.""" # Using scrot for X11; adapt for your environment subprocess.run(["scrot", "/tmp/screenshot.png"], check=True) with open("/tmp/screenshot.png", "rb") as f: return base64.standard_b64encode(f.read()).decode("utf-8")
def execute_action(action: dict): """Execute a computer use action using xdotool.""" action_type = action.get("action")
if action_type == "screenshot": return # Screenshot will be taken in the main loop
elif action_type == "mouse_move": x, y = action["coordinate"] subprocess.run(["xdotool", "mousemove", str(x), str(y)])
elif action_type == "left_click": x, y = action["coordinate"] subprocess.run(["xdotool", "mousemove", str(x), str(y)]) subprocess.run(["xdotool", "click", "1"])
elif action_type == "type": text = action["text"] subprocess.run(["xdotool", "type", "--clearmodifiers", text])
elif action_type == "key": key = action["key"] subprocess.run(["xdotool", "key", key])
elif action_type == "scroll": x, y = action["coordinate"] direction = action["direction"] amount = action["amount"] subprocess.run(["xdotool", "mousemove", str(x), str(y)]) button = "5" if direction == "down" else "4" for _ in range(amount): subprocess.run(["xdotool", "click", button])
time.sleep(0.5) # Brief pause after each action
def run_computer_use_agent(task: str, max_steps: int = 50): """Run a computer use agent loop.""" messages = [{"role": "user", "content": task}]
for step in range(max_steps): # Capture current screen state screenshot_b64 = capture_screenshot()
# Add screenshot to the conversation if step > 0: messages.append({ "role": "user", "content": [{ "type": "image", "source": { "type": "base64", "media_type": "image/png", "data": screenshot_b64, }, }], })
# Call Claude with computer use tool response = client.messages.create( model="claude-sonnet-4-20250514", max_tokens=4096, tools=[computer_tool], messages=messages, )
# Process response messages.append({"role": "assistant", "content": response.content})
# Check if the agent is done if response.stop_reason == "end_turn": # Extract final text response for block in response.content: if hasattr(block, "text"): return block.text return "Task completed."
# Execute tool calls tool_results = [] for block in response.content: if block.type == "tool_use": print(f"Step {step}: {block.input.get('action')} " f"{block.input.get('coordinate', '')}") execute_action(block.input) tool_results.append({ "type": "tool_result", "tool_use_id": block.id, "content": "Action executed successfully.", })
messages.append({"role": "user", "content": tool_results})
return "Max steps reached."해상도와 좌표 체계
Claude는 도구 정의에서 지정한 스크린샷 크기를 기준으로 시각적 콘텐츠를 x,y 픽셀 좌표에 매핑합니다. 몇 가지 중요한 세부 사항이 있습니다:
- 디스플레이 해상도를 일치시키세요. 도구 정의의
display_width_px와display_height_px와 실제 해상도가 맞아야 합니다. 불일치하면 클릭이 잘못된 위치에 떨어집니다. - 낮은 해상도가 더 좋습니다. 1280×800 스크린샷은 Claude가 텍스트를 읽고 UI 요소를 식별하기에 충분한 디테일을 제공하면서 토큰 비용을 관리 가능한 수준으로 유지합니다. 4K 스크린샷을 보내지 마세요.
- 좌표는 절대값입니다 — (0, 0)은 화면의 왼쪽 상단 모서리입니다.
안정적인 내비게이션 패턴
실제 웹사이트는 지저분합니다. 페이지가 비동기적으로 로드되고, 팝업이 예측 불가능하게 나타나며, 동적 콘텐츠가 상호작용 도중 레이아웃을 변경합니다. 안정적인 자동화 에이전트에는 이 모든 것을 처리하기 위한 패턴이 필요합니다.
로드 대기
웹 자동화에서 가장 흔한 실수 — 전통적이든 시각적이든 — 는 페이지가 준비되기 전에 동작하는 것입니다. 고정된 time.sleep() 호출을 사용하지 마세요. 대신 페이지 상태를 시각적으로 확인하세요:
def wait_for_page_load( client: anthropic.Anthropic, expected_content: str, max_retries: int = 5, delay: float = 2.0,) -> bool: """Wait for a page to load by checking for expected visual content.""" for attempt in range(max_retries): screenshot_b64 = capture_screenshot()
response = client.messages.create( model="claude-sonnet-4-20250514", max_tokens=256, messages=[{ "role": "user", "content": [ { "type": "image", "source": { "type": "base64", "media_type": "image/png", "data": screenshot_b64, }, }, { "type": "text", "text": ( f"Is this page fully loaded and showing: " f"'{expected_content}'? " f"Reply with only 'yes' or 'no'." ), }, ], }], )
answer = response.content[0].text.strip().lower() if "yes" in answer: return True
print(f"Page not ready (attempt {attempt + 1}/{max_retries}). Waiting...") time.sleep(delay)
return False팝업 처리
쿠키 배너, 알림 대화 상자, 채팅 위젯은 웹 자동화의 골칫거리입니다. 비전 기반 에이전트는 이를 자연스럽게 처리합니다:
def dismiss_popups(client: anthropic.Anthropic) -> bool: """Check for and dismiss any popup overlays.""" screenshot_b64 = capture_screenshot()
response = client.messages.create( model="claude-sonnet-4-20250514", max_tokens=관련 기사
- 에이전트 오류 복구: 프로덕션 신뢰성을 위한 5가지 패턴
- 에이전트 응답 스트리밍: 멀티스텝 워크플로우를 위한 실시간 출력
- 도구 사용 패턴: 신뢰할 수 있는 에이전트-도구 인터페이스 구축
- 에이전트 개발 소개