<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>통계학부의 데이터엔지니어링</title>
    <link>https://statistics-fox.tistory.com/</link>
    <description>주어진 리소스 내에서 최선의 해결책을 찾고 적용하는 엔지니어의 블로그 입니다.</description>
    <language>ko</language>
    <pubDate>Thu, 2 Jul 2026 14:21:31 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>CHOI JEE HYUK</managingEditor>
    <image>
      <title>통계학부의 데이터엔지니어링</title>
      <url>https://tistory1.daumcdn.net/tistory/5493566/attach/f3c3f99fe7b94046b25aa9a02dd3009b</url>
      <link>https://statistics-fox.tistory.com</link>
    </image>
    <item>
      <title>⌜글또 x Udemy 강의 후기⌟ 【한글자막】 AWS Certified Solutions Architect Associate 시험합격!</title>
      <link>https://statistics-fox.tistory.com/81</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_Photo_2024-04-29-17-47-37.png&quot; data-origin-width=&quot;627&quot; data-origin-height=&quot;286&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cmiTeI/btsG3fWM2La/1wslv5SUf0bhklkgp6Q0Bk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cmiTeI/btsG3fWM2La/1wslv5SUf0bhklkgp6Q0Bk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cmiTeI/btsG3fWM2La/1wslv5SUf0bhklkgp6Q0Bk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcmiTeI%2FbtsG3fWM2La%2F1wslv5SUf0bhklkgp6Q0Bk%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;627&quot; height=&quot;286&quot; data-filename=&quot;KakaoTalk_Photo_2024-04-29-17-47-37.png&quot; data-origin-width=&quot;627&quot; data-origin-height=&quot;286&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Udemy로 부터 두 개의 강의를 제공 받았다. 이게 2번째 강의인데 사실 처음에는 AWS SAA-003 자격증을 따고자 대차게 신청했으나 결국 시험조차 치지 못했다..(한이음 ICT 멘토링 2개랑 같이 하기 너무 빡세요ㅠㅠ) 그래도 이번에 Infra와 DE 역할을 맡게 되면서(물론 Azure를 쓰긴하지만) 전체적으로 클라우드의 기능을 알아볼 수 있는 좋은 기회라 생각해서 속성으로 들었다.&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;262&quot; data-origin-height=&quot;260&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XGNoT/btsG3p53lrX/OW5nr7r8KubsBB01U6gH4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XGNoT/btsG3p53lrX/OW5nr7r8KubsBB01U6gH4K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XGNoT/btsG3p53lrX/OW5nr7r8KubsBB01U6gH4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXGNoT%2FbtsG3p53lrX%2FOW5nr7r8KubsBB01U6gH4K%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;262&quot; height=&quot;260&quot; data-origin-width=&quot;262&quot; data-origin-height=&quot;260&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다들 이 아저씨를 아마 알 거다 사실 이 강의는 내가 후기 좋게 쓸 필요가 없는&lt;b&gt; 명실상부 최고의 강의 중 하나&lt;/b&gt;다. 정말 쉽게 잘 알려주시고 강의 퀄리티도 굉장히 높다. 특히 기초적인 부분을 단계단계로 정말 잘 알려주셔서 &lt;b&gt;공부를 하면서 머리에 스토리라인을 그리기 좋다.&lt;/b&gt; 실습 안하고 그냥 듣기만 해도 머리 속에서 아키텍쳐가 그려진다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;커리큘럼&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1402&quot; data-origin-height=&quot;1596&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cSGBYw/btsG5VI82tm/skrf7Hvu6qEAyfk7vFDjSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cSGBYw/btsG5VI82tm/skrf7Hvu6qEAyfk7vFDjSK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cSGBYw/btsG5VI82tm/skrf7Hvu6qEAyfk7vFDjSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcSGBYw%2FbtsG5VI82tm%2Fskrf7Hvu6qEAyfk7vFDjSK%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;1402&quot; height=&quot;1596&quot; data-origin-width=&quot;1402&quot; data-origin-height=&quot;1596&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;위와 같이 AWS에 있는 모든 제품에 대한 특징과 사용처를 알려주는 강의다. 강의 퀄리티는 말할 필요가 없을 정도로 높긴하지만 &amp;nbsp;여기서 제공해주는 연습문제가 진짜 극악이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;마지막에 연습 시험을 제공해주는데 진짜 처음 풀면 20점 대...가 나온다. 내가 이리도 무식한 사람이었나.. 라고 생각할때 즈음 알고보니 연습문제가 시험문제보다 10배는 어렵다는 사실을 알았다. 연습문제가 마라맛이고 실제 시험이 중간맛 정도 되기 때문에 안도했다. 또한 시험도 dump 문제에서 그대로 나온다고 하기 때문에 사실 공부 목적이 아니라 단순히 자격증 취득이 목적이라면 공부를 하지 않고도 취득할 수 있는 시험이라고 한다.&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;아무튼 나는 이 강의를 적극 추천한다. AWS에 대해서 알고 싶다 그러면 그냥 이 강의 들으면된다. 그냥 들으면 각 제품의 기능과 목적은 줄줄이 다 꿰고 있을 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;장점&lt;/h2&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;h2 data-ke-size=&quot;size26&quot;&gt;단점&lt;/h2&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;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점이라고 해봐야 연습문제가 어렴다는 것밖에 없고 또 누구에게는 장점이 될 수도 있는 단점이다. 난 AWS에 대한 제품과 대략적인 유즈케이스를 공부하고 싶다면 주저없이 이 강의를 추천해줄 것이다. &amp;nbsp;&lt;/p&gt;</description>
      <category>개인 회고</category>
      <author>CHOI JEE HYUK</author>
      <guid isPermaLink="true">https://statistics-fox.tistory.com/81</guid>
      <comments>https://statistics-fox.tistory.com/81#entry81comment</comments>
      <pubDate>Tue, 30 Apr 2024 19:07:10 +0900</pubDate>
    </item>
    <item>
      <title>⌜글또 x Udemy 강의 후기⌟ 【한글자막】 DevOps 학습: 파이프라인 및 Docker를 이용한 Jenkins 와의 CI/CD</title>
      <link>https://statistics-fox.tistory.com/80</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_Photo_2024-04-29-17-47-37.png&quot; data-origin-width=&quot;627&quot; data-origin-height=&quot;286&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c78xyu/btsG3Bqt3j2/ksPTrFeDITPQQH5dJcQDDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c78xyu/btsG3Bqt3j2/ksPTrFeDITPQQH5dJcQDDk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c78xyu/btsG3Bqt3j2/ksPTrFeDITPQQH5dJcQDDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc78xyu%2FbtsG3Bqt3j2%2FksPTrFeDITPQQH5dJcQDDk%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;627&quot; height=&quot;286&quot; data-filename=&quot;KakaoTalk_Photo_2024-04-29-17-47-37.png&quot; data-origin-width=&quot;627&quot; data-origin-height=&quot;286&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 글또 9기에 참여 하먄서 Udemy로부터 강의 쿠폰을 받았다. 강의를 고를 수 있었는데 나는 당시 학교에서 배웠던 Jenkins 수업을 좀 더 고도화하고 싶어 &lt;b&gt;파이프라인 및 Docker를 이용한 Jenkins 와의 CI/CD강의&lt;/b&gt;를 골랐다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;커리큘럼&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1426&quot; data-origin-height=&quot;1036&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nXUMc/btsG3OXy69U/JjVunj5RgXoUQUksYUyEn1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nXUMc/btsG3OXy69U/JjVunj5RgXoUQUksYUyEn1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nXUMc/btsG3OXy69U/JjVunj5RgXoUQUksYUyEn1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnXUMc%2FbtsG3OXy69U%2FJjVunj5RgXoUQUksYUyEn1%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;1426&quot; height=&quot;1036&quot; data-origin-width=&quot;1426&quot; data-origin-height=&quot;1036&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 이 강의를 고른 이유가 강의 시간이다. Jenkins의 다양한 사용법을 4시간 40분이면 배울 수 있다. 물론 강의를 보며 실습하는것까지 합치면 더 많겠지만 대부분 강의의 &lt;b&gt;러닝 타임이 짧다는 것은 Udemy 최고의 장점이라고 생각한다. &lt;/b&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;1360&quot; data-origin-height=&quot;508&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bF4l8t/btsGZ1xvYxD/K1HMSSdtKEmutw9gPQee6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bF4l8t/btsGZ1xvYxD/K1HMSSdtKEmutw9gPQee6K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bF4l8t/btsGZ1xvYxD/K1HMSSdtKEmutw9gPQee6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbF4l8t%2FbtsGZ1xvYxD%2FK1HMSSdtKEmutw9gPQee6K%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;1360&quot; height=&quot;508&quot; data-origin-width=&quot;1360&quot; data-origin-height=&quot;508&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 좋았던 파이프라인 강의다. 사실 CI/CD를 위해서 젠킨스를 배우는 것이기 때문에 젠킨스 파이프라인을 간단한 실습으로 배우는게 마음에 들었다. '개발 -&amp;gt; 테스트 -&amp;gt; 도커로 말기 -&amp;gt; 배포'의 한 과정은 프로젝트를 진행하거나 실제 회사에서 개발을 진행하는 경우가 아니면 힘들기에 더욱 더 좋았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;강의 평&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;장점&lt;/h3&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;- 젠킨스에 대한 왠만한 기능이 다 들어가 있다. 깃버킷, 빗버킷, 슬랙, 소나큐브 등 다양한 툴과의 연동을 잘 알려준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 알단 자료가 너무 오래됐다. 아래가 강의 자료인데 가장 최근 커밋이 2년 전이다. 그래서 실습을 진행하면서 오류가 나거나 막히는 부분을 내 힘으로 풀어가야 하는 점이 있다. 그래도 원래 Devops의 기본 소양은 삽질이기 때문에 개의치 않았지만 단점이라 할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1468&quot; data-origin-height=&quot;1138&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ck2jJE/btsG5sUQEFM/NuBa1cbzhnyxzpF7kwbTS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ck2jJE/btsG5sUQEFM/NuBa1cbzhnyxzpF7kwbTS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ck2jJE/btsG5sUQEFM/NuBa1cbzhnyxzpF7kwbTS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fck2jJE%2FbtsG5sUQEFM%2FNuBa1cbzhnyxzpF7kwbTS0%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;1468&quot; height=&quot;1138&quot; data-origin-width=&quot;1468&quot; data-origin-height=&quot;1138&quot;/&gt;&lt;/span&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;&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;전체적으로 나는 만족한 강의였다! 강의도 짧기에 부담없이 들을 수 있지만 또 알차기 때문에 누군가 Jenkins 도입을 위해 강의나 래퍼런스를 찾고 있다면 &lt;b&gt;고민없이 추천해줄 수 있는 강의&lt;/b&gt;다.&lt;/p&gt;</description>
      <category>개인 회고</category>
      <author>CHOI JEE HYUK</author>
      <guid isPermaLink="true">https://statistics-fox.tistory.com/80</guid>
      <comments>https://statistics-fox.tistory.com/80#entry80comment</comments>
      <pubDate>Tue, 30 Apr 2024 18:30:36 +0900</pubDate>
    </item>
    <item>
      <title>뭐? Mongo DB가 가용성을 보장하지 않는다고?</title>
      <link>https://statistics-fox.tistory.com/78</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;제목을 보면 이게 무슨 소리인가 싶을 수 있다. 나도 처음 듣고 띠요옹? 했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 알고 있는 MongoDB는 BASE(BA: &lt;b&gt;B&lt;/b&gt;&lt;span style=&quot;text-align: left;&quot;&gt;asically&amp;nbsp;&lt;/span&gt;&lt;b&gt;A&lt;/b&gt;&lt;span style=&quot;text-align: left;&quot;&gt;valiable&lt;/span&gt;) 즉, 가용성과 성능을 중시한 분산 시스템의 특성을 가지고 있고 또한 이 점이 기존에 ACID 특성을 가진 RDBMS와의 차이점이라고 알고 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더군다나 Mongo DB가 탄생하게 된 배경이 아래와 같은 고민 끝에 탄생한 것을 알았기에 더욱 의아했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;대규모 데이터를 처리해야 하는데 RDBMS는 성장 한계가 있구나 &lt;b&gt;일관성과 무결성을 버리고&lt;/b&gt; &lt;br /&gt;더 빠른 읽기 성능과 수평확장이 가능한 DB가 필요해!&lt;br /&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;이번에 대규모 시스템 설계 기초 도서를 공부하면서 CAP이론이라는 것을 처음 접하며 이 의문이 시작되었다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;CAP이론 이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;CAP 이론은 2000년에 에릭 브류어가 최초로 소개한 이론이며 어떤 분산 시스템이더라도&amp;nbsp;&lt;/span&gt;&lt;b&gt;Consistency&lt;/b&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&amp;nbsp;(일관성),&amp;nbsp;&lt;/span&gt;&lt;b&gt;Availability&lt;/b&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&amp;nbsp;(가용성),&amp;nbsp;&lt;/span&gt;&lt;b&gt;Partition tolerance&lt;/b&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&amp;nbsp;(분할 내성)를&amp;nbsp;&lt;/span&gt;&lt;b&gt;모두 만족할 수 없다는 이론&lt;/b&gt;&lt;span style=&quot;text-align: start;&quot;&gt;이다. 이 세 가지의 머리글자를 따서 CAP 이론이라고 부른다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;consistency&lt;/b&gt;: 분산 시스템에 접속하는 모든 클라이언트는 어떤 노드에 접속했느냐에 관계없이 언제나 같은 데이터를 보관해야 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;availability&lt;/b&gt;: 분산 시스템에 접속하는 클라이언트는 일부 노드에서 장애가 발생하더라도 항상 응답을 받을 수 있어야 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;partition&lt;/b&gt; &lt;b&gt;tolerance&lt;/b&gt;: 파티션은 두 노드 사이에 통신 장애가 발생하였음을 의미한다. 파티션 감내는 네트워크에 파티션이 생기더라도 시스템은 계속해서 동작하여야 한다는 것을 의미한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1578&quot; data-origin-height=&quot;1440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kwMeQ/btsE1CycXRi/n8AFx3zZYmjNiIKd0Oiqr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kwMeQ/btsE1CycXRi/n8AFx3zZYmjNiIKd0Oiqr0/img.png&quot; data-alt=&quot;CAP 이론&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kwMeQ/btsE1CycXRi/n8AFx3zZYmjNiIKd0Oiqr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkwMeQ%2FbtsE1CycXRi%2Fn8AFx3zZYmjNiIKd0Oiqr0%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;457&quot; height=&quot;417&quot; data-origin-width=&quot;1578&quot; data-origin-height=&quot;1440&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;CAP 이론&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 모두 만족할 수 없기에 CA, CP, AP로 구분되는데 P 즉 파티션 감내를 보장하기 위해서는 네트워크 장애가 절대 발생하면 안 된다는 가정이 필요하다. 그러나 &lt;b&gt;통상&amp;nbsp;네트워크&amp;nbsp;장애는&amp;nbsp;피할 수&amp;nbsp;없다.&lt;/b&gt;&amp;nbsp;때문에&amp;nbsp;분산&amp;nbsp;시스템은&amp;nbsp;반드시&amp;nbsp;파티션&amp;nbsp;문제를&amp;nbsp;감내할&amp;nbsp;수&amp;nbsp;있도록&amp;nbsp;설계해야&amp;nbsp;하므로&amp;nbsp;실세계에서&amp;nbsp;&lt;b&gt;CA시스템은&amp;nbsp;존재하지&amp;nbsp;않는다&lt;/b&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;CP 시스템&lt;/b&gt;: 일관성, 파티션 감내를 지원, 가용성을 조금 포기한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;AP 시스템&lt;/b&gt;: 가용성과 파티션 감내를 지원, 일관성을 조금 포기한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 가용성과 일관성 사이에서 Trade off를 해야 하기에 우리는 선택을 해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 문득 어떤 DB들은 어떤 요구사항을 따를까 궁금해져서 서칭을 시작했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Mongo DB는 CP를 따른다.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분이 본격적으로 내가 의문을 갖게 된 시점이다. &quot;mongoDB는 의심의 여지없이 AP일 것이라고 생각했거늘&quot; 소리가 육성으로 나왔다. 물론 CP&lt;span style=&quot;text-align: start;&quot;&gt;,&amp;nbsp;&lt;/span&gt;AP&lt;span style=&quot;text-align: start;&quot;&gt;&amp;nbsp;둘 중 하나에 쏠린 시스템은 좋지 않다. 그렇기에 대부분의 분산 시스템은 상황에 따라 일관성과 가용성의 우선순위를 다르게 설정한다. 그렇더라도 mongoDB의 특성이나 다양한 배경을 봤을 때 기본적으로 AP일 것이라고 생각했다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;PACELC 이론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역시 세상에는 똑똑하신 분이 많다. CAP이론에는 내가 가진 의문과 같은 한계가 있기에 그 &lt;b&gt;한계를 보완하고자 나온 PACELC이라는 이론이 있다&lt;/b&gt;고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PACELC이론은 현실에서는 Partition 상황과 아닌(ELSE) 상황이 함께 존재한다고 가정하고, 2가지 상황을 나눠 각각 어떤 gaurantee를 선택할지 나누어 놓은 것이다.&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;span style=&quot;text-align: start;&quot;&gt;&amp;nbsp;Partition 상황인지 아닌지를 나누어 한번 더 요구사항을 분할한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1436&quot; data-origin-height=&quot;454&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rTPzL/btsE1EQkDU9/GbmKmmQAoa3vM3F4RzELRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rTPzL/btsE1EQkDU9/GbmKmmQAoa3vM3F4RzELRK/img.png&quot; data-alt=&quot;PACELC 구성도&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rTPzL/btsE1EQkDU9/GbmKmmQAoa3vM3F4RzELRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrTPzL%2FbtsE1EQkDU9%2FGbmKmmQAoa3vM3F4RzELRK%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;1436&quot; height=&quot;454&quot; data-origin-width=&quot;1436&quot; data-origin-height=&quot;454&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;PACELC 구성도&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 구분을 다음과 같이 4가지로 나눌 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PA/EL 시스템: partition 상황에서는 가용성이 중요하고 일반적인 상황에서는 속도가 우선인 시스템&lt;/li&gt;
