본문 바로가기
  • AI 시대에 적응하는 현대인을 위한 지식 공간
  • AI를 위한 데이터를 과학으로 역어본다
AI 활용

n8n Code 노드에서 정규표현식(Regex)을 알고 씁시다

by 피크나인 2026. 1. 17.

n8n Code 노드에서 자주 사용하는 정규표현식(Regex) 완벽 가이드

n8n 워크플로우 자동화를 구축하다 보면 다양한 형태의 텍스트 데이터를 처리해야 하는 상황을 자주 만나게 됩니다. 웹 스크래핑으로 가져온 HTML에서 이메일 주소만 추출하거나, API 응답에서 특정 패턴의 데이터만 걸러내야 할 때가 있습니다. 이런 문자열 파싱 작업에서 가장 강력한 도구가 바로 정규표현식(Regular Expression, Regex)입니다.

 

이 글에서는 n8n의 Code 노드에서 JavaScript 정규표현식을 활용하여 실무에서 바로 사용할 수 있는 문자열 처리 기법을 단계별로 알아보겠습니다.

정규표현식(Regex)은 복잡한 텍스트 속에서 원하는 패턴을 정확히 찾아내는 강력한 도구입니다
정규표현식(Regex)은 복잡한 텍스트 속에서 원하는 패턴을 정확히 찾아내는 강력한 도구입니다



1. 왜 n8n에서 정규표현식이 필요한가?

n8n은 다양한 서비스와 API를 연결하여 자동화 워크플로우를 구축하는 강력한 도구입니다. 하지만 외부에서 가져오는 데이터는 항상 깔끔하게 정리된 형태로 오지 않습니다. 웹페이지에서 스크래핑한 텍스트, 이메일 본문, PDF에서 추출한 내용, API 응답 등은 대부분 비정형 데이터로 되어 있어서 원하는 정보만 추출하려면 별도의 가공 과정이 필요합니다.

 

n8n에는 기본적으로 문자열을 다루는 여러 가지 내장 기능이 있지만, 복잡한 패턴 매칭이 필요한 경우에는 한계가 있습니다. 예를 들어 "텍스트 안에 포함된 모든 이메일 주소를 찾아라"거나 "전화번호 형식에 맞는 문자열만 추출하라"와 같은 작업은 단순한 문자열 함수로는 처리하기 어렵습니다. 이때 Code 노드와 정규표현식의 조합이 빛을 발하게 됩니다.

 

Code 노드에서 JavaScript의 정규표현식을 활용하면 다음과 같은 작업을 손쉽게 처리할 수 있습니다.

  • 첫째, 특정 패턴에 맞는 문자열을 텍스트 전체에서 검색하여 추출할 수 있습니다.
  • 둘째, 원하지 않는 문자나 패턴을 일괄적으로 제거하거나 다른 문자로 대체할 수 있습니다.
  • 셋째, 텍스트가 특정 형식에 맞는지 검증하여 조건 분기에 활용할 수 있습니다.
  • 넷째, 복잡한 문자열을 여러 부분으로 분리하여 구조화된 데이터로 변환할 수 있습니다.

2. 정규표현식 기초 다지기

정규표현식이란 무엇인가?

정규표현식(Regular Expression)은 문자열에서 특정 패턴을 찾거나, 매칭하거나, 대체하기 위한 일종의 검색 언어입니다. 마치 와일드카드 검색의 확장판이라고 생각하면 이해하기 쉽습니다. 예를 들어 파일 탐색기에서 *.txt라고 검색하면 모든 텍스트 파일을 찾을 수 있는 것처럼, 정규표현식은 이보다 훨씬 정교하고 복잡한 패턴을 정의하여 원하는 텍스트를 찾아낼 수 있습니다.

 

정규표현식은 1950년대 수학자 스티븐 클레이니(Stephen Kleene)가 정규 언어를 표현하기 위해 고안한 표기법에서 시작되었습니다. 이후 유닉스 텍스트 편집기와 프로그래밍 언어에 도입되면서 개발자들의 필수 도구로 자리 잡았습니다. 오늘날 거의 모든 프로그래밍 언어가 정규표현식을 지원하며, JavaScript도 강력한 정규표현식 기능을 내장하고 있습니다.

JavaScript에서 정규표현식 생성하기

JavaScript에서 정규표현식을 만드는 방법은 두 가지가 있습니다.

  • 첫 번째는 리터럴 표기법으로, 슬래시(/)로 패턴을 감싸는 방식입니다. 이 방법은 패턴이 고정되어 있을 때 주로 사용합니다.
  • 두 번째는 RegExp 생성자를 사용하는 방법으로, 패턴을 동적으로 만들어야 할 때 유용합니다.

 

다음은 두 가지 방법의 예시입니다:

// 리터럴 표기법 - 패턴이 고정된 경우
const pattern1 = /hello/;

// RegExp 생성자 - 패턴을 동적으로 만들 때
const searchWord = "hello";
const pattern2 = new RegExp(searchWord);

// 플래그와 함께 사용
const pattern3 = /hello/gi;  // g: 전역 검색, i: 대소문자 무시
const pattern4 = new RegExp("hello", "gi");

 

위 코드에서 gi와 같은 문자를 플래그(flag)라고 부릅니다. 플래그는 정규표현식의 검색 동작을 제어하는 옵션입니다. g는 global의 약자로 텍스트 전체에서 일치하는 모든 패턴을 찾고, i는 ignore case의 약자로 대소문자를 구분하지 않고 검색합니다. 자주 사용하는 플래그는 다음과 같습니다:

플래그 의미 설명
g global 일치하는 모든 결과를 찾습니다. 이 플래그가 없으면 첫 번째 결과만 반환합니다.
i ignore case 대소문자를 구분하지 않습니다. Hello, HELLO, hello 모두 매칭됩니다.
m multiline 여러 줄 모드를 활성화합니다. ^와 $가 각 줄의 시작과 끝에도 매칭됩니다.
s dotAll .이 개행 문자(\n)도 포함하여 모든 문자에 매칭됩니다.

n8n Code 노드에서 주요 메서드

n8n의 Code 노드에서 정규표현식을 활용할 때 가장 자주 사용하는 JavaScript 메서드 네 가지를 살펴보겠습니다. 각 메서드는 서로 다른 목적에 맞게 설계되어 있으므로, 상황에 따라 적절한 메서드를 선택하는 것이 중요합니다.

test() 메서드 - 패턴 존재 여부 확인

test() 메서드는 문자열에 패턴이 존재하는지 확인하여 true 또는 false를 반환합니다. 단순히 패턴의 존재 여부만 확인하면 될 때 사용하며, n8n 워크플로우에서 조건 분기를 만들 때 특히 유용합니다.

// test() - 패턴이 존재하는지 true/false 반환
const text = "문의 이메일: support@example.com";
const hasEmail = /\S+@\S+\.\S+/.test(text);

console.log(hasEmail);  // true

match() 메서드 - 일치하는 문자열 추출

match() 메서드는 문자열에서 패턴과 일치하는 부분을 찾아 배열로 반환합니다. g 플래그를 사용하면 일치하는 모든 결과를 배열로 받을 수 있고, 플래그 없이 사용하면 첫 번째 결과와 함께 캡처 그룹 정보도 얻을 수 있습니다.

// match() - 일치하는 문자열을 배열로 반환
const text = "연락처: 010-1234-5678, 02-987-6543";
const phones = text.match(/\d{2,3}-\d{3,4}-\d{4}/g);

console.log(phones);  // ["010-1234-5678", "02-987-6543"]

replace() 메서드 - 문자열 대체

replace() 메서드는 패턴과 일치하는 부분을 다른 문자열로 대체합니다. g 플래그를 사용하면 일치하는 모든 부분을 대체하고, 플래그 없이 사용하면 첫 번째 일치 항목만 대체합니다. 대체 문자열에서 $1, $2 등을 사용하면 캡처 그룹의 내용을 참조할 수 있습니다.

// replace() - 패턴과 일치하는 부분을 대체
const text = "가격: 10000원, 할인가: 8000원";
const formatted = text.replace(/(\d+)원/g, "$1 KRW");

