개발

eval() 로 데이터 편하게 가져오기

가볍게 읽으시고 안쓰는 쪽으로 가셔야 합니다.

2025.07.20

때로는 유용한 도구 eval()

NOTE

  1. JS에서 eval() 이 때로는 굉장히 편할 때가 있습니다.
  2. 특수 상황 빼고는 해당 안되니 최후의 수단으로 사용하시길 바랍니다.
  3. 레거시는 항상 어려워 ㅜㅜ

1. 인트로

오랜만에 글을 올려보네요. 이것저것 별도 서비스도 기획 하고, 핑계 대면서 안했지만 소재는 매번 모으고 있습니다. ㅎㅎ

오늘은 제목 부터 웃기긴 합니다. JS를 다뤄본 개발자라면 eval() 의 위험성을 너무도 잘 알고 있습니다.
심지어 MDN에는 "절대 사용하지 말 것" 이라고 안내하고 있습니다.

경고문까지 살벌합니다.

하지만 특수한 상황에서는 필요할 때도 있습니다. 언제? 저처럼 스크래핑을 할 때는 종종 유용하게 쓰입니다. 동적으로 변하는 문자열을 메모리에 올려서 우리가 원하는 정보를 더욱 빠르게 가져올 수 있습니다.

물론 eval() 대신 node:vm을 이용한 context 생성 조작 등 더욱 안전하고 좋은 방법이 많다고 생각하는 분들도 많으실 겁니다.

하지만 그 모든 건 런타임이 어떠냐에 따라 다릅니다. 라이브러리를 쉽게 불러와 사용할 수 있다면 전혀 문제가 되지 않습니다. 제가 지금 일하고 있는 회사에서는 JS 엔진 구버전 으로 커스텀해서 만들어진 런타임 환경 위에서 개발을 하고 있습니다. 그 때 당시에는 모르겠지만 시간이 지나 현재는 레거시가 되어버렸고 돈을 벌고 있는 현재의 상황에서 시스템을 손대기 힘든 상황입니다. 모던 JS의 문법 뿐만 아니라 제약이 아주 많은 개발 환경이지요.

그래서 저희 개발팀은 이왕 이렇게 된거 좀 더 예전의 문법 사용, 라이브러리 없이 필요하다면 직접 오픈소스를 마이그레이션 해서 사용하는 등 다양한 방법으로 개발을 하고 있습니다.(이거 은근 재밌습니다.)


2. 사건의 발단

회사에서 신규 개발할 사이트가 3곳이 있었습니다. SNS와 관련된 사이트였고 랜덤으로 돌려서 배정을 받았는데 결과가 나오고

# 실제 대화 중 ???: "석호님이 제일 어려운거 하시네" 주인장: "이 사이트 좀 어떤가요?" ???: "아... 좀 그래요 보시면 아실 거예요.

보니까 nuxt로 만들어진 사이트였고, 마크업 최상단 구분은 나쁘지 않게 해놓은 상태라 내부에 구현된 DOM parser를 사용하려고 했으나
수집하려고 하는 데이터의 카테고리를 나누는 기준이 누락, 기준이 없는 등 어지쩌지 하긴 해도 지속적으로 파싱 오류가 발생할 것 같아서 고민하고 있었습니다.

그러다 발견한 한줄기 빛, 가장 최하단 닫는 body 태그 바로 전에 데이터를 모두 init 후 화면에 그리는 구조라 데이터가 보기좋게 전역으로 선언해서 브라우저가 로드될 때 실행되게 되어 있었습니다. 살짝 가공하기 귀찮게 말이죠.

// 찾았다.. window.__NUXT__= (function(a,b,c ... ab, ac ...) { data: [ {title: a, content: ab}, {title: c, content: cc, link: j} ] }("a인자", "b인자", ... "ab인자", "ac인자", ...));

"이거 메모리에 올려서 key, value 로 가져와야겠다. 그게 최선이다..."


3. 진짜 간단합니다.

NOTE

보기 쉽게 재구성한 코드입니다.
디테일한 인코딩 처리, 문자열 깨짐 방지 처리 등은 해당 글에 작성하지 않았습니다.
*객체 생성 의외에 보안에 위협될 만한 실행성 구문이 있는지 반드시 체크해야합니다.