&lt;li&gt;PA/EC&lt;span style=&quot;text-align: left;&quot;&gt;&amp;nbsp;시스템: partition 상황에서는 가용성이 중요하고 &lt;b&gt;일반적인 상황에서는 일관성이 우선인 시스템&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;PC/EL&lt;span style=&quot;text-align: left;&quot;&gt;&amp;nbsp;시스템: partition이든 그 외든 일관성이 가장 중요한 시스템&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;PC/EC&lt;span style=&quot;text-align: left;&quot;&gt;&amp;nbsp;시스템: partition 상황에서는 일관성을 중요하게 생각하고 그 외는 속도가 우선인 시스템&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MongoDB는 PA/EC를 따른다.&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1092&quot; data-origin-height=&quot;518&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6BWs6/btsE2DReDSM/rkvJmyHqCYekhlAxcIcDfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6BWs6/btsE2DReDSM/rkvJmyHqCYekhlAxcIcDfK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6BWs6/btsE2DReDSM/rkvJmyHqCYekhlAxcIcDfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6BWs6%2FbtsE2DReDSM%2FrkvJmyHqCYekhlAxcIcDfK%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;1092&quot; height=&quot;518&quot; data-origin-width=&quot;1092&quot; data-origin-height=&quot;518&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 나는 분산환경에서 당연히 partition이 나누어져 있는 상황에서 MongoDB를 사용한다고 생각하여 일관성을 우선시한다는 말에 공감을 하지 못했던 것이다. 하지만 언제까지나 일반적인 상황에서는&lt;b&gt; 일관성을 우선시하는 시스템&lt;/b&gt;이고 &lt;b&gt;분산 환경에서는 가용성을 우선시한다.&lt;/b&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;그렇다면 분산 상황에서는 일관성을 보장하지 않는가 하면 또 그런 것도 아니다. Mongo DB는 &lt;span style=&quot;text-align: start;&quot;&gt;Replica Set을 이용하여 클러스터를 구축한다. 이&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;때, &lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;replication의 데이터 싱크가 비동기적으로 이루어지기 때문에 secondary에 복제되지 않은 데이터가 손실될 수 있다. 하지만 MongoDB는 &lt;span style=&quot;text-align: start;&quot;&gt;&lt;a href=&quot;https://mangkyu.tistory.com/53&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;MVCC&lt;/a&gt;(다중 버전 동시성 제어)라는 기법을 제공해 일관성도 수준급으로 보장한다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇듯 MongoDB는 CP다 일반적으로는 일관성을 중시하기 때문이다. 하지만 분산 환경에서는 AP다. partition 분할 시 가용성을 우선시하기 때문이다. 이렇게 좀 더 내용적으로 공부를 하고 나서야 의문이 풀렸다. 혹시라도 대규모 시스템 설계 기초 도서를 읽으며 나와 같은 의문을 가진 분께 조금이라도 도움이 되었으면 하는 마음이다.:)&lt;/p&gt;</description>
      <category>Data Engineering</category>
      <author>CHOI JEE HYUK</author>
      <guid isPermaLink="true">https://statistics-fox.tistory.com/78</guid>
      <comments>https://statistics-fox.tistory.com/78#entry78comment</comments>
      <pubDate>Sun, 18 Feb 2024 18:21:07 +0900</pubDate>
    </item>
    <item>
      <title>다양한 Web Crawling 및 Web Scraping 방법</title>
      <link>https://statistics-fox.tistory.com/76</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Crawling과 Scraping! 데이터 분야에 발을 담근 사람이라면 안 들어볼 수가 없는 영역이다. 사실 이 두 가지는 '원하는 데이터를 추출한다.'라는 공통 목적을 가진다. 때문에 기술적으로 같이 사용되기도 하고 일반적으로 혼용되지만 엄밀히 말하면 차이가 존재한다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Crawling&lt;/b&gt;: 웹상을 돌아다니며 방대한 양의 데이터를 수집한다. &lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;웹 페이지의 링크를 타고 계속해서 탐색하여 html 페이지 및 링크 정보 등을 수집한다. - ex) &lt;b&gt;파이썬에 대해 알아보고 싶어 -&amp;gt; 파이썬 공식문서 전체 크롤링&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;&lt;b&gt;Scraping&lt;/b&gt;: 정확한 정보를 요구할 때 사용되기에 필요한 데이터만 수집한다. &lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;흩어져있는 데이터를 다양한 패키지를 통해 자동으로 추출하여 전달할 수 있다. &lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;- ex)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;daily 환율가를 수집하고 싶어 -&amp;gt; 증권 페이지 스크래핑&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;span style=&quot;color: #15171a;&quot;&gt;&lt;span style=&quot;caret-color: #15171a; background-color: #ffffff;&quot;&gt;위와 같은 차이가 있지만 사실 크게 구분할 필요는 없다. &lt;b&gt;Web&amp;nbsp;Crawling&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;의 경우 모든 데이터를 모으기 때문에 정보의 확장성이 넓다는 장점이 있고, 서버의 자리를 많이 차지하여 리소스가 많이 들어간다는 단점이 있다. 반면에 &lt;b&gt;Web Scraping&lt;/b&gt;의 경우, 적은 리소스를 들여 정확한 정보를 가져올 수 있지만, 그만큼 데이터의 한계가 있다. 보통 목적에 따라 섞어서 사용하기 때문에 이 정도 차이만 알면 크게 문제 되지 않는다.&lt;/span&gt;&lt;/span&gt;&lt;/span&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;데이터 엔지니어에게 위 두 가지는 사실 기본소양이라고 할 수 있다. 분석가나 ML 엔지니어 들은 보통 데이터를 제공받는다. 때문에 분석 방법론이나 변수처리, 모델 성능 등에 집중하면 된다.&lt;s&gt;(물론 '데이터 잡부'들은 다 함. )&lt;/s&gt; 하지만 데이터 엔지니어는 제공을 해줘야 하는 입장이고 Data Acquisition의 전반을 다루기 때문에 크롤링과 스크래핑을 반드시 다룰 줄 알아야 한다. 그럼 이제 어떤 식으로 크롤링을 하고 스크레핑을 하는지 알아보자&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. &lt;span style=&quot;text-align: left;&quot;&gt;Crawling Recursive Way&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재귀적으로 크롤링하는 방법이다. 하나의 웹페이지를 기준으로 안에 있는 링크들을 모두 수집해 나가기 때문에 재귀적으로 탐색해 나가는 것이 깔끔하다. &lt;a href=&quot;https://docs.python.org/3/library/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Python3 버전의 공식 문서 페이지&lt;/a&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;1028&quot; data-origin-height=&quot;920&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsJD3A/btsDKLJ5tmW/vbTz3y1XgNKyKf8ebiksbk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsJD3A/btsDKLJ5tmW/vbTz3y1XgNKyKf8ebiksbk/img.png&quot; data-alt=&quot;가장 상위 디렉토리를 기준으로 뻗어나가는 링크들&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsJD3A/btsDKLJ5tmW/vbTz3y1XgNKyKf8ebiksbk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsJD3A%2FbtsDKLJ5tmW%2FvbTz3y1XgNKyKf8ebiksbk%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;512&quot; height=&quot;458&quot; data-origin-width=&quot;1028&quot; data-origin-height=&quot;920&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;text-align: left;&quot;&gt;HTML에서 링크와 CSS 파일을 추출하는 함수&lt;/span&gt;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;text-align: left;&quot;&gt;html에는 태그가 존재하는데 태그별로 들어 있는 정보가 상이하다. 우리는 link와 CSS 정보를 수집할 것이므로 링크 정보가 있는 &amp;lt;a&amp;gt; 태그와 외부 CSS 파일로 연결되는 &amp;lt;link&amp;gt; 태그를 찾아 추출하는 함수를 만들 것이다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;파일을 다운로드하는 함수&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;추출을 했으면 다운을 해야 한다. 다운 시 링크가 갈라질 경우 폴더도 생성해야 하며 오류 대비 코드도 작성해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;HTML에서 링크와 CSS 파일을 추출하는 함수&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1705824514769&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from urllib.parse import urljoin
from bs4 import BeautifulSoup
from urllib.request import *
from urllib.parse import *
from os import makedirs
import os.path, time, re

# 이미 처리한 파일을 추적하기 위한 딕셔너리
proc_files = {}

# HTML에서 링크와 CSS 파일을 추출하는 함수
def enum_links(html, base):
    soup = BeautifulSoup(html, &quot;html.parser&quot;)
    links = soup.select(&quot;link[rel='stylesheet']&quot;)
    links += soup.select(&quot;a[href]&quot;)
    result = []

    for a in links:
        href = a.attrs['href']
        url = urljoin(base, href)
        result.append(url)
    
    return result&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 enum_links 함수는 주어진 html 문자 열과 기본 URL을 받아서 그 안에서 Beautiful Soup 라이브러리를 활용해 &amp;nbsp;&amp;lt;link&amp;gt;와 &amp;lt;a&amp;gt; 태그를 찾아 관련된 URL을 추출한다. html에 있는 링크들은 전부 다 상대경로로 입력이 되어 있기에 절대경로로 바꿔주고자 urljoin 함수를 사용하였고 리스트에 저장한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;파일을 다운로드하는 함수&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1705824555699&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 파일을 다운로드하는 함수
def download_file(url):
    o = urlparse(url)
    savepath = &quot;./&quot; + o.netloc + o.path

    if re.search(r&quot;/$&quot;, savepath):  # 폴더인지 여부를 확인하여 index.html 추가
        savepath += &quot;index.html&quot;
    savedir = os.path.dirname(savepath)

    if not os.path.exists(savedir):
        print(&quot;폴더 생성 =&quot;, savedir)
        makedirs(savedir)
 
    try:
        print(&quot;다운로드 중 =&quot;, url)
        urlretrieve(url, savepath)
        time.sleep(1)
        return savepath
    except:
        print(&quot;다운로드 오류 =&quot;, url)
        return None&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 함수는 주어진 URL에서 파일을 다운로드하고, 저장경로를 반환한다. 파일은 현재 디렉터리 내에./&amp;lt;netloc&amp;gt;&amp;lt;path&amp;gt;에 저장된다. 폴더 여부에 따라 인덱스가 추가되도록 만들어주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;재귀적 탐색함수&lt;/h3&gt;
&lt;pre id=&quot;code_1705824374765&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def analyze_html(url, root_url):
    savepath = download_file(url)
    if savepath is None: return
    if savepath in proc_files: return

    proc_files[savepath] = True
    print(&quot;analyzed html = &quot;, url)

    html = open(savepath, &quot;r&quot;, encoding = &quot;utf-8&quot;).read()
    links = enum_links(html, url)

    for link_url in links:
        if link_url.find(root_url) != 0:
            if not re.search(r&quot;.css&quot;, link_url): continue
        
        if re.search(r&quot;.(html|htm)$&quot;, link_url):
            analyze_html(link_url, root_url)
            continue
        
        download_file(link_url)