console.log(formatted);  // "가격: 10000 KRW, 할인가: 8000 KRW"

exec() 메서드 - 상세 매칭 정보 추출

exec() 메서드는 정규표현식 객체의 메서드로, 패턴과 일치하는 부분에 대한 상세한 정보(인덱스 위치, 캡처 그룹 등)를 반환합니다. g 플래그와 함께 반복 호출하면 일치하는 모든 결과를 순차적으로 얻을 수 있습니다.

// exec() - 상세한 매칭 정보 반환
const text = "주문번호: ORD-2024-001, ORD-2024-002";
const regex = /ORD-(\d{4})-(\d{3})/g;
let result;

while ((result = regex.exec(text)) !== null) {
  console.log(`전체 매칭: ${result[0]}`);
  console.log(`연도: ${result[1]}, 번호: ${result[2]}`);
  console.log(`위치: ${result.index}`);
}

 

자주 사용하는 메타문자와 특수문자 완벽 가이드

정규표현식의 진정한 힘은 메타문자(metacharacter)와 특수문자에서 나옵니다. 이들은 단순한 문자가 아니라 특별한 의미를 가진 기호로, 복잡한 패턴을 간결하게 표현할 수 있게 해줍니다. 처음에는 암호처럼 보일 수 있지만, 각 기호의 의미를 이해하면 매우 직관적으로 패턴을 읽고 작성할 수 있게 됩니다.

위치를 나타내는 앵커(Anchor)

앵커는 문자 자체를 매칭하는 것이 아니라, 문자열 내의 특정 위치를 지정합니다. 실제로 문자를 소비하지 않고 위치만 확인하기 때문에 "제로 너비 어서션(zero-width assertion)"이라고도 부릅니다.

기호 이름 의미 예시 매칭 결과
^ 캐럿 문자열(또는 줄)의 시작 위치를 나타냅니다. m 플래그와 함께 사용하면 각 줄의 시작에도 매칭됩니다. ^Hello "Hello World"에서 "Hello" 매칭, "Say Hello"에서는 매칭 안 됨
$ 달러 문자열(또는 줄)의 끝 위치를 나타냅니다. m 플래그와 함께 사용하면 각 줄의 끝에도 매칭됩니다. end$ "The end"에서 "end" 매칭, "endless"에서는 매칭 안 됨
\b 단어 경계 단어의 시작 또는 끝 경계를 나타냅니다. 단어 문자(\w)와 비단어 문자 사이의 경계입니다. \bcat\b "the cat sat"에서 "cat" 매칭, "category"에서는 매칭 안 됨
\B 비단어 경계 단어 경계가 아닌 위치를 나타냅니다. 단어 중간 위치에서 매칭합니다. \Bcat\B "education"에서 "cat" 매칭, "cat"에서는 매칭 안 됨

 

다음 예시를 통해 앵커의 동작을 확인해보겠습니다:

// 문자열 시작과 끝 앵커
const text = "Hello World";
console.log(/^Hello/.test(text));  // true - "Hello"로 시작함
console.log(/^World/.test(text));  // false - "World"로 시작하지 않음
console.log(/World$/.test(text));  // true - "World"로 끝남

// 단어 경계 앵커
const sentence = "The cat sat on the category mat";
console.log(sentence.match(/\bcat\b/g));  // ["cat"] - 독립된 단어 "cat"만 매칭
console.log(sentence.match(/cat/g));      // ["cat", "cat"] - "category" 안의 "cat"도 매칭

문자 클래스(Character Class)

문자 클래스는 매칭할 수 있는 문자의 집합을 정의합니다. 대괄호 [] 안에 문자들을 나열하거나, 미리 정의된 약어를 사용하여 특정 유형의 문자를 간편하게 지정할 수 있습니다.

기호 이름 의미 예시 매칭 결과
[abc] 문자 집합 대괄호 안의 문자 중 하나와 매칭합니다. "a 또는 b 또는 c"를 의미합니다. [aeiou] "hello"에서 "e", "o" 매칭
[^abc] 부정 문자 집합 대괄호 안의 문자를 제외한 모든 문자와 매칭합니다. ^가 대괄호 안 맨 앞에 올 때만 부정의 의미입니다. [^0-9] "a1b2c3"에서 "a", "b", "c" 매칭
[a-z] 범위 지정된 범위 내의 모든 문자와 매칭합니다. 하이픈(-)으로 시작과 끝을 지정합니다. [a-zA-Z] "Test123"에서 "T", "e", "s", "t" 매칭
\d 숫자 모든 숫자(0-9)와 매칭합니다. [0-9]와 동일합니다. \d{3} "abc123def"에서 "123" 매칭
\D 비숫자 숫자가 아닌 모든 문자와 매칭합니다. [^0-9]와 동일합니다. \D+ "abc123def"에서 "abc", "def" 매칭
\w 단어 문자 알파벳, 숫자, 밑줄(_)과 매칭합니다. [a-zA-Z0-9_]와 동일합니다. \w+ "hello_world!"에서 "hello_world" 매칭
\W 비단어 문자 단어 문자가 아닌 모든 문자와 매칭합니다. 공백, 구두점 등이 포함됩니다. \W "hello world!"에서 " ", "!" 매칭
\s 공백 문자 모든 공백 문자(스페이스, 탭, 개행 등)와 매칭합니다. \s+ "hello world"에서 " " 매칭
\S 비공백 문자 공백이 아닌 모든 문자와 매칭합니다. \S+ "hello world"에서 "hello", "world" 매칭
. 와일드카드 개행 문자(\n)를 제외한 모든 단일 문자와 매칭합니다. s 플래그 사용 시 개행도 포함합니다. a.c "abc", "a1c", "a c" 모두 매칭

 

다음 예시를 통해 문자 클래스의 활용법을 살펴보겠습니다:

// 문자 집합과 범위
const text = "Phone: 010-1234-5678";
console.log(text.match(/[0-9]+/g));       // ["010", "1234", "5678"]
console.log(text.match(/[^0-9\-]+/g));    // ["Phone: "] - 숫자와 하이픈 제외

// 미리 정의된 문자 클래스
const mixed = "Hello123 World456!";
console.log(mixed.match(/\d+/g));    // ["123", "456"] - 숫자만
console.log(mixed.match(/\w+/g));    // ["Hello123", "World456"] - 단어 문자
console.log(mixed.match(/\W/g));     // [" ", "!"] - 비단어 문자

한글 매칭하기: \w는 한글을 포함하지 않습니다. 한글을 매칭하려면 유니코드 범위 [가-힣] 또는 [ㄱ-ㅎㅏ-ㅣ가-힣]를 사용해야 합니다. 예를 들어 /[가-힣]+/g는 연속된 한글 문자열을 매칭합니다.

수량자(Quantifier)

수량자는 앞에 있는 요소가 몇 번 반복되어야 하는지를 지정합니다. 정확한 횟수를 지정하거나 범위를 지정할 수 있으며, 수량자의 동작 방식(탐욕적/게으른)도 조절할 수 있습니다.

기호 이름 의미 예시 매칭 결과
* 별표 앞 요소가 0번 이상 반복됩니다. 없어도 되고 여러 번 있어도 됩니다. ab*c "ac", "abc", "abbc", "abbbc" 모두 매칭
+ 더하기 앞 요소가 1번 이상 반복됩니다. 최소 한 번은 있어야 합니다. ab+c "abc", "abbc" 매칭, "ac"는 매칭 안 됨
? 물음표 앞 요소가 0번 또는 1번 나타납니다. 있어도 되고 없어도 됩니다. colou?r "color", "colour" 둘 다 매칭
{n} 정확히 n번 앞 요소가 정확히 n번 반복됩니다. \d{4} "2024"와 같은 4자리 숫자 매칭
{n,} n번 이상 앞 요소가 최소 n번 이상 반복됩니다. \d{2,} "1"은 안 되고 "12", "123", "1234" 매칭
{n,m} n번 이상 m번 이하 앞 요소가 n번 이상, m번 이하 반복됩니다. \d{2,4} "12", "123", "1234" 매칭, "1"이나 "12345"는 안 됨
*? 게으른 별표 가능한 적게 매칭합니다(최소 매칭). 탐욕적 매칭의 반대입니다. a.*?b "aXXbYYb"에서 "aXXb"만 매칭
+? 게으른 더하기 가능한 적게 매칭합니다. 최소 1번은 매칭해야 합니다. a.+?b "aXXbYYb"에서 "aXXb"만 매칭

 

