<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>JamesCoder의 개발일지</title>
    <link>https://jamescoder.tistory.com/</link>
    <description>jamescoder 님의 블로그 입니다.</description>
    <language>ko</language>
    <pubDate>Thu, 25 Jun 2026 11:51:59 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>JamesCoder</managingEditor>
    <image>
      <title>JamesCoder의 개발일지</title>
      <url>https://tistory1.daumcdn.net/tistory/8466574/attach/6c8ebdafca1f40d2b4799cdd032100c8</url>
      <link>https://jamescoder.tistory.com</link>
    </image>
    <item>
      <title>보조금24 사이트 데이터 크롤링 프로그램 만들기</title>
      <link>https://jamescoder.tistory.com/12</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 고객님께 의뢰받은 일은 한 줄로 요약하면 이렇다. &lt;b&gt;&quot;보조금24 사이트에 올라와 있는 2026년도 보조사업자 전체 명단을, 연락처랑 팩스번호까지 다 채워서 엑셀로 받고 싶다.&quot;&lt;/b&gt; 보조금24는 정부가 운영하는 국고보조사업 통합 포털이다. 어느 부처가 어떤 사업에 얼마를 배정했고, 그 사업을 누가 수행하는지가 전부 공개돼 있다. 영업이나 제안 활동을 하는 회사 입장에서는 그야말로 잠재 고객 명단 그 자체다. 다만 사이트가 잘 만들어져 있다고 데이터를 쉽게 빼낼 수 있다는 뜻은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/08Rnq/dJMcadoiGqA/k1AHKeFrbBKidEo3R1k1D0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/08Rnq/dJMcadoiGqA/k1AHKeFrbBKidEo3R1k1D0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot; data-filename=&quot;card_02_before_1.png&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/08Rnq/dJMcadoiGqA/k1AHKeFrbBKidEo3R1k1D0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F08Rnq%2FdJMcadoiGqA%2Fk1AHKeFrbBKidEo3R1k1D0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xnpZp/dJMcadoiGqz/kMdOheRjGC3NsZE34MrGLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xnpZp/dJMcadoiGqz/kMdOheRjGC3NsZE34MrGLK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot; data-filename=&quot;card_03_after_1.png&quot; data-widthpercent=&quot;50&quot; style=&quot;width: 49.4186%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xnpZp/dJMcadoiGqz/kMdOheRjGC3NsZE34MrGLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxnpZp%2FdJMcadoiGqz%2FkMdOheRjGC3NsZE34MrGLK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;사람이 직접 하면 일주일도 모자란 작업이었다&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고객이 처음 보내준 엑셀 파일을 열어봤다. 행이 만 단위였다. 회계연도, 보조사업명, 차수, 보조사업자, 중앙관서, 분야, 부문, 사업비 합계와 항목별 금액까지는 이미 들어 있었다. 문제는 그 옆 칸들이었다. &lt;b&gt;수행기관, 담당부서/담당자, 전화번호, 홈페이지, 팩스.&lt;/b&gt; 이 다섯 칸이 군데군데 비어 있었다. 어떤 행은 깔끔하게 차 있고, 어떤 행은 통째로 비어 있고, 어떤 행은 팩스만 빠져 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 결정적인 한 가지. &lt;b&gt;고객이 가지고 있는 명단은 한참 전 버전이었다.&lt;/b&gt; 2026년도 사업이 새로 갱신되면서 행이 추가되거나 사업비가 바뀐 곳이 많았다. 그러니까 단순히 빈칸을 채우는 게 아니라, 보조금24 사이트의 최신 데이터를 다시 긁어와서 기존 데이터와 맞춰 합쳐야 했다. 그 와중에 비어 있는 연락처는 보조금24 상세 페이지에서 끌어오고, &lt;b&gt;거기에도 없는 팩스번호는 각 기관 홈페이지에 직접 들어가서 찾아내는 것까지가 요구사항이었다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 사람이 한다고 상상해 보면 견적이 안 나온다. 사이트에 들어가서 부처별로 필터를 걸고, 사업 하나하나 클릭해서 상세 페이지에 들어가 담당자 정보를 베끼고, 홈페이지가 있으면 또 그 홈페이지에 들어가서 푸터까지 스크롤해서 팩스 적힌 곳을 찾는 작업. 그걸 수만 건 반복한다. 일주일도 모자란다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;사이트가 보내는 신호를 그대로 흉내 낸 수집기&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 작업을 자동화할 때 가장 먼저 보는 건 &lt;b&gt;사이트가 데이터를 어떻게 그리는가&lt;/b&gt; 하는 부분이다. 화면에 보이는 표가 실은 어디서 오는지, 어떤 형식으로 오는지를 들여다보면 길이 보인다. 보조금24의 경우 화면에 보이는 명단은 사이트 내부 API가 JSON 형태로 내려주는 데이터를 표로 그려주는 구조였다. 그러니까 사람이 페이지를 넘기면서 한 줄씩 보는 대신, 그 API에 직접 요청을 보내서 한 번에 수천 건씩 받아오는 게 가능했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 단순히 한 번 호출해서 끝나는 일이 아니었다. 중앙관서별로 코드가 따로 있어서, 부처 코드 리스트를 먼저 받아와서 그 코드 하나하나로 다시 명단을 요청해야 했다. 어떤 부처는 사업이 수십 개고, 어떤 부처는 수천 개다. 사업 단위로 다시 상세 페이지 데이터를 호출해야 담당자, 전화, 홈페이지가 나왔다. &lt;b&gt;결국 호출해야 할 횟수가 만 단위로 쌓였다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분이 자동화 설계에서 가장 신경 쓴 지점이다. 한 번 호출에 1초만 걸려도 만 번이면 세 시간이다. 그래서 동시에 여러 건을 처리하는 멀티스레드 구조로 짜되, 사이트에 부담을 주지 않을 만큼만 풀어 줬다. &lt;b&gt;그리고 한 번 받아온 결과는 전부 캐시 파일로 저장해 두는 방식으로 만들었다.&lt;/b&gt; 중간에 네트워크가 끊기거나 사이트가 잠깐 응답을 안 해도, 다시 돌리면 이미 받아둔 건 건너뛰고 빠진 것만 채운다. 만 단위 호출에서는 이 안전장치가 거의 필수다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;빈칸이었던 팩스번호를 채워 넣는 마지막 한 수&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 의뢰의 진짜 까다로운 지점은 따로 있었다. &lt;b&gt;팩스번호다.&lt;/b&gt; 보조금24 상세 페이지에서 팩스를 제공하는 기관은 일부였고, 나머지는 빈칸이었다. 영업 자료로 쓰려면 팩스가 있어야 의미가 있는데 사이트에는 없다. 그래서 추가로 만든 게 홈페이지 자동 방문기다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 보조사업자에게는 안내 홈페이지 주소가 같이 들어 있다. 거기로 직접 들어가면 푸터든 연락처 페이지든 어딘가에 팩스가 적혀 있는 경우가 많다. 그래서 &lt;b&gt;빈칸으로 남은 사업자만 추려서, 그 홈페이지를 자동으로 방문해 본문을 읽고 팩스 패턴을 찾아내는 로직을 붙였다.&lt;/b&gt; &quot;팩스&quot;, &quot;FAX&quot;, &quot;F:&quot; 같은 표시 옆에 붙어 있는 전화번호 형식의 숫자를 정규표현식으로 잡아낸다. 010으로 시작하는 휴대폰 번호는 제외한다. 본인 휴대폰을 팩스라고 적어 둘 일은 없으니까.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;홈페이지가 https에서 막히면 http로 한 번 더 시도하고, 자바스크립트나 메타 리프레시로 다른 주소로 보내는 사이트는 그 리다이렉트를 따라간다. 한 사이트에서 모든 경로를 시도해도 못 찾으면 깔끔하게 빈칸으로 남겨둔다. 잘못 추출한 번호를 채우는 것보다 빈칸이 낫다. &lt;b&gt;그리고 한 번 방문한 홈페이지의 결과도 캐시에 저장해 둔다.&lt;/b&gt; 같은 홈페이지를 가진 사업자가 여럿이면 한 번만 방문하고 결과를 공유한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;결과: 며칠치 작업이 한 번 실행으로 끝난다&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다 돌리고 나면 한 파일이 떨어진다. &lt;b&gt;'전체' 시트에는 모든 사업자가 들어 있고, 그 뒤에 중앙관서별로 시트가 자동으로 분리돼서 붙는다.&lt;/b&gt; 부처별로 영업할 때 바로 쓸 수 있게 한 구조다. 헤더에는 색이 들어가 있고, 굵은 글씨로 표시되고, 1행은 고정되어 스크롤해도 헤더가 따라온다. 자동필터까지 걸려 있어서 분야나 부문으로 곧바로 거를 수 있다. 컬럼 너비도 컬럼 성격에 맞게 미리 잡아 뒀다. 받아서 그대로 영업팀이나 제안팀에 넘기면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수치로 정리해 보면 임팩트가 분명하다. &lt;b&gt;사람이 했으면 며칠은 잡아야 했을 작업이, 한 번 실행에 30분 안쪽에 끝난다.&lt;/b&gt; 이미 한 번 돌려서 캐시가 차 있는 상태라면 그 다음 실행은 몇 분이면 충분하다. 데이터가 갱신되는 시점에 다시 한 번만 돌리면 최신 명단이 항상 손에 들어온다는 뜻이다. 빈칸 비율도 크게 줄었다. 사이트 자체에서 비어 있어 어쩔 수 없는 일부를 빼면 거의 채워졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1915&quot; data-origin-height=&quot;1042&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cA5PHx/dJMcahRMw5n/gz8fJkxCLoXvgtbbwelj70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cA5PHx/dJMcahRMw5n/gz8fJkxCLoXvgtbbwelj70/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cA5PHx/dJMcahRMw5n/gz8fJkxCLoXvgtbbwelj70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcA5PHx%2FdJMcahRMw5n%2Fgz8fJkxCLoXvgtbbwelj70%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1915&quot; height=&quot;1042&quot; data-origin-width=&quot;1915&quot; data-origin-height=&quot;1042&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;비슷한 데이터로 영업 자료 만들고 있다면&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 프로젝트에서 가장 마음에 들었던 부분은 &lt;b&gt;&quot;사이트에는 없는데 홈페이지에는 있는 정보까지 끌어왔다&quot;는 점이다.&lt;/b&gt; 단순한 크롤러는 화면에 있는 걸 그대로 베끼는 데서 끝난다. 진짜 영업 가능한 명단을 만들려면 한 단계 더 들어가야 한다. 빈칸을 어떻게 메울지, 못 메우는 빈칸은 어떻게 솔직하게 비워둘지에 대한 판단이 같이 들어가야 결과물이 실무에서 쓸 만해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정부 공공 포털, 협회 회원사 명단, 박람회 참가업체 리스트처럼 영업이나 제안에 쓸 만한 공개 데이터는 생각보다 곳곳에 흩어져 있다. 그런데 막상 들여다보면 다운로드 버튼이 없거나, 페이지를 일일이 넘겨야 하거나, 정작 필요한 연락처는 빠져 있는 식이다. &lt;b&gt;이런 건 거의 다 자동화가 된다.&lt;/b&gt; 데이터를 어떻게 가져올지, 빠진 부분을 어디서 메울지, 어떤 형태로 엑셀에 떨어뜨릴지를 정해두면 그 다음은 프로그램이 알아서 한다. 비슷한 작업이 손에 잡혀 있는데 어디서부터 손대야 할지 막막하다면, 편하게 연락을 주시라. 언제든지 도움을 드릴 수 있으니!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cX1NUh/dJMcaarCFM3/xhQXNVPAj6juSB0Tz5v5tK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cX1NUh/dJMcaarCFM3/xhQXNVPAj6juSB0Tz5v5tK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot; data-filename=&quot;card_04_closing_1.png&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cX1NUh/dJMcaarCFM3/xhQXNVPAj6juSB0Tz5v5tK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcX1NUh%2FdJMcaarCFM3%2FxhQXNVPAj6juSB0Tz5v5tK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Msfcw/dJMcafl92n8/xAB5JolelvyK0P9xzKhhnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Msfcw/dJMcafl92n8/xAB5JolelvyK0P9xzKhhnk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot; data-filename=&quot;card_01_title_1.png&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Msfcw/dJMcafl92n8/xAB5JolelvyK0P9xzKhhnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMsfcw%2FdJMcafl92n8%2FxAB5JolelvyK0P9xzKhhnk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>크롤링</category>
      <category>공공데이터 엑셀 정리</category>
      <category>보조금24</category>
      <category>보조금24 데이터</category>
      <category>보조금24 크롤링</category>
      <category>정부 데이터 크롤링</category>
      <category>정부 사이트 크롤링</category>
      <author>JamesCoder</author>
      <guid isPermaLink="true">https://jamescoder.tistory.com/12</guid>
      <comments>https://jamescoder.tistory.com/12#entry12comment</comments>
      <pubDate>Fri, 1 May 2026 02:52:38 +0900</pubDate>
    </item>
    <item>
      <title>쿠팡 상품 리뷰 크롤링 데이터 추출 프로그램 만들기</title>
      <link>https://jamescoder.tistory.com/11</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;쿠팡 상품 리뷰 데이터를 원하시는 고객 분들이 많아서 제대로 프로그램 하나를 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠팡 리뷰 데이터 크롤링 난이도는 크롤링 난이도 중에서 가장 높은 것으로 알려져있는 만큼 쉬운 작업은 아니었지만 결국에는 데이터를 가져올 수 있었다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;리뷰는 보기 위한 게 아니라 분석하기 위한 것이다&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고객님으로부터 처음 연락을 받았을 때 나에게 하신 말씀은 이거였다. &quot;쿠팡에 우리 상품 경쟁사가 있는데, 거기 리뷰를 다 보고 싶어요. 근데 페이지를 한 장씩 넘기면서 보려니 끝이 없네요.&quot; 정확한 상황이었다. 쿠팡은 리뷰를 한 페이지에 10개씩만 보여준다. 리뷰가 1천 개면 페이지를 100번 넘겨야 하고, 5천 개면 500번이다. 게다가 그 와중에 별점 1점짜리만 보고 싶다든가, 특정 키워드가 들어간 리뷰만 추리고 싶다든가 하는 건 사실상 불가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리뷰 분석은 보통 두 가지 목적으로 한다. 하나는 자기 상품의 약점 파악이다. 어떤 점이 자주 컴플레인으로 나오는지, 별점 1~2점짜리 리뷰의 공통 키워드는 뭔지 알면 다음 개선 방향이 잡힌다. 다른 하나는 경쟁사 리뷰 분석이다. 잘 팔리는 경쟁 상품의 좋은 평가는 어떤 게 많은지, 안 좋은 평가는 어디서 나오는지를 파악하면 자기 상품의 포지셔닝에 바로 써먹을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 이 분석 자체가 어려운 게 아니라, &lt;b&gt;분석할 수 있는 형태로 데이터를 만드는 게 어렵다&lt;/b&gt;는 거다. 엑셀에 리뷰가 한 행씩 정렬돼 있고, 별점&amp;middot;작성일&amp;middot;도움돼요 수&amp;middot;내용이 컬럼별로 들어 있으면 필터 걸고 정렬하는 건 일도 아니다. 그런데 그 엑셀을 만들기 위해 페이지 500번을 넘기는 게 현실적으로 안 되니까, 분석 자체가 시작도 못 되고 있던 거다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r39nk/dJMcagSKsCz/kWs71w3B7115ZKLHKLjZW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r39nk/dJMcagSKsCz/kWs71w3B7115ZKLHKLjZW0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot; data-filename=&quot;card_02_before_1.png&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r39nk/dJMcagSKsCz/kWs71w3B7115ZKLHKLjZW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr39nk%2FdJMcagSKsCz%2FkWs71w3B7115ZKLHKLjZW0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QI8tl/dJMcabjCM0o/L6YGtyTZI1ZQjZHEbZuLN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QI8tl/dJMcabjCM0o/L6YGtyTZI1ZQjZHEbZuLN1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot; data-filename=&quot;card_03_after_1.png&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QI8tl/dJMcabjCM0o/L6YGtyTZI1ZQjZHEbZuLN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQI8tl%2FdJMcabjCM0o%2FL6YGtyTZI1ZQjZHEbZuLN1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;쿠팡이 리뷰를 그리는 방식&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠팡 상품 페이지에서 리뷰 영역을 보면 화면 위에 별점 분포 그래프가 있고, 그 아래에 리뷰가 한 페이지에 10개씩 뜬다. 페이지 번호를 클릭하면 그 페이지의 리뷰가 새로 로드되는데, 이때 페이지 전체가 새로고침되는 게 아니라 내부 API 한 번 호출로 다음 10개 리뷰만 받아와서 그 자리에 그려준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 API는 외부에 공식적으로 공개돼 있진 않지만, 페이지가 자기 자신을 위해 호출하는 내부 엔드포인트가 있다. 그 API를 직접 호출해서 데이터를 받아오는 방식으로 만들었다. 화면 렌더링을 거치지 않으니 빠르고, JSON으로 받으니 데이터 구조가 깨끗하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 쿠팡 입장에서 보면 이 API는 &quot;정상적인 사용자가 자기 페이지를 보면서 호출하는&quot; 것만 정상으로 인식한다. 외부에서 그냥 쳐서는 인증이 안 통한다. 그래서 프로그램은 사용자가 이미 쿠팡 상품 페이지를 열어놓은 상태의 브라우저 세션을 그대로 빌려 쓴다. 사용자 입장에서는 따로 로그인하거나 토큰을 다룰 필요 없이, 평소처럼 쿠팡 페이지 한 번 열어놓고 프로그램 버튼만 누르면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예로 다음과 같은 상품을 리뷰해보겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3840&quot; data-origin-height=&quot;2088&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnvGSw/dJMcacivC0w/uyr4ZmK97kklsTF7INUem0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnvGSw/dJMcacivC0w/uyr4ZmK97kklsTF7INUem0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnvGSw/dJMcacivC0w/uyr4ZmK97kklsTF7INUem0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnvGSw%2FdJMcacivC0w%2Fuyr4ZmK97kklsTF7INUem0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3840&quot; height=&quot;2088&quot; data-origin-width=&quot;3840&quot; data-origin-height=&quot;2088&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;엑셀에 들어가는 컬럼&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수집이 끝나면 자동으로 XLSX 파일이 다운로드 폴더에 떨어진다. 컬럼은 리뷰 ID, 별점, 제목, 본문, 작성일, 작성자명, 도움돼요 수, 상품명, 판매자명, 설문 답변, 이미지 URL, 동영상 URL로 구성된다. 본문은 길면 잘라내지 않고 그대로 다 들어간다. 이미지와 동영상은 URL을 콤마로 묶어서 한 셀에 넣어두기 때문에, 필요하면 분할해서 쓸 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설문 답변이 좀 재미있는 부분이다. 쿠팡 리뷰는 본문 외에 &quot;사이즈는 어땠나요? 정사이즈 / 작아요 / 커요&quot; 같은 객관식 답변을 따로 받는데, 이게 분석에 굉장히 유용하다. 기존 화면에서는 흩어져 있어서 모아보기가 어려운데, 엑셀로 떨어지면 같은 컬럼에 들어가니까 피벗으로 비율이 바로 나온다. &quot;사이즈 작다고 답한 사람의 비중이 60%네&quot; 같은 인사이트가 한 번에 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엑셀 파일 자체는 외부 라이브러리 없이 XLSX 표준 그대로 만들어진다. 워크북 안에 워크시트 하나, 헤더 한 줄, 그 아래 데이터 행이 쭉 들어가는 구조다. 엑셀에서 그냥 더블클릭하면 열리고, 한글도 안 깨진다. 받자마자 필터 걸고 분석에 들어갈 수 있는 상태다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 뽑고 엑셀을 열어보면 다음과 같이 나온다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3840&quot; data-origin-height=&quot;2342&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cZ1WeB/dJMcaffgJMr/X79wxWtgWYb3L687SIRQyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cZ1WeB/dJMcaffgJMr/X79wxWtgWYb3L687SIRQyK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cZ1WeB/dJMcaffgJMr/X79wxWtgWYb3L687SIRQyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcZ1WeB%2FdJMcaffgJMr%2FX79wxWtgWYb3L687SIRQyK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3840&quot; height=&quot;2342&quot; data-origin-width=&quot;3840&quot; data-origin-height=&quot;2342&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리뷰 데이터란을 넓혀보면 다음과 같이 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3840&quot; data-origin-height=&quot;2340&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCnuve/dJMcacivC8Y/ES1KN1IwTglKWAeAS8IRwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCnuve/dJMcacivC8Y/ES1KN1IwTglKWAeAS8IRwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCnuve/dJMcacivC8Y/ES1KN1IwTglKWAeAS8IRwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCnuve%2FdJMcacivC8Y%2FES1KN1IwTglKWAeAS8IRwk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3840&quot; height=&quot;2340&quot; data-origin-width=&quot;3840&quot; data-origin-height=&quot;2340&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 모든 리뷰 데이터를 가져올 수 있다!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;진행 상황은 실시간으로&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수집은 시간이 좀 걸린다. 리뷰 1천 개면 100번, 5천 개면 500번 API를 호출해야 하니까, 짧으면 1분, 길면 10분 가까이 걸리는 작업이다. 그동안 화면이 멈춰 있으면 사용자가 답답하니까, 프로그램 창에 진행 상황이 계속 업데이트된다. &quot;수집 중 &amp;mdash; 1페이지 확인 후 전체 페이지 수를 계산합니다&quot;, &quot;XLSX 생성 중 &amp;mdash; 수집한 리뷰 데이터를 워크북으로 변환합니다&quot; 같은 메시지가 단계마다 뜬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수집이 끝나면 결과 패널에 상품명, 수집된 리뷰 수, API가 알려준 총 리뷰 수, 처리한 페이지 수, 저장된 파일 이름이 정리돼서 표시된다. 혹시 API 총 리뷰 수와 실제 수집된 리뷰 수가 차이가 나면 그 자리에서 바로 확인할 수 있다. 보통 쿠팡 쪽에서 일부 리뷰를 노출 안 하는 경우가 있어서 약간의 차이는 정상인데, 너무 많이 차이 나면 뭔가 문제가 있다는 신호다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;마무리&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리뷰 데이터는 쿠팡 위에서는 거의 분석 불가능한 형태로 갇혀 있다. 화면으로 보라고 만들어진 거지, 사람이 분석하라고 만들어진 게 아니기 때문이다. 그걸 엑셀 한 장으로 꺼내놓으면 그때부터 진짜 의미가 생긴다. 별점별 분포, 시기별 변화, 키워드별 빈도, 설문 답변 비율 &amp;mdash; 평소에 보던 그 리뷰가 완전히 다른 시야로 보이기 시작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비슷하게 어떤 사이트의 데이터가 화면 안에 갇혀 있어서 분석이 막혀 있는 상황이 있다면, 편하게 물어보면 된다. 화면 안의 데이터를 엑셀 위로 꺼내는 건 생각보다 많은 경우 가능하거든.&lt;/p&gt;</description>
      <category>크롤링</category>
      <category>쿠팡 리뷰 데이터 수집</category>
      <category>쿠팡 리뷰 데이터 크롤링</category>
      <category>쿠팡 리뷰 수집</category>
      <category>쿠팡 리뷰 엑셀 추출</category>
      <category>쿠팡 리뷰 크롤링</category>
      <category>쿠팡 상품 리뷰 다운로드</category>
      <category>쿠팡 크롤링 프로그램</category>
      <author>JamesCoder</author>
      <guid isPermaLink="true">https://jamescoder.tistory.com/11</guid>
      <comments>https://jamescoder.tistory.com/11#entry11comment</comments>
      <pubDate>Sat, 18 Apr 2026 16:48:27 +0900</pubDate>
    </item>
    <item>
      <title>SKT&amp;middot;KT&amp;middot;LGU+ 통신3사 공시지원금 데이터 크롤링 프로그램 만들기</title>
      <link>https://jamescoder.tistory.com/10</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;SKT, KT, LGU+ 세 통신사의 공시지원금 데이터를 한 번에 긁어서 엑셀로 떨어뜨리는 프로그램을 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연히 고객님의 요청으로 개발하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;세 통신사, 세 가지 다른 세계&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공시지원금이 뭔지부터 짚고 가자. 휴대폰 구입할 때 통신사에서 할인해 주는 금액이다. 기종에 따라 다르고, 요금제에 따라 다르고, 기기변경인지 번호이동인지 신규가입인지에 따라 또 다르다. 이 조합은 어마어마하게 많다. SKT만 해도 5G폰과 LTE폰을 나누고, 요금제가 수십 개, 가입유형이 세 가지다. 여기에 KT랑 LGU+까지 더하면 조합이 수천 개를 훌쩍 넘긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고객이 원한 건 단순했다. 통신3사의 모든 공시지원금 데이터를 주기적으로 모아서 엑셀 한 장으로 보고 싶다는 거다. 여기서 &quot;모든&quot;이 문제였다. 세 통신사 홈페이지를 하나씩 열어서 기종별로 요금제별로 가입유형별로 일일이 눌러보려면 하루 종일 붙어 있어도 끝나지 않는다. 게다가 지원금은 수시로 바뀐다. 이번 주에 뽑은 데이터가 다음 주면 이미 구데이터가 돼 있다. 결국 이 작업은 기계한테 맡기지 않으면 답이 없었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 미팅에서 또 하나 튀어나온 요구사항이 있었다. 통신사마다 데이터 구조가 완전히 다른데, 나온 결과 엑셀은 통일된 형식이어야 한다는 점이었다. &quot;삼성 갤럭시 S24 Ultra 512GB&quot;라고 쓰여 있든 &quot;SM-S928N_512G&quot;라고 쓰여 있든, 엑셀에서는 같은 기기로 정리돼야 한다는 거다. 이게 생각보다 까다로운 작업이 될 거라는 게 그 자리에서 바로 감이 왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;통신사 세 곳, 세 번의 탐색&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수집 자체는 각 통신사 홈페이지가 내부적으로 호출하는 API를 활용하는 구조로 잡았다. 세 곳의 API 구조가 다 다르기 때문에, 각각을 따로 뜯어보고 거기에 맞는 요청 방식을 구현했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SKT는 요금제 카테고리를 먼저 가져오고, 그 카테고리 안의 요금제 리스트를 받아오고, 요금제마다 5G폰&amp;middot;LTE폰 &amp;times; 기기변경&amp;middot;번호이동&amp;middot;신규가입 조합으로 공시지원 페이지를 긁는 방식이다. 페이지가 HTML로 내려오는데, 그 안에 자바스크립트 변수로 상품 리스트가 박혀 있다. 그걸 정규식으로 뽑아내서 JSON으로 변환하는 과정이 한 단계 더 있다. KT는 요금제 리스트를 JSON API로 받아온 뒤, 요금제&amp;times;가입유형 조합마다 기종 리스트를 페이지 단위로 넘겨가며 전부 받아온다. LGU+는 5G폰과 LTE폰을 따로 호출해야 하고, 요금제 응답이 중첩 구조로 내려와서 이걸 한 번 평탄화하는 로직이 필요했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 통신사의 데이터 스키마는 필드명까지 전부 다르다. SKT는 subscriptionNm이라는 필드에 요금제 이름이 들어 있고, KT는 pplNm, LGU+는 urcMblPpCd 같은 식이다. 기기 모델명, 출고가, 지원금 필드도 전부 이름이 다르다. 그래서 각 통신사별로 &quot;이 응답에서 어느 필드를 뽑아 쓴다&quot;는 매핑을 따로 만들어 두고, 최종 출력 단계에서 통일된 스키마로 변환한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kBfll/dJMcaaE0yCz/sPgjrF5KWKXKJtKSoLO0iK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kBfll/dJMcaaE0yCz/sPgjrF5KWKXKJtKSoLO0iK/img.png&quot; data-origin-width=&quot;1940&quot; data-origin-height=&quot;1542&quot; data-is-animation=&quot;false&quot; data-filename=&quot;이미지1.png&quot; style=&quot;width: 49.3411%; margin-right: 10px;&quot; data-widthpercent=&quot;49.92&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kBfll/dJMcaaE0yCz/sPgjrF5KWKXKJtKSoLO0iK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkBfll%2FdJMcaaE0yCz%2FsPgjrF5KWKXKJtKSoLO0iK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1940&quot; height=&quot;1542&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwefYA/dJMb99TBaM4/d3xnNhrPGeDL078oN1UKR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwefYA/dJMb99TBaM4/d3xnNhrPGeDL078oN1UKR1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1936&quot; data-origin-height=&quot;1534&quot; data-filename=&quot;이미지2.png&quot; style=&quot;width: 49.4961%;&quot; data-widthpercent=&quot;50.08&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwefYA/dJMb99TBaM4/d3xnNhrPGeDL078oN1UKR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwefYA%2FdJMb99TBaM4%2Fd3xnNhrPGeDL078oN1UKR1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1936&quot; height=&quot;1534&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;기종명 통일이 절반이었다&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;Samsung Galaxy S24 Ultra&quot;, &quot;갤럭시S24울트라&quot;, &quot;SM-S928N&quot;, &quot;삼성 갤럭시 에스24 울트라&quot;가 전부 같은 폰이다. 그런데 통신사마다 부르는 이름이 다르고, 같은 통신사 안에서도 페이지마다 조금씩 다르다. 이걸 그대로 엑셀로 뽑으면 같은 기기가 서로 다른 이름으로 수십 줄씩 중복된다. 고객 입장에서는 &quot;같은 기기로 묶어줬으면&quot; 하는 생각이 당연히 드는 구간이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 정규화 로직을 꽤 단단하게 만들었다. 한글 표기는 영문으로 바꾼다. &quot;갤럭시&quot;는 &quot;Galaxy&quot;, &quot;아이폰&quot;은 &quot;iPhone&quot;, &quot;프로 맥스&quot;는 &quot;Pro Max&quot;, &quot;울트라&quot;는 &quot;Ultra&quot; 같은 식으로 매핑 테이블을 깔아뒀다. 용량 표기도 따로 뽑아낸다. &quot;512GB&quot;, &quot;512G&quot;, &quot;512기가&quot; 같은 여러 표기를 한 가지 형태(&quot;512G&quot;)로 통일한다. 모델 코드 뒤에 붙는 숫자(예: &quot;S928N512&quot;)를 보고 용량을 추정하는 fallback 로직도 넣었다. 제조사도 마찬가지로, 여러 표기를 Samsung, Apple, Xiaomi 같은 표준 이름으로 매핑한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과 엑셀의 컬럼은 두 종류로 나눠뒀다. 정규화된 값(&quot;Galaxy S24 Ultra&quot;, &quot;512G&quot;) 컬럼과, 원본 그대로의 값(&quot;갤럭시 S24 울트라 512GB&quot;) 컬럼을 나란히 둔 거다. 데이터를 분석할 때는 정규화된 값으로 필터링하고, 원본 확인이 필요할 때는 원본 컬럼을 보면 된다. 덕분에 나중에 데이터 검증할 때 &quot;이게 왜 이렇게 정규화됐지?&quot;를 추적할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;IP 차단을 피하는 기본기&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공시지원금 수집은 요청 수가 엄청 많다. 요금제 수백 개 &amp;times; 가입유형 3가지 &amp;times; 통신사 3곳을 다 돌면 API 호출이 쉽게 수천 건을 넘는다. 아무런 제어 없이 쏘면 두 가지가 터진다. 하나는 IP 차단이고, 다른 하나는 일시적 서버 오류다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 모든 HTTP 요청을 전담 클라이언트 객체에 맡기고, 거기에 세 가지 장치를 넣었다. 첫째, 요청 사이에 최소 간격을 둔다. 기본값은 120밀리초 정도로 짧지만, 이걸 두는 것과 안 두는 것의 차이가 크다. 사람 눈에는 여전히 빠르지만, 서버 입장에서는 정상 사용자의 요청 패턴과 크게 다르지 않게 보인다. 둘째, 실패하면 간격을 벌려가며 재시도한다. 429나 5xx 응답은 일시적인 경우가 많아서, 몇 초 기다렸다가 다시 치면 대부분 해결된다. 셋째, 세션을 유지해서 쿠키와 헤더가 계속 살아 있게 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 실패하는 건 있다. 특정 요금제가 서버 쪽 오류로 아예 안 내려오거나, 네트워크가 순간적으로 끊기거나. 이런 경우에 프로그램 전체를 중단시키지 않고, 그 항목만 &quot;실패 로그&quot;에 적어두고 다음 항목으로 넘어간다. 수집이 다 끝나면 failed_requests.log라는 파일에 어떤 통신사의 어떤 요금제, 어떤 가입유형에서 실패했는지가 전부 남아 있다. 고객이 필요하면 그 로그를 보고 수동으로 다시 돌릴 수도 있고, 전체적으로는 일부 실패가 나도 나머지 수천 건의 데이터는 정상적으로 얻을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;실행은 그냥 버튼 하나&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고객은 개발자가 아니다. 스크립트 같은 걸 건드릴 일 없이, 그냥 더블클릭해서 실행 가능한 형태여야 했다. 그래서 Tkinter로 GUI를 감쌌다. 프로그램을 실행하면 단순한 창이 하나 뜬다. 상단에 저장 폴더를 지정하는 입력란과 폴더 선택 버튼, 그 아래에 &quot;수집 시작&quot; 버튼과 &quot;저장 폴더 열기&quot; 버튼. 그 아래는 수집이 진행되는 동안 실시간 로그가 쭉 흐르는 텍스트 박스다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버튼을 누르면 수집이 시작된다. 로그에는 &quot;[SKT] 수집 시작&quot;, &quot;[SKT] 요금제 82건 확보&quot;, &quot;[SKT] 5G 휴대폰 / 기기변경 / 5G 스탠다드 (12/82) &amp;rarr; 34건&quot;, 이런 식으로 실시간 진행이 뜬다. 어디까지 돌았는지, 지금 무슨 통신사의 어떤 조합을 긁고 있는지가 눈으로 다 보인다. 수집은 별도 스레드에서 돌기 때문에, 창이 멈추거나 얼어붙는 일 없이 로그가 계속 업데이트된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수집이 끝나면 완료 팝업이 뜬다. 총 몇 건이 수집됐는지, 엑셀 파일은 어디에 저장됐는지, 실패 로그는 어디에 있는지가 팝업 한 장에 정리돼 있다. 저장 폴더 열기 버튼을 누르면 탐색기가 바로 열려서 파일을 확인할 수 있다. 복잡할 게 하나도 없다. 고객은 일주일에 한 번 프로그램 실행하고 엑셀 파일 받아가는 게 전부다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kON2S/dJMcagFd5hw/BjDGYiVhzUAdOO6GBiZqy1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kON2S/dJMcagFd5hw/BjDGYiVhzUAdOO6GBiZqy1/img.png&quot; data-origin-width=&quot;1930&quot; data-origin-height=&quot;1534&quot; data-is-animation=&quot;false&quot; data-filename=&quot;이미지3.png&quot; style=&quot;width: 49.3553%; margin-right: 10px;&quot; data-widthpercent=&quot;49.94&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kON2S/dJMcagFd5hw/BjDGYiVhzUAdOO6GBiZqy1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkON2S%2FdJMcagFd5hw%2FBjDGYiVhzUAdOO6GBiZqy1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1930&quot; height=&quot;1534&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/80BwK/dJMcagFd5hx/jTLJyVrBsqqXpQF0QKqab1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/80BwK/dJMcagFd5hx/jTLJyVrBsqqXpQF0QKqab1/img.png&quot; data-origin-width=&quot;1940&quot; data-origin-height=&quot;1538&quot; data-is-animation=&quot;false&quot; data-filename=&quot;이미지4.png&quot; style=&quot;width: 49.482%;&quot; data-widthpercent=&quot;50.06&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/80BwK/dJMcagFd5hx/jTLJyVrBsqqXpQF0QKqab1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F80BwK%2FdJMcagFd5hx%2FjTLJyVrBsqqXpQF0QKqab1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1940&quot; height=&quot;1538&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;엑셀 파일로 오는 결과물&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과는 CSV와 XLSX 두 가지로 동시에 저장된다. 컬럼은 통신사, 제조사, 모델명, 용량, 출고가, 요금제, 월요금, 가입유형, 공통지원금, 공시일, 수집일시, 크롤링 URL, 그리고 Raw 값 컬럼들로 구성된다. 엑셀 파일은 헤더를 고정하고 굵게 표시해 놨기 때문에, 열자마자 바로 필터 걸고 피벗 돌릴 수 있는 상태다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 수집을 매번 돌릴 때마다 결과가 새 폴더에 저장된다. run_20251115_143022 같은 타임스탬프 폴더가 자동으로 생기고, 그 안에 CSV, XLSX, 실패 로그가 같이 들어간다. 덕분에 매주 수집한 데이터를 시계열로 쌓을 수 있고, 지난주 대비 공시지원금이 어떻게 변했는지도 추적 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중복 제거는 자동이다. 같은 통신사, 같은 모델번호, 같은 요금제, 같은 가입유형, 같은 공시일 조합이 있으면 한 번만 남긴다. 수집 과정에서 같은 조합이 여러 번 수집되는 경우가 있어서, 이걸 마지막 단계에서 정리해 준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3622&quot; data-origin-height=&quot;1908&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TbiTu/dJMcacCOzAO/WIlvkd4TXHVJkKfvIpLFSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TbiTu/dJMcacCOzAO/WIlvkd4TXHVJkKfvIpLFSK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TbiTu/dJMcacCOzAO/WIlvkd4TXHVJkKfvIpLFSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTbiTu%2FdJMcacCOzAO%2FWIlvkd4TXHVJkKfvIpLFSK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3622&quot; height=&quot;1908&quot; data-origin-width=&quot;3622&quot; data-origin-height=&quot;1908&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 데이터를 뽑을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;마무리&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 프로젝트의 핵심은 &quot;수집 자체&quot;가 아니라 &quot;서로 다른 데이터를 하나의 형식으로 묶는 일&quot;이었다. 통신사별로 다른 이름, 다른 구조, 다른 표기법을 일관된 엑셀 한 장으로 정리하는 게 진짜 가치다. 이런 종류의 작업은 제조사 다릅고 사이트 다를 뿐 어디에서나 반복된다. 시장 조사, 가격 모니터링, 경쟁사 분석 같은 이름으로 불리지만, 본질은 다 비슷한 문제다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 프로그램이 필요하시거나, 궁금하신 점이 있으시다면 언제든지 편하게 문의 주세요!&lt;/p&gt;</description>
      <category>크롤링</category>
      <category>SKT KT LGU 지원금 크롤링</category>
      <category>공시지원금 데이터 크롤링</category>
      <category>공시지원금데이터</category>
      <category>공시지원금수집</category>
      <category>공시지원금크롤링</category>
      <category>통신 3사 데이터 수집</category>
      <category>통신3사 공시지원금</category>
      <category>휴대폰 가격 모니터링</category>
      <author>JamesCoder</author>
      <guid isPermaLink="true">https://jamescoder.tistory.com/10</guid>
      <comments>https://jamescoder.tistory.com/10#entry10comment</comments>
      <pubDate>Fri, 17 Apr 2026 18:35:52 +0900</pubDate>
    </item>
    <item>
      <title>12개월 급여대장 병합부터 원장 대조까지 자동 병합 및 변환 프로그램 만들기</title>
      <link>https://jamescoder.tistory.com/9</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;매달 쌓이는 급여대장 파일 12개를 열어서, 직원별로 연간 합계를 내고, 그걸 다시 업로드 양식에 맞춰 옮기는 작업. 해본 사람은 안다. 이게 얼마나 지루하고, 얼마나 실수가 나기 쉬운 일인지.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘 문의 주신 고객님은 사회복지시설에서 일을 하시는데, 본인이 모든 직원의 12개월 급여를 관리하고 대장 병합부터 원장 대조까지 모두 진행해야하는데 실수도 잦고 하나하나 비교해가면서 작업하는게 너무 힘들어 고민 끝에 연락을 주신 케이스이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;연말마다 찾아오는 급여 정리 지옥&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고객님은 매달 급여대장을 엑셀로 관리하고 있는데, 연말이 되면 이 파일들을 전부 합쳐서 &quot;업로드 양식&quot;이라는 정해진 포맷으로 변환해야 했다. 문제는 이 과정이 전부 수작업이라는 거다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;월별 급여대장 파일이 12개. 각 파일에 직원 수십 명의 급여, 공제, 보험료 데이터가 들어있다. 이걸 하나하나 열어서 직원별로 연간 합계를 내야 한다. 거기에 국민연금, 건강보험, 고용보험, 장기요양보험, 소득세, 지방소득세 같은 항목들을 각각 따로 계산해야 하고, 직접비와 간접비도 구분해서 나눠야 한다. 산재보험이나 퇴직금은 별도 비율로 산출까지 해야 했다. 여기에 총계정원장이라는 또 다른 엑셀과 대조해서 숫자가 맞는지 검증하는 작업까지 더해지면, 꼬박 이틀은 걸리는 작업이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 골치 아팠던 건 &quot;직원 매핑&quot; 문제였다. 월별 급여대장에 적힌 사원명과 업로드 양식에 들어갈 직원명이 다른 경우가 있었다. 이름 뒤에 괄호로 부서명이 붙어있거나, 중도 퇴사자가 다른 코드로 재등록되어 있거나. 이런 예외 케이스를 하나씩 눈으로 확인하면서 맞춰야 했던 거다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&quot;자동으로 합쳐주고, 양식에 맞게 변환해주면 안 되나요?&quot;&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고객의 요청은 단순해 보였지만, 파고 들어가면 꽤 복잡한 구조였다. 처음 대화를 나누면서 정리한 요구사항은 크게 네 가지였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫째, 월별 급여대장 파일 12개를 자동으로 읽어서 직원별 연간 합계를 내는 것. 둘째, 그 합계를 기관에서 지정한 업로드 양식 포맷에 맞게 변환하는 것. 셋째, 총계정원장과 대조해서 숫자가 맞는지 자동으로 검증하는 것. 넷째, 직원 이름이 다르거나 코드가 바뀐 경우에도 알아서 매칭해주는 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 대화를 하면서 몇 가지 더 발견했다. 시설 유형에 따라 업로드 양식의 시트가 &quot;주간&quot;과 &quot;요양&quot;으로 나뉘어 있었고, 직접비/간접비 구분에 따라 급여 항목이 다른 열에 들어가야 했다. 산재보험 비율이나 퇴직금 산출 기준도 상황에 따라 포함하거나 제외할 수 있어야 했다. 단순히 &quot;합치고 옮기는&quot; 수준이 아니라, 기관 회계 기준에 맞는 정교한 변환이 필요했던 거다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;974&quot; data-origin-height=&quot;809&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgbps8/dJMcaju20bm/37ZPQI9YHrq99zTaJ7T940/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgbps8/dJMcaju20bm/37ZPQI9YHrq99zTaJ7T940/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgbps8/dJMcaju20bm/37ZPQI9YHrq99zTaJ7T940/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcgbps8%2FdJMcaju20bm%2F37ZPQI9YHrq99zTaJ7T940%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;974&quot; height=&quot;809&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;974&quot; data-origin-height=&quot;809&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;헤더가 제각각인 엑셀들을 하나로 읽어야 했다&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발에서 제일 먼저 부딪힌 문제는 월별 급여대장의 헤더 구조였다. 엑셀 파일마다 열 이름이 미묘하게 달랐다. 어떤 파일에서는 &quot;장기요양보험료&quot;이고, 다른 파일에서는 &quot;장기요양보험&quot;이다. &quot;주민세&quot;라고 적힌 걸 &quot;지방소득세&quot;로 읽어야 하는 경우도 있었고, 엑셀 내부에서 줄바꿈 문자가 섞여 들어간 헤더도 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 헤더를 읽을 때 정규화 과정을 거치도록 만들었다. 줄바꿈과 공백을 정리하고, 미리 정의해둔 별칭 사전으로 같은 항목인데 이름만 다른 경우를 통일한다. 이 한 단계가 없으면 12개 파일을 합치는 순간 열이 어긋나면서 숫자가 엉뚱한 곳에 들어가는 사고가 난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직원 매핑은 3단계 폴백 구조로 설계했다. 1순위는 사용자가 직접 만든 매핑 파일. 여기에 원본 사원코드와 업로드용 직원번호를 직접 지정할 수 있다. 2순위는 업로드 양식 시트에 이미 등록된 직원 정보. 3순위는 이름 기반 자동 매칭인데, 여기서 괄호나 하이픈 뒤의 부가 정보를 제거한 단순화된 이름으로 비교한다. 이 구조 덕분에 대부분의 직원은 자동으로 매칭되고, 예외 케이스만 매핑 파일에서 수동으로 잡아주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;총계정원장 대조 기능도 넣었다. 업로드 데이터의 직접비/간접비별 급여 합계, 퇴직금, 사회보험 회사부담금을 총계정원장의 해당 계정과 비교해서 차이를 한눈에 보여준다. 여기서 한 걸음 더 나아가, &quot;원장 보정&quot; 옵션을 켜면 총계정원장의 합계에 맞춰서 업로드 데이터의 금액을 자동으로 배분해주는 기능까지 만들었다. 직원별 급여 비중에 따라 단수 차이 없이 정수로 배분하는 로직이라, 1원 단위까지 맞아떨어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dH7hZk/dJMcaaZbLiG/ZrKWrOduzXADiEtK6HuglK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dH7hZk/dJMcaaZbLiG/ZrKWrOduzXADiEtK6HuglK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot; data-filename=&quot;payroll_card_02_before.png&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dH7hZk/dJMcaaZbLiG/ZrKWrOduzXADiEtK6HuglK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdH7hZk%2FdJMcaaZbLiG%2FZrKWrOduzXADiEtK6HuglK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ba9Ynv/dJMb990j9vv/Ap9SDEyfHgOgMKArtiB5Ek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ba9Ynv/dJMb990j9vv/Ap9SDEyfHgOgMKArtiB5Ek/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot; data-filename=&quot;payroll_card_03_after.png&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ba9Ynv/dJMb990j9vv/Ap9SDEyfHgOgMKArtiB5Ek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fba9Ynv%2FdJMb990j9vv%2FAp9SDEyfHgOgMKArtiB5Ek%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;프로그램 사용은 이렇게 간단하다&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GUI로 만들었기 때문에 사용법이 직관적이다. 프로그램을 켜면 입력 항목이 정리된 화면이 뜬다. 월별 급여대장이 들어있는 폴더를 선택하면, 프로그램이 알아서 파일 패턴을 감지하고, 같은 폴더에 업로드 양식이나 총계정원장 파일이 있으면 그것도 자동으로 잡아준다. 템플릿 시트 목록도 드롭다운으로 보여줘서 클릭만 하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회계년도, 급여년월, 산재보험 포함 여부 같은 세부 옵션도 GUI에서 체크박스 하나로 조절할 수 있다. 변환 버튼을 누르면 12개 파일을 읽어서 병합하고, 직원별 합계를 내고, 업로드 양식을 생성하고, 원장 대조표까지 만들어준다. 결과는 리포트 파일, 업로드 파일, 직원 매핑 템플릿 세 개가 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리포트 파일에는 월별 병합 데이터, 직원 연간 합계, 업로드 데이터, 직원 매핑 검토, 원장 대조 &amp;mdash; 이 다섯 개 시트가 들어있다. 담당자가 결과를 검토할 때 어느 단계에서든 확인할 수 있도록 중간 과정을 전부 남겨둔 거다. 업로드 파일은 기관 양식 그대로의 포맷으로 나오기 때문에, 따로 손대지 않고 바로 제출할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;이틀 걸리던 작업이 몇 분이면 끝난다&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buc6hC/dJMcaf0tX9g/QGgOmKqfoRHl2lg33PG7c1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buc6hC/dJMcaf0tX9g/QGgOmKqfoRHl2lg33PG7c1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot; data-filename=&quot;payroll_card_01_title.png&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buc6hC/dJMcaf0tX9g/QGgOmKqfoRHl2lg33PG7c1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbuc6hC%2FdJMcaf0tX9g%2FQGgOmKqfoRHl2lg33PG7c1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l2Phz/dJMcaf0tX9f/ynHL3jboEukow3X6XUZC4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l2Phz/dJMcaf0tX9f/ynHL3jboEukow3X6XUZC4K/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot; data-filename=&quot;payroll_card_04_closing.png&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l2Phz/dJMcaf0tX9f/ynHL3jboEukow3X6XUZC4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl2Phz%2FdJMcaf0tX9f%2FynHL3jboEukow3X6XUZC4K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;12개월 급여대장 병합부터 업로드 양식 생성, 원장 대조까지 &amp;mdash; 이전에 이틀 가까이 걸리던 연말 작업이 버튼 한 번에 몇 분이면 끝나게 됐다. 수작업에서 가장 무서운 건 &quot;실수해도 모른다&quot;는 거다. 한 셀을 잘못 복사해도, 공제항목 하나를 빼먹어도, 숫자만 봐서는 알기 어렵다. 자동화는 이 공포를 없앤다. 같은 로직이 매번 같은 결과를 내니까.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 프로젝트를 하면서 느낀 건, 회계 업무 자동화는 &quot;편해지는 것&quot;보다 &quot;정확해지는 것&quot;이 더 큰 가치라는 거다. 사람이 엑셀 12개를 넘나들면서 항목을 맞추다 보면, 아무리 꼼꼼해도 실수가 생긴다. 그런데 프로그램은 헤더 정규화, 직원 매핑, 원장 대조까지 매번 똑같이 정확하게 처리한다. 담당자는 결과만 확인하면 되는 거다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비슷한 급여 업무를 수작업으로 하고 있다면, 자동화를 고려해볼 만하다. 특히 매년 반복되는 연말 정산이나 양식 변환 작업은, 한번 프로그램을 만들어두면 그 다음부터는 시간이 거의 들지 않기 때문이다.&lt;/p&gt;</description>
      <category>자동화 프로그램</category>
      <category>급여대장 변환 자동화</category>
      <category>급여대장 양식 변환</category>
      <category>급여대장 업로드 변환</category>
      <category>급여대장 업로드 자동화</category>
      <category>급여대장 자동화</category>
      <category>사회복지 시설 급여 프로그램</category>
      <category>엑셀 급여 병합 자동화</category>
      <category>총계정원장 대조 프로그램</category>
      <author>JamesCoder</author>
      <guid isPermaLink="true">https://jamescoder.tistory.com/9</guid>
      <comments>https://jamescoder.tistory.com/9#entry9comment</comments>
      <pubDate>Fri, 10 Apr 2026 20:40:15 +0900</pubDate>
    </item>
    <item>
      <title>스레드(Threads) 자동 게시글 작성 프로그램 만들기</title>
      <link>https://jamescoder.tistory.com/8</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;어제 한 분 고객님께서 문의를 주셨다. 고객님은 매일 수십 개의 게시글을 Threads에 올리는 일을 하고 계셨다. 로그인하고, 글 쓰고, 사진 붙이고, 게시 버튼 누르고. 한두 개면 괜찮은데, 이게 20개, 30개가 넘어가면 이야기가 달라진다. 하루의 상당 시간을 복붙과 클릭에 쓰고 있었던 거다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;매일 반복되는 수작업, 그 안에 숨은 진짜 문제&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고객이 처음 연락해왔을 때 상황은 이랬다. Threads 계정을 운영하면서 하루에 수십 건의 게시글을 올려야 했는데, 전부 수작업이었다. 매번 브라우저를 열고, 로그인하고, 글을 쓰고, 이미지를 첨부하고, 게시 버튼을 누른다. 한 건당 2~3분이면 빠른 편이지만, 20건이면 거의 한 시간이다. 거기에 중간중간 로그인이 풀리기라도 하면, 다시 아이디 비밀번호 입력부터 해야 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 단순 반복만이 아니었다. 게시글마다 본문이 다르고, 첨부 이미지도 각각 다르고, 일부 게시글에는 댓글까지 달아야 했다. 그러다 보니 엑셀에 게시할 내용을 미리 정리해두고, 하나씩 복붙하는 방식으로 일하고 있었다. 엑셀은 이미 있었지만, 그걸 실제로 올리는 과정은 전부 손으로 하고 있던 셈이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고객이 원한 건 명확했다. &quot;엑셀에 정리한 내용을 그대로 Threads에 자동으로 올려주는 프로그램.&quot; 로그인도 자동으로 하고, 본문 입력도, 사진 첨부도, 게시도 전부 프로그램이 알아서 해주면 좋겠다는 거였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;624&quot; data-origin-height=&quot;697&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sAT7y/dJMcafTKBhT/KWzB4pceg3bn5UrtkWSrvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sAT7y/dJMcafTKBhT/KWzB4pceg3bn5UrtkWSrvk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sAT7y/dJMcafTKBhT/KWzB4pceg3bn5UrtkWSrvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsAT7y%2FdJMcafTKBhT%2FKWzB4pceg3bn5UrtkWSrvk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;624&quot; height=&quot;697&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;624&quot; data-origin-height=&quot;697&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;단순 자동화가 아니라 '진짜 쓸 수 있는' 프로그램을 만들어야 했다&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항을 듣고 나서 바로 개발에 들어가지 않았다. 이런 종류의 자동화에서 가장 까다로운 건 &quot;실제 환경에서 안정적으로 돌아가느냐&quot;이기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선, Threads는 Instagram 계정으로 로그인한다. 2단계 인증(2FA)이 걸려있는 경우가 대부분이라, 완전 무인 자동화는 처음부터 제외했다. 대신 첫 로그인만 사용자가 인증하면, 이후에는 세션이 유지되도록 설계하는 방향을 택했다. 계정별로 브라우저 프로필을 따로 저장해서, 한번 로그인하면 다음에는 자동으로 로그인 상태가 유지되는 구조다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음으로 신경 쓴 건 브라우저 호환성 문제였다. 셀레니움 기반 자동화에서 가장 흔한 에러가 Chrome 버전과 ChromeDriver 버전이 안 맞아서 생기는 문제다. 고객이 개발자가 아닌 이상, &quot;ChromeDriver를 수동으로 업데이트하세요&quot;는 답이 될 수 없었다. 그래서 프로그램이 실행될 때 설치된 Chrome 버전을 자동으로 감지하고, 맞는 ChromeDriver를 알아서 다운로드하고 캐시하는 구조를 만들었다. 한번 받으면 같은 버전에서는 다시 안 받으니까 실행 속도도 빠르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;텍스트 입력 방식도 두 가지를 준비했다. 한 글자씩 치는 타이핑 모드와, 클립보드에 복사해서 붙여넣는 모드다. 타이핑 모드는 사람처럼 보이는 장점이 있고, 붙여넣기 모드는 이모지 같은 특수문자가 깨지지 않는 장점이 있다. 고객이 상황에 따라 선택할 수 있도록 GUI에서 라디오 버튼으로 제공했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cWIAQp/dJMcadVSHPj/56neeWFGqVzKDqMitLKtA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cWIAQp/dJMcadVSHPj/56neeWFGqVzKDqMitLKtA1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot; data-filename=&quot;card_02_before.png&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cWIAQp/dJMcadVSHPj/56neeWFGqVzKDqMitLKtA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcWIAQp%2FdJMcadVSHPj%2F56neeWFGqVzKDqMitLKtA1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GlxH7/dJMcab4RTAL/5KQdi4KGaqokfsOjb9ya7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GlxH7/dJMcab4RTAL/5KQdi4KGaqokfsOjb9ya7K/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot; data-filename=&quot;card_03_after.png&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GlxH7/dJMcab4RTAL/5KQdi4KGaqokfsOjb9ya7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGlxH7%2FdJMcab4RTAL%2F5KQdi4KGaqokfsOjb9ya7K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;엑셀 한 장이면 끝나는 사용 흐름&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램을 켜면 깔끔한 GUI 창이 뜬다. 사용법은 간단하다. 엑셀 파일을 선택하고, 게시글 간 대기 시간을 설정하고, 시작 버튼을 누르면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엑셀 양식도 직관적으로 만들었다. A열에 계정 아이디, B열에 비밀번호, C열에 동영상 경로, D열에 사진 경로, E열에 본문 텍스트, F열에 댓글 텍스트. 한 행이 게시글 하나다. 고객이 이미 엑셀로 게시 내용을 관리하고 있었기 때문에, 기존 워크플로우를 거의 바꾸지 않아도 되는 구조였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시작 버튼을 누르면 프로그램이 Chrome을 열고, Instagram 로그인 페이지로 이동한다. 처음이면 아이디와 비밀번호를 자동 입력하고, 2FA 인증을 위해 30초간 대기한다. 이 시간 동안 사용자가 휴대폰에서 인증을 완료하면 된다. 로그인이 끝나면 Threads 페이지로 이동해서, 엑셀에 있는 게시글을 하나씩 자동으로 올린다. 본문 입력, 미디어 첨부, 댓글 추가, 게시까지 전 과정이 자동이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게시글 사이에는 랜덤한 간격으로 대기한다. 최소/최대 시간을 사용자가 직접 설정할 수 있어서, 너무 기계적으로 보이지 않게 조절이 가능하다. 실행 중 언제든 중지 버튼을 누르면 현재 작업을 마무리하고 안전하게 멈춘다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1712&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bq2IMw/dJMcafTKBhW/o8A6fcBabX22zO8GVr8Sb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bq2IMw/dJMcafTKBhW/o8A6fcBabX22zO8GVr8Sb1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bq2IMw/dJMcafTKBhW/o8A6fcBabX22zO8GVr8Sb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbq2IMw%2FdJMcafTKBhW%2Fo8A6fcBabX22zO8GVr8Sb1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1712&quot; height=&quot;800&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1712&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;보이지 않는 곳에서 더 많은 문제를 잡았다&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;겉보기에는 단순해 보이는 자동화지만, 실제로 안정적으로 동작하게 만들려면 챙겨야 할 게 많았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Threads의 UI는 다국어로 동작한다. 같은 버튼이 한국어 환경에서는 &quot;새로운 소식이 있나요&quot;이고, 영어에서는 &quot;What's new?&quot;다. 게시 버튼, 로그인 버튼도 마찬가지다. 그래서 하나의 UI 요소를 찾을 때 여러 언어의 텍스트를 폴백으로 순회하는 구조를 적용했다. 고객의 브라우저 언어 설정이 바뀌더라도 프로그램이 깨지지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저가 비정상 종료되면 프로필 폴더에 잠금 파일이 남아서 다음 실행이 안 되는 문제도 처리했다. 프로그램이 시작될 때 자동으로 잠금 파일을 정리하고, 혹시 이전 실행에서 남은 ChromeDriver 프로세스가 있으면 그것도 정리한다. 프로필 폴더 자체가 손상되었을 때는 자동으로 백업을 만들고 새 프로필로 재시도하는 로직까지 들어가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ChromeDriver 실행에도 다단계 복구 전략을 적용했다. 1차로 기본 Selenium Manager를 시도하고, 실패하면 프로필을 초기화해서 다시 시도하고, 그래도 안 되면 Chrome 버전에 정확히 매칭되는 ChromeDriver를 직접 다운로드해서 시도한다. 이 과정 전체가 타임아웃과 에러 핸들링으로 보호되어 있어서, 어디서 문제가 생겨도 프로그램이 멈추거나 먹통이 되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1722&quot; data-origin-height=&quot;798&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwqDrl/dJMcafTKBhV/We4pKxJbC1WjL2Jm1K27bK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwqDrl/dJMcafTKBhV/We4pKxJbC1WjL2Jm1K27bK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwqDrl/dJMcafTKBhV/We4pKxJbC1WjL2Jm1K27bK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwqDrl%2FdJMcafTKBhV%2FWe4pKxJbC1WjL2Jm1K27bK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1722&quot; height=&quot;798&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1722&quot; data-origin-height=&quot;798&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;수작업 한 시간이 버튼 한 번으로&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로, 20~30개 게시글을 올리는 데 한 시간 가까이 걸리던 작업이 시작 버튼 한 번으로 끝나게 됐다. 사용자가 할 일은 엑셀에 내용을 정리하는 것, 그리고 첫 로그인 때 2FA 인증을 해주는 것뿐이다. 나머지는 프로그램이 전부 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 프로젝트에서 제일 많이 느낀 건, SNS 자동화는 &quot;되긴 되는&quot; 수준과 &quot;실제로 매일 돌려도 되는&quot; 수준 사이의 간극이 크다는 거다. 로그인 세션 유지, 브라우저 버전 호환, UI 변경 대응, 비정상 종료 복구 &amp;mdash; 이런 것들을 하나씩 잡아가는 게 전체 개발 시간의 절반 이상이었다. 하지만 이 부분을 제대로 잡아야 고객이 불편 없이 쓸 수 있는 프로그램이 되는 거다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시 SNS 운영하면서 매일 같은 작업을 반복하고 있다면, 한번쯤 자동화를 생각해볼 만하다. 생각보다 많은 반복 작업이 프로그램 하나로 해결되니까.&lt;/p&gt;</description>
      <category>자동화 프로그램</category>
      <category>스레드 게시물 자동화</category>
      <category>스레드 자동 게시물 등록</category>
      <category>스레드 자동게시물</category>
      <category>스레드 자동화 프로그램</category>
      <category>스레드 포스팅 자동화</category>
      <category>쓰레드 자동 게시물</category>
      <category>쓰레드 자동화 프로그램</category>
      <author>JamesCoder</author>
      <guid isPermaLink="true">https://jamescoder.tistory.com/8</guid>
      <comments>https://jamescoder.tistory.com/8#entry8comment</comments>
      <pubDate>Fri, 10 Apr 2026 19:18:27 +0900</pubDate>
    </item>
    <item>
      <title>인크루트 포지션 제안 자동화 프로그램 만들기</title>
      <link>https://jamescoder.tistory.com/7</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;나의 고객이신 한 분이 대기업에서 채용 담당자로 일을 하고 계신다. 채용 담당자가 하루에 해야 하는 일 중에 이런 게 있다. 인크루트에서 후보자를 검색하고, 한 명씩 프로필을 열어서, 포지션 제안 버튼을 누르고, 공고를 선택하고, 담당자를 지정하고, 보내기를 클릭하는 것. 이걸 한 명당 5~6번의 클릭으로 끝내야 하는데, 문제는 이게 한두 명이 아니라는 거다. 검색 결과가 수십 페이지, 페이지당 30명이면 한 번 돌릴 때 수백 명. 클릭 횟수로 따지면 하루에 1,000번이 넘어간다. 손목이 남아나지 않는 작업이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;채용 실무의 반복 루틴&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인크루트 같은 채용 플랫폼에서 &quot;포지션 제안&quot;은 기업이 먼저 후보자에게 관심을 표현하는 기능이다. 좋은 후보자를 발견하면 제안을 보내서 지원을 유도하는 건데, 이게 한두 건이면 수작업으로 충분하다. 그런데 대량 채용을 하거나, 넓은 풀에 제안을 뿌려야 하는 상황이 되면 이야기가 달라진다. 검색 조건을 세팅해두고 결과 목록을 쭉 훑으면서, 한 명씩 제안을 보내는 과정을 수십 페이지에 걸쳐 반복해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 작업의 구조를 뜯어보면 사실 매번 똑같은 패턴이다. 목록에서 &quot;포지션 제안&quot; 버튼을 누르면 새 창이 뜬다. 새 창에서 제안 버튼을 한 번 더 누르면 팝업이 나온다. 팝업에서 등록된 포지션을 선택하고, 담당자를 검색해서 지정하고, 보내기를 클릭한다. 그러면 확인 알림이 뜨고, 그걸 닫고 나서 원래 목록으로 돌아와서 다음 사람을 처리한다. 한 명당 이 과정이 통째로 반복된다. 내용은 같은데 클릭만 계속 하는 거다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고객이 원했던 건 명확했다. 검색 조건은 본인이 직접 세팅하고, 그 결과 목록에서 포지션 제안을 보내는 반복 작업만 프로그램이 대신해주는 것. 검색 조건 자체를 자동화하는 게 아니라, &quot;이 사람들한테 제안 보내기&quot;라는 단순 반복 클릭을 없애고 싶었던 거다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bz0cIc/dJMcadIemzM/jfWaRSFjKbpUYAOb1txei1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bz0cIc/dJMcadIemzM/jfWaRSFjKbpUYAOb1txei1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;894&quot; data-origin-height=&quot;727&quot; data-filename=&quot;인크1.png&quot; style=&quot;width: 39.9038%; margin-right: 10px;&quot; data-widthpercent=&quot;40.37&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bz0cIc/dJMcadIemzM/jfWaRSFjKbpUYAOb1txei1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbz0cIc%2FdJMcadIemzM%2FjfWaRSFjKbpUYAOb1txei1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;894&quot; height=&quot;727&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmtVqg/dJMcadIemzO/FmW1ds23tUD4iD0CoTE4b0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmtVqg/dJMcadIemzO/FmW1ds23tUD4iD0CoTE4b0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1867&quot; data-origin-height=&quot;1028&quot; data-filename=&quot;인크2.png&quot; style=&quot;width: 58.9335%;&quot; data-widthpercent=&quot;59.63&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmtVqg/dJMcadIemzO/FmW1ds23tUD4iD0CoTE4b0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmtVqg%2FdJMcadIemzO%2FFmW1ds23tUD4iD0CoTE4b0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1867&quot; height=&quot;1028&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;브라우저를 직접 조종하는 방식&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 프로젝트에서 가장 중요한 설계 결정은, 이미 열려 있는 브라우저에 프로그램을 연결하는 방식을 택한 것이었다. 보통 자동화 프로그램은 새 브라우저를 열어서 처음부터 로그인하고 들어가는 구조로 만드는데, 인크루트 같은 채용 플랫폼은 그렇게 하기가 까다롭다. 로그인 과정에 보안 인증이 걸려 있을 수 있고, 기업 계정은 추가 인증을 요구하기도 한다. 그래서 사용자가 먼저 크롬을 특수 모드로 열고, 직접 로그인해서 검색까지 마친 상태에서 프로그램을 연결하는 방식으로 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작동 원리는 이렇다. 프로그램 화면에서 &quot;Chrome Remote 실행&quot; 버튼을 누르면 디버깅 포트가 열린 상태의 크롬이 뜬다. 사용자가 그 브라우저에서 인크루트에 로그인하고, 원하는 검색 조건을 설정한다. 여기까지는 사람이 한다. 그다음 프로그램에서 시작 페이지와 끝 페이지를 지정하고 &quot;포지션 제안 시작&quot; 버튼을 누르면, 프로그램이 해당 브라우저에 붙어서 나머지를 알아서 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식의 장점은 로그인 상태와 검색 조건을 사용자가 완전히 통제할 수 있다는 점이다. 프로그램이 로그인 정보를 저장하거나 건드리지 않으니 보안 걱정이 없고, 검색 조건도 사이트에서 직접 세팅하니까 자유도가 높다. 프로그램은 순수하게 &quot;클릭 반복&quot;만 담당하는 셈이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;11단계 클릭을 자동으로&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 명에게 포지션 제안을 보내는 과정이 실제로는 11단계나 된다. 목록에서 제안 버튼 클릭, 새 창 열림 확인, 새 창에서 제안 버튼 다시 클릭, 팝업에서 &quot;등록된 포지션&quot; 라디오 선택, 드롭다운 열기, 포지션 선택, 담당자명 입력, 검색 버튼 클릭, 담당자 선택, 보내기 클릭, 확인 알림 처리. 이 11단계를 한 명마다 빠짐없이 밟아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;까다로운 부분은 이 과정 중간중간에 알림창이 불시에 튀어나온다는 점이었다. 이미 제안을 보낸 후보자거나, 이력서 열람이 불가한 경우에 브라우저 알림이 뜨는데, 이게 어느 단계에서 뜰지 예측이 안 된다. 목록에서 버튼을 클릭한 직후에 뜰 수도 있고, 새 창이 열린 다음에 뜰 수도 있고, 제안 버튼을 누른 후에 뜰 수도 있다. 그래서 주요 단계마다 알림창이 떴는지 확인하는 방어 로직을 넣었다. 알림이 뜨면 그 후보자는 건너뛰고, 새 창이 열려 있으면 닫고, 원래 목록 화면으로 정확히 돌아온 다음 다음 사람을 처리하도록 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새 창과 원래 창 사이를 오가는 것도 신경을 써야 했다. 포지션 제안 버튼을 누르면 새 브라우저 창이 열리는데, 거기서 작업을 끝내고 나면 그 창을 닫고 원래 목록 창으로 돌아와야 한다. 이 &quot;창 전환&quot;이 한 번이라도 꼬이면 프로그램이 엉뚱한 화면에서 버튼을 찾으려고 헤매다가 에러가 난다. 그래서 매 반복마다 현재 어느 창에 있는지를 추적하고, 에러가 나더라도 반드시 메인 목록 창으로 복귀하는 안전장치를 넣었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지 이동도 자동이다. 현재 URL에서 페이지 파라미터를 분리해서, 사용자가 지정한 시작 페이지부터 끝 페이지까지 순서대로 넘기면서 각 페이지의 후보자 전원에게 제안을 보낸다. 기존 검색 조건 파라미터는 건드리지 않고 페이지 번호만 바꾸는 방식이라, 사용자가 세팅한 필터가 그대로 유지된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chszWL/dJMcafTDr0D/Mc4dKPEwxjf5fQc4P6OPpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chszWL/dJMcafTDr0D/Mc4dKPEwxjf5fQc4P6OPpK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;955&quot; data-origin-height=&quot;1029&quot; data-filename=&quot;인크3.png&quot; style=&quot;width: 33.5048%; margin-right: 10px;&quot; data-widthpercent=&quot;33.9&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chszWL/dJMcafTDr0D/Mc4dKPEwxjf5fQc4P6OPpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchszWL%2FdJMcafTDr0D%2FMc4dKPEwxjf5fQc4P6OPpK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;955&quot; height=&quot;1029&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;사용 흐름과 결과&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 사용 과정은 간단하다. 프로그램을 켜면 안내 문구와 함께 &quot;Chrome Remote 실행&quot; 버튼이 보인다. 이걸 누르면 크롬이 뜨고, 인크루트 검색 페이지가 자동으로 열린다. 사용자가 로그인하고 검색 조건을 맞추면 준비 끝. 프로그램으로 돌아와서 시작 페이지와 끝 페이지를 지정하고 시작 버튼을 누르면, 로그 창에 진행 상황이 실시간으로 찍히면서 제안이 하나씩 나간다. &quot;포지션 제안 3/30 처리 중&amp;hellip;&quot;, &quot;발송 성공&quot;, &quot;발송 실패: 열람 불가&quot; 같은 메시지가 올라오고, 프로그레스 바가 페이지 단위로 차오른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수작업으로 한 명당 30초~1분 걸리던 게, 프로그램이 알아서 단계를 밟으면서 처리하니까 사람은 그 시간에 다른 일을 할 수 있게 됐다. 10페이지면 300명인데, 수작업이면 반나절짜리 작업이 프로그램 돌려놓고 커피 마시러 갔다 오면 끝나 있는 수준이다. 무엇보다 클릭 실수로 엉뚱한 사람에게 제안을 보내거나, 같은 사람에게 두 번 보내는 일이 사라졌다. 발송 실패한 건은 로그에 남으니까 나중에 확인도 쉽다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;인크4.png&quot; data-origin-width=&quot;1864&quot; data-origin-height=&quot;1030&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqSWNy/dJMcacCB6ZA/PkQkjk2sZ25VUT7IM5yNC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqSWNy/dJMcacCB6ZA/PkQkjk2sZ25VUT7IM5yNC0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqSWNy/dJMcacCB6ZA/PkQkjk2sZ25VUT7IM5yNC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqSWNy%2FdJMcacCB6ZA%2FPkQkjk2sZ25VUT7IM5yNC0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1864&quot; height=&quot;1030&quot; data-filename=&quot;인크4.png&quot; data-origin-width=&quot;1864&quot; data-origin-height=&quot;1030&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;마무리하며&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;채용 플랫폼에서의 반복 작업은 생각보다 자동화 여지가 많다. 검색 조건 세팅이나 후보자 판단처럼 사람의 판단이 필요한 부분은 사람이 하고, 판단이 끝난 후의 반복 클릭은 프로그램이 대신하는 구조가 가장 현실적이다. 전부 맡기는 게 아니라, 단순 반복 부분만 깔끔하게 떼어내는 것. 비슷한 클릭 노가다에 시달리는 상황이라면, 그 패턴이 자동화 가능한 구조인지 한번 살펴볼 만하다.&lt;/p&gt;</description>
      <category>자동화 프로그램</category>
      <category>인크루트 매크로</category>
      <category>인크루트 자동 발송</category>
      <category>인크루트 자동화</category>
      <category>인크루트 포지션 제안</category>
      <category>인크루트 포지션 제안 자동화</category>
      <category>채용 플랫폼 자동화</category>
      <category>포지션 제안 매크로</category>
      <category>포지션 제안 자동화</category>
      <author>JamesCoder</author>
      <guid isPermaLink="true">https://jamescoder.tistory.com/7</guid>
      <comments>https://jamescoder.tistory.com/7#entry7comment</comments>
      <pubDate>Thu, 2 Apr 2026 02:13:35 +0900</pubDate>
    </item>
    <item>
      <title>진학사 세특  탐구보고서 크롤링 프로그램 만들기</title>
      <link>https://jamescoder.tistory.com/6</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dShnRr/dJMcafe3Px2/uAEun7hXLvhgGtcB8SHkKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dShnRr/dJMcafe3Px2/uAEun7hXLvhgGtcB8SHkKK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot; data-filename=&quot;card_01_title_1.png&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dShnRr/dJMcafe3Px2/uAEun7hXLvhgGtcB8SHkKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdShnRr%2FdJMcafe3Px2%2FuAEun7hXLvhgGtcB8SHkKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LTNyB/dJMcafe3Px3/0z0b2W84kT0SkOzN4WRsY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LTNyB/dJMcafe3Px3/0z0b2W84kT0SkOzN4WRsY1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot; data-filename=&quot;card_02_before_1.png&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LTNyB/dJMcafe3Px3/0z0b2W84kT0SkOzN4WRsY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLTNyB%2FdJMcafe3Px3%2F0z0b2W84kT0SkOzN4WRsY1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고객 한 분 중에서, 진학사 사이트에 올라와 있는 세특 탐구보고서 데이터를 수천 건 단위로 정리해야 하는 사람이 있었다. 문제는 그 데이터가 한 페이지에 수십 개씩, 수백 페이지에 걸쳐 흩어져 있다는 거다. 학교명, 학부, 주제, 과목, 키워드, 보고서 유형, 업데이트일까지 &amp;mdash; 항목마다 일곱 가지 정보를 하나씩 긁어서 엑셀에 옮기는 작업이었다. 한두 페이지야 괜찮겠지만, 수백 페이지를 손으로 넘기면서 복붙한다? 하루종일 매달려도 끝이 안 보이는 종류의 일이었다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;처음 요청은 단순했다&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;진학사에 있는 세특 탐구보고서 목록을 엑셀로 뽑고 싶다&quot;는 게 시작이었다. 단순해 보이지만, 실제로 사이트를 열어보면 이야기가 달라진다. 진학사의 세특 검색 페이지는 단순한 HTML 테이블이 아니었다. 카드 형태로 배열된 항목들이 동적으로 로딩되고, 페이지네이션도 버튼 클릭으로 이동하는 구조였다. 게다가 상단에 스와이퍼 슬라이더로 추천 콘텐츠가 따로 돌아가고 있어서, 데이터를 긁을 때 이 추천 영역과 실제 검색 결과를 구분하는 작업도 필요했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항을 정리하면 이랬다. 학교명과 학부, 탐구 주제, 보고서 유형, 과목, 활동, 키워드, 업데이트일을 한 건도 빠짐없이 뽑을 것. 페이지 수를 지정하거나 전체 페이지를 한 번에 돌릴 수 있을 것. 그리고 결과는 엑셀 파일로 깔끔하게 내보낼 것. 여기까지는 크롤러의 기본 스펙이었는데, 한 가지 조건이 더 붙었다. 사용하는 사람이 개발자가 아니기 때문에, 코드를 직접 만질 필요 없이 프로그램 창에서 설정하고 실행할 수 있어야 한다는 점이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;진학사.png&quot; data-origin-width=&quot;896&quot; data-origin-height=&quot;731&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bePxBm/dJMcacP8pa2/WnW5ExzFakcfJYk7d2AGfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bePxBm/dJMcacP8pa2/WnW5ExzFakcfJYk7d2AGfk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bePxBm/dJMcacP8pa2/WnW5ExzFakcfJYk7d2AGfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbePxBm%2FdJMcacP8pa2%2FWnW5ExzFakcfJYk7d2AGfk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;896&quot; height=&quot;731&quot; data-filename=&quot;진학사.png&quot; data-origin-width=&quot;896&quot; data-origin-height=&quot;731&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;GUI가 필요했던 이유&lt;/b&gt;&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크롤러를 만들 때 보통은 터미널에서 명령어 한 줄 치면 되는 스크립트로 끝내는 경우가 많다. 그런데 이번 프로젝트는 사용자가 비개발자였다. 페이지 수를 바꾸고 싶을 때마다 코드를 열어 숫자를 고치라고 할 수는 없었다. 대기 시간 조절, 브라우저 숨김 모드 같은 설정도 마찬가지였다. 그래서 프로그램을 켜면 바로 보이는 조작 화면을 만들기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화면 구성은 꽤 직관적으로 잡았다. 상단에 크롤링 설정 영역이 있고, 최대 페이지 수와 페이지 간 대기시간을 입력할 수 있다. 전체 페이지를 돌리고 싶으면 0을 넣으면 된다. 헤드리스 모드 체크박스를 켜면 브라우저 창 없이 백그라운드에서 돌아간다. 가운데에는 진행 상태 바와 수집 건수가 실시간으로 올라가고, 아래쪽 로그 창에는 지금 몇 페이지를 긁고 있는지, 항목이 몇 개 추출됐는지 시간 단위로 찍힌다. 크롤링 도중 멈추고 싶으면 중지 버튼을 누르면 현재 페이지까지 마무리하고 안전하게 멈춘다. 데이터가 쌓이면 Excel 저장 버튼 하나로 바로 파일이 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 GUI가 있으면 사용자 입장에서는 &quot;프로그램 하나 실행해서 버튼 누르면 끝&quot;이 되는 거다. 코드를 몰라도, 터미널을 몰라도 상관없다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;데이터 추출의 까다로운 부분&lt;br /&gt;&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크롤러의 핵심은 결국 &quot;페이지에서 원하는 데이터를 정확히 뽑아내는 것&quot;인데, 진학사 사이트의 구조가 그렇게 친절하지는 않았다. 카드 하나에 학교 정보, 주제, 메타 정보, 날짜가 각각 다른 위치에 다른 방식으로 들어가 있었다. 학교명과 학부는 첫 번째 div 안의 p 태그 두 개에 나뉘어 있었고, 주제는 특정 CSS 클래스가 붙은 div에서 뽑아야 했다. 보고서 유형, 과목, 활동, 키워드는 하나의 메타 영역 안에 파이프(|) 기호로 구분된 span들로 나열돼 있었는데, 이걸 순서대로 정확히 분리하는 로직이 필요했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 신경 쓴 부분은 스와이퍼 영역과 실제 검색 결과를 구분하는 것이었다. 사이트 상단에 추천 콘텐츠가 슬라이더로 돌아가는데, 이 안에 있는 카드와 실제 검색 결과 카드의 HTML 구조가 비슷했다. 그래서 XPath를 설계할 때 스와이퍼 컨테이너의 하위 요소는 명시적으로 제외하는 조건을 넣었다. 이 한 줄이 빠지면 같은 데이터가 중복으로 잡히거나, 엉뚱한 추천 콘텐츠가 섞여 들어오는 문제가 생긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지 이동도 단순하지 않았다. 페이지 번호를 클릭하면 되는 것 같지만, 한 번에 보이는 번호 그룹이 제한돼 있어서 11페이지, 21페이지처럼 다음 그룹으로 넘어가야 하는 경우가 있었다. 이때는 &quot;Next page&quot; 버튼을 먼저 눌러서 그룹을 이동시킨 다음, 원하는 번호를 다시 찾아 클릭하는 재귀 로직을 넣었다. 단순히 다음 버튼만 누르는 방식이었으면 페이지 그룹 경계에서 크롤링이 멈춰버렸을 거다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 페이지를 이동한 뒤에 데이터가 정말 바뀌었는지 확인하는 과정도 추가했다. 이전 페이지 첫 번째 카드의 URL을 기억해두고, 새 페이지에서 첫 번째 카드 URL이 달라질 때까지 기다리는 방식이었다. 단순히 &quot;2초 기다리기&quot;로 처리하면 네트워크 상태에 따라 아직 데이터가 안 바뀐 상태에서 긁어버리는 일이 생기기 때문이다. 이 대기 로직 덕분에 데이터 누락이나 중복 없이 안정적으로 수집할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;진학사 이미지.png&quot; data-origin-width=&quot;1915&quot; data-origin-height=&quot;760&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IhI1B/dJMcadOYRXh/DCKLP1vt2khKdKC7ABKiy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IhI1B/dJMcadOYRXh/DCKLP1vt2khKdKC7ABKiy0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IhI1B/dJMcadOYRXh/DCKLP1vt2khKdKC7ABKiy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIhI1B%2FdJMcadOYRXh%2FDCKLP1vt2khKdKC7ABKiy0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1915&quot; height=&quot;760&quot; data-filename=&quot;진학사 이미지.png&quot; data-origin-width=&quot;1915&quot; data-origin-height=&quot;760&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;결과와 사용 흐름&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램을 실행하면 간단한 설정 창이 뜬다. 최대 페이지 수를 넣고, 시작 버튼을 누르면 끝이다. 진행 바가 움직이면서 로그 창에 &quot;페이지 1 크롤링 중&amp;hellip; 12개 항목 수집 완료&quot;처럼 실시간 상황이 찍힌다. 100페이지 기준으로 대략 수백에서 수천 건의 데이터가 쌓이고, 크롤링이 끝나면 Excel 저장 버튼을 눌러 바로 파일로 받으면 된다. 파일을 열어보면 학교, 학부, 주제, 과목, 키워드까지 깔끔하게 정리돼 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람이 직접 했으면 한 페이지당 복붙에 최소 2~3분은 걸렸을 거다. 100페이지면 3~5시간. 거기에 복붙 과정에서 빠뜨리는 항목이나 셀이 밀리는 실수까지 감안하면, 나중에 검수하는 데 또 시간이 든다. 이 프로그램은 같은 양을 에러 없이 10분 안에 처리한다. 사용자가 직접 건드리는 건 페이지 수 입력과 버튼 두 번 클릭뿐이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;이 프로젝트에서 얻은 것&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;교육 관련 데이터 수집 프로젝트를 하면서 느낀 건, 사이트마다 동적 렌더링 방식이 다 다르다는 점이다. 진학사처럼 SPA 구조에 페이지네이션까지 동적으로 처리하는 사이트는, 단순 HTTP 요청으로는 데이터를 가져올 수 없다. 실제 브라우저를 띄워서 사용자가 보는 것과 똑같은 화면에서 데이터를 읽어야 한다. 그 과정에서 &quot;데이터가 진짜 로딩됐는지&quot;를 판단하는 대기 전략이 크롤러의 안정성을 좌우한다는 걸 다시 한번 확인했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 GUI를 붙이는 건 개발 시간이 조금 더 들지만, 비개발자 고객에게는 그게 프로그램의 전부다. 아무리 크롤링 로직이 정교해도, 터미널에서 명령어를 쳐야 하는 순간 사용자 절반은 포기한다. 버튼 하나, 입력 칸 하나가 접근성을 완전히 바꿔놓는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시 특정 사이트에서 데이터를 대량으로 수집하거나 정리해야 하는 상황이 있다면, 생각보다 깔끔하게 자동화할 수 있는 경우가 많다. 사이트 구조만 파악되면, 나머지는 프로그램이 알아서 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yUSii/dJMcabDHl7w/1C8dsJLxEMoQBPqMHXBaZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yUSii/dJMcabDHl7w/1C8dsJLxEMoQBPqMHXBaZk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot; data-filename=&quot;card_03_after_1.png&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yUSii/dJMcabDHl7w/1C8dsJLxEMoQBPqMHXBaZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyUSii%2FdJMcabDHl7w%2F1C8dsJLxEMoQBPqMHXBaZk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ESKWl/dJMcagrsgjP/EFB0W2XEpScO66lrJEhmO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ESKWl/dJMcagrsgjP/EFB0W2XEpScO66lrJEhmO1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot; data-filename=&quot;card_04_closing_1.png&quot; data-widthpercent=&quot;50&quot; style=&quot;width: 49.4186%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ESKWl/dJMcagrsgjP/EFB0W2XEpScO66lrJEhmO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FESKWl%2FdJMcagrsgjP%2FEFB0W2XEpScO66lrJEhmO1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>크롤링</category>
      <category>교육 데이터 자동 수집</category>
      <category>세특 탐구보고서</category>
      <category>세특 탐구보고서 데이터 수집</category>
      <category>웹 크롤링 자동화 프로그램</category>
      <category>진학사 데이터 엑셀 변환</category>
      <category>진학사 세특</category>
      <category>진학사 세특 탐구보고서</category>
      <category>진학사 크롤링</category>
      <author>JamesCoder</author>
      <guid isPermaLink="true">https://jamescoder.tistory.com/6</guid>
      <comments>https://jamescoder.tistory.com/6#entry6comment</comments>
      <pubDate>Thu, 2 Apr 2026 01:30:31 +0900</pubDate>
    </item>
    <item>
      <title>컴퓨존 부품 매물 구매 매크로 프로그램 만들기</title>
      <link>https://jamescoder.tistory.com/5</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;새로고침을 눌러야 하는 사람들&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터 부품을 다루는 사람이라면 한 번쯤 겪어봤을 거다. 인기 있는 부품은 컴퓨존 같은 사이트에 매물이 올라오자마자 순식간에 빠진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가격 좋은 물건, 한정 수량 부품은 올라온 지 몇 분 만에 누군가 장바구니에 담아간다. 그래서 이걸 잡으려면 결국 사람이 모니터 앞에 앉아서, 해당 카테고리 페이지를 계속 새로고침하면서 기다려야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 이게 언제 올라올지 모른다는 거다. 아침일 수도 있고, 점심일 수도 있고, 밤일 수도 있다. 그 사이에 다른 일을 하다가 놓치면 그만이다. 고객님이 나에게 처음 연락을 주셨을 때 상황이 딱 이거였다. 특정 카테고리의 새 매물을 놓치지 않고 잡아야 하는데, 하루종일 브라우저만 들여다볼 수는 없으니 자동으로 감지하고 구매까지 넣어주는 프로그램을 만들어달라는 거였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zNl9N/dJMcafeTPwX/EYRsCFeFDTHhMr4Fy2naNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zNl9N/dJMcafeTPwX/EYRsCFeFDTHhMr4Fy2naNk/img.png&quot; data-origin-width=&quot;1384&quot; data-origin-height=&quot;1254&quot; data-is-animation=&quot;false&quot; style=&quot;width: 38.6181%; margin-right: 10px;&quot; data-widthpercent=&quot;39.07&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zNl9N/dJMcafeTPwX/EYRsCFeFDTHhMr4Fy2naNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzNl9N%2FdJMcafeTPwX%2FEYRsCFeFDTHhMr4Fy2naNk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1384&quot; height=&quot;1254&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boKyWQ/dJMcabp1n24/Cu2b26n54Ktj8mkDYQIRb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boKyWQ/dJMcabp1n24/Cu2b26n54Ktj8mkDYQIRb0/img.png&quot; data-origin-width=&quot;3146&quot; data-origin-height=&quot;1828&quot; data-is-animation=&quot;false&quot; style=&quot;width: 60.2191%;&quot; data-widthpercent=&quot;60.93&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boKyWQ/dJMcabp1n24/Cu2b26n54Ktj8mkDYQIRb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboKyWQ%2FdJMcabp1n24%2FCu2b26n54Ktj8mkDYQIRb0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3146&quot; height=&quot;1828&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;프로그램 이미지&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;감지 후 바로 구매까지&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 &quot;새 상품이 올라오면 알림만 주면 되지 않나?&quot; 하고 생각할 수 있다. 그런데 고객님과 대화를 나눠보니, 알림만으로는 부족했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알림이 울려서 컴퓨터 앞에 달려가고, 페이지 열고, 상품 클릭하고, 구매 버튼 누르고 &amp;mdash; 이 과정이 30초만 늦어도 이미 누군가 먼저 잡아가는 상황이었다. 그래서 감지와 구매가 하나의 흐름으로 연결되어야 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면 이런 요구사항이 됐다. 컴퓨존의 특정 카테고리 페이지를 일정 간격으로 자동 스캔하면서, 새로운 제품이 등록되면 즉시 감지한다. 감지되는 순간 알람을 울리고, 동시에 해당 상품의 상세 페이지로 이동해서 구매 버튼을 누르고 결제 단계까지 자동으로 진행한다. 그리고 이 모든 과정을 GUI 프로그램으로 만들어서, 시작 버튼 하나면 모니터링이 돌아가고, 로그 창에서 실시간으로 상태를 확인할 수 있어야 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;사람처럼 브라우저를 쓰되, 사람보다 빠르게&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 프로젝트의 핵심은 속도와 정확도, 두 가지였다. 먼저, 새 제품을 어떻게 감지할 것인가. 나는 로직을 다음과 같이 구성했다. 컴퓨존 카테고리 페이지에는 제품이 목록으로 쭉 뜨는데, 각 제품에는 고유한 제품번호가 붙어 있다. 프로그램이 처음 실행되면 현재 목록의 첫 번째 제품 번호를 기억해둔다.&amp;nbsp; 이후 매 스캔마다 목록의 첫 번째 제품 번호를 비교해서, 바뀌었으면 새 매물이 올라온 거다. 단순하지만 확실한 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에 중복 방지 로직도 넣었다. 한 번 감지한 제품 번호는 따로 기록해두고, 같은 제품에 대해 알림이나 구매가 중복으로 실행되지 않도록 했다. 페이지 새로고침 중에 팝업이 뜨는 경우도 있어서, 스캔 전에 남아있는 팝업을 자동으로 닫는 처리도 들어갔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구매 프로세스는 실제 사람이 하는 동작을 그대로 따라간다. 제품 상세 페이지로 이동하고, &quot;구매하기&quot; 버튼을 찾아 클릭하고, 결제 단계까지 진입한다. 구매 버튼의 위치가 페이지마다 조금씩 다를 수 있어서, 여러 방식으로 버튼을 탐지하는 폴백 로직을 넣었다. 결제 과정에서 새 창이 뜨거나 확인 팝업이 나오는 것도 자동으로 처리된다. 혹시 한 번에 안 되면 자동 재시도까지 가능하도록 설계했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;새로고침 대신 버튼 하나&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램을 실행하면 GUI 창이 뜬다. &quot;Chrome 실행&quot;을 누르면 컴퓨존 페이지가 열린 크롬이 뜨고, 거기서 로그인만 하면 준비 끝이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로고침 간격은 기본 30초인데, 5초부터 3600초까지 원하는 대로 조절할 수 있다. &quot;시작&quot; 버튼을 누르면 모니터링이 돌아간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그 창에는 스캔할 때마다 현재 상태가 찍힌다. 제품 몇 개가 감지됐는지, 새 제품이 있는지 없는지, 있으면 제품명과 제품번호까지. 새 매물이 잡히면 시스템 알람음이 울리고, 바로 구매 프로세스가 시작된다. 사용자는 로그 창만 지켜보고 있으면 된다. 아니, 사실 지켜보지 않아도 된다. 알람이 울리면 확인하면 되니까.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 해서 오늘 고객님이 원하시는 컴퓨존 부품 매물 자동 구매 프로그램도 완성하여 제공해드렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고객님이 굉장히 만족해하셔서 나도 기쁘다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJYzyw/dJMcafzbeQM/rTbX2D7EVpBoAeAMG6ocC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJYzyw/dJMcafzbeQM/rTbX2D7EVpBoAeAMG6ocC0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot; data-filename=&quot;card_02_before.png&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJYzyw/dJMcafzbeQM/rTbX2D7EVpBoAeAMG6ocC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJYzyw%2FdJMcafzbeQM%2FrTbX2D7EVpBoAeAMG6ocC0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cE6U1P/dJMcabKkwPS/Cql1a2NeLivclviEegWjKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cE6U1P/dJMcabKkwPS/Cql1a2NeLivclviEegWjKk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot; data-filename=&quot;card_03_after.png&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cE6U1P/dJMcabKkwPS/Cql1a2NeLivclviEegWjKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcE6U1P%2FdJMcabKkwPS%2FCql1a2NeLivclviEegWjKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>자동화 프로그램</category>
      <category>컴퓨존 매물 자동감지</category>
      <category>컴퓨존 매크로</category>
      <category>컴퓨존 모니터링</category>
      <category>컴퓨존 선착순</category>
      <category>컴퓨존 자동 구매</category>
      <author>JamesCoder</author>
      <guid isPermaLink="true">https://jamescoder.tistory.com/5</guid>
      <comments>https://jamescoder.tistory.com/5#entry5comment</comments>
      <pubDate>Fri, 20 Mar 2026 18:20:32 +0900</pubDate>
    </item>
    <item>
      <title>구글 지도 데이터 크롤링 프로그램 만들기</title>
      <link>https://jamescoder.tistory.com/4</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2.png&quot; data-origin-width=&quot;1729&quot; data-origin-height=&quot;897&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mP81v/dJMcacvFGa3/pKW6KJ3i8NiuImVpP1EjZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mP81v/dJMcacvFGa3/pKW6KJ3i8NiuImVpP1EjZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mP81v/dJMcacvFGa3/pKW6KJ3i8NiuImVpP1EjZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmP81v%2FdJMcacvFGa3%2FpKW6KJ3i8NiuImVpP1EjZ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1729&quot; height=&quot;897&quot; data-filename=&quot;2.png&quot; data-origin-width=&quot;1729&quot; data-origin-height=&quot;897&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;매장 하나에 클릭 다섯 번&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘 의뢰주신 고객님은 여러 지역에 흩어진 매장 수백 곳의 정보를 정리해야 하는 업무를 맡고 있었다. 평점, 리뷰 수, 주소, 전화번호, 웹사이트, 예약 플랫폼 등 이걸 구글 지도에서 하나씩 검색해서 엑셀에 옮겨 적고 있었다. 한 매장당 검색하고, 장소 페이지 들어가서, 정보를 눈으로 읽고, 엑셀에 복붙하는 과정이 반복됐다. 전화번호 하나 찾는 데 클릭이 대여섯 번이고, 그게 수백 건이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 단순 반복이라는 것만이 아니었다. 같은 이름의 매장이 여러 도시에 있는 경우가 있었고, 검색 결과가 리스트로 뜨면 어떤 걸 골라야 할지 판단해야 했다. 임시 휴업이나 폐업 상태인 매장도 섞여 있어서, 수집한 데이터를 나중에 다시 걸러내는 작업까지 해야 했다. 결국 단순해 보이는 &quot;정보 수집&quot;이 하루를 통째로 잡아먹는 업무가 되어 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt; 막연한 요청에서 구체적인 설계까지&lt;/b&gt; &lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 요청은 &quot;구글 지도에서 매장 정보 자동으로 뽑아주는 프로그램&quot;이었다. 대화를 나눠보니 빠져 있던 조건들이 하나씩 나왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 입력 방식. 도시명과 장소명이 열로 나뉜 엑셀 파일을 그대로 넣으면 동작해야 했다. 기존에 쓰던 목록 엑셀을 그대로 쓸 수 있어야 했으니까. 수집 항목도 정리가 필요했다. &lt;b&gt;평점과 리뷰 수는 숫자만 깔끔하게, 주소는 전체 주소로, 전화번호와 웹사이트는 있는 것만, 그리고 예약 플랫폼 정보까지&lt;/b&gt; &amp;mdash; 총 7가지 항목을 장소마다 뽑아야 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에 예외 처리 조건이 붙었다. 검색했는데 결과가 리스트로 나오면 어떻게 할 건지, 임시 휴업이나 폐업 상태면 어떻게 처리할 건지, 아예 검색 결과가 없으면 어떻게 할 건지. 이런 케이스마다 그냥 넘어가는 게 아니라 &quot;왜 못 뽑았는지&quot;를 사유로 남겨달라는 요구도 있었다. 나중에 수작업으로 보완할 때 어디를 봐야 하는지 알아야 하니까.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt; 크롬 브라우저와 연동&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글 지도의 장소 페이지는 단순한 HTML이 아니다. 화면이 로딩되고 나서도 데이터가 나중에 채워지는 구조라, 페이지 소스만 가져와서는 정보를 뽑을 수 없다. 그래서 실제 크롬 브라우저를 띄워서, 사람이 검색하는 것과 동일하게 동작하는 방식을 택했다. 검색창에 입력하고, 결과 페이지가 완전히 로딩되길 기다리고, 거기서 필요한 데이터를 추출하는 순서다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;평점은 &quot;별 4.4개&quot; 같은 텍스트에서 숫자만 뽑아내고, 리뷰 수는 &quot;(1,112)&quot; 같은 괄호 안 숫자를 파싱한다. 좌표는 URL에 포함된 위도/경도를 추출하고, 주소와 전화번호와 웹사이트는 각각의 버튼 요소에서 텍스트를 가져온다. 예약 정보는 &quot;예약하기&quot; 섹션이 있을 때만 예약 플랫폼 이름을 추출하는 식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글 지도의 HTML 구조가 워낙 복잡하고 자주 바뀌기 때문에, 각 항목마다 선택자를 여러 개 준비해두고 순서대로 시도하는 방어적 설계를 했다. 하나가 안 되면 다음 걸 시도하는 식이라, 구글이 페이지 구조를 약간 바꿔도 바로 못 쓰게 되지 않는다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WxOGv/dJMcacvFGa1/8ma2akT6l0D0bo2Zu9Dw41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WxOGv/dJMcacvFGa1/8ma2akT6l0D0bo2Zu9Dw41/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;895&quot; data-origin-height=&quot;728&quot; data-filename=&quot;3.png&quot; style=&quot;width: 49.3101%; margin-right: 10px;&quot; data-widthpercent=&quot;49.89&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WxOGv/dJMcacvFGa1/8ma2akT6l0D0bo2Zu9Dw41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWxOGv%2FdJMcacvFGa1%2F8ma2akT6l0D0bo2Zu9Dw41%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;895&quot; height=&quot;728&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dp0IZr/dJMb99TdnBA/BE6CC2HqkQTeyQGJKKmnmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dp0IZr/dJMb99TdnBA/BE6CC2HqkQTeyQGJKKmnmk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;894&quot; data-origin-height=&quot;724&quot; data-filename=&quot;5.png&quot; style=&quot;width: 49.5271%;&quot; data-widthpercent=&quot;50.11&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dp0IZr/dJMb99TdnBA/BE6CC2HqkQTeyQGJKKmnmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdp0IZr%2FdJMb99TdnBA%2FBE6CC2HqkQTeyQGJKKmnmk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;894&quot; height=&quot;724&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;개발한 프로그램의 GUI와, 실행될 때 나타나는 로그&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;4.png&quot; data-origin-width=&quot;1720&quot; data-origin-height=&quot;1018&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwQbnY/dJMcagEP0K0/haAgxqLTB60liVXtzeaR8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwQbnY/dJMcagEP0K0/haAgxqLTB60liVXtzeaR8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwQbnY/dJMcagEP0K0/haAgxqLTB60liVXtzeaR8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwQbnY%2FdJMcagEP0K0%2FhaAgxqLTB60liVXtzeaR8K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1720&quot; height=&quot;1018&quot; data-filename=&quot;4.png&quot; data-origin-width=&quot;1720&quot; data-origin-height=&quot;1018&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;프로그램에 엑셀 파일을 넣고 실행하면 자동으로 구글맵에서 데이터를 가져오는 형식&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt; 하루 종일이 자동 수집 한 번으로 &lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과를 정리하면 이렇다. 기존에 수백 건의 매장 정보를 하루 종일 수작업으로 옮겨 적던 게, 엑셀 파일 불러오고 시작 버튼 누르면 프로그램이 알아서 수집해준다. 한 건당 대략 5~8초 정도 걸리니까, 300건이면 30~40분이면 끝난다. 그 사이에 다른 일을 하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수집이 끝나면 엑셀 저장 버튼 한 번으로 결과가 정리된 파일이 나온다. 번호, 도시명, 장소명, 평점, 리뷰 수, 위도/경도, 주소, 전화번호, 웹사이트, 예약 플랫폼, 그리고 크롤링 사유까지 &amp;mdash; 성공한 건, 임시 휴업인 건, 폐업인 건, 검색 리스트로 나온 건이 한눈에 구분된다. 나중에 수작업으로 보완해야 할 건만 필터링해서 처리하면 되니까, 전체 업무 시간이 크게 줄었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용 흐름은 단순하다. 프로그램을 켜고, 엑셀 파일을 선택하고, 시작 버튼을 누르면 된다. 진행 바와 로그 창에서 몇 번째까지 진행됐는지, 어떤 매장에서 어떤 결과가 나왔는지 실시간으로 확인할 수 있다. 급하면 중지 버튼으로 현재 작업까지만 완료하고 멈출 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;1911&quot; data-origin-height=&quot;944&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/N54cc/dJMcacvFGa2/OvYcQdSVgzuzYVeuN9V5q0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/N54cc/dJMcacvFGa2/OvYcQdSVgzuzYVeuN9V5q0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/N54cc/dJMcacvFGa2/OvYcQdSVgzuzYVeuN9V5q0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FN54cc%2FdJMcacvFGa2%2FOvYcQdSVgzuzYVeuN9V5q0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1911&quot; height=&quot;944&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;1911&quot; data-origin-height=&quot;944&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;엑셀로 출력시 나타나는 데이터의 모습&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt; 크롤링 자동화에서 중요한 건 결국 예외 처리였다&lt;br /&gt;&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 프로젝트에서 가장 시간을 많이 쓴 부분은 사실 예외 처리다. 검색하면 바로 장소 페이지가 뜨는 경우도 있지만, 비슷한 이름의 장소가 리스트로 나오는 경우도 있다. 장소 페이지에 들어갔는데 임시 휴업이나 폐업 상태인 경우도 있다. 어떤 매장은 전화번호가 없고, 어떤 매장은 예약 섹션 자체가 없다. 이런 케이스를 하나하나 분기 처리하고, &quot;왜 수집을 못했는지&quot;까지 기록으로 남기는 게 실무에서 쓰이는 프로그램의 조건이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글 지도처럼 구조가 복잡한 사이트에서 데이터를 뽑는 일은, 단순히 기술을 아는 것만으로는 부족하다. 실제 업무에서 어떤 예외가 나오는지, 못 뽑은 데이터를 어떻게 후처리하는지까지 알아야 쓸 수 있는 프로그램이 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비슷한 종류의 데이터 수집은 모두 가능하다. 그것이 구글 지도든, 네이버 지도든, 어떤 플랫폼이든 위처럼 동일한 작업을 반복하고 있다면, 자동화할 수 있는 범위가 생각보다 넓으니 자동화를 해서 좀 더 편하게 일을 할 수 있으면 좋겠다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yU1Tv/dJMcacvFGaF/jksabGyiWrqeXQfCBVxkR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yU1Tv/dJMcacvFGaF/jksabGyiWrqeXQfCBVxkR0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot; data-filename=&quot;card_02_before.png&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yU1Tv/dJMcacvFGaF/jksabGyiWrqeXQfCBVxkR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyU1Tv%2FdJMcacvFGaF%2FjksabGyiWrqeXQfCBVxkR0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8mhd4/dJMcacvFGaE/IBEnKIUOq4sF0ILlzN9ZIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8mhd4/dJMcacvFGaE/IBEnKIUOq4sF0ILlzN9ZIk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot; data-filename=&quot;card_03_after.png&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8mhd4/dJMcacvFGaE/IBEnKIUOq4sF0ILlzN9ZIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8mhd4%2FdJMcacvFGaE%2FIBEnKIUOq4sF0ILlzN9ZIk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>자동화 프로그램</category>
      <category>구글 지도 전화번호 수집</category>
      <category>구글 지도 크롤링</category>
      <category>구글 지도 평점 추출</category>
      <category>구글맵 데이터 수집</category>
      <category>구글맵 크롤러 프로그램</category>
      <category>데이터 수집 자동화</category>
      <category>매장 정보 엑셀 정리</category>
      <category>매장 정보 자동 수집</category>
      <category>장소 데이터 크롤링</category>
      <author>JamesCoder</author>
      <guid isPermaLink="true">https://jamescoder.tistory.com/4</guid>
      <comments>https://jamescoder.tistory.com/4#entry4comment</comments>
      <pubDate>Thu, 19 Mar 2026 10:41:03 +0900</pubDate>
    </item>
    <item>
      <title>문서24 문서 공문 발송 자동화 프로그램 만들기</title>
      <link>https://jamescoder.tistory.com/3</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1ZnDS/dJMcahqbWdK/gF9pOyKszwjFnO7sKoVvD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1ZnDS/dJMcahqbWdK/gF9pOyKszwjFnO7sKoVvD1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot; data-filename=&quot;card_01_title.png&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1ZnDS/dJMcahqbWdK/gF9pOyKszwjFnO7sKoVvD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1ZnDS%2FdJMcahqbWdK%2FgF9pOyKszwjFnO7sKoVvD1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dWvbSv/dJMcafFUnPf/5IwounZ8ScHnekBvRxNGL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dWvbSv/dJMcafFUnPf/5IwounZ8ScHnekBvRxNGL0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot; data-filename=&quot;card_02_before.png&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dWvbSv/dJMcafFUnPf/5IwounZ8ScHnekBvRxNGL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdWvbSv%2FdJMcafFUnPf%2F5IwounZ8ScHnekBvRxNGL0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘 개발을 의뢰하신 고객님이 처음 연락해왔을 때 상황은 이랬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문서24라는 정부 전자문서 시스템을 통해 학교 수백 곳에 공문서를 보내는 업무를 맡고 있었는데, 이 시스템이 한 번에 한 곳씩만 전송할 수 있는 구조였다. 그러니까 매일 아침 출근해서 문서24에 로그인하고, 보낸 문서함에서 문서를 열고, 재작성 버튼을 누르고, 받는 기관을 검색해서 학교를 찾고, 전송 버튼을 누르는 과정을 300번 넘게 반복하고 있었던 거다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 건 보내는 데 클릭만 일곱여덟 번이 필요하다. 거기에 학교 이름이 같은 곳이 여러 개 검색되면 주소까지 일일이 확인해야 하니까, 집중력 소모도 장난이 아니었다. 잘못된 학교에 공문서를 보내는 사고라도 나면 꽤 골치 아파지는 업무라 긴장도 풀 수 없었다. 하루 풀타임을 이 작업에만 쏟아도 간신히 끝나는 양이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;개발 의뢰 내용 구체화&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 들어온 요청 자체는 단순했다. 문서24에서 여러 학교에 문서를 자동으로 보내주는 프로그램을 만들어달라는 거였다. 그런데 대화를 나눠보니 고려해야 할 게 생각보다 많았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 계정 문제가 있었다. 계정 하나당 전송 건수에 제한이 있어서, 계정 여러 개를 순서대로 돌려가며 써야 했다. A 계정으로 50건 보내면 자동으로 로그아웃하고, B 계정으로 로그인해서 51번째 학교부터 이어서 보내는 식이다. 계정별로 몇 건씩 보낼지도 직접 지정할 수 있어야 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음은 동명 학교 문제였다. &quot;서울 ○○초등학교&quot;로 검색하면 전국에 같은 이름의 학교가 여러 개 나온다. 사람이 하면 주소를 보고 골라내지만, 프로그램이 하려면 주소를 자동으로 비교해서 가장 일치하는 학교를 선택하는 로직이 필요했다. 여기에 중간에 멈출 수 있어야 한다는 조건, 진행 상황을 실시간으로 확인할 수 있어야 한다는 조건, 전송 실패한 건은 건너뛰고 다음으로 넘어가야 한다는 조건까지 추가됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-end=&quot;249&quot; data-start=&quot;187&quot; data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;크롬 브라우저 연동&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-end=&quot;249&quot; data-start=&quot;187&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문서24가 일반적인 웹사이트가 아니라 정부 시스템이다 보니, 보안 정책이 까다롭고 팝업도 여러 단계로 뜬다. 그래서 실제 크롬 브라우저를 그대로 제어하는 방식을 택했다. 브라우저를 원격으로 조종하는 기술을 활용해, 사람이 클릭하는 것과 완전히 동일한 방식으로 동작하되 프로그램이 대신 해주는 구조다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 접근의 장점은 세 가지였다. 정부 시스템의 보안 정책을 우회하지 않고 정상적인 경로로 동작한다는 점, 실제 브라우저 화면이 그대로 보이니까 문제가 생기면 바로 눈으로 확인할 수 있다는 점, 그리고 팝업이 몇 단계가 뜨든 순서대로 처리할 수 있다는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동명 학교 처리에는 주소 유사도 비교 알고리즘을 넣었다. 엑셀에 적어둔 주소와 검색 결과에 나온 기관 정보를 비교해서, 가장 일치도가 높은 학교를 자동으로 골라준다. &quot;서울 ○○초등학교&quot;가 세 개 나와도 엑셀의 주소 데이터를 기준으로 정확한 학교를 찾아내는 거다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ppaxz/dJMcab4yTbr/ieaysirB4dLbaAZ0zOZRb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ppaxz/dJMcab4yTbr/ieaysirB4dLbaAZ0zOZRb1/img.png&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;775&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;50&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ppaxz/dJMcab4yTbr/ieaysirB4dLbaAZ0zOZRb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPpaxz%2FdJMcab4yTbr%2FieaysirB4dLbaAZ0zOZRb1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;746&quot; height=&quot;775&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCTglk/dJMcagY5Z3g/S9cKUDh2JdyRMtDJJRWHX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCTglk/dJMcagY5Z3g/S9cKUDh2JdyRMtDJJRWHX0/img.png&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;775&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCTglk/dJMcagY5Z3g/S9cKUDh2JdyRMtDJJRWHX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCTglk%2FdJMcagY5Z3g%2FS9cKUDh2JdyRMtDJJRWHX0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;746&quot; height=&quot;775&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;프로그램 화면&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1197&quot; data-origin-height=&quot;609&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/I0r6q/dJMcahwXPS1/A581KRUwFxRvDJK2MgV7K0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/I0r6q/dJMcahwXPS1/A581KRUwFxRvDJK2MgV7K0/img.png&quot; data-alt=&quot;프로그램 작동 화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/I0r6q/dJMcahwXPS1/A581KRUwFxRvDJK2MgV7K0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FI0r6q%2FdJMcahwXPS1%2FA581KRUwFxRvDJK2MgV7K0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1197&quot; height=&quot;609&quot; data-origin-width=&quot;1197&quot; data-origin-height=&quot;609&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;프로그램 작동 화면&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2f9Nt/dJMcajaqeFO/rEj7hkhPKj2ng9MyJNAenK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2f9Nt/dJMcajaqeFO/rEj7hkhPKj2ng9MyJNAenK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot; data-filename=&quot;card_03_after.png&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2f9Nt/dJMcajaqeFO/rEj7hkhPKj2ng9MyJNAenK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2f9Nt%2FdJMcajaqeFO%2FrEj7hkhPKj2ng9MyJNAenK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/co3xpS/dJMcadH2wDS/z3oDtDU8UKzkkRzKSmT0e0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/co3xpS/dJMcadH2wDS/z3oDtDU8UKzkkRzKSmT0e0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot; data-filename=&quot;card_04_closing.png&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/co3xpS/dJMcadH2wDS/z3oDtDU8UKzkkRzKSmT0e0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fco3xpS%2FdJMcadH2wDS%2Fz3oDtDU8UKzkkRzKSmT0e0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt; 하루 종일이 30분이 됐다 / 사실은 0분, 왜?&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과부터 말하면, 하루 종일 매달려 있던 작업이 30~40분이면 끝나게 됐다. 그것도 프로그램이 알아서 돌아가는 30분이라서 30분이라고 표기한 것이지 그 사이에 다른 업무를 볼 수 있기 때문에 사실 상 0분이다. 완전 자동화를 실현한 것이다. 건당 클릭 일곱여덟 번에 검색과 주소 확인까지 하던 과정이, 엑셀 파일 두 개 불러오고 시작 버튼 한 번 누르는 걸로 대체됐다. 계정 전환도 자동이고, 주소 매칭도 자동이다. 오전송 위험도 사람이 직접 하던 때보다 줄었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용 흐름도 단순하게 만들었다. 프로그램을 켜면 깔끔한 화면이 뜨고, 계정 정보가 담긴 엑셀 파일을 불러오면 테이블로 목록이 뜬다. 여기서 계정별 처리 건수를 입력하고, 학교 목록 엑셀을 불러온 뒤 시작 버튼을 누르면 된다. 화면 하단의 로그 창에는 어떤 학교에 전송했고, 어디서 스킵했는지가 실시간으로 표시된다. 급한 일이 생기면 중지 버튼으로 바로 멈출 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요하신 분들은 말씀 주시면 도와드리겠습니다!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>자동화 프로그램</category>
      <category>공문서 발송 프로그램</category>
      <category>문서 자동 발송</category>
      <category>문서24 자동화</category>
      <category>문서24 프로그램</category>
      <category>업무 자동화 외주</category>
      <category>정부 전자문서 자동화</category>
      <category>학교 공문서 자동화</category>
      <author>JamesCoder</author>
      <guid isPermaLink="true">https://jamescoder.tistory.com/3</guid>
      <comments>https://jamescoder.tistory.com/3#entry3comment</comments>
      <pubDate>Thu, 19 Mar 2026 01:32:53 +0900</pubDate>
    </item>
    <item>
      <title>개발 의뢰 및 문의 안내입니다.</title>
      <link>https://jamescoder.tistory.com/2</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;안녕하세요 개발자 &lt;b&gt;JamesCoder&lt;/b&gt; 입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 블로그에 소개되었거나 앞으로 소개될 프로젝트들은 실제 저의 고객님의 업무 환경과 요구를 바탕으로 진행한 개발 사례입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;저는 단순히 기능을 구현하는 것보다, 고객님과 정밀한 소통을 통하여 &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;고객님의 현재 상황을 정확히 이해하고 장기적으로 사용가능한 프로그램을 개발하는 사람입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;저는 &lt;b&gt;자동화, 크롤링, 내부 관리 시스템, 노코드 기반 서비스 확장&lt;/b&gt; 등 &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;반복적인 업무나 비효율적인 흐름을 개선하는 개발을 주로 진행해왔습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;고객님께서 저에게 개발 의뢰를 남길 때는 &amp;ldquo;무엇을 만들어 달라&amp;rdquo;는 요청보다,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;지금 어떤 문제가 있고 어떤 불편을 겪고 있는지&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;이를 어떤 방식으로 해결하기 원하는지&lt;/b&gt;를 &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;중심으로 최대한 자세하게 적어주는 것이 중요합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;아래 문의 링크를 통해 연락을 남길 때는 다음과 같은 내용을 포함해주시길 바랍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;업무의 목적, 현재 처리 방식, 필요한 기능, 사용 인원, 일정이나 예산에 대한 조건 등&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;내용이나 조건이 명확할수록 초기 소통 과정이 훨씬 효율적입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;아직 정리되지 않은 상태라 하더라도 괜찮습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;저와 고객님께서 대화함으로써 고객님께서 제공해주시는 상황 설명만으로도 충분히 구조를 함께 정리할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;문의 내용은 하나하나 직접 확인하며, 구현 가능 여부와 적합한 방식부터 차분히 검토합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;감사합니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://open.kakao.com/me/jamescoder&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://open.kakao.com/me/jamescoder&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1767025481106&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;JamesCoder님의 오픈프로필&quot; data-og-description=&quot;&quot; data-og-host=&quot;open.kakao.com&quot; data-og-source-url=&quot;https://open.kakao.com/me/jamescoder&quot; data-og-url=&quot;https://open.kakao.com/me/jamescoder&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/iWPTv/hyZQAtKoh7/R1kyk4b6dQAYcge8OsuURK/img.jpg?width=1200&amp;amp;height=628&amp;amp;face=543_316_648_430&quot;&gt;&lt;a href=&quot;https://open.kakao.com/me/jamescoder&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://open.kakao.com/me/jamescoder&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/iWPTv/hyZQAtKoh7/R1kyk4b6dQAYcge8OsuURK/img.jpg?width=1200&amp;amp;height=628&amp;amp;face=543_316_648_430');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;JamesCoder님의 오픈프로필&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;open.kakao.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <author>JamesCoder</author>
      <guid isPermaLink="true">https://jamescoder.tistory.com/2</guid>
      <comments>https://jamescoder.tistory.com/2#entry2comment</comments>
      <pubDate>Tue, 30 Dec 2025 01:25:23 +0900</pubDate>
    </item>
    <item>
      <title>신상마켓 매장 신상품 데이터 크롤링 프로그램</title>
      <link>https://jamescoder.tistory.com/1</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;안녕하세요 개발자 &lt;b&gt;JamesCoder&lt;/b&gt; 입니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;오늘 고객님의 요청으로 개발한 프로그램 내용입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프로그램을 한마디로 정리하면 다음과 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;패션 도매 사업자가 매일 수백 개 브랜드의 신상품을 일일이 확인하던 2시간을 5분으로 단축시킨 자동 수집 프로그램&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;고객님의 &lt;b&gt;의뢰 배경&lt;/b&gt;은 다음과 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;저에게 의뢰주신 고객님은 신상마켓에서 여러 브랜드의 신상품을 매일 체크해야 하는 패션 도매 사업자였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;문제는 신상마켓이 여러 브랜드를 한 번에 조회하는 기능을 제공하지 않고, 각 브랜드마다 신상품을 일일이 제품을 하나하나 보면서 &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;소싱하기에는 너무 비효율적이고 불편하다는 문제가 있었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;고객님께서는 각 브랜드마다 올라오는 신상품을 일일이 클릭하고 스크롤하며 확인하셔야했고,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;괜찮은 상품이 있다면 엑셀에 수동으로 복사 - 붙여넣기를 하셔야 했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;상품명, 가격, 색상, 사이즈 정보를 일일이 타이핑하는 번거로움이 있었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 작업으로 매일 제품을 소싱하는 시간을 고객님은 2시간 이상 소요하고 계셨습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;집중력이 떨어지거나, 가끔은 중복 기록을 하는 실수도 잦으셨다고 저에게 말씀주셨습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;무엇보다 &lt;b&gt;&quot;단순 반복 작업에 시간을 쓰는게 아깝다&quot;&lt;/b&gt;는 것이 고객님의 가장 큰 고민이었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그리하여 저에게 개발 요청을 주시게 되었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;고객님의 초기 요청은 간단했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&quot;여러 브랜드의 신상품을 한 번에 엑셀로 받고 싶어요.&quot; &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만 대화를 나누며 실제 필요한 것들을 정리해보니 생각보다 복잡했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;고객님의 &lt;b&gt;핵심 요구사항&lt;/b&gt;은 다음과 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;1/ 최대 100개 브랜드를 동시에 수집&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;2/ 브랜드별로 수집 개수 조절 가능 (신상 10개만, 전체 등)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;3/ 상품명, 가격, 색상, 사이즈, 혼용률, 제조국, 상품 링크 포함&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;4/ 데스크탑 엑셀 파일로 바로 저장 가능하도록&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;신마 2.png&quot; data-origin-width=&quot;2880&quot; data-origin-height=&quot;1750&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQTOJk/dJMcaaYeMDm/MNlRxetqvYYvZ5QgUxKDf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQTOJk/dJMcaaYeMDm/MNlRxetqvYYvZ5QgUxKDf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQTOJk/dJMcaaYeMDm/MNlRxetqvYYvZ5QgUxKDf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQTOJk%2FdJMcaaYeMDm%2FMNlRxetqvYYvZ5QgUxKDf0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2880&quot; height=&quot;1750&quot; data-filename=&quot;신마 2.png&quot; data-origin-width=&quot;2880&quot; data-origin-height=&quot;1750&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;신마 1.png&quot; data-origin-width=&quot;2880&quot; data-origin-height=&quot;1750&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kEgDD/dJMcabQmJmp/VQs5hVcg4D66XwDXJPpjXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kEgDD/dJMcabQmJmp/VQs5hVcg4D66XwDXJPpjXk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kEgDD/dJMcabQmJmp/VQs5hVcg4D66XwDXJPpjXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkEgDD%2FdJMcabQmJmp%2FVQs5hVcg4D66XwDXJPpjXk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2880&quot; height=&quot;1750&quot; data-filename=&quot;신마 1.png&quot; data-origin-width=&quot;2880&quot; data-origin-height=&quot;1750&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위 이미지는 해당 프로그램 실행 시 모습입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;데이터 수집은 신상마켓의 API를 조합하여 데이터를 가져오도록 하였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Windows와 Mac 둘 다 작동 가능하도록 했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;데이터는 다음과 같이 엑셀 파일로 확인할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;(브랜드명 일부는 가렸습니다)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-12-29 오후 11.32.14.png&quot; data-origin-width=&quot;2816&quot; data-origin-height=&quot;1658&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/C7X5a/dJMcaiaTBp0/xVkShE5qsuA3YB1oSI1gDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/C7X5a/dJMcaiaTBp0/xVkShE5qsuA3YB1oSI1gDk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/C7X5a/dJMcaiaTBp0/xVkShE5qsuA3YB1oSI1gDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FC7X5a%2FdJMcaiaTBp0%2FxVkShE5qsuA3YB1oSI1gDk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2816&quot; height=&quot;1658&quot; data-filename=&quot;스크린샷 2025-12-29 오후 11.32.14.png&quot; data-origin-width=&quot;2816&quot; data-origin-height=&quot;1658&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이런식으로 원하는 상점의 제품을 갯수를 정하여 가져올 수 있도록 하였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;혹여나 신상마켓을 이용하시는 분께서 위 프로그램이 필요하신 분이 계시다면,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;제 오픈채팅으로 문의주시면 됩니다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>크롤링</category>
      <category>데이터크롤링</category>
      <category>신상마켓</category>
      <category>신상마켓 데이터크롤링</category>
      <category>신상마켓 크롤링</category>
      <author>JamesCoder</author>
      <guid isPermaLink="true">https://jamescoder.tistory.com/1</guid>
      <comments>https://jamescoder.tistory.com/1#entry1comment</comments>
      <pubDate>Mon, 29 Dec 2025 23:37:01 +0900</pubDate>
    </item>
  </channel>
</rss>