if __name__ == &quot;__main__&quot;:
    url = &quot;https://docs.python.org/3/library/&quot;
    analyze_html(url, url)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 함수는 main 함수로 주어진 URL에서 HTML을 다운로드하고 그 HTML의 링크들을 추출하여 재귀적으로 다운로드한다. 이미 처리한 링크나 파일이 있을 경우 중복이 있어 리소스가 너무 많이 소모될 수 있기에 그것들은 &lt;span style=&quot;text-align: left;&quot;&gt;proc_files = {}에 넣어두고 겹치면 생략하는 식으로 만들었다. 또한, 우리는 파이썬 3 공식 문서만 궁금한 것 이기 때문에 root 경로가 다른 링크들은 전부 제외하게끔 작성하였다. 얘들을 어 내부 링크가 library가 아닌 plugin일 경우 생략한다.&lt;/span&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. WEP api Scraping&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부 api를 활용해 원하는 데이터를 추출할 수 있다. api에도 종류가 있지만 이번에는 web api를 통해 받아오고자 한다. Web api는 보통 공공기관에서 정보를 가져올 때 쓰이는데 어떤 자원들을 필요로 하느냐에 따라 기준이 있고 그 기준에 맞추어 정보를 가져올 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://openweathermap.org&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;open weathermap이라는&lt;/a&gt; 날씨 정보 제공 사이트가 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 api의 경우 json 형태로 기준을 안내해 주는데 아래 사진은 main에서 temp를 명시하여 온도 정보를 가져올 수 있다는 뜻이다.&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;404&quot; data-origin-height=&quot;132&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rxGP5/btsDNJkHxeP/77Zdi1SQqphk9FlLSKpl2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rxGP5/btsDNJkHxeP/77Zdi1SQqphk9FlLSKpl2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rxGP5/btsDNJkHxeP/77Zdi1SQqphk9FlLSKpl2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrxGP5%2FbtsDNJkHxeP%2F77Zdi1SQqphk9FlLSKpl2K%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;404&quot; height=&quot;132&quot; data-origin-width=&quot;404&quot; data-origin-height=&quot;132&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1705826643248&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import requests
import json

# requests.get() == urllib.urlopen()

key = &quot;본인의 API KEY&quot;

cities = [&quot;Seoul,KR&quot;, &quot;Tokyo,JP&quot;, &quot;Manchester&quot;]
api = &quot;https://api.openweathermap.org/data/2.5/weather?q={name}&amp;amp;appid={key}&quot;

for name in cities:
    url = api.format(name=name, key=key)
    res = requests.get(url)
    data = json.loads(res.text)
    
    print(&quot;+ CITY = &quot;, data[&quot;name&quot;])
    print(&quot;| WEATHER =&quot;, data[&quot;weather&quot;][0][&quot;description&quot;])
    print(&quot;| MIN TEMP =&quot;, round(data[&quot;main&quot;]['temp_min'] - 273, 1)) # 켈빈온도 섭씨로 변환
    print(&quot;| MIN TEMP =&quot;, round(data[&quot;main&quot;]['temp_max'] - 273, 1))
    print(&quot;| HUMIDIDTY =&quot;, data[&quot;main&quot;]['humidity'])
    print(&quot;| PRESSURE =&quot;, data[&quot;main&quot;]['pressure'])
    print(&quot;| DEG =&quot;, data[&quot;wind&quot;]['deg'])
    print(&quot;| SPEED =&quot;, data[&quot;wind&quot;]['speed'])&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;408&quot; data-origin-height=&quot;682&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bd5PWJ/btsDHp2UpBP/oZUcJAb7z0JdQAGeBBV7k0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bd5PWJ/btsDHp2UpBP/oZUcJAb7z0JdQAGeBBV7k0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bd5PWJ/btsDHp2UpBP/oZUcJAb7z0JdQAGeBBV7k0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbd5PWJ%2FbtsDHp2UpBP%2FoZUcJAb7z0JdQAGeBBV7k0%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;243&quot; height=&quot;406&quot; data-origin-width=&quot;408&quot; data-origin-height=&quot;682&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위처럼 원하는 도시의 정보를 api를 이용해 가져올 수 있다. 이렇게 간단하게 원하는 정보를 가져올 수 있다는 장점이 있기에 보통 학생 때 많이 사용한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. RSS Scraping&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RSS란 웹사이트 상의 콘텐츠들을 요약하고, 이를 서로 공유할 수 있도록 만든 '표준'이라고 정의되어 있다. 너무 어려우니 쉽게 얘기하면 간단한 게 우리가 웹사이트에 방문하지 않고도 실시간으로 업데이트된 내용을 전달받을 수 있게 만들어진 구독이라고 생각하면 된다. 가장 대표적으로 우리나라 기상청에서 RSS를 제공하는데 우리나라 각 지역의 날씨를 코드만 돌리면 언제든지 확인 가능하다.&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.weather.go.kr/w/pop/rss-guide.do&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;기상청 RSS&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1705827693093&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#!/usr/bin/bin/env python3

import requests
from bs4 import BeautifulSoup

def main():
    url = &quot;http://www.kma.go.kr/weather/forecast/mid-term-rss3.jsp&quot;
    res = requests.get(url)

    soup = BeautifulSoup(res.text)
    locations = soup.find_all(&quot;location&quot;)

    for location in locations:
        city = location.find(&quot;city&quot;).text
        date = location.find(&quot;data&quot;).find(&quot;tmef&quot;).text
        weather = location.find(&quot;data&quot;).find(&quot;wf&quot;).text

        print(f&quot;[ {date} ] {city} : {weather}&quot;)

if __name__ == &quot;__main__&quot;:
    main()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;670&quot; data-origin-height=&quot;692&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1qhwb/btsDIyyflXc/KtGZ5iWe9OKsh6lDJBrk30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1qhwb/btsDIyyflXc/KtGZ5iWe9OKsh6lDJBrk30/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1qhwb/btsDIyyflXc/KtGZ5iWe9OKsh6lDJBrk30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1qhwb%2FbtsDIyyflXc%2FKtGZ5iWe9OKsh6lDJBrk30%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;314&quot; height=&quot;324&quot; data-origin-width=&quot;670&quot; data-origin-height=&quot;692&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기상청 RSS 사이트에 들어가면 확장자가 jsp로 된 RSS 주소를 주는데 해당 주소를 불러와 지역변수를 뽑고 그 지역에 대한 현재 날씨 정보를 가져올 수 있다. 나는 도시 이름, 날짜, 현재 날씨 이렇게 3개를&amp;nbsp;format 하여 보기 좋게 출력하였다.&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. BeautifulSoup&amp;nbsp;Scraping&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 Scraping 하면 절대 빼놓을 수 없는 것이 바로 어여쁜 수프다. Scraping의 대명사라고도할 수 있고 정형 데이터뿐만 아니라 이미지와 같은 비정형 데이터도 수집가능하다. 아주 무궁무진하고 자주 사용하기 때문에 '이쁜 수프를 못쓴다' == 'Scraping을 할 줄 모른다'와 같은 소리도 들린다.&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;a href=&quot;https://finance.naver.com/marketindex/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;네이버 페이 증권 사이트&lt;/a&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;아래 사진에서 보는 것처럼 개발자 도구를 열고 원하는 정보가 기록된 html 부분에서 selector 정보를 복사해 select_one 함수에 인자로 넣어주면 된다. 이렇게만 하면 이런 사이트에서 원하는 정보만 쏙 하고 빼올 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3020&quot; data-origin-height=&quot;1806&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/T1iyw/btsDLunNGt0/9QXvw2vTwoLoy22izBVnak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/T1iyw/btsDLunNGt0/9QXvw2vTwoLoy22izBVnak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/T1iyw/btsDLunNGt0/9QXvw2vTwoLoy22izBVnak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FT1iyw%2FbtsDLunNGt0%2F9QXvw2vTwoLoy22izBVnak%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;782&quot; height=&quot;468&quot; data-origin-width=&quot;3020&quot; data-origin-height=&quot;1806&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1705827951554&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from bs4 import BeautifulSoup
import urllib.request as req
import datetime

url =&quot;https://finance.naver.com/marketindex/&quot;
now = datetime.datetime.now().strftime(&quot;%Y-%m-%d %H:%M:%S&quot;)

res = req.urlopen(url).read()

soup = BeautifulSoup(res, &quot;html.parser&quot;)
currency = soup.select_one(&quot;#exchangeList &amp;gt; li.on &amp;gt; a.head.usd &amp;gt; div &amp;gt; span.value&quot;)

print(&quot;Date: &quot; + now + &quot; USD:  &quot; + currency.string)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;592&quot; data-origin-height=&quot;34&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LWL2E/btsDJEEFKAr/Kkw1b9hiXypcWrXgUiErj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LWL2E/btsDJEEFKAr/Kkw1b9hiXypcWrXgUiErj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LWL2E/btsDJEEFKAr/Kkw1b9hiXypcWrXgUiErj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLWL2E%2FbtsDJEEFKAr%2FKkw1b9hiXypcWrXgUiErj0%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;592&quot; height=&quot;34&quot; data-origin-width=&quot;592&quot; data-origin-height=&quot;34&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;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 다양한 크롤링 스크래핑 방법을 통해 데이터를 수집할 수 있다. 꾸준히 수집하고 싶을 경우 crontab을 이용하여 정기적인 실행이 가능하게 만들어도 되고 나중에는 airflow와 같은 워크플로 관리기술과 함께 파이프라인 작성에도 유용하게 사용할 수 있다. 사실 위에서 다룬 방법들은 굉장히 기초적인 방법이다. REST API를 통해서도 다룰 수 있는 데이터가 많기에 데이터 수집 목적과 종류에 따라 적절히 수집 방법을 선택하면 될 것 같다. 이제 수집 부분을 공부했으니 데이터를 적재할 수 있는 스토리지와 DB 등을 좀 더 자세히 다뤄 볼 예정이다.&amp;nbsp;&lt;/p&gt;</description>
      <category>Data Engineering</category>
      <category>Crawling</category>
      <category>Scraping</category>
      <author>CHOI JEE HYUK</author>
      <guid isPermaLink="true">https://statistics-fox.tistory.com/76</guid>
      <comments>https://statistics-fox.tistory.com/76#entry76comment</comments>
      <pubDate>Sun, 21 Jan 2024 19:10:28 +0900</pubDate>
    </item>
    <item>
      <title>[airflow] - 오퍼레이터 with Xcom</title>
      <link>https://statistics-fox.tistory.com/74</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;아래 글은 옵시디언으로 작성되었습니다.&lt;/p&gt;
&lt;h1&gt;Xcom이란?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cross Communication의 약자로 Airflow DAG 안 ==Task 간&lt;b&gt; 데이터 공유를 위해 사용되는 기술&lt;/b&gt;==이다.(Task1의 수행 중 내용이나 결과를 Task2에서 사용 또는 입력으로 주고 싶은 경우) ==&lt;b&gt;주로 작은 규모의 데이터 공유를 위해 사용&lt;/b&gt;==되며 Xcom의 내용은 메타 DB의 Xcom 테이블에 값이 저장된다. 만약, 1GB 이상의 대용량 데이터 공유를 위해서는 외부 솔루션을 사용해야 한다.(AWS S3, HDEF 등)&lt;/p&gt;
&lt;h1&gt;Python 오퍼레이터에서 Xcom사용하기&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크게 두 가지 방법으로 Xcom 사용이 가능하다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1) **kwargs에 존재하는 ti(task_instance) 객체 활용&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Xcom에 데이터 push&lt;/h3&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;@task(task_id='python_xcom_push_task') 
def xcom_push(**kwargs): 	
	ti = kwargs['ti'] 	
	ti.xcom_push(key=&quot;result1&quot;, value=&quot;value_1&quot;) 	
	ti.xcom_push(key=&quot;result2&quot;, value=[1,2,3])&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;template 변수인 kwargs 변수에서 ti객체를 얻을 수 있으며 task_istance객체가 가진 xcom_push 메서드를 활용할 수 있다. 이때 ti에 딕셔너리 형태로 데이터가 저장되며 다른 task에서 꺼내쓸 수 있도록 메타 DB의 Xcom에 저장된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Xcom에서 데이터 pull&lt;/h3&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;@task(task_id='python_xcom_pull_task') 
def xcom_pull(**kwargs): 	
	ti = kwargs['ti'] 	
    value_key1 = ti.xcom_pull(key=&quot;result1&quot;) 	
    value_key2 = ti.xcom_pull(key=&quot;result2&quot;, 
    task_ids='python_xcom_push_task') 	
    print(value_key1) 	
    print(value_key2)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 역시 마찬가지로 ti객체에서 xcom_pull 메서드를 이용해 저장했던 객체를 다른 task에서 꺼낼 수 있다. 다만 task_ids를 명시해 주는 게 좋다. 만약 task_ids를 명시하지 않았는데 key값이 같은 데이터가 xcom 내에 존재하는 경우 마지막으로 넣은 value가 불러와진다. key를 unique 하게 유지하는 경우가 아니라면 task_ids는 생략하면 안 된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2) 파이썬 함수의 return값 활용(1안)&lt;/h2&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;@task (task_id='xcom_push by return') 
def xcom_push_by_return(**kwargs): 	
	transaction_value = 'status Good' 	
	return transaction_value&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;@task(task_id='xcom_pull_by_return') 
def xcom_pull_by_return(status, **kwargs): 	
	print(status)&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;xcom_pull_by_return(xcom_push_by_return())&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 사례는 task1의 반환값이 다른 task의 입력값이 되는 경우이다. 이렇게 하면 ti 객체를 이용하지 않고 값을 DAG내 다른 task로 전달할 수 있다. 또한, task 데코레이터를 사용할 경우 airflow는&lt;/p&gt;
&lt;p&gt;&lt;mark&gt;&lt;b&gt;함수 입출력 관계 만으로도 자동으로 순서가 정해진다.&lt;/b&gt;&lt;/mark&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 TASK 간 수행관계도 명시할 필요가 없다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;파이썬 함수의 return값 활용(2안)&lt;/h2&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;@task(task_id='xcom_push_by_return') 
def xcom_push_return(**kwargs): 	
	transaction_value = 'statusGood' 	
    return transaction_value&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 1안과 차이가 없다. return 한 값은 다동으로 xcom에 key=&amp;lsquo;return value&amp;rsquo;, task_ids = task_id로 저장이 된다.&lt;/p&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;@task(task_id='xcom_pull_by_return') 
def xcom_pull_return_by_method(**kwargs): 	
	ti=kwargs['ti'] 		
    pull_value = ti.xcom_pull(key='return_value', task_ids='xcom_push_by_return' )  
    
xcom_push_by_return() &amp;gt;&amp;gt; xcom_pull_by_return()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1안에서는 task1의 출력값이 두 번째 task의 인자로 들어가게끔 하였으나 이번에는 return값이 자동으로 xcom에 들어간다는 사실을 이용하여 두 번째 task에서 xcom_pull로 값을 당겨왔다. 이런 경우에는 task관 관계를 명시해주어야 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;# Python 오퍼레이터를 이용한 xcom DAG 직접 짜보기&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1안 DAG&lt;/h2&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;from airflow import DAG
import pendulum
import datetime
from airflow.decorators import task