탐욕적(greedy) 매칭과 게으른(lazy) 매칭의 차이를 이해하는 것이 중요합니다:

// 탐욕적 매칭 vs 게으른 매칭
const html = "<div>첫번째</div><div>두번째</div>";

// 탐욕적: 가능한 많이 매칭 (기본 동작)
console.log(html.match(/<div>.*<\/div>/)[0]);
// "<div>첫번째</div><div>두번째</div>" - 전체를 하나로 매칭

// 게으른: 가능한 적게 매칭 (? 추가)
console.log(html.match(/<div>.*?<\/div>/g));
// ["<div>첫번째</div>", "<div>두번째</div>"] - 각각 따로 매칭

그룹과 참조(Group & Reference)

그룹은 여러 문자나 패턴을 하나의 단위로 묶어줍니다. 그룹을 사용하면 복잡한 패턴을 구성하고, 매칭된 부분을 나중에 참조하거나, 대체 문자열에서 활용할 수 있습니다.

기호 이름 의미 예시 매칭 결과
(abc) 캡처 그룹 패턴을 그룹으로 묶고 매칭된 내용을 캡처합니다. $1, $2 등으로 참조할 수 있습니다. (\d{3})-(\d{4}) "010-1234"에서 그룹1: "010", 그룹2: "1234"
(?:abc) 비캡처 그룹 패턴을 그룹으로 묶지만 캡처하지 않습니다. 참조가 필요 없을 때 사용하면 성능이 향상됩니다. (?:ab)+ "ababab"에서 "ababab" 매칭, 그룹 저장 안 함
(?<name>abc) 이름 있는 캡처 그룹 그룹에 이름을 붙여 참조를 더 명확하게 합니다. groups.name으로 접근합니다. (?<year>\d{4}) "2024"에서 groups.year로 접근 가능
\1, \2 역참조 이전에 캡처한 그룹을 다시 참조합니다. 같은 패턴이 반복되는 경우를 찾을 때 유용합니다. (\w+)\s+\1 "hello hello"에서 중복된 단어 매칭
(a|b) 선택(OR) 파이프(|) 기호로 여러 대안 중 하나를 선택합니다. (cat|dog) "I have a cat"에서 "cat" 매칭

 

그룹을 활용한 실제 예시를 살펴보겠습니다:

// 캡처 그룹으로 전화번호 분리
const phone = "전화: 010-1234-5678";
const match = phone.match(/(\d{3})-(\d{4})-(\d{4})/);
// match(/\d{2,3}-\d{3,4}-\d{4}/g); 좀더 다양한 형태의 전화번호 매칭
console.log(`지역: ${match[1]}, 중간: ${match[2]}, 끝: ${match[3]}`);
// "지역: 010, 중간: 1234, 끝: 5678"

// 이름 있는 캡처 그룹
const date = "2024-03-15";
const dateMatch = date.match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/);
console.log(dateMatch.groups);
// { year: "2024", month: "03", day: "15" }

// replace에서 캡처 그룹 참조
const name = "홍길동";
const result = name.replace(/(.)(.)(.)/,"성: $1, 이름: $2$3");
console.log(result);  // "성: 홍, 이름: 길동"

// 역참조로 중복 단어 찾기
const text = "the the quick brown fox fox";
console.log(text.match(/\b(\w+)\s+\1\b/g));
// ["the the", "fox fox"]

전후방 탐색(Lookahead & Lookbehind)

전후방 탐색은 특정 패턴이 앞이나 뒤에 있는지 확인하되, 해당 패턴 자체는 매칭 결과에 포함하지 않습니다. 이를 통해 더 정교한 조건부 매칭이 가능합니다.

기호 이름 의미 예시 매칭 결과

기호 이름 의미 예시 매칭 결과
(?=abc) 긍정 전방탐색 뒤에 해당 패턴이 있는 위치를 찾습니다. 패턴 자체는 결과에 포함되지 않습니다. \d+(?=원) "가격 1000원"에서 "1000" 매칭 ("원"은 미포함)
(?!abc) 부정 전방탐색 뒤에 해당 패턴이 없는 위치를 찾습니다. \d+(?!원) "1000원 2000달러"에서 "2000" 매칭
(?<=abc) 긍정 후방탐색 앞에 해당 패턴이 있는 위치를 찾습니다. 패턴 자체는 결과에 포함되지 않습니다. (?<=\$)\d+ "가격 $100"에서 "100" 매칭 ("$"는 미포함)
(?<!abc) 부정 후방탐색 앞에 해당 패턴이 없는 위치를 찾습니다. (?<!\$)\d+ "$100 200"에서 "200" 매칭

 

전후방 탐색의 실용적인 예시를 살펴보겠습니다:

// 긍정 전방탐색: "원" 앞의 숫자만 추출
const prices = "사과 1000원, 배 2000원, 포도 $30";
console.log(prices.match(/\d+(?=원)/g));
// ["1000", "2000"] - 원화 금액만 추출

// 부정 전방탐색: 특정 확장자가 아닌 파일명
const files = "doc.txt report.pdf image.txt data.csv";
console.log(files.match(/\w+(?!\.pdf)\.\w+/g));
// ["doc.txt", "image.txt", "data.csv"] - pdf 제외

// 긍정 후방탐색: 달러 기호 뒤의 숫자
const amounts = "USD $100, KRW 50000원";
console.log(amounts.match(/(?<=\$)\d+/g));
// ["100"] - 달러 금액만 추출

// 후방탐색으로 특정 레이블 뒤의 값 추출
const info = "이름: 홍길동, 나이: 30, 직업: 개발자";
console.log(info.match(/(?<=이름: )\S+/)[0]);
// "홍길동,"

이스케이프 문자

정규표현식에서 특별한 의미를 가진 문자를 일반 문자로 사용하려면 백슬래시(\)를 앞에 붙여 이스케이프해야 합니다. 이는 해당 문자의 특수한 의미를 "탈출(escape)"시켜 문자 그대로 매칭하게 합니다.

이스케이프 필요 문자 원래 의미 이스케이프 후
\. 아무 문자 매칭 마침표 문자 그대로 매칭
\* 0회 이상 반복 별표 문자 그대로 매칭
\+ 1회 이상 반복 더하기 문자 그대로 매칭
\? 0 또는 1회 물음표 문자 그대로 매칭
\^ 문자열 시작 캐럿 문자 그대로 매칭
\$ 문자열 끝 달러 기호 그대로 매칭
| OR 연산 파이프 문자 그대로 매칭
\\ 이스케이프 백슬래시 그대로 매칭
\[, \] 문자 클래스 대괄호 그대로 매칭
\(, \) 그룹화 소괄호 그대로 매칭
\{, \} 수량자 중괄호 그대로 매칭
// 이스케이프가 필요한 경우
const text = "가격은 $100.00 입니다. (할인 적용)";

// 잘못된 예: 특수문자가 정규표현식으로 해석됨
// /$.00/ - $는 줄 끝, .는 아무 문자

// 올바른 예: 이스케이프 처리
console.log(text.match(/\$\d+\.\d{2}/)[0]);  // "$100.00"
console.log(text.match(/\(.*?\)/)[0]);        // "(할인 적용)"

// URL에서 도메인 추출 (점 이스케이프)
const url = "https://www.example.com/path";
console.log(url.match(/www\.\w+\.com/)[0]);   // "www.example.com"

 

동적으로 정규표현식을 만들 때 주의사항: new RegExp() 생성자를 사용할 때는 문자열 안에서 백슬래시를 한 번 더 써야 합니다. 예를 들어 \d는 "\\d"로, \.는 "\\."로 작성해야 합니다. 이는 JavaScript 문자열에서 백슬래시 자체가 이스케이프 문자로 사용되기 때문입니다.


