헤드리스 브라우저
고친 사람 github-actions[bot]
헤드리스 브라우저는 사람 대신 프로그램이 웹 페이지를 열고 다루게 해 줍니다. 창을 화면에 띄우지 않으므로 모니터가 없는 서버에서도 돌아갑니다. 페이지를 받아 읽고 그리는 일은 사람이 쓰는 브라우저와 똑같이 합니다. 자동 테스트와 페이지 수집에 주로 씁니다.
쉽고 빠른 이해
헤드리스 브라우저는 창 없이 도는 브라우저입니다. 프로그램이 이 브라우저로 페이지를 열고 누르고 읽습니다. 테스트 코드가 로그인 화면을 열어 아이디를 넣고 버튼을 누르는 것이 그 예입니다.
이게 없으면 곤란한 일이 둘 있습니다. 자바스크립트가 나중에 내용을 채우는 페이지는 서버에 요청을 한 번 보내는 것만으로는 내용이 안 보입니다. 화면을 눌러 보는 테스트도 모니터 없는 서버에서는 돌릴 수 없습니다.
어떻게 도나:
- 프로그램이 브라우저를 창 없이 띄웁니다
- 프로그램이 주소 열기 · 누르기 · 글자 넣기 명령을 보냅니다
- 브라우저는 페이지를 평소처럼 그린 뒤 결과를 프로그램에 돌려줍니다
대가도 있습니다. 브라우저 하나가 온전히 돌아가므로 무겁고 느립니다. 페이지가 언제 다 그려졌는지 기다리는 코드도 따로 적어야 합니다. 요청 한 번으로 되는 일에는 쓰지 않습니다.
상세
배달 전문 음식점에는 손님이 앉는 홀이 없습니다. 주방은 여느 식당과 같습니다. 주문이 들어오면 똑같이 요리해서 포장해 내보냅니다.
헤드리스 브라우저는 홀을 뗀 브라우저입니다. 페이지를 받아 해석하고 그리는 부분은 남겨 둡니다. 사람이 보는 창만 없습니다. 그리고 그린 결과를 모니터 대신 프로그램에 넘깁니다.
브라우저에서 떼어 낸 창
헤드리스 브라우저가 무엇을 뗐는지 알려면 브라우저가 무엇으로 이루어졌는지부터 봐야 합니다. 브라우저는 속과 겉으로 나눠 볼 수 있습니다.
속에는 페이지를 해석하는 엔진이 있습니다. 렌더링 엔진은 HTML(HyperText Markup Language)과 CSS(Cascading Style Sheets)를 읽어 글자와 상자를 어디에 어떤 크기로 놓을지 계산합니다. HTML 은 문서의 구조를, CSS 는 모양을 적는 언어입니다.
자바스크립트 엔진은 페이지에 들어 있는 자바스크립트 코드를 실행합니다. 자바스크립트는 페이지가 열린 뒤에 내용을 바꾸거나 더합니다. 버튼을 누르면 목록이 늘어나는 동작이 이 코드로 돌아갑니다.
두 엔진은 DOM(Document Object Model, 문서 객체 모델)을 함께 씁니다. DOM 은 브라우저가 HTML 을 읽어 메모리에 펼쳐 놓은 나무 모양의 구조입니다. 자바스크립트가 DOM 을 고치면 화면도 따라 바뀝니다. 그래서 지금 페이지에 무엇이 보이는지는 처음 받은 HTML 이 아니라 DOM 이 말해 줍니다.
겉은 사람이 보고 만지는 부분입니다. 창과 주소창과 탭이 겉입니다. 마우스와 키보드 입력을 받는 것도 겉의 일입니다. 헤드리스 브라우저가 뗀 것이 이 겉입니다. 아래 표가 둘을 견줍니다.
| 하는 일 | 사람이 쓰는 브라우저 | 헤드리스 브라우저 |
|---|---|---|
| 페이지와 딸린 파일 받아 오기 | ✓ | ✓ |
| HTML 을 읽어 DOM 만들기 | ✓ | ✓ |
| 자바스크립트 실행 | ✓ | ✓ |
| 글자와 상자의 위치 계산 | ✓ | ✓ |
| 쿠키 보관 | ✓ | ✓ |
| 모니터에 창 띄우기 | ✓ | ✗ |
| 입력을 받는 쪽 | 사람의 마우스 · 키보드 | 프로그램의 명령 |
위쪽 다섯 줄은 똑같습니다. 헤드리스 브라우저로 연 페이지도 사람이 연 페이지와 같은 DOM 을 갖습니다. 다른 것은 아래 두 줄뿐입니다.
「헤드」는 영어로 머리입니다. 컴퓨터 쪽에서는 앞에서 겉이라 부른 부분, 곧 사람이 보고 만지는 쪽을 가리킵니다. 헤드리스는 그 겉을 뗐다는 뜻입니다.
요청 한 번으로는 안 보이는 내용
백엔드 개발자가 웹 페이지를 가져올 때 먼저 떠올리는 방법은 HTTP(HyperText Transfer Protocol) 요청 한 번입니다. 그런데 그 방법으로는 내용이 안 보이는 페이지가 있습니다. 헤드리스 브라우저가 필요한 곳이 바로 그런 페이지입니다.
요즘 페이지 가운데는 서버가 빈 뼈대만 보내는 것이 많습니다. 뼈대 HTML 에는 내용이 들어갈 빈 상자 하나와 자바스크립트 파일 주소만 적혀 있습니다. 브라우저가 그 자바스크립트를 실행하면 코드가 서버에 데이터를 따로 요청합니다. 받은 데이터로 DOM 을 채워야 비로소 내용이 보입니다.
단일 페이지 애플리케이션(SPA, Single Page Application)은 이렇게 페이지 하나를 열어 두고 자바스크립트로 내용을 갈아 끼우는 웹 앱입니다. 데이터는 대개 JSON(JavaScript Object Notation) 형식으로 옵니다. 이 데이터를 내주는 서버 쪽 창구가 API(Application Programming Interface)입니다.
아래는 헤드리스 브라우저가 이런 페이지를 여는 흐름입니다.
sequenceDiagram
participant 브라우저 as 헤드리스 브라우저
participant 서버 as 웹 서버
브라우저->>서버: 페이지 주소를 요청한다
서버-->>브라우저: 뼈대 HTML
Note over 브라우저,서버: HTTP 요청 한 번은 여기서 끝난다
브라우저->>서버: 자바스크립트 파일을 요청한다
서버-->>브라우저: 자바스크립트 파일
Note over 브라우저: 자바스크립트를 실행한다
브라우저->>서버: API 로 상품 목록을 요청한다
서버-->>브라우저: JSON 데이터
Note over 브라우저: 받은 데이터로 DOM 을 채운다
HTTP 클라이언트는 요청을 보내고 응답만 받아 오는 프로그램입니다. 이런 프로그램은 첫 왕복에서 멈춥니다. 자바스크립트를 실행하지 않으므로 뒤의 요청을 보낼 줄 모릅니다. 손에 남는 것은 빈 상자가 든 뼈대 HTML 입니다. 헤드리스 브라우저는 마지막 줄까지 갑니다.
같은 상품 목록 페이지를 두 방법으로 읽어 보면 차이가 드러납니다. 아래는 파이썬의 HTTP 클라이언트 라이브러리 requests 로 요청 한 번을 보낸 것입니다.
html = requests.get(url).text
"무선 이어폰" in html # False
뼈대 HTML 에는 상품 이름이 없습니다. 상품 목록은 자바스크립트가 나중에 채우기 때문입니다.
그다음은 헤드리스 브라우저를 다루는 라이브러리 Playwright 로 같은 페이지를 연 것입니다. page 는 헤드리스 브라우저에 열린 탭 하나입니다. page.content() 는 지금의 DOM 을 HTML 글자로 적어 돌려줍니다.
page.goto(url)
"무선 이어폰" in page.content() # True
자바스크립트가 채운 뒤의 DOM 을 읽었으므로 상품 이름이 있습니다. 헤드리스 브라우저를 쓰는 까닭이 이 한 줄의 차이입니다.
프로그램이 브라우저를 조종하는 통로
창이 없으니 사람이 누를 수 없습니다. 프로그램은 다른 길로 명령을 넣습니다.
헤드리스로 뜬 브라우저는 원격 조종용 통로를 하나 열어 둡니다. 대개 같은 컴퓨터 안의 네트워크 포트입니다. 프로그램은 이 통로에 붙어 명령을 보내고 답을 받습니다. 명령은 「이 주소를 열어라」·「이 버튼을 눌러라」·「이 칸에 글자를 넣어라」·「지금 화면의 글자를 돌려 달라」 같은 것입니다.
통로로 오가는 명령의 모양은 규약으로 정해 둡니다. 웹 표준으로 정한 WebDriver 가 하나입니다. 크롬 계열 브라우저가 여는 Chrome DevTools Protocol 도 널리 쓰입니다.
규약을 직접 다루는 코드는 번거롭습니다. 보통은 규약을 감싸 함수로 내놓는 라이브러리를 씁니다. Selenium · Puppeteer · Playwright 가 그런 라이브러리입니다. 앞에서 본 page.goto(url) 한 줄도 속으로는 이 통로에 「주소를 열어라」 명령을 보냅니다.
헤드리스 브라우저는 대개 따로 있는 프로그램이 아닙니다. 사람이 쓰는 브라우저를 옵션 하나로 창 없이 띄운 것입니다. 그래서 테스트가 실패하면 같은 코드를 창을 띄워 돌려 보고 눈으로 확인하기도 합니다.
헤드리스 브라우저를 쓰는 일
백엔드 개발자가 헤드리스 브라우저를 만나는 곳은 크게 넷입니다. 표를 보기 전에 표에 나오는 낱말을 먼저 풉니다.
종단 간 테스트는 사용자가 화면에서 거치는 흐름을 처음부터 끝까지 따라가 보는 테스트입니다. 화면과 서버와 데이터베이스가 함께 맞물려야 통과합니다.
지속적 통합(CI, Continuous Integration)은 코드가 저장소에 들어올 때마다 빌드와 테스트를 자동으로 돌리는 일입니다. 이 일을 맡는 CI 서버에는 대개 모니터가 없습니다.
페이지 수집은 사람이 보는 페이지에서 값을 자동으로 긁어 오는 일입니다. 웹 크롤러는 링크를 따라 페이지를 두루 돌며 모으는 프로그램입니다.
PDF(Portable Document Format)는 어느 기기에서 열어도 같은 모양으로 보이는 문서 파일 형식입니다. 인쇄할 영수증이나 보고서를 흔히 이 형식으로 내줍니다.
사전 렌더링은 페이지를 미리 그려 완성된 HTML 로 저장해 두는 일입니다. 검색 엔진이 보내는 수집기 가운데는 자바스크립트를 실행하지 않는 것이 있습니다. 그런 수집기에는 미리 그려 둔 HTML 을 대신 내줍니다.
| 쓰임 | 헤드리스 브라우저가 하는 일 | 브라우저가 필요한 까닭 |
|---|---|---|
| 종단 간 테스트 | 로그인부터 결제까지 누르고 결과 글자를 확인한다 | 화면을 눌러야 흐름이 돈다. CI 서버에는 모니터가 없다 |
| 페이지 수집 | 자바스크립트가 채운 뒤의 DOM 에서 값을 읽는다 | 요청 한 번으로는 뼈대만 온다 |
| PDF · 이미지 떠내기 | 그린 페이지를 파일로 저장한다 | 영수증 · 보고서를 HTML 과 CSS 로 짜서 그 모양을 파일로 얻는다 |
| 사전 렌더링 | 단일 페이지 애플리케이션을 미리 돌려 완성된 HTML 을 저장한다 | 자바스크립트를 안 돌리는 검색 엔진 수집기에도 내용을 보여 준다 |
창이 없어도 드는 비용
창을 뗐다고 가벼워지지는 않습니다. 치르는 비용은 크게 셋입니다. 그 비용 때문에 헤드리스 브라우저를 안 쓰는 때도 있습니다.
먼저 무겁습니다. 렌더링 엔진과 자바스크립트 엔진이 모두 돌아갑니다. 요청 한 번이면 끝날 일에 이미지 · 스타일 · 스크립트 파일까지 받습니다.
브라우저 하나가 서버의 메모리와 처리 시간을 HTTP 클라이언트보다 훨씬 많이 씁니다. 여러 페이지를 동시에 열면 이 비용이 페이지 수만큼 늘어납니다.
다음으로 기다리기가 어렵습니다. 자바스크립트가 데이터를 받아 DOM 을 채우는 시점은 페이지마다 다릅니다. 브라우저는 「이제 다 그렸다」는 신호를 따로 주지 않습니다. 코드는 스스로 원하는 버튼이나 글자가 나타날 때까지 기다리라고 적어야 합니다.
이 기다림을 잘못 적으면 테스트가 어떤 때는 통과하고 어떤 때는 실패합니다. 서버가 조금만 느려도 버튼이 나타나기 전에 누르려 들기 때문입니다. 불안정한 테스트는 코드를 안 고쳤는데 결과가 오락가락하는 테스트입니다.
마지막으로 막히기도 합니다. 사이트 가운데는 사람이 아닌 방문을 알아보고 막는 곳이 있습니다. 봇 탐지는 이렇게 자동 방문을 가려내는 일입니다. 페이지 수집에서 자주 부딪힙니다.
그래서 헤드리스 브라우저는 다른 방법이 안 될 때 꺼냅니다. 아래는 페이지에서 무엇을 얻으려는지에 따라 방법을 고르는 순서입니다.
flowchart TD
A["페이지에서 무엇이 필요한가"] -->|그려진 모양 · 눌러 본 결과| H["헤드리스 브라우저"]
A -->|글자 · 데이터| B["첫 HTML 에 내용이 있나"]
B -->|있다| R["HTTP 요청 한 번"]
B -->|없다| C["데이터를 주는 API 가 따로 있나"]
C -->|있다| P["그 API 를 부른다"]
C -->|없다| H
서버가 완성된 HTML 을 만들어 보내는 페이지라면 첫 HTML 에 내용이 다 있습니다. 이 방식이 서버 사이드 렌더링입니다. 이때는 HTTP 요청 한 번으로 충분합니다.
첫 HTML 이 비어 있어도 페이지가 부르는 API 를 찾으면 그걸 바로 부르면 됩니다. JSON 을 받아 읽으면 되므로 브라우저를 띄울 필요가 없습니다. 화면 모양 자체가 필요할 때만 곧장 헤드리스 브라우저로 갑니다. 눌러 보는 테스트와 PDF 떠내기가 그렇습니다.
관련 항목
웹 서버에 요청을 보내는 사용자 에이전트
브라우저 · 사용자 에이전트 · 웹뷰 · 웹 크롤러 · 명령줄 HTTP 클라이언트 · HTTP 클라이언트
헤드리스 브라우저 안에서 페이지를 그리는 구성 요소
렌더링 엔진 · 자바스크립트 엔진 · DOM · HTML · CSS · JavaScript
헤드리스로 띄우는 브라우저
Chromium · Google Chrome · Firefox · WebKit · PhantomJS
헤드리스 브라우저를 조종하는 규약과 라이브러리
브라우저 자동화 · WebDriver · Chrome DevTools Protocol · Selenium · Puppeteer · Playwright · Cypress
헤드리스 브라우저를 쓰는 테스트
종단 간 테스트 · 지속적 통합 · 불안정한 테스트 · 크로스 브라우저 테스트 · 시각적 회귀 테스트 · 회귀 테스트
헤드리스 브라우저로 페이지를 모으는 수집과 그 방어
웹 크롤러 · 웹 스크래핑 · 봇 탐지 · CAPTCHA · 로봇 배제 표준
자바스크립트가 화면을 채우는 렌더링 방식
단일 페이지 애플리케이션 · 클라이언트 사이드 렌더링 · 서버 사이드 렌더링 · 사전 렌더링 · 검색 엔진 최적화
헤드리스 브라우저가 떠내는 출력 형식
PDF · 스크린샷 · PNG · HTML
헤드리스라는 이름이 붙는 다른 기술
헤드리스 서버 · 헤드리스 CMS · 헤드리스 커머스 · 헤드리스 UI 컴포넌트
다른 이름: headless browser · 헤드리스브라우저