with DAG(
    dag_id=&quot;dags_python_with_xcom_eg1&quot;,
    schedule=&quot;30 6 * * *&quot;,
    start_date=pendulum.datetime(2023, 3, 1, tz=&quot;Asia/Seoul&quot;),
    catchup=False
) as dag:
    
    @task(task_id='python_xcom_push_task1')
    def xcom_push1(**kwargs):
        ti = kwargs['ti']
        ti.xcom_push(key=&quot;result1&quot;, value=&quot;value_1&quot;)
        ti.xcom_push(key=&quot;result2&quot;, value=[1,2,3])

    @task(task_id='python_xcom_push_task2')
    def xcom_push2(**kwargs):
        ti = kwargs['ti']
        ti.xcom_push(key=&quot;result1&quot;, value=&quot;value_2&quot;)
        ti.xcom_push(key=&quot;result2&quot;, value=[1,2,3,4])

    @task(task_id='python_xcom_pull_task')
    def xcom_pull(**kwargs):
        ti = kwargs['ti']
        value1 = ti.xcom_pull(key=&quot;result1&quot;)
        value2 = ti.xcom_pull(key=&quot;result2&quot;, task_ids='python_xcom_push_task1')
        print(value1)
        print(value2)


    xcom_push1() &amp;gt;&amp;gt; xcom_push2() &amp;gt;&amp;gt; xcom_pull()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;python_xcom_push_task1&lt;/code&gt;에서 먼저 두 데이터를 xcom에 집어넣는다. 그 후 &lt;code&gt;python_xcom_push_task2&lt;/code&gt;에서 task1과 같은 key값을 가진 두 데이터를 집어넣는다.&lt;br /&gt;마지막으로 &lt;code&gt;python_xcom_pull_task&lt;/code&gt;에서 xcom_pull을 하는데 이때 value1에는 task_ids를 명시하지 않았으므로 마지막 입력 값인 value_2가 들어가고 value2에는 task1에서 가져오라는 task_ids를 명시하였으므로 [1,2,3]가 출력될 것이다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/DHwvap7.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;위 출력결과를 보면 잘 출력이 된 것을 알 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2안 DAG&lt;/h2&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;from airflow import DAG
import pendulum
import datetime
from airflow.decorators import task

with DAG(
    dag_id=&quot;dags_python_with_xcom_eg2&quot;,
    schedule=&quot;30 6 * * *&quot;,
    start_date=pendulum.datetime(2023, 3, 1, tz=&quot;Asia/Seoul&quot;),
    catchup=False
) as dag:
    
    @task(task_id='python_xcom_push_by_return')
    def xcom_push_result(**kwargs):
        return 'Success'


    @task(task_id='python_xcom_pull_1')
    def xcom_pull_1(**kwargs):
        ti = kwargs['ti']
        value1 = ti.xcom_pull(task_ids='python_xcom_push_by_return')
        print('xcom_pull 메서드로 직접 찾은 리턴 값:' + value1)

    @task(task_id='python_xcom_pull_2')
    def xcom_pull_2(status, **kwargs):
        print('함수 입력값으로 받은 값:' + status)


    python_xcom_push_by_return = xcom_push_result()
    xcom_pull_2(python_xcom_push_by_return)
    python_xcom_push_by_return &amp;gt;&amp;gt; xcom_pull_1()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 위 DAG을 보면 자동으로 해당 로직이 머리에 떠올라야 한다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/lACMxbh.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;가장 먼저 &lt;code&gt;python_xcom_push_by_return&lt;/code&gt;은 단순히 'Success&amp;rsquo;라는 string을 return 하는 task이다.&lt;br /&gt;&lt;code&gt;python_xcom_pull_1&lt;/code&gt; task는 ti 객체를 통해 xcom_pull 메서드로 직접 찾은 값을 출력하는 형태고,&lt;br /&gt;&lt;code&gt;python_xcom_pull_2&lt;/code&gt; task는 return값 'Success&amp;rsquo;를 직접 인자로 받은 후 task 내 status에 넣어주는 형태다.&lt;br /&gt;그 후, task 포함관계를 보면 우선 &lt;code&gt;python_xcom_push_by_return&lt;/code&gt;가 실행된 후 &lt;code&gt;xcom_pull_2&lt;/code&gt;와 &lt;code&gt;xcom_pull_1&lt;/code&gt;이 동시에 실행되는 로직을 갖고 있는 것을 알 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/bJrFXTu.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/ZT1hrEv.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;두 출력값 모두 정상적으로 잘 나온 것을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/NNJymmC.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;Xcom push를 하는 두 가지 방법과 Xcom pull 하는 두 가지 방법이 있고 개발자가 원하는 방식으로 만들어주면 된다. 나라면 굳이 task 간 관계를 명시하지 않고 불필요한 객체 생성을 하지 않는 함수 return 방식을 사용할 것 같다.&lt;/p&gt;
&lt;h1&gt;BASH 오퍼레이터에서 Xcom 사용하기&lt;/h1&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;from airflow import DAG
import pendulum
import datetime
from airflow.operators.bash import BashOperator

with DAG(
    dag_id=&quot;dags_bash_with_xcom&quot;,
    schedule=&quot;10 0 * * *&quot;,
    start_date=pendulum.datetime(2023, 3, 1, tz=&quot;Asia/Seoul&quot;),
    catchup=False
) as dag:
    bash_push = BashOperator(
    task_id='bash_push',
    bash_command=&quot;echo START &amp;amp;&amp;amp; &quot;
                 &quot;echo XCOM_PUSHED &quot;
                 &quot;{{ ti.xcom_push(key='bash_pushed',value='first_bash_message') }} &amp;amp;&amp;amp; &quot;
                 &quot;echo COMPLETE&quot;
    )

    bash_pull = BashOperator(
        task_id='bash_pull',
        env={'PUSHED_VALUE':&quot;{{ ti.xcom_pull(key='bash_pushed') }}&quot;,
            'RETURN_VALUE':&quot;{{ ti.xcom_pull(task_ids='bash_push') }}&quot;},
        bash_command=&quot;echo $PUSHED_VALUE &amp;amp;&amp;amp; echo $RETURN_VALUE &quot;,
        do_xcom_push=False
    )

    bash_push &amp;gt;&amp;gt; bash_pull&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bash_push에서 Xcom으로 들어가는 값은 총 2개다. 먼저 xcom_push를 통해 넣은 first_bash_message라는 값과 마지막으로 echo 된 COMPLETE라는 string이다. 파이썬에서 return값이 자동으로 Xcom에 들어간다고 했었는데 bash에서는 마지막에 echo 된 값도 return값으로 취급된다. 아래 사진을 보면 값이 제대로 xcom에 input 된 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/oQ4HduS.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bash_pull을 보면 key를 명시하여 xcom_pull을 하는 경우와 task_ids만 명사하여 xcom_pull을 하는 경우가 있다. key를 명시한 경우는 그 key 해당하는 value값인 first_bash_message 스트링 값이 잘 출력이 될 것이다. task_ids만 명시한 경우는 해당 task에서 마지막으로 xcom에 들어간 value가 출력된다. 따라서 COMPLETA 스트링 값이 출력될 것이다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/lFgDfBv.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가적으로 bash_pull task에서 마지막으로 echo 된 값 또한 결국 return 값이므로 Xcom에 들어가야 마땅하나 do_xcom_push 파라미터를 False값으로 주면 Xcom으로 값을 집어넣지 않는다.&lt;/p&gt;
&lt;h1&gt;Python &amp;amp; Bash 오퍼레이터 간 Xcom 사용&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Python to Bash 오퍼레이터 Xcom 전달&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;편의상 DAG 정의 부분은 생략하였다.&lt;/p&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;    @task(task_id='python_push')
    def python_push_xcom():
        result_dict = {'status':'Good','data':[1,2,3],'options_cnt':100}
        return result_dict

    bash_pull = BashOperator(
        task_id='bash_pull',
        env={
            'STATUS':'{{ti.xcom_pull(task_ids=&quot;python_push&quot;)[&quot;status&quot;]}}',
            'DATA':'{{ti.xcom_pull(task_ids=&quot;python_push&quot;)[&quot;data&quot;]}}',
            'OPTIONS_CNT':'{{ti.xcom_pull(task_ids=&quot;python_push&quot;)[&quot;options_cnt&quot;]}}'

        },
        bash_command='echo $STATUS &amp;amp;&amp;amp; echo $DATA &amp;amp;&amp;amp; echo $OPTIONS_CNT'
    )
    python_push_xcom() &amp;gt;&amp;gt; bash_pull&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 위와 같은 코드도 작성할 수 있다. python_push 테스트에서 dict 형태로 반환값을 만들면 Dict가 Xcom에 담긴다. 이것을 bash 오퍼레이터에서 뺄 수 있는데 bash_pull task를 보면 STATUS라는 환경변수에 Good, DATA라는 환경변수에 리스트[1, 2, 3]를 담으라는 뜻인 것을 알 수 있다. 출력값을 보면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/e7rWC23.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Bash to Python 오퍼레이터 Xcom 전달&lt;/h2&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;bash_push = BashOperator(
    task_id='bash_push',
    bash_command='echo PUSH_START '
                 '{{ti.xcom_push(key=&quot;bash_pushed&quot;,value=200)}} &amp;amp;&amp;amp; '
                 'echo PUSH_COMPLETE'
    )

    @task(task_id='python_pull')
    def python_pull_xcom(**kwargs):
        ti = kwargs['ti']
        status_value = ti.xcom_pull(key='bash_pushed')
        return_value = ti.xcom_pull(task_ids='bash_push')
        print('status_value:' + str(status_value))
        print('return_value:' + return_value)

    bash_push &amp;gt;&amp;gt; python_pull_xcom()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 이렇게 코드를 짜는 것도 가능하다. 마찬가지로 bash_push task에서 두 값을 Xcom에 넣고 python_pull 태스크에서 데이터를 가져온 모습을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/e3t5ZAI.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;Python &amp;amp; email 오퍼레이터 간 Xcom 사용&lt;/h1&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;from airflow import DAG
import pendulum
import datetime
from airflow.decorators import task
from airflow.operators.email import EmailOperator

with DAG(
    dag_id=&quot;dags_python_email_operator&quot;,
    schedule=&quot;0 8 1 * *&quot;,
    start_date=pendulum.datetime(2023, 3, 1, tz=&quot;Asia/Seoul&quot;),
    catchup=False
) as dag:
    
    @task(task_id='something_task')
    def some_logic(**kwargs):
        from random import choice 
        return choice(['Success','Fail'])


    send_email = EmailOperator(
        task_id='send_email',
        to='akfktl328@naver.com',
        subject='{{ data_interval_end.in_timezone(&quot;Asia/Seoul&quot;) | ds }} some_logic 처리결과',
        html_content='{{ data_interval_end.in_timezone(&quot;Asia/Seoul&quot;) | ds }} 처리 결과는 &amp;lt;br&amp;gt; \
                    {{ti.xcom_pull(task_ids=&quot;something_task&quot;)}} 했습니다 &amp;lt;br&amp;gt;'
    )

    some_logic() &amp;gt;&amp;gt; send_email&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드도 크게 어려울 건 없다. something_task에서 단순히 Success인지 Fail인지 랜덤으로 값을 받는다. 그 후 EmailOperator 안에서 xcom_pull을 통해 Success or Fail String값을 반환하고 해당 내용을 이메일로 보내주는 코드이다. 제목은 템플릿을 활용하여 data_interval_end 날짜를 넣어주었다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/6KHEwaM.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;</description>
      <category>airflow</category>
      <category>airflow</category>
      <category>XCOM</category>
      <author>CHOI JEE HYUK</author>
      <guid isPermaLink="true">https://statistics-fox.tistory.com/74</guid>
      <comments>https://statistics-fox.tistory.com/74#entry74comment</comments>
      <pubDate>Mon, 1 Jan 2024 18:52:46 +0900</pubDate>
    </item>
    <item>
      <title>[Airflow] - Macro 변수</title>
      <link>https://statistics-fox.tistory.com/65</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 macro 변수에 대해서 알아보자 jinja 템플릿 내에서 날짜 연산을 가능하게끔 해주는 기능이다. macro가 있다면 만들지 못하는 날짜가 없다고 한다. 스케쥴러를 잘 다루지 못하면 airflow를 잘 다룬다고 할 수 없는 만큼 날짜 연산은 중요하므로 이번 기회에 잘 포스팅 해보고자 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;airflow에서 macro란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;airflow dags를 구성할때 jinja 템플릿 내에서 날짜 연산을 가능하게끔 해주는 기능이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;macro 변수의 필요성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dag 스케줄이 매월 말 일에 도는 스케줄인데 Between 값을 전월 마지막일부터 어제 날짜까지 주고 싶을 경우가 있다고 하자 이때 어제 날짜는 스케쥴러가 끝나는 오늘 날부터 하루 전 날짜이므로 아래처럼 식을 구성해야 하는데 그것을 macro가 해준다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;  {{data_interval_end}} - 1day&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;macro를 지원하는 파이썬의 라이브러리는 다음과 같은데 대부분 &lt;b&gt;datetime, dateutil 라이브러리&lt;/b&gt;만 사용하므로 파이썬의 &lt;b&gt;datetime, dateutil 라이브러리&lt;/b&gt;에 익숙해져야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1292&quot; data-origin-height=&quot;702&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dYFdhi/btsCUAP7eDi/UN3CcJNzofYdU5BgRKKWf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dYFdhi/btsCUAP7eDi/UN3CcJNzofYdU5BgRKKWf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dYFdhi/btsCUAP7eDi/UN3CcJNzofYdU5BgRKKWf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdYFdhi%2FbtsCUAP7eDi%2FUN3CcJNzofYdU5BgRKKWf1%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;1292&quot; height=&quot;702&quot; data-origin-width=&quot;1292&quot; data-origin-height=&quot;702&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;아래는 macro를 사용하여 날짜연산을 해본 간단한 예시이다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;from datetime import datetime
from dateutil import relativedelta

now = datetime(year = 2024, month = 3, day=30)

print(f'현재시간: {str(now)}')
print('------------------월 연산------------------')
print(now + relativedelta.relativedelta(month=1)) # 1월로 변경
print(now.replace(month=1)) # 1월로 변경
print(now + relativedelta.relativedelta(months=-1)) # 1개월 빼기
print('------------------일 연산------------------')
print(now + relativedelta.relativedelta(day=1)) # 1일로 변경
print(now.replace(day=1)) # 1일로 변경
print(now + relativedelta.relativedelta(days=-1)) # 1일 빼기
print('----------------연산 여러 개----------------')
print(now + relativedelta.relativedelta(months=-1) + relativedelta.relativedelta(days=-1)) # 1개월 하고 1일 빼기&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;bash 오퍼레이터에서 macro 사용해보기&lt;/h2&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;from airflow import DAG
import pendulum
from airflow.operators.bash import BashOperator