3. n8n 실무에서 자주 쓰는 Regex 패턴 10선

실무에서 n8n 워크플로우를 구축할 때 가장 자주 사용하게 되는 정규표현식 패턴 10가지를 소개합니다. 각 패턴은 바로 복사하여 Code 노드에서 사용할 수 있으며, 필요에 따라 수정하여 활용할 수 있습니다.


이메일 주소 추출

이메일 주소는 웹 스크래핑, 문서 처리, 고객 데이터 정리 등 다양한 상황에서 추출해야 할 때가 많습니다. 이메일의 기본 구조는 "로컬파트@도메인"이며, 각 부분에 허용되는 문자 규칙이 있습니다.

// n8n Code 노드에서 사용
const text = $input.first().json.content;

// 기본 이메일 패턴
const emailPattern = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;

const emails = text.match(emailPattern) || [];

return emails.map(email => ({ json: { email } }));

 

위 패턴을 분석해보면 다음과 같습니다. [a-zA-Z0-9._%+-]+는 이메일의 로컬 파트(@ 앞부분)를 매칭하며, 알파벳, 숫자, 그리고 ., _, %, +, - 문자를 1개 이상 포함합니다. @는 이메일 구분자입니다. [a-zA-Z0-9.-]+는 도메인 이름을 매칭합니다. \.[a-zA-Z]{2,}는 최상위 도메인(TLD)을 매칭하며, 점 뒤에 2글자 이상의 알파벳이 와야 합니다.

전화번호 추출 (한국 형식)

한국 전화번호는 다양한 형식으로 표기됩니다. 휴대폰은 010-XXXX-XXXX 형식이고, 지역번호가 있는 유선전화는 02-XXX-XXXX 또는 031-XXX-XXXX 형식입니다. 하이픈 없이 붙여 쓰는 경우도 있습니다.

// n8n Code 노드에서 사용
const text = $input.first().json.content;

// 한국 전화번호 패턴 (다양한 형식 지원)
const phonePattern = /(?:0(?:10|1[1-9]|2|[3-6][1-5]|70))[-.\s]?\d{3,4}[-.\s]?\d{4}/g;

const phones = text.match(phonePattern) || [];

// 형식 통일 (하이픈으로 구분)
const formatted = phones.map(phone => {
  const digits = phone.replace(/\D/g, '');
  if (digits.startsWith('02')) {
    return digits.length === 9 
      ? `${digits.slice(0,2)}-${digits.slice(2,5)}-${digits.slice(5)}`
      : `${digits.slice(0,2)}-${digits.slice(2,6)}-${digits.slice(6)}`;
  }
  return `${digits.slice(0,3)}-${digits.slice(3,7)}-${digits.slice(7)}`;
});

return formatted.map(phone => ({ json: { phone } }));

 

이 패턴은 0(?:10|1[1-9]|2|[3-6][1-5]|70)로 시작하는데, 이는 010(휴대폰), 011-019(옛 번호), 02(서울), 031-065(지역번호), 070(인터넷전화) 등을 매칭합니다. [-.\s]?는 하이픈, 점, 공백 중 하나가 있거나 없어도 됩니다. \d{3,4}는 중간 번호 3-4자리, \d{4}는 마지막 4자리입니다.

URL 추출 및 파싱

웹 스크래핑이나 문서 처리에서 URL을 추출하고 분석하는 작업은 매우 빈번합니다. URL은 프로토콜, 도메인, 경로, 쿼리 문자열 등 여러 구성 요소로 이루어져 있습니다.

// n8n Code 노드에서 사용
const text = $input.first().json.content;

// URL 추출 패턴
const urlPattern = /https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&\/=]*)/g;

const urls = text.match(urlPattern) || [];

// URL 파싱하여 구성 요소 분리
const parsed = urls.map(url => {
  const urlObj = new URL(url);
  return {
    full: url,
    protocol: urlObj.protocol,
    domain: urlObj.hostname,
    path: urlObj.pathname,
    query: urlObj.search
  };
});

return parsed.map(data => ({ json: data }));

URL 정규표현식 상세 분석

해당 정규표현식을 각 구성 요소별로 분해하여 설명하겠습니다.