// 시작 // 유틸 function 문자열grab함수(string, start, end, index) { return index번째 string에서 start 문자열과 end 문자열 안에 모든 문자열; } // 가공 대상 var 통신결과_HTML태그 = ` <!doctype html> <title>AI가 스크래핑은 못해주나 열심히 연구중</title> <body> ... <script> window.__NUXT__=(function(a,b,c ... ab, ac ...) { return data: [ {title: a, content: ab}, {title: c, content: cc, link: j} ]; }("a인자", "b인자", ... "ab인자", "ac인자", ...)); </script> </body> </html> ` // "window.__NUXT__" 에서 ");</script>" 까지 var __NUXT__ = (통신결과_HTML태그, 'window.__NUXT__', ');</script>'); // 최종 데이터 list var 최종데이터 = []; var window = {}; // __NUXT__ eval로 실행시킬 때 데이터가 담길 window __NUXT__ = __NUXT__.split(";</script>")[0]; // ;</script> 잘라서 (function (){...}() ); 가져오기 // 보안에 위험이 될 만한 코드가 있는지 검사 if (보안검사코드(__NUXT__)) { throw new Error("실행성 구문 확인 - 위험 감지") } try { eval(__NUXT__) // var window 에 할당 } catch (e) { throw new Error("데이터 eval 실패 - 확인 필요") } if ( !window.__NUXT__ && !window.__NUXT__.data && !Array.isArray(window.__NUXT__.data) && ) { throw new Error("데이터 형식 다름 - 확인 필요") } 최종데이터 = window.__NUXT__.data; // 리스트 순회 for (var index = 0; index < 최종데이터.length; index++) { var curList = 최종데이터[index]; ... 파싱 로직 }

유효성 검증 줄바꿈 제외하면 10줄도 안되서 데이터를 편하게 가공할 수 있습니다.

사이트의 요 데이터가 우리의 window 변수에 할당 됩니다.


4. 어쩐지 너무 쉽다 했습니다.

저희 개발환경에서는 너무도 잘 실행되기에 끝난 줄 알았습니다.
그래서 동일 코드를 Jest로 옮겨서 실행해보니 eval() 실행 부에 catch로 잡혀서 에러가 발생하고 있었습니다.

// syntaxError ? SyntaxError: Unexpected identifier 'tiktok' ... stack 블라블라

아... 객체의 키 중 html 태그를 통으로 넣는 부분이 있는데 거기서 문자열 구분자의 혼용으로 발생한 에러였습니다.

// 어우 까다로워 { html:"\u003Cblockquote class=\"tiktok-embed\" ..." }

정규식으로 처리할 수도 있지만 유지보수 하기 편하게 encodeURI 해주고 사용할 때 decodeURI 해서 사용하도록
html 키의 값을 바꿔 주었습니다. 로직을 eval() 전에 추가했습니다.

// html syntax error 발생 가능성으로 안전하게 처리 var htmlIndex = 0; while (true) { var html = 문자열grab함수(__NUXT__, "html:", ",", htmlIndex); if (!html) break; var splitText = __NUXT__.split(html); var beforeText = splitText[0]; var afterText = splitText[1]; // html 키에 "" 등 문자열이 끼어 있어 eval 파싱 실패 방지 __NUXT__ = beforeText + "\"" + encodeURIComponent(html) + "\"" + afterText; htmlIndex++; }

바로 위의 로직만 추가 후 결과값을 비교해보면 eval() 시 에러도 발생하지 않고
저희의 개발 환경 JS 엔진과, node(v8) 에서의 결과가 완전히 동일한 것을 확인할 수 있습니다.

https://jsondiff.com 사이트 좋습니다.

html 키 처리 로직을 추가 안해도 운영하는데 문제가 없지만
엔진 호환성을 높인다는 취지로 봤을 때는 의미가 있는 부분인 것 같아서 추가하였습니다.

이렇게 해서 eval() 로 실행한 문자열이 var window 변수에 객체가 할당 되고 data.key 블라블라 하면서 손쉽게 데이터를 가져오면 됩니다.


마무리

eval() 함수는 검증 따위 없이 실행시켜버리는 보안에 굉장히 위험한 함수입니다.
하지만 "무조건 너무 나빠!" 보다는 이렇게 특수한 상황에서 효과적으로 쓰일 수도 있겠구나 라는 걸 한번 공유하고 싶어서 오늘 글을 작성했습니다.
물론 더 안전하고 좋은 방법 많으니 최후의 수단으로 선택하시길 바랍니다.

댓글

0