with DAG(
	dag_id=&quot;dags_bash_with_macro_eg2&quot;,
	schedule=&quot;10 0 * * 6#2&quot;,
	start_date=pendulum.datetime(2023, 3, 1, tz=&quot;Asia/Seoul&quot;),
	catchup=False
) as dag:
	# START_DATE: 2주전 월요일, END_DATE: 2주전 토요일
	bash_task_2 = BashOperator(
		task_id='bash_task_2',
		env={'START_DATE':'{{ (data_interval_end.in_timezone(&quot;Asia/Seoul&quot;) - macros.dateutil.relativedelta.relativedelta(days=19)) | ds}}',
			 'END_DATE':'{{ (data_interval_end.in_timezone(&quot;Asia/Seoul&quot;) - macros.dateutil.relativedelta.relativedelta(days=14)) | ds}}'
		},
		bash_command='echo &quot;START_DATE: $START_DATE&quot; &amp;amp;&amp;amp; echo &quot;END_DATE: $END_DATE&quot;'
	)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매주 둘째 주 토요일 00시 10분에 도는 월 단위 dag이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재는 12월 31일으로 {{data_interval_end}}는 12월의 둘째 주 토요일인 12월 9일이다. 나는 2주전 월요일을 START_DATE로 삼고 싶었기에 -19일 END_DATE는 -14일을 해주었다. 이렇게 진자 템플릿 안에 macro를 사용하여 날짜 연산을 하고 dags 출력결과를 보자&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;970&quot; data-origin-height=&quot;100&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clrTGI/btsCOyF1YOg/VY3OpoqBQENrKEUalqjffk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clrTGI/btsCOyF1YOg/VY3OpoqBQENrKEUalqjffk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clrTGI/btsCOyF1YOg/VY3OpoqBQENrKEUalqjffk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclrTGI%2FbtsCOyF1YOg%2FVY3OpoqBQENrKEUalqjffk%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;970&quot; height=&quot;100&quot; data-origin-width=&quot;970&quot; data-origin-height=&quot;100&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;12월 9일의 19일 전인 11월 20일이 START_DATE로 출력되었으며 END_DATE도 잘 나온 것을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Python 오퍼레이터에서 macro 사용해보기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 작성한 dags 파일을 보자&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;from airflow import DAG
import pendulum
from airflow.decorators import task



with DAG(
	dag_id=&quot;dags_python_with_macro&quot;,
	schedule=&quot;10 0 * * *&quot;,
	start_date=pendulum.datetime(2023, 3, 1, tz=&quot;Asia/Seoul&quot;),
	catchup=False
) as dag:
	# 파이썬 오퍼레이터에서 템플릿과 함께 macro를 사용한 경우
	@task(task_id='task_using_macros',
	  templates_dict={'start_date':'{{ (data_interval_end.in_timezone(&quot;Asia/Seoul&quot;) + macros.dateutil.relativedelta.relativedelta(months=-1, day=1)) | ds }}',
					  'end_date': '{{ (data_interval_end.in_timezone(&quot;Asia/Seoul&quot;).replace(day=1) + macros.dateutil.relativedelta.relativedelta(days=-1)) | ds }}'
	 }
	)
	def get_datetime_macro(**kwargs):
		# 위 templates_dict라는 string이 key값 나머지 전체가 value값으로 들어감 
		templates_dict = kwargs.get('templates_dict') or {}
		if templates_dict:
			start_date = templates_dict.get('start_date') or 'start_date없음' # key 값이 없으면 스트링으로 없다고 할당
			end_date = templates_dict.get('end_date') or 'end_date없음'
			print(start_date)
			print(end_date)

	# dag 안에서 직접 날짜 연산을 한 경우 
	@task(task_id='task_direct_calc')
	def get_datetime_calc(**kwargs):
		from dateutil.relativedelta import relativedelta
		# 스케줄러 부하 경감을 위해 패키지 import 하는 부분을 task 안에 명명 주기적으로 문법 검사를 하는 airflow의 부하를 줄일 수 있음
		# 오퍼레이터 안에만 필요한 함수는 오퍼레이터 안에 선언을 하는게 나중에 신상에 좋음
		data_interval_end = kwargs['data_interval_end']
		prev_month_day_first = data_interval_end.in_timezone('Asia/Seoul') + relativedelta(months=-1, day=1)
		prev_month_day_last = data_interval_end.in_timezone('Asia/Seoul').replace(day=1) + relativedelta(days=-1)
		print(prev_month_day_first.strftime('%Y-%m-%d')) # 날짜 형식 지정
		print(prev_month_day_last.strftime('%Y-%m-%d'))

	get_datetime_macro() &amp;gt;&amp;gt; get_datetime_calc()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 파이썬의 task데코레이터를 사용해서 dags를 구성해봤다. 데일리 스케쥴로 task를 두 개 만들었는데 차이점을 살펴보자&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;task_using_macros task - macro를 사용한 연산&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;말그대로 macro를 사용해서 만든 task다. templates_dict 안에 json 형태로 start_date와 end_date를 넣어 주었는데 이게 get_datetime_macro함수의 인자로 들어간다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특이한 것은 **kwargs로 인자가 들어갈때 start_date가 key로 들어가는 것이 아니라 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&lt;code&gt;templates_dict라는 string 값이 key&lt;/code&gt;&lt;/b&gt;&lt;/span&gt;값으로 들어가고 나머지 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&lt;code&gt;start_date와 end_date전체가 value 값&lt;/code&gt;&lt;/b&gt;&lt;/span&gt;으로 들어간다는 사실이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 함수에 값을 넣어주고 각 date를 출력해주는 함수를 만들었다. 만약 값이 할당되지 않았을 경우에는 그냥 없다고 string 값 할당하도록 안전하게 코딩했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;start_date를 보면 매월 1일 00시 10분에 시작되는 배치에 한 개의 월을 빼고 1일로 고정하라는 의미다. 즉 현재는 12월 data_interval_end는 12월 31일 이므로 11월 1일을 start_date로 하라는 뜻이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;end_date는 전월 말일로 설정하라는 의미로, 현재 달의 1일에서 1일을 빼 전월 말일로 설정하게 된다. 즉, 현재는 12월 이므로 11월 30일이 end_date가 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;874&quot; data-origin-height=&quot;64&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MEbj0/btsCN37mvYs/WciSdD51x3XdQUKTbBS3pk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MEbj0/btsCN37mvYs/WciSdD51x3XdQUKTbBS3pk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MEbj0/btsCN37mvYs/WciSdD51x3XdQUKTbBS3pk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMEbj0%2FbtsCN37mvYs%2FWciSdD51x3XdQUKTbBS3pk%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;874&quot; height=&quot;64&quot; data-origin-width=&quot;874&quot; data-origin-height=&quot;64&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력결과를 보면 동일한 것을 알 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;task_direct_calc task - 직접 연산&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;macro를 사용하지 않고도 날짜 연산은 가능하다. 선행 task가 오퍼레이터 안에서 macro를 사용해 변수를 할당해 준 것 이라면 아예 task의 함수 안에서 replace와 relativedelta를 활용해 직접적으로 날짜 연산을 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;macro를 사용할때는 jinja 템플릿을 사용한 것이기 때문에 ds를 사용해 날짜형식을 지정해 주었다면 이번에는 strftime를 사용해 수동으로 형식을 지정해 주어야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;858&quot; data-origin-height=&quot;56&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beui2y/btsCReUzpg0/K56rUnFgiRzE3iKBj0ojq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beui2y/btsCReUzpg0/K56rUnFgiRzE3iKBj0ojq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beui2y/btsCReUzpg0/K56rUnFgiRzE3iKBj0ojq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbeui2y%2FbtsCReUzpg0%2FK56rUnFgiRzE3iKBj0ojq0%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;858&quot; height=&quot;56&quot; data-origin-width=&quot;858&quot; data-origin-height=&quot;56&quot;/&gt;&lt;/span&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&lt;code&gt;from dateutil.relativedelta import relativedelta&lt;/code&gt;&lt;/b&gt;&lt;/span&gt;함수 선언문을 데코레이터 안에 넣어주었는데 이게 다 이유가 있다. airflow는 정기적으로 스케줄러를 통해 문법 검사를 진행하는데 task 안에 들어가 있는 코드는 검사를 하지않는다. 때문에 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&lt;code&gt;스케줄러 부하 경감&lt;/code&gt;&lt;/b&gt;&lt;/span&gt;&lt;b&gt;을 위해 &lt;/b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&lt;code&gt;오퍼레이터 안에만 필요한 함수는 오퍼레이터 안에 선언&lt;/code&gt;&lt;/b&gt;&lt;/span&gt;&lt;b&gt; 해주는 것이 나중에 대규모 데이터 처리 할때 신상에 좋다.&lt;/b&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>airflow</category>
      <category>airflow</category>
      <category>macro변수</category>
      <author>CHOI JEE HYUK</author>
      <guid isPermaLink="true">https://statistics-fox.tistory.com/65</guid>
      <comments>https://statistics-fox.tistory.com/65#entry65comment</comments>
      <pubDate>Sun, 31 Dec 2023 18:09:43 +0900</pubDate>
    </item>
    <item>
      <title>기능 개발</title>
      <link>https://statistics-fox.tistory.com/64</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42586&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/42586&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1703322115608&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;프로그래머스&quot; data-og-description=&quot;코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42586&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Y1acj/hyUTAYm5HT/MYDzxaxX0kYkRlMoFRJ200/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bcW7nP/hyUTy7kGIL/h2uRbKr426ddZzHeBIbTe0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42586&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42586&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Y1acj/hyUTAYm5HT/MYDzxaxX0kYkRlMoFRJ200/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bcW7nP/hyUTy7kGIL/h2uRbKr426ddZzHeBIbTe0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&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;프로그래머스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&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;
&lt;p data-ke-size=&quot;size16&quot;&gt;흠 이번에는 문제를 조금은 빠르게 풀었다. 리스트 컴프리핸션은 분석 때 많이 쓰던 거라 쉬웠는데 문제를 다 풀고 테스트 케이스 11번이 계속 통과가 안 돼서 보니 D를 만들어 주는 과정에서 &lt;span style=&quot;color: #ef5369; background-color: #dddddd;&quot;&gt;math.ceil()&lt;/span&gt;을 안쓰고 그냥 나눠준 것이 문제였다... 뭔가 계산 방식이 다른 것 같다. ceil을 안 쓰고 푼 다른 사람 풀이를 보니 너무 복잡해서 도무지 이해가 가지 않았다. 또한 이 문제는 내가 푼 것처럼 풀 순 있지만 다른 사람 풀이를 보니 한 명도 나처럼 푼 사람을 찾을 수 없었다. 알고 보니 이 문제는 시간 복잡도라는 알고리즘을 이용해서 푸는 문제였다. 나는 아직 배우지 못한 부분...ㅎㅎ 어찌 됐든 풀긴 했으나 출제의도를 맞추어서 푼 문제는 아니므로 나중에 알고리즘 스터디를 확실히 하면 다시 풀어본 생각이다.&lt;/p&gt;
&lt;pre id=&quot;code_1703322099308&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import math
def solution(progresses, speeds):
    answer = []; c =[]
    remaining_pg = [100 - i for i in progresses]
    D = [math.ceil(x / y) for x, y in zip(remaining_pg, speeds)]
    for i in D:
        if len(c)==0 or c[0] &amp;gt;= i:
            c.append(i)
        else:
            answer.append(len(c)); c = []
            c.append(i)
    answer.append(len(c))
    
    print(D)
    return answer&lt;/code&gt;&lt;/pre&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 시간복잡도 알고리즘을 푼 사람의 코드이다. 나중에 다시 와서 풀어볼 예정이다.&lt;/p&gt;
&lt;pre id=&quot;code_1703322486046&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def solution(progresses, speeds):
    print(progresses)
    print(speeds)
    answer = []
    time = 0
    count = 0
    while len(progresses)&amp;gt; 0:
        if (progresses[0] + time*speeds[0]) &amp;gt;= 100:
            progresses.pop(0)
            speeds.pop(0)
            count += 1
        else:
            if count &amp;gt; 0:
                answer.append(count)
                count = 0
            time += 1
    answer.append(count)
    return answer&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>CodingTest/알고리즘 고득점 Kit</category>
      <category>코딩테스트</category>
      <category>프로그래머스</category>
      <author>CHOI JEE HYUK</author>
      <guid isPermaLink="true">https://statistics-fox.tistory.com/64</guid>
      <comments>https://statistics-fox.tistory.com/64#entry64comment</comments>
      <pubDate>Sat, 23 Dec 2023 18:09:55 +0900</pubDate>
    </item>
    <item>
      <title>자료구조 #1 Array</title>
      <link>https://statistics-fox.tistory.com/62</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;원래는 ipynb 파일을 html로 바꾸고 html의 소스를 붙여넣어 포스팅을 하려 했으나 생각보다 눈에 잘 들어오지도 않고 왜인지 모르겠지만 코드 출력 결과가 엄청 깨져서 나욌다. 때문에 git Gist를 활용하여 nbviewer을 만들어 링크를 공유하는 방식으로 포스팅을 하려고 한다.&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;a href=&quot;https://nbviewer.org/gist/StatisticsFox/45145e49b9f3e403412d33262a67c444&quot;&gt;https://nbviewer.org/gist/StatisticsFox/45145e49b9f3e403412d33262a67c444&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1703150926522&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;Jupyter Notebook Viewer&quot; data-og-description=&quot;&quot; data-og-host=&quot;nbviewer.org&quot; data-og-source-url=&quot;https://nbviewer.org/gist/StatisticsFox/45145e49b9f3e403412d33262a67c444&quot; data-og-url=&quot;https://nbviewer.org/gist/StatisticsFox/45145e49b9f3e403412d33262a67c444&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://nbviewer.org/gist/StatisticsFox/45145e49b9f3e403412d33262a67c444&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://nbviewer.org/gist/StatisticsFox/45145e49b9f3e403412d33262a67c444&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&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;Jupyter Notebook Viewer&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;nbviewer.org&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>
      <category>Python</category>
      <category>Array</category>
      <category>자료구조</category>
      <author>CHOI JEE HYUK</author>
      <guid isPermaLink="true">https://statistics-fox.tistory.com/62</guid>
      <comments>https://statistics-fox.tistory.com/62#entry62comment</comments>
      <pubDate>Thu, 21 Dec 2023 18:27:45 +0900</pubDate>
    </item>
    <item>
      <title>자료구조 #2 Queue</title>
      <link>https://statistics-fox.tistory.com/59</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/require.js/2.1.10/require.min.js&quot;&gt;&lt;/script&gt;