/https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&\/=]*)/g
```

---

## 전체 구조 개요
```
https?:\/\/  (?:www\.)?  [-a-zA-Z0-9@:%._\+~#=]{1,256}  \.  [a-zA-Z0-9()]{1,6}  \b  (?:[-a-zA-Z0-9()@:%_\+.~#?&\/=]*)
|_________|  |________|  |_______________________________|  |_________________| |__|  |__|  |________________________|
  프로토콜     www 선택      도메인 이름                    점    TLD(최상위도메인)       경계   경로/쿼리 문자열
```

---

## 구성 요소별 상세 설명

### 1. 프로토콜 부분: `https?:\/\/`

| 요소 | 의미 |
|------|------|
| `http` | 문자 그대로 "http" 매칭 |
| `s?` | "s"가 0번 또는 1번 나타남 (http 또는 https 모두 매칭) |
| `:` | 콜론 문자 그대로 매칭 |
| `\/\/` | 슬래시 두 개 (`//`). 슬래시는 정규표현식 구분자이므로 `\/`로 이스케이프 |

**매칭 예시:** `http://` 또는 `https://`

---

### 2. www 선택 부분: `(?:www\.)?`

| 요소 | 의미 |
|------|------|
| `(?:...)` | 비캡처 그룹 - 그룹화하지만 결과를 저장하지 않음 |
| `www` | 문자 그대로 "www" 매칭 |
| `\.` | 점 문자 그대로 매칭 (이스케이프 필요) |
| `?` | 전체 그룹이 0번 또는 1번 나타남 (있어도 되고 없어도 됨) |

**매칭 예시:** `www.` 또는 아무것도 없음

---

### 3. 도메인 이름 부분: `[-a-zA-Z0-9@:%._\+~#=]{1,256}`

| 요소 | 의미 |
|------|------|
| `[...]` | 문자 클래스 - 대괄호 안의 문자 중 하나와 매칭 |
| `-` | 하이픈 (맨 앞이나 맨 뒤에 있으면 문자 그대로 의미) |
| `a-zA-Z` | 영문 소문자 a-z, 대문자 A-Z |
| `0-9` | 숫자 0-9 |
| `@:%._\+~#=` | 특수문자들 (도메인에 허용되는 문자). `\+`는 `+` 이스케이프 |
| `{1,256}` | 앞의 문자 클래스가 1번 이상 256번 이하 반복 |

**매칭 예시:** `example`, `sub.domain`, `my-site123`

> [!INFO]
> 256자 제한은 URL 도메인 부분의 실질적인 최대 길이를 고려한 것입니다.

---

### 4. 점과 TLD 부분: `\.[a-zA-Z0-9()]{1,6}`

| 요소 | 의미 |
|------|------|
| `\.` | 점 문자 그대로 매칭 |
| `[a-zA-Z0-9()]` | 영문자, 숫자, 소괄호 |
| `{1,6}` | 1자 이상 6자 이하 (대부분의 TLD는 2-6자) |

**매칭 예시:** `.com`, `.co`, `.kr`, `.museum`

---

### 5. 단어 경계: `\b`

| 요소 | 의미 |
|------|------|
| `\b` | 단어 경계 - 단어 문자(`\w`)와 비단어 문자 사이의 위치 |

**역할:** TLD 뒤에서 URL이 명확하게 끝나도록 보장합니다. 예를 들어 `example.command`에서 `.com`만 TLD로 잘못 매칭되는 것을 방지합니다.

---

### 6. 경로/쿼리 부분: `(?:[-a-zA-Z0-9()@:%_\+.~#?&\/=]*)`

| 요소 | 의미 |
|------|------|
| `(?:...)` | 비캡처 그룹 |
| `[-a-zA-Z0-9()@:%_\+.~#?&\/=]` | URL 경로와 쿼리에 허용되는 문자들 |
| `*` | 0번 이상 반복 (경로가 없어도 됨) |

**허용 문자 상세:**

| 문자 | 용도 |
|------|------|
| `-` | 하이픈 (경로에 사용) |
| `a-zA-Z0-9` | 영문자와 숫자 |
| `()` | 괄호 (위키피디아 등에서 사용) |
| `@` | 사용자 정보 (드물게 사용) |
| `:` | 포트 번호 구분자 |
| `%` | URL 인코딩 (예: `%20`) |
| `_` | 밑줄 |
| `\+` | 더하기 (공백 대체) |
| `.` | 점 (파일 확장자 등) |
| `~` | 틸드 (사용자 디렉토리) |
| `#` | 앵커/프래그먼트 |
| `?` | 쿼리 문자열 시작 |
| `&` | 쿼리 파라미터 구분자 |
| `\/` | 경로 구분자 (슬래시) |
| `=` | 쿼리 값 할당 |

**매칭 예시:** `/path/to/page`, `?id=123&name=test`, `/file.html#section`

---

### 7. 플래그: `g`

| 플래그 | 의미 |
|--------|------|
| `g` | global - 텍스트 전체에서 일치하는 모든 URL을 찾음 |

---

## 전체 매칭 흐름 시각화
```
https://www.example.com/path/page?query=value#anchor
|_____|  |_|  |______|  |_|  |________________________|
  │      │      │       │              │
  │      │      │       │              └─ (?:[-a-zA-Z0-9()@:%_\+.~#?&\/=]*)
  │      │      │       │                  경로, 쿼리, 앵커 (선택)
  │      │      │       │
  │      │      │       └─ \.[a-zA-Z0-9()]{1,6}\b
  │      │      │          TLD (.com, .kr 등)
  │      │      │
  │      │      └─ [-a-zA-Z0-9@:%._\+~#=]{1,256}
  │      │         도메인 이름
  │      │
  │      └─ (?:www\.)?
  │         www. (선택)
  │
  └─ https?:\/\/
     프로토콜

매칭 테스트 예시

const urlPattern = /https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&\/=]*)/g;

const text = `
  방문하세요: https://www.example.com
  문서 링크: http://docs.google.com/document/d/abc123
  API 주소: https://api.example.co.kr/v1/users?id=100&status=active
  위키: https://en.wikipedia.org/wiki/Regular_expression_(computer_science)
`;

const urls = text.match(urlPattern);
console.log(urls);
// [
//   "https://www.example.com",
//   "http://docs.google.com/document/d/abc123",
//   "https://api.example.co.kr/v1/users?id=100&status=active",
//   "https://en.wikipedia.org/wiki/Regular_expression_(computer_science)"
// ]

패턴의 한계점

이 패턴은 대부분의 일반적인 URL을 잘 매칭하지만, 다음과 같은 한계가 있습니다:

한계 설명
포트 번호 http://localhost:8080과 같은 포트 번호가 포함된 URL은 부분적으로만 매칭될 수 있습니다
IP 주소 http://192.168.1.1과 같은 IP 기반 URL은 매칭되지 않습니다
국제화 도메인 한글 도메인(http://한글.kr)은 매칭되지 않습니다
긴 TLD .technology와 같이 6자를 초과하는 TLD는 매칭되지 않습니다

필요에 따라 패턴을 수정하여 이러한 케이스를 추가로 처리할 수 있습니다.

이러한 패턴의 한계점을 극복하려면 조금은 복잡하지만 아래의 패턴을 사용할 수 있습니다.

const urlPattern = /https?:\/\/(?:www\.)?(?:(?:\d{1,3}\.){3}\d{1,3}|[-a-zA-Z0-9@:%._\+~#=\u3131-\u3163\uac00-\ud7a3]{1,256}\.[-a-zA-Z0-9()\u3131-\u3163\uac00-\ud7a3]{1,63})(?::\d{1,5})?\b(?:[-a-zA-Z0-9()@:%_\+.~#?&\/=]*)/g;

날짜 형식 추출 및 변환

날짜는 2024-03-15, 2024/03/15, 2024.03.15, 2024년 3월 15일 등 매우 다양한 형식으로 표기됩니다. 여러 형식을 한 번에 처리할 수 있는 패턴이 필요합니다.

// n8n Code 노드에서 사용
const text = $input.first().json.content;

// 다양한 날짜 형식 매칭
const datePatterns = {
  // 2024-03-15 또는 2024/03/15 또는 2024.03.15
  iso: /\d{4}[-\/\.]\d{1,2}[-\/\.]\d{1,2}/g,
  // 2024년 3월 15일
  korean: /\d{4}년\s*\d{1,2}월\s*\d{1,2}일/g,
  // 03/15/2024 (미국식)
  us: /\d{1,2}\/\d{1,2}\/\d{4}/g
};

// 모든 패턴으로 날짜 추출
let allDates = [];
for (const [format, pattern] of Object.entries(datePatterns)) {
  const matches = text.match(pattern) || [];
  matches.forEach(date => {
    allDates.push({ original: date, format });
  });
}

// ISO 형식으로 통일
const normalized = allDates.map(item => {
  let normalized;
  const digits = item.original.match(/\d+/g);
  
  if (item.format === 'us') {
    normalized = `${digits[2]}-${digits[0].padStart(2,'0')}-${digits[1].padStart(2,'0')}`;
  } else {
    normalized = `${digits[0]}-${digits[1].padStart(2,'0')}-${digits[2].padStart(2,'0')}`;
  }
  
  return { ...item, normalized };
});

return normalized.map(data => ({ json: data }));

금액 및 숫자 추출

금액 데이터는 통화 기호, 천 단위 구분자, 소수점 등 다양한 형식으로 표현됩니다. 이를 정확하게 추출하고 숫자로 변환하는 것이 중요합니다.

// n8n Code 노드에서 사용
const text = $input.first().json.content;

// 금액 패턴 (원화, 달러 등)
const moneyPattern = /(?:₩|\$|USD|KRW|원)?\s*[\d,]+(?:\.\d{1,2})?(?:\s*(?:원|달러|USD|KRW))?/g;

const amounts = text.match(moneyPattern) || [];

// 숫자만 추출하여 정리
const parsed = amounts
  .filter(amt => /\d/.test(amt))  // 숫자가 포함된 것만
  .map(amt => {
    const currency = amt.includes('$') || amt.includes('달러') || amt.includes('USD') 
      ? 'USD' : 'KRW';
    const value = parseFloat(amt.replace(/[^\d.]/g, ''));
    return { original: amt.trim(), currency, value };
  });

return parsed.map(data => ({ json: data }));

금액 정규표현식 상세 분석

/(?:₩|\$|USD|KRW|원)?\s*[\d,]+(?:\.\d{1,2})?(?:\s*(?:원|달러|USD|KRW))?/g
```

---

## 전체 구조 개요
```
(?:₩|\$|USD|KRW|원)?  \s*  [\d,]+  (?:\.\d{1,2})?  (?:\s*(?:원|달러|USD|KRW))?
|_________________|  |__| |_____|  |___________|  |________________________|
   앞쪽 통화 기호        공백  정수부분    소수점부분        뒤쪽 통화 단위
     (선택)           (선택) (필수)      (선택)             (선택)
```

---

## 구성 요소별 상세 설명

### 1. 앞쪽 통화 기호: `(?:₩|\$|USD|KRW|원)?`

| 요소 | 의미 |
|------|------|
| `(?:...)` | 비캡처 그룹 - 그룹화하지만 결과를 저장하지 않음 |
| `₩` | 원화 기호 (₩) |
| `|` | OR 연산자 - 여러 대안 중 하나 선택 |
| `\$` | 달러 기호 (`$`는 정규표현식에서 줄 끝을 의미하므로 이스케이프 필요) |
| `USD` | 문자 그대로 "USD" |
| `KRW` | 문자 그대로 "KRW" |
| `원` | 문자 그대로 "원" |
| `?` | 전체 그룹이 0번 또는 1번 (있어도 되고 없어도 됨) |

**매칭 예시:** `₩`, `$`, `USD`, `KRW`, `원` 또는 아무것도 없음

---

### 2. 공백: `\s*`

| 요소 | 의미 |
|------|------|
| `\s` | 공백 문자 (스페이스, 탭 등) |
| `*` | 0번 이상 반복 |

**역할:** 통화 기호와 숫자 사이의 공백 허용 (예: `$ 100`, `$100` 모두 매칭)

---

### 3. 정수 부분: `[\d,]+`

| 요소 | 의미 |
|------|------|
| `[...]` | 문자 클래스 |
| `\d` | 숫자 0-9 |
| `,` | 쉼표 (천 단위 구분자), 쉼표는 필수가 아니라 선택. 있으면 포함하고, 없으면 숫자만으로 매칭  |
| `+` | 1번 이상 반복 (최소 한 자리 숫자 필수) |

**매칭 예시:** `100`, `1,000`, `1,000,000`

---

### 4. 소수점 부분: `(?:\.\d{1,2})?`

| 요소 | 의미 |
|------|------|
| `(?:...)` | 비캡처 그룹 |
| `\.` | 점 문자 그대로 (소수점) |
| `\d{1,2}` | 숫자 1자리 또는 2자리 |
| `?` | 전체 그룹이 0번 또는 1번 (소수점 없어도 됨) |

**매칭 예시:** `.5`, `.99` 또는 아무것도 없음

---

### 5. 뒤쪽 통화 단위: `(?:\s*(?:원|달러|USD|KRW))?`

| 요소 | 의미 |
|------|------|
| `(?:...)` | 비캡처 그룹 (외부) |
| `\s*` | 공백 0번 이상 |
| `(?:원\|달러\|USD\|KRW)` | 통화 단위 중 하나 선택 (내부 비캡처 그룹) |
| `?` | 전체 그룹이 0번 또는 1번 |

**매칭 예시:** `원`, ` 달러`, `USD` 또는 아무것도 없음

---

### 6. 플래그: `g`

| 플래그 | 의미 |
|--------|------|
| `g` | global - 텍스트 전체에서 일치하는 모든 금액을 찾음 |

---

## 전체 매칭 흐름 시각화
```
$1,234.56달러
|  |   | |  |
|  |   | |  └─ (?:\s*(?:원|달러|USD|KRW))?
|  |   | |     뒤쪽 통화 단위 (선택)
|  |   | |
|  |   | └─ (?:\.\d{1,2})?
|  |   |    소수점 부분 (선택)
|  |   |
|  |   └─ [\d,]+
|  |      정수 부분 (필수)
|  |
|  └─ \s*
|     공백 (선택)
|
└─ (?:₩|\$|USD|KRW|원)?
   앞쪽 통화 기호 (선택)

매칭 테스트 예시

const moneyPattern = /(?:₩|\$|USD|KRW|원)?\s*[\d,]+(?:\.\d{1,2})?(?:\s*(?:원|달러|USD|KRW))?/g;

const text = `
  가격: ₩10,000
  할인가: 8,000원
  달러: $99.99
  환율: USD 1,350
  합계: 1,234,567원
  팁: $5.5달러
  그냥 숫자: 12345
`;

const amounts = text.match(moneyPattern);
console.log(amounts);
// [
//   "₩10,000",
//   "8,000원",
//   "$99.99",
//   "USD 1,350",
//   "1,234,567원",
//   "$5.5달러",
//   "12345"
// ]

패턴의 한계점

한계 설명
통화 기호 없는 숫자 12345처럼 통화 기호가 없는 순수 숫자도 매칭됨 (오탐 가능성)
잘못된 형식 1,00,000처럼 잘못된 천 단위 구분도 매칭됨
소수점 3자리 이상 .999는 .99까지만 매칭되고 마지막 9는 제외됨
다른 통화 유로(€), 엔(¥), 파운드(£) 등은 매칭되지 않음

단순한 구조의 일반적 매칭을 사용해도 왠만한 케이스에서 해결이 가능합니다. 제한점을 극복하기 위한 방법은 아래 코드블럭을 참조하시되, 다소 복잡한 경향이 있습니다.

const moneyPattern = /(?:[₩$€¥£]|USD|KRW|EUR|JPY|GBP|원)?\s*\d{1,3}(?:,\d{3})*(?:\.\d+)?(?:\s*(?:원|달러|유로|엔|파운드|USD|KRW|EUR|JPY|GBP))?/g;

HTML 태그 제거

웹 스크래핑 후 순수한 텍스트만 필요한 경우가 많습니다. HTML 태그를 제거하면서도 의미 있는 공백은 유지해야 텍스트가 읽기 좋게 됩니다.

// n8n Code 노드에서 사용
const html = $input.first().json.content;

// HTML 태그 제거
let text = html
  // 스크립트와 스타일 태그 전체 제거
  .replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
  .replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
  // 블록 요소 태그를 줄바꿈으로 대체
  .replace(/<\/(p|div|br|h[1-6]|li|tr)>/gi, '\n')
  // 나머지 태그 제거
  .replace(/<[^>]+>/g, '')
  // HTML 엔티티 변환
  .replace(/&nbsp;/g, ' ')
  .replace(/&lt;/g, '<')
  .replace(/&gt;/g, '>')
  .replace(/&amp;/g, '&')
  .replace(/&quot;/g, '"')
  // 연속 공백과 줄바꿈 정리
  .replace(/\n\s*\n/g, '\n\n')
  .replace(/[ \t]+/g, ' ')
  .trim();

return [{ json: { text } }];

특정 구분자 기준 분리

CSV가 아닌 비정형 텍스트에서 특정 패턴을 기준으로 데이터를 분리해야 할 때가 있습니다. 구분자가 불규칙하거나 여러 종류일 때 정규표현식이 유용합니다.

// n8n Code 노드에서 사용
const text = $input.first().json.content;

// 예: "항목1 | 항목2 / 항목3, 항목4" 처럼 다양한 구분자
const items = text
  .split(/[|\/,;]\s*/)  // 여러 구분자로 분리
  .map(item => item.trim())
  .filter(item => item.length > 0);

// 키-값 쌍 분리 예: "이름: 홍길동, 나이: 30"
const kvText = "이름: 홍길동, 나이: 30, 직업: 개발자";
const kvPattern = /(\S+):\s*([^,]+)/g;
const data = {};
let match;

while ((match = kvPattern.exec(kvText)) !== null) {
  data[match[1]] = match[2].trim();
}

return [{ json: { items, data } }];

한글, 영문, 숫자 분리

다국어 텍스트에서 언어별로 분리하거나, 특정 언어만 추출해야 할 때 사용합니다.

// n8n Code 노드에서 사용
const text = $input.first().json.content;

// 각 언어/숫자별 추출
const korean = text.match(/[가-힣]+/g) || [];
const english = text.match(/[a-zA-Z]+/g) || [];
const numbers = text.match(/\d+/g) || [];

// 한글만 남기고 나머지 제거
const koreanOnly = text.replace(/[^가-힣\s]/g, '').trim();

// 영문만 남기고 나머지 제거
const englishOnly = text.replace(/[^a-zA-Z\s]/g, '').trim();

return [{
  json: {
    original: text,
    korean: korean.join(' '),
    english: english.join(' '),
    numbers: numbers.join(', '),
    koreanOnly,
    englishOnly
  }
}];

공백 및 특수문자 정리

데이터 정제 작업에서 불필요한 공백, 특수문자, 제어문자 등을 제거하여 깔끔한 텍스트를 만드는 것은 기본 중의 기본입니다.

// n8n Code 노드에서 사용
const text = $input.first().json.content;

const cleaned = text
  // 제어 문자 제거 (탭, 줄바꿈 제외)
  .replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '')
  // 연속 공백을 하나로
  .replace(/[ \t]+/g, ' ')
  // 연속 줄바꿈을 두 개로 제한
  .replace(/\n{3,}/g, '\n\n')
  // 줄 시작/끝 공백 제거
  .replace(/^[ \t]+|[ \t]+$/gm, '')
  // 전체 앞뒤 공백 제거
  .trim();

// 특수문자 제거 버전 (한글, 영문, 숫자, 기본 문장부호만 유지)
const alphanumericOnly = text
  .replace(/[^가-힣a-zA-Z0-9\s.,!?()-]/g, '')
  .replace(/\s+/g, ' ')
  .trim();

return [{
  json: {
    cleaned,
    alphanumericOnly
  }
}];

JSON 내 특정 값 추출

API 응답이나 로그에서 JSON 문자열 내의 특정 값을 빠르게 추출해야 할 때가 있습니다. 정규표현식으로 간단히 처리할 수 있습니다.

// n8n Code 노드에서 사용
const text = $input.first().json.content;

// JSON 문자열에서 특정 키의 값 추출
// 예: {"name": "홍길동", "age": 30}
function extractJsonValue(text, key) {
  // 문자열 값
  const stringPattern = new RegExp(`"${key}"\\s*:\\s*"([^"]*)"`, 'g');
  // 숫자 값
  const numberPattern = new RegExp(`"${key}"\\s*:\\s*(\\d+(?:\\.\\d+)?)`, 'g');
  
  const stringMatches = [...text.matchAll(stringPattern)].map(m => m[1]);
  const numberMatches = [...text.matchAll(numberPattern)].map(m => parseFloat(m[1]));
  
  return [...stringMatches, ...numberMatches];
}

