정규식으로 날짜나 이메일을 통째로 찾는 것까지는 해봤어요. 그런데 실무에서는 찾은 것에서 필요한 조각만 따로 빼내야 할 때가 훨씬 많아요. 예를 들어 2024-01-15라는 날짜를 찾았으면, 거기서 연도 2024만 따로, 월 01만 따로 꺼내 쓰고 싶은 거죠. 이걸 해주는 게 오늘의 주인공 캡처 그룹(잡아서 저장하는 묶음)이에요. 소괄호 하나만 붙였을 뿐인데 정규식이 몇 배는 쓸모 있어져요. 완전 처음이어도 따라 하면서 이해할 수 있게 예제를 잔뜩 준비했으니 천천히 같이 가봐요.

13.1 캡처 그룹이 뭐예요?

캡처 그룹은 정규식에서 소괄호 ( )로 감싼 부분이에요. 소괄호로 묶으면 그 안쪽에 매치된 글자가 따로 저장(캡처)돼요. 그러니까 "전체 매치 말고 이 부분만 줘"라고 조각을 꺼내 쓸 수 있는 거예요. 정규식이 "찾기"만 하는 도구에서 "찾아서 분해하기"까지 하는 도구로 확 넓어지는 순간이에요.


번호를 매기는 규칙이 딱 하나 있어요. 여는 소괄호가 나온 순서대로 1번, 2번, 3번이 붙어요. 닫는 괄호가 아니라 여는 괄호 기준이라는 게 포인트예요. 괄호가 서로 겹쳐 있어도 여는 괄호를 왼쪽부터 세면 헷갈리지 않아요. 그리고 0번은 좀 특별한데, 캡처와 상관없이 매치된 전체를 가리켜요. 그러니까 0번은 "찾은 것 전부", 1번부터는 "그중에서 내가 괄호로 묶어둔 조각들"이라고 기억하면 돼요.

13.2 날짜를 연·월·일로 어떻게 쪼개나요?

[정규식 13] 원하는 부분만 빼내는 캡처 그룹

상황부터 볼게요. 2024-01-15 같은 날짜에서 연도, 월, 일을 각각 따로 얻고 싶어요. 세 덩어리니까 괄호도 세 개로 각각 묶어주면 돼요. 이렇게요.


(\d{4})-(\d{2})-(\d{2})


여기서 \d{4}는 숫자 네 개(연도), \d{2}는 숫자 두 개(월과 일)를 뜻해요. 세 부분을 각각 괄호로 감쌌으니 그룹1은 연도, 그룹2는 월, 그룹3은 일이 돼요. 사이의 -(하이픈)은 괄호 밖에 있으니 저장은 안 되고 그냥 자리만 맞추는 역할이에요.


대상 문자열은 date 2024-01-15 end예요. 파이썬으로 실행해볼게요.


import re


re.findall(r'(\d{4})-(\d{2})-(\d{2})', 'date 2024-01-15 end')


결과는 이래요.


[('2024', '01', '15')]


결과 안에 ('2024', '01', '15')라는 게 들어 있죠? 이게 튜플(순서가 있는 값 묶음)이에요. 괄호로 그룹을 여러 개 만들면, 파이썬의 findall은 그룹들을 순서대로 묶은 튜플을 돌려줘요. 앞에서부터 그룹1(연도) 2024, 그룹2(월) 01, 그룹3(일) 15가 딱 순서대로 들어 있어요. 이제 연도만 쓰고 싶으면 튜플의 첫 번째 값을 꺼내면 되는 거예요.

13.3 그룹 번호로 어떻게 꺼내나요?

이번엔 이메일 비슷한 문자열에서 아이디와 도메인을 나눠 얻어볼게요. 상황은 hong@test에서 @ 앞의 아이디와 뒤의 도메인을 따로 뽑는 거예요. 패턴은 두 부분을 각각 괄호로 묶어요.


(\w+)@(\w+)


\w+는 글자·숫자·밑줄이 한 개 이상 이어진 덩어리예요. 앞의 (\w+)가 그룹1, 뒤의 (\w+)가 그룹2예요. 이번엔 개수를 세는 findall 대신, 한 곳을 찾아 자세히 뜯어보는 search를 써서 그룹을 번호로 꺼내볼게요.


m = re.search(r'(\w+)@(\w+)', 'hong@test')


m.group(0)


m.group(1)


m.group(2)


각각의 결과는 이래요.


'hong@test'


'hong'


'test'


보이시죠? group(0)은 매치된 전체hong@test예요. group(1)은 첫 번째 괄호가 잡은 hong(아이디), group(2)는 두 번째 괄호가 잡은 test(도메인)이고요. 여는 괄호 순서대로 번호가 매겨진다는 규칙이 여기서 그대로 확인돼요. 이렇게 번호로 콕 집어 꺼내는 게 캡처 그룹의 진짜 매력이에요.