&lt;!-- Load mathjax --&gt;
&lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/latest.js?config=TeX-AMS_CHTML-full,Safe&quot;&gt; &lt;/script&gt;
&lt;!-- MathJax configuration --&gt;
&lt;script type=&quot;text/x-mathjax-config&quot;&gt;
    init_mathjax = function() {
        if (window.MathJax) {
        // MathJax loaded
            MathJax.Hub.Config({
                TeX: {
                    equationNumbers: {
                    autoNumber: &quot;AMS&quot;,
                    useLabelIds: true
                    }
                },
                tex2jax: {
                    inlineMath: [ ['$','$'], [&quot;\\(&quot;,&quot;\\)&quot;] ],
                    displayMath: [ ['$$','$$'], [&quot;\\[&quot;,&quot;\\]&quot;] ],
                    processEscapes: true,
                    processEnvironments: true
                },
                displayAlign: 'center',
                CommonHTML: {
                    linebreaks: {
                    automatic: true
                    }
                }
            });

            MathJax.Hub.Queue([&quot;Typeset&quot;, MathJax.Hub]);
        }
    }
    init_mathjax();
    &lt;/script&gt;
&lt;!-- End of mathjax configuration --&gt;
&lt;div class=&quot;jp-Cell jp-MarkdownCell jp-Notebook-cell&quot;&gt;
&lt;div class=&quot;jp-Cell-inputWrapper&quot;&gt;
&lt;div class=&quot;jp-Collapser jp-InputCollapser jp-Cell-inputCollapser&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-InputArea jp-Cell-inputArea&quot;&gt;
&lt;div class=&quot;jp-InputPrompt jp-InputArea-prompt&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput &quot; data-mime-type=&quot;text/markdown&quot;&gt;
&lt;h1 id=&quot;%ED%81%90(Queue)-%EA%B0%80%EC%9E%A5-%EA%B8%B0%EB%B3%B8-%EC%A0%81%EC%9D%B8-%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-%EC%A4%91-%ED%95%98%EB%82%98&quot;&gt;큐(Queue) 가장 기본 적인 자료구조 중 하나&lt;a class=&quot;anchor-link&quot; href=&quot;#%ED%81%90(Queue)-%EA%B0%80%EC%9E%A5-%EA%B8%B0%EB%B3%B8-%EC%A0%81%EC%9D%B8-%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-%EC%A4%91-%ED%95%98%EB%82%98&quot;&gt;&amp;para;&lt;/a&gt;&lt;/h1&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;jp-Cell jp-MarkdownCell jp-Notebook-cell&quot;&gt;
&lt;div class=&quot;jp-Cell-inputWrapper&quot;&gt;
&lt;div class=&quot;jp-Collapser jp-InputCollapser jp-Cell-inputCollapser&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-InputArea jp-Cell-inputArea&quot;&gt;
&lt;div class=&quot;jp-InputPrompt jp-InputArea-prompt&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput &quot; data-mime-type=&quot;text/markdown&quot;&gt;
&lt;h2 id=&quot;1.-%ED%81%90-%EA%B5%AC%EC%A1%B0&quot; data-ke-size=&quot;size26&quot;&gt;1. 큐 구조&lt;a class=&quot;anchor-link&quot; href=&quot;#1.-%ED%81%90-%EA%B5%AC%EC%A1%B0&quot;&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;jp-Cell jp-MarkdownCell jp-Notebook-cell&quot;&gt;
&lt;div class=&quot;jp-Cell-inputWrapper&quot;&gt;
&lt;div class=&quot;jp-Collapser jp-InputCollapser jp-Cell-inputCollapser&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-InputArea jp-Cell-inputArea&quot;&gt;
&lt;div class=&quot;jp-InputPrompt jp-InputArea-prompt&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput &quot; data-mime-type=&quot;text/markdown&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가장 먼저 넣은 데이터를 가장 먼저 꺼낼 수 있는 구조
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;음식점에서 재료 관리할 때 선입선출하는 것과 동일 함&lt;/li&gt;
&lt;li&gt;FIFO(first-in, first-out) 또는 LILO(Last-in, Last-out) 방식으로 스택과 꺼내는 순서가 반대&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;jp-Cell jp-MarkdownCell jp-Notebook-cell&quot;&gt;
&lt;div class=&quot;jp-Cell-inputWrapper&quot;&gt;
&lt;div class=&quot;jp-Collapser jp-InputCollapser jp-Cell-inputCollapser&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-InputArea jp-Cell-inputArea&quot;&gt;
&lt;div class=&quot;jp-InputPrompt jp-InputArea-prompt&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput &quot; data-mime-type=&quot;text/markdown&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Rcx73/btq0XoA8XYB/Ct5GXjU4SeXp7NMFv7ix4k/img.png&quot; alt=&quot;Queue 구조&quot; /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;jp-Cell jp-MarkdownCell jp-Notebook-cell&quot;&gt;
&lt;div class=&quot;jp-Cell-inputWrapper&quot;&gt;
&lt;div class=&quot;jp-Collapser jp-InputCollapser jp-Cell-inputCollapser&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-InputArea jp-Cell-inputArea&quot;&gt;
&lt;div class=&quot;jp-InputPrompt jp-InputArea-prompt&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput &quot; data-mime-type=&quot;text/markdown&quot;&gt;
&lt;h2 id=&quot;2.-%EC%95%8C%EC%95%84%EB%91%98-%EC%9A%A9%EC%96%B4&quot; data-ke-size=&quot;size26&quot;&gt;2. 알아둘 용어&lt;a class=&quot;anchor-link&quot; href=&quot;#2.-%EC%95%8C%EC%95%84%EB%91%98-%EC%9A%A9%EC%96%B4&quot;&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Enqueue: 큐에 데이터를 넣는 기능&lt;/li&gt;
&lt;li&gt;Dequeue: 큐에서 데이터를 꺼내는 기능&lt;/li&gt;
&lt;li&gt;Visualgo 사이트에서 시연해보며 이해하기: &lt;a href=&quot;https://visualgo.net/en/list&quot;&gt;https://visualgo.net/en/list&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;jp-Cell jp-MarkdownCell jp-Notebook-cell&quot;&gt;
&lt;div class=&quot;jp-Cell-inputWrapper&quot;&gt;
&lt;div class=&quot;jp-Collapser jp-InputCollapser jp-Cell-inputCollapser&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-InputArea jp-Cell-inputArea&quot;&gt;
&lt;div class=&quot;jp-InputPrompt jp-InputArea-prompt&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput &quot; data-mime-type=&quot;text/markdown&quot;&gt;
&lt;h2 id=&quot;3.-queue%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%ED%99%9C%EC%9A%A9%ED%95%98%EC%97%AC-%ED%81%90-%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0&quot; data-ke-size=&quot;size26&quot;&gt;3. queue라이브러리 활용하여 큐 자료구조 사용하기&lt;a class=&quot;anchor-link&quot; href=&quot;#3.-queue%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%ED%99%9C%EC%9A%A9%ED%95%98%EC%97%AC-%ED%81%90-%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0&quot;&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;jp-Cell jp-MarkdownCell jp-Notebook-cell&quot;&gt;
&lt;div class=&quot;jp-Cell-inputWrapper&quot;&gt;
&lt;div class=&quot;jp-Collapser jp-InputCollapser jp-Cell-inputCollapser&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-InputArea jp-Cell-inputArea&quot;&gt;
&lt;div class=&quot;jp-InputPrompt jp-InputArea-prompt&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput &quot; data-mime-type=&quot;text/markdown&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로그램을 작성할 때 프로그램에 따라 적합한 자료구조를 사용한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Queue(): 가장 일반적인 큐 자료&lt;/li&gt;
&lt;li&gt;LifoQueue(): 나중에 입력된 데이터가 먼저 출력되는 구조(스택 구조라고 보면 됨)&lt;/li&gt;
&lt;li&gt;PriorityQueue(): 데이터마다 우선순위를 넣어서 우선순위가 높은 순으로 데이터 출력&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;jp-Cell jp-MarkdownCell jp-Notebook-cell&quot;&gt;
&lt;div class=&quot;jp-Cell-inputWrapper&quot;&gt;
&lt;div class=&quot;jp-Collapser jp-InputCollapser jp-Cell-inputCollapser&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-InputArea jp-Cell-inputArea&quot;&gt;
&lt;div class=&quot;jp-InputPrompt jp-InputArea-prompt&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput &quot; data-mime-type=&quot;text/markdown&quot;&gt;
&lt;h3 id=&quot;3.1-Queue()%EB%A1%9C-%ED%81%90-%EB%A7%8C%EB%93%A4%EA%B8%B0(%EA%B0%80%EC%9E%A5-%EC%9D%BC%EB%B0%98-%EC%A0%81%EC%9D%B8-%ED%81%90)&quot; data-ke-size=&quot;size23&quot;&gt;3.1 Queue()로 큐 만들기(가장 일반 적인 큐)&lt;a class=&quot;anchor-link&quot; href=&quot;#3.1-Queue()%EB%A1%9C-%ED%81%90-%EB%A7%8C%EB%93%A4%EA%B8%B0(%EA%B0%80%EC%9E%A5-%EC%9D%BC%EB%B0%98-%EC%A0%81%EC%9D%B8-%ED%81%90)&quot;&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;jp-Cell jp-CodeCell jp-Notebook-cell jp-mod-noOutputs  &quot;&gt;
&lt;div class=&quot;jp-Cell-inputWrapper&quot;&gt;
&lt;div class=&quot;jp-Collapser jp-InputCollapser jp-Cell-inputCollapser&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-InputArea jp-Cell-inputArea&quot;&gt;
&lt;div class=&quot;jp-InputPrompt jp-InputArea-prompt&quot;&gt;In&amp;nbsp;[1]:&lt;/div&gt;
&lt;div class=&quot;jp-CodeMirrorEditor jp-Editor jp-InputArea-editor&quot; data-type=&quot;inline&quot;&gt;
&lt;div class=&quot;CodeMirror cm-s-jupyter&quot;&gt;
&lt;div class=&quot; highlight hl-ipython3&quot;&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;import queue