// 사용 예
const names = extractJsonValue(text, 'name');
const ages = extractJsonValue(text, 'age');

// 전체 JSON 객체 추출
const jsonPattern = /\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}/g;
const jsonStrings = text.match(jsonPattern) || [];
const parsedObjects = jsonStrings
  .map(str => {
    try { return JSON.parse(str); }
    catch { return null; }
  })
  .filter(obj => obj !== null);

return [{
  json: {
    names,
    ages,
    parsedObjects
  }
}];

4. 실전 예제: n8n Code 노드 활용

이제 앞서 배운 정규표현식 패턴들을 실제 n8n 워크플로우에서 어떻게 활용하는지 구체적인 예제를 통해 살펴보겠습니다. 각 예제는 실무에서 자주 발생하는 시나리오를 기반으로 작성되었습니다.

예제 1  |  웹 스크래핑 데이터에서 연락처 정보 추출

웹페이지에서 스크래핑한 텍스트에서 이메일 주소와 전화번호를 모두 추출하여 구조화된 데이터로 변환하는 워크플로우입니다. HTTP Request 노드로 웹페이지를 가져온 후 Code 노드에서 처리합니다.

// 입력: HTTP Request 노드에서 가져온 웹페이지 텍스트
const htmlContent = $input.first().json.data;