한꺼번에 받고 싶으면 m.groups()를 쓰면 돼요. 0번(전체)은 빼고 캡처 그룹들만 튜플로 모아주는데, 여기서는 ('hong', 'test')가 나와요. 그룹이 많을 때 하나씩 group(1), group(2) 부르는 대신 한 번에 받아 쓸 수 있어 편해요. 상황에 따라 번호로 하나씩 꺼낼지, groups()로 한꺼번에 받을지 골라 쓰면 돼요.

13.4 findall이 왜 이상한 값을 주나요?

여기서 초보가 거의 다 한 번씩 걸려 넘어지는 함정이 있어요. 저도 처음에 이거 때문에 한참 헤맸어요. 상황을 볼게요. 2024-01, 2025-12 같은 문자열에서 연도만 뽑고 싶어서 연도에만 괄호를 씌웠어요.


(\d{4})-\d{2}


대상은 2024-01 and 2025-12예요. 얼핏 생각하면 매치되는 전체인 2024-01, 2025-12가 나올 것 같죠? 그런데 실행하면 이래요.


re.findall(r'(\d{4})-\d{2}', '2024-01 and 2025-12')


결과는 이거예요.


['2024', '2025']


2024-01이 아니라 2024만 나왔어요. 왜냐하면 findall에는 숨은 규칙이 있거든요. 패턴에 캡처 그룹이 하나 있으면, findall은 전체 매치가 아니라 그 그룹이 잡은 값만 리스트로 돌려줘요. 그룹을 하나 만들어놓으니 "아, 이 사람은 이 부분만 원하는구나" 하고 그 부분만 준 거예요. 함정처럼 느껴지지만 사실은 친절한 동작이에요.


정리하면 findall은 세 가지로 움직여요. 괄호가 없으면 매치된 전체 문자열을, 괄호가 하나면 그 그룹 값만, 괄호가 여럿이면 튜플로 줘요. 13.2에서 튜플이 나온 것도 이 규칙 때문이었어요. 이걸 모르면 "왜 앞부분이 잘렸지?" 하고 한참 헤매게 돼요.

13.5 그럼 전체를 그대로 받고 싶으면요?

연도만 필요한 게 아니라 2024-01 전체를 그대로 받고 싶으면 방법은 간단해요. 괄호를 아예 안 쓰면 돼요. 그룹이 없으니 findall이 전체 매치를 돌려주거든요.


re.findall(r'\d{4}-\d{2}', '2024-01 and 2025-12')


이번엔 결과가 이래요.


['2024-01', '2025-12']


괄호 하나 뺐을 뿐인데 원하던 전체 문자열이 그대로 나왔죠? 그런데 만약 괄호로 묶기는 해야 하는데(예를 들어 여러 글자에 반복 기호를 걸어야 할 때) 저장은 하기 싫을 때가 있어요. 그럴 땐 비캡처 그룹(저장 안 하는 묶음) (?:...)를 써요. 여는 괄호 바로 뒤에 ?:를 붙이면 "묶기만 하고 번호는 안 매겨줘"라는 뜻이 돼요. 그러면 findall도 이 묶음을 그룹으로 안 세고 전체 매치를 돌려줘요. 저장이 정말 필요한 부분에만 맨 괄호를 쓰고, 묶기용은 (?:...)로 구분하는 습관을 들이면 나중에 그룹 번호가 꼬이는 사고를 크게 줄일 수 있어요.

13.6 오늘 배운 것 정리

캡처 그룹은 소괄호 ( )로 감싼 부분을 따로 저장하는 기능이에요. 번호는 여는 괄호 순서대로 1번, 2번이고 0번은 전체 매치예요. group(1), group(2)로 조각을 콕 집어 꺼낼 수 있고요. 가장 조심할 건 findall의 함정이에요. 괄호가 있으면 전체가 아니라 그룹 값만 돌려주고, 괄호가 여럿이면 튜플로 준다는 것. 전체가 필요하면 괄호를 빼거나, 묶기만 하고 싶으면 저장 안 하는 (?:...)를 쓰면 돼요. 이 감각만 잡아두면 앞으로 정규식에서 원하는 조각만 야무지게 빼내 쓸 수 있을 거예요. 참고로 여기서 쓴 \d\w 같은 기호는 파이썬 문자열 앞에 r을 붙인 r'...'(원시 문자열) 안에 쓰면 역슬래시가 그대로 전해져서 안전해요. 저도 이걸 몰라 처음엔 역슬래시가 사라지는 버그로 고생했으니, r을 붙이는 습관도 같이 챙겨두세요.