data_queue = queue.Queue() # FIFO
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;jp-Cell jp-CodeCell jp-Notebook-cell jp-mod-noOutputs  &quot;&gt;
&lt;div class=&quot;jp-Cell-inputWrapper&quot;&gt;
&lt;div class=&quot;jp-Collapser jp-InputCollapser jp-Cell-inputCollapser&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-InputArea jp-Cell-inputArea&quot;&gt;
&lt;div class=&quot;jp-InputPrompt jp-InputArea-prompt&quot;&gt;In&amp;nbsp;[2]:&lt;/div&gt;
&lt;div class=&quot;jp-CodeMirrorEditor jp-Editor jp-InputArea-editor&quot; data-type=&quot;inline&quot;&gt;
&lt;div class=&quot;CodeMirror cm-s-jupyter&quot;&gt;
&lt;div class=&quot; highlight hl-ipython3&quot;&gt;
&lt;pre class=&quot;gams&quot;&gt;&lt;code&gt;data_queue.put('jihyuk')
data_queue.put(1)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;jp-Cell jp-CodeCell jp-Notebook-cell   &quot;&gt;
&lt;div class=&quot;jp-Cell-inputWrapper&quot;&gt;
&lt;div class=&quot;jp-Collapser jp-InputCollapser jp-Cell-inputCollapser&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-InputArea jp-Cell-inputArea&quot;&gt;
&lt;div class=&quot;jp-InputPrompt jp-InputArea-prompt&quot;&gt;In&amp;nbsp;[3]:&lt;/div&gt;
&lt;div class=&quot;jp-CodeMirrorEditor jp-Editor jp-InputArea-editor&quot; data-type=&quot;inline&quot;&gt;
&lt;div class=&quot;CodeMirror cm-s-jupyter&quot;&gt;
&lt;div class=&quot; highlight hl-ipython3&quot;&gt;
&lt;pre class=&quot;autohotkey&quot;&gt;&lt;code&gt;data_queue.qsize()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;jp-Cell-outputWrapper&quot;&gt;
&lt;div class=&quot;jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-OutputArea jp-Cell-outputArea&quot;&gt;
&lt;div class=&quot;jp-OutputArea-child&quot;&gt;
&lt;div class=&quot;jp-OutputPrompt jp-OutputArea-prompt&quot;&gt;Out[3]:&lt;/div&gt;
&lt;div class=&quot;jp-RenderedText jp-OutputArea-output jp-OutputArea-executeResult&quot; data-mime-type=&quot;text/plain&quot;&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;2&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;jp-Cell jp-CodeCell jp-Notebook-cell   &quot;&gt;
&lt;div class=&quot;jp-Cell-inputWrapper&quot;&gt;
&lt;div class=&quot;jp-Collapser jp-InputCollapser jp-Cell-inputCollapser&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-InputArea jp-Cell-inputArea&quot;&gt;
&lt;div class=&quot;jp-InputPrompt jp-InputArea-prompt&quot;&gt;In&amp;nbsp;[4]:&lt;/div&gt;
&lt;div class=&quot;jp-CodeMirrorEditor jp-Editor jp-InputArea-editor&quot; data-type=&quot;inline&quot;&gt;
&lt;div class=&quot;CodeMirror cm-s-jupyter&quot;&gt;
&lt;div class=&quot; highlight hl-ipython3&quot;&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;data_queue.get()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;jp-Cell-outputWrapper&quot;&gt;
&lt;div class=&quot;jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-OutputArea jp-Cell-outputArea&quot;&gt;
&lt;div class=&quot;jp-OutputArea-child&quot;&gt;
&lt;div class=&quot;jp-OutputPrompt jp-OutputArea-prompt&quot;&gt;Out[4]:&lt;/div&gt;
&lt;div class=&quot;jp-RenderedText jp-OutputArea-output jp-OutputArea-executeResult&quot; data-mime-type=&quot;text/plain&quot;&gt;
&lt;pre class=&quot;scheme&quot;&gt;&lt;code&gt;'jihyuk'&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;jp-Cell jp-CodeCell jp-Notebook-cell   &quot;&gt;
&lt;div class=&quot;jp-Cell-inputWrapper&quot;&gt;
&lt;div class=&quot;jp-Collapser jp-InputCollapser jp-Cell-inputCollapser&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-InputArea jp-Cell-inputArea&quot;&gt;
&lt;div class=&quot;jp-InputPrompt jp-InputArea-prompt&quot;&gt;In&amp;nbsp;[5]:&lt;/div&gt;
&lt;div class=&quot;jp-CodeMirrorEditor jp-Editor jp-InputArea-editor&quot; data-type=&quot;inline&quot;&gt;
&lt;div class=&quot;CodeMirror cm-s-jupyter&quot;&gt;
&lt;div class=&quot; highlight hl-ipython3&quot;&gt;
&lt;pre class=&quot;autohotkey&quot;&gt;&lt;code&gt;data_queue.qsize()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;jp-Cell-outputWrapper&quot;&gt;
&lt;div class=&quot;jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-OutputArea jp-Cell-outputArea&quot;&gt;
&lt;div class=&quot;jp-OutputArea-child&quot;&gt;
&lt;div class=&quot;jp-OutputPrompt jp-OutputArea-prompt&quot;&gt;Out[5]:&lt;/div&gt;
&lt;div class=&quot;jp-RenderedText jp-OutputArea-output jp-OutputArea-executeResult&quot; data-mime-type=&quot;text/plain&quot;&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;jp-Cell jp-CodeCell jp-Notebook-cell   &quot;&gt;
&lt;div class=&quot;jp-Cell-inputWrapper&quot;&gt;
&lt;div class=&quot;jp-Collapser jp-InputCollapser jp-Cell-inputCollapser&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-InputArea jp-Cell-inputArea&quot;&gt;
&lt;div class=&quot;jp-InputPrompt jp-InputArea-prompt&quot;&gt;In&amp;nbsp;[6]:&lt;/div&gt;
&lt;div class=&quot;jp-CodeMirrorEditor jp-Editor jp-InputArea-editor&quot; data-type=&quot;inline&quot;&gt;
&lt;div class=&quot;CodeMirror cm-s-jupyter&quot;&gt;
&lt;div class=&quot; highlight hl-ipython3&quot;&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;data_queue.get()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;jp-Cell-outputWrapper&quot;&gt;
&lt;div class=&quot;jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-OutputArea jp-Cell-outputArea&quot;&gt;
&lt;div class=&quot;jp-OutputArea-child&quot;&gt;
&lt;div class=&quot;jp-OutputPrompt jp-OutputArea-prompt&quot;&gt;Out[6]:&lt;/div&gt;
&lt;div class=&quot;jp-RenderedText jp-OutputArea-output jp-OutputArea-executeResult&quot; data-mime-type=&quot;text/plain&quot;&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;jp-Cell jp-CodeCell jp-Notebook-cell   &quot;&gt;
&lt;div class=&quot;jp-Cell-inputWrapper&quot;&gt;
&lt;div class=&quot;jp-Collapser jp-InputCollapser jp-Cell-inputCollapser&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-InputArea jp-Cell-inputArea&quot;&gt;
&lt;div class=&quot;jp-InputPrompt jp-InputArea-prompt&quot;&gt;In&amp;nbsp;[7]:&lt;/div&gt;
&lt;div class=&quot;jp-CodeMirrorEditor jp-Editor jp-InputArea-editor&quot; data-type=&quot;inline&quot;&gt;
&lt;div class=&quot;CodeMirror cm-s-jupyter&quot;&gt;
&lt;div class=&quot; highlight hl-ipython3&quot;&gt;
&lt;pre class=&quot;autohotkey&quot;&gt;&lt;code&gt;data_queue.qsize()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;jp-Cell-outputWrapper&quot;&gt;
&lt;div class=&quot;jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-OutputArea jp-Cell-outputArea&quot;&gt;
&lt;div class=&quot;jp-OutputArea-child&quot;&gt;
&lt;div class=&quot;jp-OutputPrompt jp-OutputArea-prompt&quot;&gt;Out[7]:&lt;/div&gt;
&lt;div class=&quot;jp-RenderedText jp-OutputArea-output jp-OutputArea-executeResult&quot; data-mime-type=&quot;text/plain&quot;&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;0&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;jp-Cell jp-MarkdownCell jp-Notebook-cell&quot;&gt;
&lt;div class=&quot;jp-Cell-inputWrapper&quot;&gt;
&lt;div class=&quot;jp-Collapser jp-InputCollapser jp-Cell-inputCollapser&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-InputArea jp-Cell-inputArea&quot;&gt;
&lt;div class=&quot;jp-InputPrompt jp-InputArea-prompt&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput &quot; data-mime-type=&quot;text/markdown&quot;&gt;
&lt;h3 id=&quot;3.2-LifoQueue()%EB%A1%9C-%ED%81%90-%EB%A7%8C%EB%93%A4%EA%B8%B0(Last-In,-First-Out)&quot; data-ke-size=&quot;size23&quot;&gt;3.2 LifoQueue()로 큐 만들기(Last-In, First-Out)&lt;a class=&quot;anchor-link&quot; href=&quot;#3.2-LifoQueue()%EB%A1%9C-%ED%81%90-%EB%A7%8C%EB%93%A4%EA%B8%B0(Last-In,-First-Out)&quot;&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;jp-Cell jp-CodeCell jp-Notebook-cell jp-mod-noOutputs  &quot;&gt;
&lt;div class=&quot;jp-Cell-inputWrapper&quot;&gt;
&lt;div class=&quot;jp-Collapser jp-InputCollapser jp-Cell-inputCollapser&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-InputArea jp-Cell-inputArea&quot;&gt;
&lt;div class=&quot;jp-InputPrompt jp-InputArea-prompt&quot;&gt;In&amp;nbsp;[8]:&lt;/div&gt;
&lt;div class=&quot;jp-CodeMirrorEditor jp-Editor jp-InputArea-editor&quot; data-type=&quot;inline&quot;&gt;
&lt;div class=&quot;CodeMirror cm-s-jupyter&quot;&gt;
&lt;div class=&quot; highlight hl-ipython3&quot;&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;import queue
data_queue = queue.LifoQueue() # Lifo
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;jp-Cell jp-CodeCell jp-Notebook-cell jp-mod-noOutputs  &quot;&gt;
&lt;div class=&quot;jp-Cell-inputWrapper&quot;&gt;
&lt;div class=&quot;jp-Collapser jp-InputCollapser jp-Cell-inputCollapser&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-InputArea jp-Cell-inputArea&quot;&gt;
&lt;div class=&quot;jp-InputPrompt jp-InputArea-prompt&quot;&gt;In&amp;nbsp;[9]:&lt;/div&gt;
&lt;div class=&quot;jp-CodeMirrorEditor jp-Editor jp-InputArea-editor&quot; data-type=&quot;inline&quot;&gt;
&lt;div class=&quot;CodeMirror cm-s-jupyter&quot;&gt;
&lt;div class=&quot; highlight hl-ipython3&quot;&gt;
&lt;pre class=&quot;gams&quot;&gt;&lt;code&gt;data_queue.put('jihyuk')
data_queue.put(9999999)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;jp-Cell jp-CodeCell jp-Notebook-cell   &quot;&gt;
&lt;div class=&quot;jp-Cell-inputWrapper&quot;&gt;
&lt;div class=&quot;jp-Collapser jp-InputCollapser jp-Cell-inputCollapser&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-InputArea jp-Cell-inputArea&quot;&gt;
&lt;div class=&quot;jp-InputPrompt jp-InputArea-prompt&quot;&gt;In&amp;nbsp;[10]:&lt;/div&gt;
&lt;div class=&quot;jp-CodeMirrorEditor jp-Editor jp-InputArea-editor&quot; data-type=&quot;inline&quot;&gt;
&lt;div class=&quot;CodeMirror cm-s-jupyter&quot;&gt;
&lt;div class=&quot; highlight hl-ipython3&quot;&gt;
&lt;pre class=&quot;autohotkey&quot;&gt;&lt;code&gt;data_queue.qsize()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;jp-Cell-outputWrapper&quot;&gt;
&lt;div class=&quot;jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-OutputArea jp-Cell-outputArea&quot;&gt;
&lt;div class=&quot;jp-OutputArea-child&quot;&gt;
&lt;div class=&quot;jp-OutputPrompt jp-OutputArea-prompt&quot;&gt;Out[10]:&lt;/div&gt;
&lt;div class=&quot;jp-RenderedText jp-OutputArea-output jp-OutputArea-executeResult&quot; data-mime-type=&quot;text/plain&quot;&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;2&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;jp-Cell jp-CodeCell jp-Notebook-cell   &quot;&gt;
&lt;div class=&quot;jp-Cell-inputWrapper&quot;&gt;
&lt;div class=&quot;jp-Collapser jp-InputCollapser jp-Cell-inputCollapser&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-InputArea jp-Cell-inputArea&quot;&gt;
&lt;div class=&quot;jp-InputPrompt jp-InputArea-prompt&quot;&gt;In&amp;nbsp;[11]:&lt;/div&gt;
&lt;div class=&quot;jp-CodeMirrorEditor jp-Editor jp-InputArea-editor&quot; data-type=&quot;inline&quot;&gt;
&lt;div class=&quot;CodeMirror cm-s-jupyter&quot;&gt;
&lt;div class=&quot; highlight hl-ipython3&quot;&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;data_queue.get()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;jp-Cell-outputWrapper&quot;&gt;
&lt;div class=&quot;jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-OutputArea jp-Cell-outputArea&quot;&gt;
&lt;div class=&quot;jp-OutputArea-child&quot;&gt;
&lt;div class=&quot;jp-OutputPrompt jp-OutputArea-prompt&quot;&gt;Out[11]:&lt;/div&gt;
&lt;div class=&quot;jp-RenderedText jp-OutputArea-output jp-OutputArea-executeResult&quot; data-mime-type=&quot;text/plain&quot;&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;9999999&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;jp-Cell jp-MarkdownCell jp-Notebook-cell&quot;&gt;
&lt;div class=&quot;jp-Cell-inputWrapper&quot;&gt;
&lt;div class=&quot;jp-Collapser jp-InputCollapser jp-Cell-inputCollapser&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-InputArea jp-Cell-inputArea&quot;&gt;
&lt;div class=&quot;jp-InputPrompt jp-InputArea-prompt&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput &quot; data-mime-type=&quot;text/markdown&quot;&gt;
&lt;h3 id=&quot;3.3-PriorityQueue()%EB%A1%9C-%ED%81%90-%EB%A7%8C%EB%93%A4%EA%B8%B0&quot; data-ke-size=&quot;size23&quot;&gt;3.3 PriorityQueue()로 큐 만들기&lt;a class=&quot;anchor-link&quot; href=&quot;#3.3-PriorityQueue()%EB%A1%9C-%ED%81%90-%EB%A7%8C%EB%93%A4%EA%B8%B0&quot;&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;jp-Cell jp-CodeCell jp-Notebook-cell jp-mod-noOutputs  &quot;&gt;
&lt;div class=&quot;jp-Cell-inputWrapper&quot;&gt;
&lt;div class=&quot;jp-Collapser jp-InputCollapser jp-Cell-inputCollapser&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-InputArea jp-Cell-inputArea&quot;&gt;
&lt;div class=&quot;jp-InputPrompt jp-InputArea-prompt&quot;&gt;In&amp;nbsp;[12]:&lt;/div&gt;
&lt;div class=&quot;jp-CodeMirrorEditor jp-Editor jp-InputArea-editor&quot; data-type=&quot;inline&quot;&gt;
&lt;div class=&quot;CodeMirror cm-s-jupyter&quot;&gt;
&lt;div class=&quot; highlight hl-ipython3&quot;&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;import queue
data_queue = queue.PriorityQueue()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;jp-Cell jp-CodeCell jp-Notebook-cell jp-mod-noOutputs  &quot;&gt;
&lt;div class=&quot;jp-Cell-inputWrapper&quot;&gt;
&lt;div class=&quot;jp-Collapser jp-InputCollapser jp-Cell-inputCollapser&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-InputArea jp-Cell-inputArea&quot;&gt;
&lt;div class=&quot;jp-InputPrompt jp-InputArea-prompt&quot;&gt;In&amp;nbsp;[13]:&lt;/div&gt;
&lt;div class=&quot;jp-CodeMirrorEditor jp-Editor jp-InputArea-editor&quot; data-type=&quot;inline&quot;&gt;
&lt;div class=&quot;CodeMirror cm-s-jupyter&quot;&gt;
&lt;div class=&quot; highlight hl-ipython3&quot;&gt;
&lt;pre class=&quot;gams&quot;&gt;&lt;code&gt;data_queue.put((10, 'jihyuk'))
data_queue.put((5, 9999999))
data_queue.put((15, 'Korea'))
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;jp-Cell jp-CodeCell jp-Notebook-cell   &quot;&gt;
&lt;div class=&quot;jp-Cell-inputWrapper&quot;&gt;
&lt;div class=&quot;jp-Collapser jp-InputCollapser jp-Cell-inputCollapser&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-InputArea jp-Cell-inputArea&quot;&gt;
&lt;div class=&quot;jp-InputPrompt jp-InputArea-prompt&quot;&gt;In&amp;nbsp;[14]:&lt;/div&gt;
&lt;div class=&quot;jp-CodeMirrorEditor jp-Editor jp-InputArea-editor&quot; data-type=&quot;inline&quot;&gt;
&lt;div class=&quot;CodeMirror cm-s-jupyter&quot;&gt;
&lt;div class=&quot; highlight hl-ipython3&quot;&gt;
&lt;pre class=&quot;autohotkey&quot;&gt;&lt;code&gt;data_queue.qsize()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;jp-Cell-outputWrapper&quot;&gt;
&lt;div class=&quot;jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-OutputArea jp-Cell-outputArea&quot;&gt;
&lt;div class=&quot;jp-OutputArea-child&quot;&gt;
&lt;div class=&quot;jp-OutputPrompt jp-OutputArea-prompt&quot;&gt;Out[14]:&lt;/div&gt;
&lt;div class=&quot;jp-RenderedText jp-OutputArea-output jp-OutputArea-executeResult&quot; data-mime-type=&quot;text/plain&quot;&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;3&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;jp-Cell jp-CodeCell jp-Notebook-cell   &quot;&gt;
&lt;div class=&quot;jp-Cell-inputWrapper&quot;&gt;
&lt;div class=&quot;jp-Collapser jp-InputCollapser jp-Cell-inputCollapser&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-InputArea jp-Cell-inputArea&quot;&gt;
&lt;div class=&quot;jp-InputPrompt jp-InputArea-prompt&quot;&gt;In&amp;nbsp;[15]:&lt;/div&gt;
&lt;div class=&quot;jp-CodeMirrorEditor jp-Editor jp-InputArea-editor&quot; data-type=&quot;inline&quot;&gt;
&lt;div class=&quot;CodeMirror cm-s-jupyter&quot;&gt;
&lt;div class=&quot; highlight hl-ipython3&quot;&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;data_queue.get()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;jp-Cell-outputWrapper&quot;&gt;
&lt;div class=&quot;jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-OutputArea jp-Cell-outputArea&quot;&gt;
&lt;div class=&quot;jp-OutputArea-child&quot;&gt;
&lt;div class=&quot;jp-OutputPrompt jp-OutputArea-prompt&quot;&gt;Out[15]:&lt;/div&gt;
&lt;div class=&quot;jp-RenderedText jp-OutputArea-output jp-OutputArea-executeResult&quot; data-mime-type=&quot;text/plain&quot;&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;(5, 9999999)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;jp-Cell jp-MarkdownCell jp-Notebook-cell&quot;&gt;
&lt;div class=&quot;jp-Cell-inputWrapper&quot;&gt;
&lt;div class=&quot;jp-Collapser jp-InputCollapser jp-Cell-inputCollapser&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-InputArea jp-Cell-inputArea&quot;&gt;
&lt;div class=&quot;jp-InputPrompt jp-InputArea-prompt&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput &quot; data-mime-type=&quot;text/markdown&quot;&gt;
&lt;h2 id=&quot;%EC%B0%B8%EA%B3%A0:-%EC%96%B4%EB%94%94%EC%97%90-%ED%81%90%EA%B0%80-%EB%A7%8E%EC%9D%B4-%EC%93%B0%EC%9D%BC%EA%B9%8C?&quot; data-ke-size=&quot;size26&quot;&gt;참고: 어디에 큐가 많이 쓰일까?&lt;a class=&quot;anchor-link&quot; href=&quot;#%EC%B0%B8%EA%B3%A0:-%EC%96%B4%EB%94%94%EC%97%90-%ED%81%90%EA%B0%80-%EB%A7%8E%EC%9D%B4-%EC%93%B0%EC%9D%BC%EA%B9%8C?&quot;&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;jp-Cell jp-MarkdownCell jp-Notebook-cell&quot;&gt;
&lt;div class=&quot;jp-Cell-inputWrapper&quot;&gt;
&lt;div class=&quot;jp-Collapser jp-InputCollapser jp-Cell-inputCollapser&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-InputArea jp-Cell-inputArea&quot;&gt;
&lt;div class=&quot;jp-InputPrompt jp-InputArea-prompt&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput &quot; data-mime-type=&quot;text/markdown&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;멀티 태스킹을 위한 프로세스 스케쥴링 방식을 구현하기 위해 많이 사용됨 (운영체제 참조)&lt;br /&gt;-&amp;gt; 큐의 장단점 보다는, 큐의 활용 예로 프로세스 스케쥴링 방식을 함꼐 이해해두는 것이 좋다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;jp-Cell jp-MarkdownCell jp-Notebook-cell&quot;&gt;
&lt;div class=&quot;jp-Cell-inputWrapper&quot;&gt;
&lt;div class=&quot;jp-Collapser jp-InputCollapser jp-Cell-inputCollapser&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-InputArea jp-Cell-inputArea&quot;&gt;
&lt;div class=&quot;jp-InputPrompt jp-InputArea-prompt&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput &quot; data-mime-type=&quot;text/markdown&quot;&gt;
&lt;h3 id=&quot;%EB%A6%AC%EC%8A%A4%ED%8A%B8-%EB%B3%80%EC%88%98%EB%A1%9C-%ED%81%90%EB%A5%BC-%EB%8B%A4%EB%A3%A8%EB%8A%94-enqueue,-dequeue-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84%ED%95%B4%EB%B3%B4%EA%B8%B0&quot; data-ke-size=&quot;size23&quot;&gt;리스트 변수로 큐를 다루는 enqueue, dequeue 기능 구현해보기&lt;a class=&quot;anchor-link&quot; href=&quot;#%EB%A6%AC%EC%8A%A4%ED%8A%B8-%EB%B3%80%EC%88%98%EB%A1%9C-%ED%81%90%EB%A5%BC-%EB%8B%A4%EB%A3%A8%EB%8A%94-enqueue,-dequeue-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84%ED%95%B4%EB%B3%B4%EA%B8%B0&quot;&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;jp-Cell jp-CodeCell jp-Notebook-cell jp-mod-noOutputs  &quot;&gt;
&lt;div class=&quot;jp-Cell-inputWrapper&quot;&gt;
&lt;div class=&quot;jp-Collapser jp-InputCollapser jp-Cell-inputCollapser&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-InputArea jp-Cell-inputArea&quot;&gt;
&lt;div class=&quot;jp-InputPrompt jp-InputArea-prompt&quot;&gt;In&amp;nbsp;[16]:&lt;/div&gt;
&lt;div class=&quot;jp-CodeMirrorEditor jp-Editor jp-InputArea-editor&quot; data-type=&quot;inline&quot;&gt;
&lt;div class=&quot;CodeMirror cm-s-jupyter&quot;&gt;
&lt;div class=&quot; highlight hl-ipython3&quot;&gt;
&lt;pre class=&quot;haskell&quot;&gt;&lt;code&gt;queue_list = []