// HTML 태그 제거하여 순수 텍스트 추출
const textContent = htmlContent
  .replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
  .replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
  .replace(/<[^>]+>/g, ' ')
  .replace(/\s+/g, ' ');

// 이메일 추출
const emailPattern = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
const emails = [...new Set(textContent.match(emailPattern) || [])];

// 전화번호 추출 (한국 형식)
const phonePattern = /(?:0(?:10|1[1-9]|2|[3-6][1-5]|70))[-.\s]?\d{3,4}[-.\s]?\d{4}/g;
const rawPhones = textContent.match(phonePattern) || [];

// 전화번호 형식 통일
const phones = [...new Set(rawPhones.map(phone => {
  const digits = phone.replace(/\D/g, '');
  if (digits.startsWith('02')) {
    return digits.length === 9
      ? `${digits.slice(0,2)}-${digits.slice(2,5)}-${digits.slice(5)}`
      : `${digits.slice(0,2)}-${digits.slice(2,6)}-${digits.slice(6)}`;
  }
  return `${digits.slice(0,3)}-${digits.slice(3,7)}-${digits.slice(7)}`;
}))];

// 결과 반환
return [{
  json: {
    extractedAt: new Date().toISOString(),
    emailCount: emails.length,
    phoneCount: phones.length,
    emails,
    phones,
    contacts: emails.map((email, i) => ({
      email,
      phone: phones[i] || null
    }))
  }
}];

 

위 코드는 먼저 HTML 태그를 제거하여 순수한 텍스트를 얻습니다. 그 다음 이메일과 전화번호 패턴을 각각 적용하여 추출하고, Set을 사용하여 중복을 제거합니다. 전화번호는 추출 후 하이픈으로 구분된 통일된 형식으로 변환합니다. 마지막으로 추출된 정보를 구조화된 JSON으로 반환합니다.

예제 2  |  비정형 텍스트에서 날짜 정규화

OCR로 스캔한 문서나 이메일 본문 등에서 다양한 형식의 날짜를 찾아 ISO 8601 형식으로 통일하는 예제입니다. 이런 처리는 데이터베이스 저장이나 후속 처리를 위해 필수적입니다.

// 입력: 다양한 형식의 날짜가 포함된 텍스트
const text = $input.first().json.content;

// 결과를 저장할 배열
const results = [];

// 패턴 1: ISO 형식 (2024-03-15, 2024/03/15, 2024.03.15)
const isoPattern = /(\d{4})[-\/\.](\d{1,2})[-\/\.](\d{1,2})/g;
let match;
while ((match = isoPattern.exec(text)) !== null) {
  results.push({
    original: match[0],
    format: 'ISO',
    year: match[1],
    month: match[2].padStart(2, '0'),
    day: match[3].padStart(2, '0'),
    position: match.index
  });
}

// 패턴 2: 한국어 형식 (2024년 3월 15일)
const koreanPattern = /(\d{4})년\s*(\d{1,2})월\s*(\d{1,2})일/g;
while ((match = koreanPattern.exec(text)) !== null) {
  results.push({
    original: match[0],
    format: 'Korean',
    year: match[1],
    month: match[2].padStart(2, '0'),
    day: match[3].padStart(2, '0'),
    position: match.index
  });
}

// 패턴 3: 미국 형식 (03/15/2024, March 15, 2024)
const usPattern1 = /(\d{1,2})\/(\d{1,2})\/(\d{4})/g;
while ((match = usPattern1.exec(text)) !== null) {
  results.push({
    original: match[0],
    format: 'US',
    year: match[3],
    month: match[1].padStart(2, '0'),
    day: match[2].padStart(2, '0'),
    position: match.index
  });
}

// 영어 월 이름 매핑
const monthMap = {
  'jan': '01', 'feb': '02', 'mar': '03', 'apr': '04',
  'may': '05', 'jun': '06', 'jul': '07', 'aug': '08',
  'sep': '09', 'oct': '10', 'nov': '11', 'dec': '12'
};

const usPattern2 = /([A-Za-z]+)\s+(\d{1,2}),?\s*(\d{4})/g;
while ((match = usPattern2.exec(text)) !== null) {
  const monthKey = match[1].toLowerCase().slice(0, 3);
  if (monthMap[monthKey]) {
    results.push({
      original: match[0],
      format: 'US-Text',
      year: match[3],
      month: monthMap[monthKey],
      day: match[2].padStart(2, '0'),
      position: match.index
    });
  }
}

// ISO 8601 형식으로 정규화하여 반환
const normalized = results
  .sort((a, b) => a.position - b.position)
  .map(item => ({
    ...item,
    normalized: `${item.year}-${item.month}-${item.day}`,
    isoDate: new Date(`${item.year}-${item.month}-${item.day}`).toISOString()
  }));

return normalized.map(data => ({ json: data }));

 

이 코드는 네 가지 날짜 형식을 순차적으로 검색합니다. 각 패턴에서 연, 월, 일을 캡처 그룹으로 추출하고, 월과 일은 2자리로 패딩합니다. 영어 월 이름의 경우 매핑 객체를 사용하여 숫자로 변환합니다. 모든 결과는 원본 텍스트에서의 위치순으로 정렬되어 반환됩니다.

예제 3  |  API 응답에서 특정 패턴 필터링

외부 API에서 받은 로그 데이터나 메시지에서 특정 패턴에 맞는 항목만 필터링하는 예제입니다. 에러 메시지 추출, 특정 사용자 활동 로그 필터링 등에 활용할 수 있습니다.

// 입력: API 응답의 로그 메시지 배열
const logs = $input.first().json.logs;

// 에러 패턴 정의
const patterns = {
  // 에러 코드 패턴: ERR-숫자 또는 Error: 메시지
  error: /(?:ERR-\d+|Error:\s*.+)/i,
  // 타임스탬프 패턴: [YYYY-MM-DD HH:MM:SS]
  timestamp: /\[(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\]/,
  // IP 주소 패턴
  ip: /\b(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\b/,
  // 사용자 ID 패턴: user_숫자 또는 @username
  userId: /(?:user_\d+|@\w+)/i,
  // HTTP 상태 코드: 4xx 또는 5xx 에러
  httpError: /\b[45]\d{2}\b/
};

// 로그 분석 및 필터링
const analyzed = logs.map(log => {
  const analysis = {
    original: log,
    isError: patterns.error.test(log) || patterns.httpError.test(log),
    timestamp: null,
    ip: null,
    userId: null,
    errorCode: null
  };
  
  // 타임스탬프 추출
  const tsMatch = log.match(patterns.timestamp);
  if (tsMatch) analysis.timestamp = tsMatch[1];
  
  // IP 주소 추출
  const ipMatch = log.match(patterns.ip);
  if (ipMatch) analysis.ip = ipMatch[1];
  
  // 사용자 ID 추출
  const userMatch = log.match(patterns.userId);
  if (userMatch) analysis.userId = userMatch[0];
  
  // 에러 코드 추출
  const errMatch = log.match(/ERR-(\d+)/i);
  if (errMatch) analysis.errorCode = errMatch[1];
  
  const httpMatch = log.match(/\b([45]\d{2})\b/);
  if (httpMatch) analysis.httpStatus = httpMatch[1];
  
  return analysis;
});

// 에러 로그만 필터링
const errorLogs = analyzed.filter(log => log.isError);

// 통계 생성
const stats = {
  totalLogs: logs.length,
  errorCount: errorLogs.length,
  errorRate: ((errorLogs.length / logs.length) * 100).toFixed(2) + '%',
  uniqueIPs: [...new Set(analyzed.filter(l => l.ip).map(l => l.ip))].length,
  uniqueUsers: [...new Set(analyzed.filter(l => l.userId).map(l => l.userId))].length
};

return [{
  json: {
    stats,
    errorLogs,
    allAnalyzed: analyzed
  }
}];

 

이 코드는 여러 정규표현식 패턴을 객체로 정의하여 관리합니다. 각 로그 메시지를 순회하며 모든 패턴을 적용하고, 추출된 정보를 구조화된 객체로 만듭니다. 에러에 해당하는 로그만 필터링하고, 전체 통계 정보도 함께 생성합니다. 이런 방식으로 대량의 로그 데이터를 효율적으로 분석할 수 있습니다.


5. 정규표현식 작성 팁과 디버깅

정규표현식을 작성하다 보면 예상대로 동작하지 않는 경우가 많습니다. 이 섹션에서는 정규표현식을 효과적으로 작성하고 디버깅하는 방법을 알아보겠습니다.

정규표현식 테스트 도구 활용

정규표현식을 작성할 때 실시간으로 테스트하고 시각적으로 패턴의 동작을 확인할 수 있는 온라인 도구들이 있습니다. 이 도구들을 활용하면 개발 시간을 크게 단축할 수 있습니다.

  • regex101.com은 가장 널리 사용되는 정규표현식 테스트 도구입니다. 패턴을 입력하면 각 부분이 어떤 의미인지 상세하게 설명해주고, 테스트 문자열에서 어떤 부분이 매칭되는지 실시간으로 하이라이트해줍니다. JavaScript를 포함한 여러 언어의 정규표현식 엔진을 선택할 수 있어서, n8n Code 노드에서 사용할 패턴을 정확하게 테스트할 수 있습니다.
  • regexr.com도 훌륭한 대안입니다. 직관적인 인터페이스와 함께 정규표현식 참조 가이드를 제공하여 문법을 빠르게 확인할 수 있습니다. 커뮤니티에서 공유한 유용한 패턴들도 찾아볼 수 있습니다.

흔한 실수와 해결 방법

정규표현식 작성 시 자주 발생하는 실수들과 그 해결 방법을 정리했습니다.

실수 유형 잘못된 예 올바른 예 설명
특수문자 미이스케이프 /www.example.com/ /www\.example\.com/ .은 모든 문자를 의미하므로 점으로 사용하려면 \.로 이스케이프해야 합니다
g 플래그 누락 text.match(/\d+/) text.match(/\d+/g) 모든 결과를 찾으려면 g 플래그가 필요합니다
탐욕적 매칭 문제 /<div>.*<\/div>/ /<div>.*?<\/div>/ .*는 가능한 많이 매칭하므로 .*?로 최소 매칭을 사용합니다
대소문자 구분 /hello/ /hello/i 대소문자를 구분하지 않으려면 i 플래그를 추가합니다
문자열 경계 무시 /cat/ /\bcat\b/ 단어 전체를 매칭하려면 \b 단어 경계를 사용합니다
RegExp 생성자 이스케이프 new RegExp("\d+") new RegExp("\\d+") 문자열에서는 백슬래시를 두 번 써야 합니다

성능을 고려한 패턴 작성법

정규표현식은 잘못 작성하면 성능 문제를 일으킬 수 있습니다. 특히 대량의 텍스트를 처리할 때는 다음 사항들을 고려해야 합니다.

  • 첫째, 가능하면 앵커(^, $)를 사용하여 검색 범위를 제한하세요. 문자열 전체를 검색하는 것보다 시작이나 끝 위치를 지정하면 훨씬 빠릅니다.
  • 둘째, 중첩된 수량자를 피하세요. (a+)+와 같은 패턴은 "재앙적 역추적(catastrophic backtracking)"을 일으켜 처리 시간이 기하급수적으로 증가할 수 있습니다.
  • 셋째, 캡처가 필요 없는 그룹은 비캡처 그룹 (?:...)을 사용하세요. 캡처 그룹은 메모리를 추가로 사용하고 처리 시간도 더 걸립니다.
// 성능 비교 예시

// 느린 패턴: 불필요한 캡처 그룹과 탐욕적 매칭
const slowPattern = /(<div class="item">)(.*)(</div>)/g;

// 빠른 패턴: 비캡처 그룹과 게으른 매칭
const fastPattern = /<div class="item">.*?<\/div>/g;

// 더 빠른 패턴: 부정 문자 클래스 사용
const fasterPattern = /<div class="item">[^<]*<\/div>/g;

6. 마무리

이 글에서는 n8n Code 노드에서 JavaScript 정규표현식을 활용하여 문자열을 효과적으로 처리하는 방법을 살펴보았습니다. 정규표현식의 기본 문법부터 시작하여 메타문자와 특수문자의 의미를 이해하고, 실무에서 바로 사용할 수 있는 10가지 패턴과 3가지 실전 예제를 다루었습니다.

 

정규표현식은 처음에는 암호처럼 보일 수 있지만, 각 기호의 의미를 하나씩 익히다 보면 매우 직관적이고 강력한 도구라는 것을 알게 됩니다. n8n 워크플로우에서 정규표현식을 활용하면 웹 스크래핑, 데이터 정제, API 응답 처리 등 다양한 자동화 작업을 훨씬 효율적으로 수행할 수 있습니다.

 

핵심 내용을 정리하면 다음과 같습니다.

  • test(), match(), replace(), exec() 네 가지 메서드를 상황에 맞게 사용하세요.
  • 특수문자를 그대로 매칭하려면 반드시 이스케이프(\)하세요.
  • 모든 결과를 찾으려면 g 플래그를, 대소문자를 무시하려면 i 플래그를 사용하세요.
  • 탐욕적 매칭(*, +)과 게으른 매칭(*?, +?)의 차이를 이해하고 적절히 활용하세요.
  • regex101.com과 같은 테스트 도구를 적극 활용하세요.

다음 단계로는 이 글에서 다룬 패턴들을 실제 n8n 워크플로우에 적용해보시기 바랍니다. 실제 데이터로 테스트하면서 패턴을 수정하고 최적화하는 과정을 통해 정규표현식 실력이 빠르게 향상될 것입니다.


참고 자료