def enqueue(data):
    queue_list.append(data)

def dequeue():
    data = queue_list[0]
    del queue_list[0]
    return data
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;jp-Cell jp-CodeCell jp-Notebook-cell jp-mod-noOutputs  &quot;&gt;
&lt;div class=&quot;jp-Cell-inputWrapper&quot;&gt;
&lt;div class=&quot;jp-Collapser jp-InputCollapser jp-Cell-inputCollapser&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-InputArea jp-Cell-inputArea&quot;&gt;
&lt;div class=&quot;jp-InputPrompt jp-InputArea-prompt&quot;&gt;In&amp;nbsp;[17]:&lt;/div&gt;
&lt;div class=&quot;jp-CodeMirrorEditor jp-Editor jp-InputArea-editor&quot; data-type=&quot;inline&quot;&gt;
&lt;div class=&quot;CodeMirror cm-s-jupyter&quot;&gt;
&lt;div class=&quot; highlight hl-ipython3&quot;&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;for i in range(10):
    enqueue(i)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;jp-Cell jp-CodeCell jp-Notebook-cell   &quot;&gt;
&lt;div class=&quot;jp-Cell-inputWrapper&quot;&gt;
&lt;div class=&quot;jp-Collapser jp-InputCollapser jp-Cell-inputCollapser&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-InputArea jp-Cell-inputArea&quot;&gt;
&lt;div class=&quot;jp-InputPrompt jp-InputArea-prompt&quot;&gt;In&amp;nbsp;[18]:&lt;/div&gt;
&lt;div class=&quot;jp-CodeMirrorEditor jp-Editor jp-InputArea-editor&quot; data-type=&quot;inline&quot;&gt;
&lt;div class=&quot;CodeMirror cm-s-jupyter&quot;&gt;
&lt;div class=&quot; highlight hl-ipython3&quot;&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;len(queue_list)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;jp-Cell-outputWrapper&quot;&gt;
&lt;div class=&quot;jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-OutputArea jp-Cell-outputArea&quot;&gt;
&lt;div class=&quot;jp-OutputArea-child&quot;&gt;
&lt;div class=&quot;jp-OutputPrompt jp-OutputArea-prompt&quot;&gt;Out[18]:&lt;/div&gt;
&lt;div class=&quot;jp-RenderedText jp-OutputArea-output jp-OutputArea-executeResult&quot; data-mime-type=&quot;text/plain&quot;&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;10&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;jp-Cell jp-CodeCell jp-Notebook-cell   &quot;&gt;
&lt;div class=&quot;jp-Cell-inputWrapper&quot;&gt;
&lt;div class=&quot;jp-Collapser jp-InputCollapser jp-Cell-inputCollapser&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-InputArea jp-Cell-inputArea&quot;&gt;
&lt;div class=&quot;jp-InputPrompt jp-InputArea-prompt&quot;&gt;In&amp;nbsp;[19]:&lt;/div&gt;
&lt;div class=&quot;jp-CodeMirrorEditor jp-Editor jp-InputArea-editor&quot; data-type=&quot;inline&quot;&gt;
&lt;div class=&quot;CodeMirror cm-s-jupyter&quot;&gt;
&lt;div class=&quot; highlight hl-ipython3&quot;&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;dequeue()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;jp-Cell-outputWrapper&quot;&gt;
&lt;div class=&quot;jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;jp-OutputArea jp-Cell-outputArea&quot;&gt;
&lt;div class=&quot;jp-OutputArea-child&quot;&gt;
&lt;div class=&quot;jp-OutputPrompt jp-OutputArea-prompt&quot;&gt;Out[19]:&lt;/div&gt;
&lt;div class=&quot;jp-RenderedText jp-OutputArea-output jp-OutputArea-executeResult&quot; data-mime-type=&quot;text/plain&quot;&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;0&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
      <category>Python</category>
      <author>CHOI JEE HYUK</author>
      <guid isPermaLink="true">https://statistics-fox.tistory.com/59</guid>
      <comments>https://statistics-fox.tistory.com/59#entry59comment</comments>
      <pubDate>Thu, 21 Dec 2023 17:56:13 +0900</pubDate>
    </item>
    <item>
      <title>[Airflow] - Airflow의 날짜개념</title>
      <link>https://statistics-fox.tistory.com/58</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;airflow는 데이터 처리를 목적으로 만들어진 툴이기 때문에 우리도 ETL 처리를 할 때 데이터 관점에서 생각해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 데이터 추출 예시&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;604&quot; data-origin-height=&quot;414&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cNMJxw/btsClJAuBPY/k8WcLFmTBwvwnDX23DjPC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cNMJxw/btsClJAuBPY/k8WcLFmTBwvwnDX23DjPC1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cNMJxw/btsClJAuBPY/k8WcLFmTBwvwnDX23DjPC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcNMJxw%2FbtsClJAuBPY%2Fk8WcLFmTBwvwnDX23DjPC1%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;492&quot; height=&quot;337&quot; data-origin-width=&quot;604&quot; data-origin-height=&quot;414&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 예시 테이블이 있다고 생각해보자 이러한 테이블을 조회하기 위한 Daily ETL 조회쿼리는 다음과 같다.(2023/02/25 0시 실행)&lt;/p&gt;
&lt;pre id=&quot;code_1703052291309&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT NAME, ADDRESS
FROM TBL_REG
WHERE REG_DATE BETWEEN TIMESTAMP(&amp;lsquo;2023-02-24 00:00:00&amp;rsquo;)
AND TIMESTAMP(&amp;lsquo;2023-02-24 23:59:59&amp;rsquo;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 쿼리를 보면 실제 쿼리가 시작되는 시점은 2023/02/25 0시이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 관점에서 보면 다음과 같다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;데이터 관점의 시작일: 2023-02-24&lt;br /&gt;데이터 관점의 종료일: 2023-02-25&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;이걸 자연스럽게 DAG를 구성할 때 변수로 넣어주어야 하는데 이때 &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;Airflow&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;날짜&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;Template&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;변수를 이용해 값들을 가져올 수 있다.&lt;/span&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;span style=&quot;letter-spacing: 0px;&quot;&gt;2. Airflow&amp;nbsp;날짜&amp;nbsp;Template&amp;nbsp;변수&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1016&quot; data-origin-height=&quot;308&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5OKlM/btsCozw4wSG/TDIIzV1LlPFFEOSpJhwPg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5OKlM/btsCozw4wSG/TDIIzV1LlPFFEOSpJhwPg0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5OKlM/btsCozw4wSG/TDIIzV1LlPFFEOSpJhwPg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5OKlM%2FbtsCozw4wSG%2FTDIIzV1LlPFFEOSpJhwPg0%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;1016&quot; height=&quot;308&quot; data-origin-width=&quot;1016&quot; data-origin-height=&quot;308&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;이때 이전 배치일 즉 25일에 배치 시 처리하는 날짜 범위의 첫부분(2023-02-24)은 다음과 같은 명령어로 가져올 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;data_intervel_start&lt;/li&gt;
&lt;li&gt;dag_run.logical_date&lt;/li&gt;
&lt;li&gt;ds(yyyy-mm-dd 형식)&lt;/li&gt;
&lt;li&gt;ls(타임스탬프)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배치당일 즉 배치시 처리하는 날짜의 마지막 부분(2023-02-25)은 다음과 같은 명령어로 가져올 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;data_intervel_end&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 실제 dags 코드를 통해 구현해 보면 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1703052984416&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from airflow import DAG
import pendulum
import datetime
from airflow.operators.python import PythonOperator
from airflow.decorators import task

with DAG(
    dag_id=&quot;dags_python_template&quot;,
    schedule=&quot;30 9 * * *&quot;,
    start_date=pendulum.datetime(2023, 3, 10, tz=&quot;Asia/Seoul&quot;),
    catchup=False
) as dag:
    
    def python_function1(start_date, end_date, **kwargs):
        print(start_date)
        print(end_date)

    python_t1 = PythonOperator(
        task_id='python_t1',
        python_callable=python_function1,
        op_kwargs={'start_date':'{{data_interval_start | ds}}', 'end_date':'{{data_interval_end | ds}}'}
    )
    
    @task(task_id='python_t2')
    def python_function2(**kwargs):
        print('ds:' + kwargs['ds'])
        print('ts:' + kwargs['ts'])
        print('data_interval_start:' + str(kwargs['data_interval_start']))
        print('data_interval_end:' + str(kwargs['data_interval_end']))

    python_t1 &amp;gt;&amp;gt; python_function2()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 두 개의 task가 정의되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;python1 task -&amp;gt; op_kwargs 파라미터에 &lt;a href=&quot;https://statistics-fox.tistory.com/57&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;jinja templates&lt;/a&gt;를 적용하여 data_interval_start과 data_interval_end을 가져온 것&lt;br /&gt;python2 task -&amp;gt; kwargs 안에 있는 data_interval_end과 data_interval_start를 비롯한 다양한 날짜 호출 값들을 인덱싱으로 꺼내온&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;task1의 출력값은 다음과 같다. 데이터 관점에서 starttime과 endtime이 잘 출력된 것을 알 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;496&quot; data-origin-height=&quot;126&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cPa66i/btsCkmsgnTV/YzWIpSmcm4AgO4NnDTKvu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cPa66i/btsCkmsgnTV/YzWIpSmcm4AgO4NnDTKvu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cPa66i/btsCkmsgnTV/YzWIpSmcm4AgO4NnDTKvu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcPa66i%2FbtsCkmsgnTV%2FYzWIpSmcm4AgO4NnDTKvu0%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;425&quot; height=&quot;108&quot; data-origin-width=&quot;496&quot; data-origin-height=&quot;126&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;task2의 출력값은 다음과 같다. 마찬가지로 잘 출력된 것을 알 수 있으며 ds와 ts 역시 형식에 맞추어 잘 출력된 것을 알 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1438&quot; data-origin-height=&quot;264&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccbYcv/btsCok1e48n/Hm8TpAlOcqkKKOBmNtCogK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccbYcv/btsCok1e48n/Hm8TpAlOcqkKKOBmNtCogK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccbYcv/btsCok1e48n/Hm8TpAlOcqkKKOBmNtCogK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FccbYcv%2FbtsCok1e48n%2FHm8TpAlOcqkKKOBmNtCogK%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;1438&quot; height=&quot;264&quot; data-origin-width=&quot;1438&quot; data-origin-height=&quot;264&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;task가 나뉜 것을 그저 원하는 배치 시점을 가져오는 코드를 두 개로 나눈 것에 불과하다. 둘 중 편한 것 을 쓰면 된다. 나는 task 데코레이터를 사용하는 2번 task 방법이 더 편해 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>airflow</category>
      <category>airflow</category>
      <category>날짜</category>
      <category>데이터관점</category>
      <author>CHOI JEE HYUK</author>
      <guid isPermaLink="true">https://statistics-fox.tistory.com/58</guid>
      <comments>https://statistics-fox.tistory.com/58#entry58comment</comments>
      <pubDate>Wed, 20 Dec 2023 15:53:51 +0900</pubDate>
    </item>
  </channel>
</rss>