<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>do_theji.log</title>
        <link>https://velog.io/</link>
        <description>일단 하긴 합니다.</description>
        <lastBuildDate>Wed, 16 Jul 2025 07:47:04 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>do_theji.log</title>
            <url>https://velog.velcdn.com/images/woojin-devv/profile/8e8dfaa4-1bb6-4437-a10b-bee997a4f547/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. do_theji.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/woojin-devv" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[프로그래머스] 탐욕기법(Greedy) | 구명보트 ]]></title>
            <link>https://velog.io/@woojin-devv/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EA%B5%AC%EB%AA%85%EB%B3%B4%ED%8A%B8</link>
            <guid>https://velog.io/@woojin-devv/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EA%B5%AC%EB%AA%85%EB%B3%B4%ED%8A%B8</guid>
            <pubDate>Wed, 16 Jul 2025 07:47:04 GMT</pubDate>
            <description><![CDATA[<h2 id="1-문제-설명">1. 문제 설명</h2>
<blockquote>
<p>무인도에 갇힌 사람들을 구명보트를 이용하여 구출하려고 합니다. 구명보트는 작아서 한 번에 <em><strong>최대 2명씩</strong></em> 밖에 탈 수 없고, _<strong>무게 제한</strong>_도 있습니다.예를 들어, 사람들의 몸무게가 [70kg, 50kg, 80kg, 50kg]이고 구명보트의 무게 제한이 100kg이라면 2번째 사람과 4번째 사람은 같이 탈 수 있지만 1번째 사람과 3번째 사람의 무게의 합은 150kg이므로 구명보트의 무게 제한을 초과하여 같이 탈 수 없습니다.구명보트를 최대한 적게 사용하여 모든 사람을 구출하려고 합니다.사람들의 몸무게를 담은 배열 people과 구명보트의 무게 제한 limit가 매개변수로 주어질 때, 모든 사람을 구출하기 위해 필요한 구명보트 개수의 최솟값을 return 하도록 solution 함수를 작성해주세요.</p>
</blockquote>
<h2 id="2-제한사항">2. 제한사항</h2>
<blockquote>
<p>무인도에 갇힌 사람은 1명 이상 50,000명 이하입니다.
각 사람의 몸무게는 40kg 이상 240kg 이하입니다.
구명보트의 무게 제한은 40kg 이상 240kg 이하입니다.
<strong>구명보트의 무게 제한은 항상 사람들의 몸무게 중 최댓값보다 크게</strong> 주어지므로 사람들을 구출할 수 없는 경우는 없습니다.</p>
</blockquote>
<h2 id="3-해결전략">3. 해결전략</h2>
<blockquote>
<ul>
<li>최대 2명, 혹은 1명씩 빠져나갈 수 있음 </li>
</ul>
</blockquote>
<ul>
<li>최대 2명을 기준으로 people의 원소의 수를 줄여나감</li>
<li>return은 보트의 수 </li>
</ul>
<h2 id="4-pseudo-code">4. pseudo code</h2>
<pre><code>p = people
p의 원소의 개수가 0이 될 때 까지, 

if, max(p) + min(p) &gt; L
    배열에서 max(p)만 제거 
    count ++

else if, max(p) + min(p) &lt;= L
         배열에서 max(p) and min(p) 제거 
         count ++

else if, people의 원소가 1개 남으면, 제거 
         count ++</code></pre><h2 id="5-pseudo-code대로-구현--문제점-발생">5. pseudo code대로 구현 &amp; 문제점 발생</h2>
<blockquote>
<pre><code class="language-python3">def solution(people, limit):
    count = 0
    while people: #O(n)
        if len(people) == 1:
            people.pop() 
            count += 1
        elif max(people) + min(people) &gt; limit: #min(n), max(n)각각 O(n)
            people.remove(max(people))
            count += 1
        else:
            people.remove(min(people))
            people.remove(max(people))
            count += 1
    return count</code></pre>
</blockquote>
<pre><code>- people을 순회할 때, 시간복잡도 $O(n)$
- min(people), max(people) 각각 $O(n)$
- 총, $O(n^2)$의 시간복잡도 (효율성에서 시간초과 남.)


## 6. 해결책 
&gt; - people을 `sorted()`함수로 정렬 
- people의 index값 중 가장 낮은 값, 가장 높은 값을 min(people), max(people)로 치환하여 생각하면 됨
```python
def solution(people, limit):
    people.sort()  # 오름차순 정렬, O(n log n)
    i, j = 0, len(people) - 1
    count = 0
    while i &lt;= j:
        if people[i] + people[j] &lt;= limit:
            i += 1  # 가장 가벼운 사람 태움
        # 무거운 사람은 항상 태움
        j -= 1
        count += 1
    return count</code></pre><ul>
<li>python <code>sorted()</code> 함수의 시간복잡도 $O(n \log n)$ </li>
<li>while 문 시간복잡도 $O(n)$</li>
<li>$O(n \log n) + O(n) \rightarrow O(n)$</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[flutter | 상태관리에서 놓치기 쉬운 객체 동등성]]></title>
            <link>https://velog.io/@woojin-devv/flutter-%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC%EC%97%90%EC%84%9C-%EB%86%93%EC%B9%98%EA%B8%B0-%EC%89%AC%EC%9A%B4-%EA%B0%9D%EC%B2%B4-%EB%8F%99%EB%93%B1%EC%84%B1</link>
            <guid>https://velog.io/@woojin-devv/flutter-%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC%EC%97%90%EC%84%9C-%EB%86%93%EC%B9%98%EA%B8%B0-%EC%89%AC%EC%9A%B4-%EA%B0%9D%EC%B2%B4-%EB%8F%99%EB%93%B1%EC%84%B1</guid>
            <pubDate>Tue, 01 Jul 2025 14:01:47 GMT</pubDate>
            <description><![CDATA[<h1 id="1-서론">1. 서론</h1>
<blockquote>
<p><em>일단, Object equality를 비교하기 전에 <strong>Dart의 타입 시스템</strong>, 특히 <code>final</code>과 <code>const</code>의 차이를 명확히 이해하는 것이 중요하다. Dart 문법 공부 처음할 땐 대충 넘겼지만ㅎㅎ(뭐.. 하다보니 깨달을 수도 있는거죠..그냥 api 받아와서 UI그리면 그만인 줄 알았어요)</em></p>
</blockquote>
<h3 id="11-짚고-넘어가야하는-이유">1.1 짚고 넘어가야하는 이유</h3>
<ul>
<li>외부에서 들어온 데이터를 <code>List</code>나 <code>State</code>로 관리할 때, 객체의 값은 같은데 <code>==</code> 비교가 <code>false</code>가 나오면?</li>
<li>불필요한 <code>setState()</code> 발생, 위젯 리빌드, 캐시 미스 등의 문제가 생긴다.</li>
<li>즉, 객체가 같다고 판단되게 하려면 단순히 <code>값</code>만 같아선 안 되고, <strong>객체 비교 기준을 직접 정의</strong>해야 한다.</li>
</ul>
<h3 id="12-final과-const의-차이점-비교">1.2 final과 const의 차이점 비교</h3>
<table>
<thead>
<tr>
<th>구분</th>
<th>final</th>
<th>const</th>
</tr>
</thead>
<tbody><tr>
<td>선언 시점</td>
<td>런타임에 결정됨</td>
<td>컴파일 타임에 결정됨</td>
</tr>
<tr>
<td>인스턴스 생성</td>
<td>매번 새 객체</td>
<td>같은 값이면 재사용 (싱글턴처럼)</td>
</tr>
<tr>
<td>메모리</td>
<td>각각 따로 있음</td>
<td>공유됨 (정적 메모리로 할당됨)</td>
</tr>
<tr>
<td>동일성 (<code>==</code> 또는 <code>identical</code>)</td>
<td>값이 같아도 <code>false</code></td>
<td>값이 같으면 <code>true</code></td>
</tr>
</tbody></table>
<blockquote>
<h3 id="다음과-같은-예제를-살펴보자">다음과 같은 예제를 살펴보자</h3>
</blockquote>
<ul>
<li>먼저 <code>Person</code> 객체를 정의한다.</li>
<li><code>Person</code> 클래스의 property는 <code>id</code>, <code>name</code>, <code>email</code>이다.</li>
</ul>
<h3 id="13-persondart-person-객체-생성">1.3 person.dart (person 객체 생성)</h3>
<pre><code class="language-dart">// ignore_for_file: public_member_api_docs, sort_constructors_first
class Person {
  final int id;
  final String name;
  final String email;

  Person({required this.id, required this.name, required this.email});

  @override
  String toString() =&gt; &#39;Person(id: $id, name: $name, email: $email)&#39;;

  Person copyWith({int? id, String? name, String? email}) {
    return Person(
      id: id ?? this.id,
      name: name ?? this.name,
      email: email ?? this.email,
    );
  }
}
</code></pre>
<h3 id="person_pagedart">person_page.dart</h3>
<pre><code class="language-dart">import &#39;package:dart_class/models/person.dart&#39;;
import &#39;package:flutter/material.dart&#39;;

class PersonPage extends StatelessWidget {
  const PersonPage({super.key});

  @override
  Widget build(BuildContext context) {
    final person1 = Person(id: 1, name: &#39;John&#39;, email: &#39;john@gmail.com&#39;);
    final person2 = person1.copyWith(id: 2, email: &#39;Johndoe@gmail.com&#39;);
    final person3 = Person(id: 1, name: &#39;John&#39;, email: &#39;john@gmail.com&#39;);

    print(person1);
    print(person2);

    print(person1 == person3);
    print(person1.hashCode);
    print(person3.hashCode);

    return Scaffold(appBar: AppBar(title: Text(&#39;Person&#39;)));
  }
}</code></pre>
<blockquote>
<p>*<em>출력 결과: *</em>
false
949309463
581349880</p>
</blockquote>
<ul>
<li>현재 <code>person1</code>과 <code>person3</code>의 <code>property</code>는 같은 상태이다. 하지만 equality 연산자의 값이 <code>false</code>임을 확인할 수 있다.</li>
<li>dart의 기본 equality는 referential equality이다. 즉, 두 object가 메모리상 같은 곳을 가르킬 때, equality -&gt; true 라고 반환하게 된다. </li>
<li><code>person1</code>과 <code>person3</code>의 hash값이 다른걸 보아,, 다른 메모리에 할당되었음을 알 수 있다.</li>
<li>고로 다른 존재.</li>
</ul>
<h3 id="결론적으로-object의-프로퍼티가-같을-때-논리적-동등성을-가지기-위해서는-hash값을-override하던지-아니면-처음부터-불변값으로-정해야한다">결론적으로, object의 프로퍼티가 같을 때 논리적 동등성을 가지기 위해서는 hash값을 override하던지, 아니면 처음부터 불변값으로 정해야한다.</h3>
<h1 id="2-본론">2. 본론</h1>
<h2 id="21-construct의-type을-const로-지정-const-인스턴스-사용">2.1. Construct의 type을 const로 지정, const 인스턴스 사용</h2>
<h3 id="persondart-const-생성자">person.dart (const 생성자)</h3>
<pre><code class="language-dart">const Person({required this.id, required this.name, required this.email});</code></pre>
<h3 id="person_pagedart-1">person_page.dart</h3>
<pre><code class="language-dart">import &#39;package:dart_class/models/person.dart&#39;;
import &#39;package:flutter/material.dart&#39;;

class PersonPage extends StatelessWidget {
  const PersonPage({super.key});

  @override
  Widget build(BuildContext context) {
    const person1 = Person(id: 1, name: &#39;John&#39;, email: &#39;john@gmail.com&#39;);
    final person2 = person1.copyWith(id: 2, email: &#39;Johndoe@gmail.com&#39;);
    const person3 = Person(id: 1, name: &#39;John&#39;, email: &#39;john@gmail.com&#39;);

    print(person1);
    print(person2);

    print(person1 == person3);
    print(person1.hashCode);
    print(person3.hashCode);

    return Scaffold(appBar: AppBar(title: Text(&#39;Person&#39;)));
  }
}</code></pre>
<h3 id="출력결과">출력결과</h3>
<blockquote>
<p>true 423981177 // person 1 == person3의 논리적 equality</p>
</blockquote>
<h2 id="22-hash값-override">2.2. hash값 override</h2>
<p><img src="https://velog.velcdn.com/images/woojin-devv/post/df763ad4-1dc9-4816-9c87-09294ac32f3b/image.png" alt=""></p>
<ul>
<li><p>vscode extension -&gt; 객체 생성 도와주는 Tool
<img src="https://velog.velcdn.com/images/woojin-devv/post/2d67321d-a6f0-403c-90d9-4da84dbc0692/image.png" alt=""></p>
</li>
<li><p><code>Generate equality</code>클릭 </p>
<pre><code class="language-dart">// ignore_for_file: public_member_api_docs, sort_constructors_first
class Person {
final int id;
final String name;
final String email;

const Person({required this.id, required this.name, required this.email});

@override
String toString() =&gt; &#39;Person(id: $id, name: $name, email: $email)&#39;;

Person copyWith({int? id, String? name, String? email}) {
  return Person(
    id: id ?? this.id,
    name: name ?? this.name,
    email: email ?? this.email,
  );
}

@override
bool operator ==(covariant Person other) {
  if (identical(this, other)) return true;

  return other.id == id &amp;&amp; other.name == name &amp;&amp; other.email == email;
}

@override
int get hashCode =&gt; id.hashCode ^ name.hashCode ^ email.hashCode;
}</code></pre>
</li>
</ul>
<h2 id="23-equatable-패키지-사용">2.3. <code>equatable</code> 패키지 사용</h2>
<ul>
<li><a href="https://pub.dev/packages/equatable">pub.dev equatable 링크</a></li>
<li><code>==</code> 연산자와 <code>hashCode</code>를 직접 override하는 대신,<code>Equatable</code>을 상속받고 <code>props</code>만 정의해주면 된다.</li>
<li>내부적으로는 <code>props</code> 리스트에 들어간 값들을 기준으로 <code>==</code> 비교와 <code>hashCode</code> 생성을 자동 처리한다.</li>
<li>결국 hash값을 override하는 방식은 동일하지만, 훨씬 간결하다</li>
</ul>
<pre><code class="language-dart">import &#39;package:equatable/equatable.dart&#39;;

// ignore_for_file: public_member_api_docs, sort_constructors_first
class Person extends Equatable {
  final int id;
  final String name;
  final String email;

  const Person({required this.id, required this.name, required this.email});

  @override
  String toString() =&gt; &#39;Person(id: $id, name: $name, email: $email)&#39;;

  Person copyWith({int? id, String? name, String? email}) {
    return Person(
      id: id ?? this.id,
      name: name ?? this.name,
      email: email ?? this.email,
    );
  }

  @override
  List&lt;Object&gt; get props =&gt; [id, name, email]; 
}</code></pre>
<ul>
<li>props 정의할 때 <pre><code class="language-dart">@override
List&lt;Object&gt; get props =&gt; [id]; </code></pre>
로 정의하면, id이 같지만 나머지 property가 다른 객체들의 동등성이 부여된다.!</li>
</ul>
<p><img src="https://velog.velcdn.com/images/woojin-devv/post/d6cf886f-3a09-4ac0-aad1-edc6250a2eb2/image.png" alt=""></p>
<h1 id="3-마무리">3. 마무리</h1>
<blockquote>
<p>쉬운게 하나도 없다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[웹 크롤링 뜯어먹기 #4 | bs4로  HTML 추출, 요소 추출, txt 저장]]></title>
            <link>https://velog.io/@woojin-devv/%EC%9B%B9-%ED%81%AC%EB%A1%A4%EB%A7%81-%EB%9C%AF%EC%96%B4%EB%A8%B9%EA%B8%B0-4-bs4%EB%A1%9C-HTML-%EC%B6%94%EC%B6%9C-%EC%9A%94%EC%86%8C-%EC%B6%94%EC%B6%9C-txt-%EC%A0%80%EC%9E%A5</link>
            <guid>https://velog.io/@woojin-devv/%EC%9B%B9-%ED%81%AC%EB%A1%A4%EB%A7%81-%EB%9C%AF%EC%96%B4%EB%A8%B9%EA%B8%B0-4-bs4%EB%A1%9C-HTML-%EC%B6%94%EC%B6%9C-%EC%9A%94%EC%86%8C-%EC%B6%94%EC%B6%9C-txt-%EC%A0%80%EC%9E%A5</guid>
            <pubDate>Tue, 10 Jun 2025 13:38:18 GMT</pubDate>
            <description><![CDATA[<h3 id="💭-들어가며">💭 들어가며..</h3>
<blockquote>
<p>저번 포스팅에서 bs4를 간단히 다루었는데, 이번 포스팅에서는 bs4를 활용해 웹사이트에서 HTML을 가져오기, 페이지에서 원하는 내용 추출하기, 그리고 추출한 내용을 txt 파일로 저장하는 방법을 실습해보려 한다.</p>
</blockquote>
<hr>
<p><img src="https://velog.velcdn.com/images/woojin-devv/post/d9e4f0b8-9f5a-40db-8fc1-4d0fa91f7738/image.png" alt=""></p>
<h2 id="1-introduction">1. Introduction</h2>
<ul>
<li><code>BeautifulSoup</code> is a Python library used for web scraping.</li>
<li>It extracts data from HTML and XML files.</li>
<li>Dependencies: Requires <code>requests</code> for fetching web pages and an XML/HTML parser.</li>
</ul>
<h3 id="11-installing">1.1 Installing</h3>
<pre><code class="language-bash">pip install bs4
pip install requests
pip install lxml</code></pre>
<h3 id="12-import">1.2 Import</h3>
<pre><code class="language-python">from bs4 import BeautifulSoup
import requests</code></pre>
<hr>
<h2 id="2-steps-before-scraping-a-website">2. Steps before scraping a website</h2>
<h3 id="21-fetch-the-pages-obtained-a-response-object">2.1 Fetch the pages (obtained a response object)</h3>
<p><code>result = requests.get(&quot;www.google.com&quot;)</code></p>
<h3 id="22-page-content">2.2 Page content</h3>
<p><code>content = result.text</code> </p>
<h3 id="23-create-soup-객체-생성">2.3 Create Soup (객체 생성)</h3>
<p><code>soup=BeautifulSoup(content, &quot;lxml&quot;)</code></p>
<h3 id="24-html-element-찾기">2.4. HTML element 찾기</h3>
<p><img src="https://velog.velcdn.com/images/woojin-devv/post/52de92d7-567b-4580-899c-4610dd7a942b/image.png" alt=""></p>
<pre><code class="language-html">&lt;article class=&quot;main-article&quot;&gt;
    &lt;h1&gt; Titanic (1997) &lt;/h1&gt;
    &lt;p class=&quot;plot&quot;&gt; 84 years later ... &lt;/p&gt;
    &lt;div class=&quot;full-script&quot;&gt; 13 meters. You ... &lt;/div&gt;
&lt;/article&gt;</code></pre>
<ul>
<li><strong>ID로 element 찾기</strong></li>
</ul>
<pre><code class="language-python">element = soup.find(id=&quot;element_id&quot;)</code></pre>
<ul>
<li><strong>클래스 이름으로 element 찾기</strong></li>
</ul>
<pre><code class="language-python">element = soup.find(&quot;tag_name&quot;, class_=&quot;class_name&quot;)</code></pre>
<ul>
<li><strong>tag 이름으로 element 찾기</strong></li>
</ul>
<pre><code class="language-python">h1_element = soup.find(&quot;h1&quot;)</code></pre>
<ul>
<li><strong>여러 elements 찾기 : <code>find_all()</code> 메서드 사용</strong></li>
</ul>
<pre><code class="language-python">h2_elements = soup.find_all(&quot;h2&quot;)
for h2 in h2_elements:
    print(h2)</code></pre>
<p><img src="https://velog.velcdn.com/images/woojin-devv/post/0d8e214d-74ac-4e91-b0f3-e37a7d350f81/image.png" alt=""></p>
<h2 id="3-scrape-examples">3. Scrape Examples</h2>
<p><a href="https://subslikescript.com/movie/Titanic-120338">https://subslikescript.com/movie/Titanic-120338</a> → 예제 웹사이트</p>
<h3 id="ex_6py--get-the-html-from-a-website">ex_6.py | Get the HTML from a website</h3>
<pre><code class="language-python">from bs4 import BeautifulSoup
import requests

website = &#39;https://subslikescript.com/movie/Titanic-120338&#39;
result = requests.get(website)
content = result.text

soup = BeautifulSoup(content, &#39;lxml&#39;)
print(soup.prettify())</code></pre>
<ul>
<li><code>lxml</code> 은 Python을 위한 고성능, 쉬운 사용법을 제공하는 XML 및 HTML 처리 라이브러리</li>
<li><code>prettify()</code>는 html 구조를 파악하기 쉽게 바꿔줌</li>
</ul>
<h3 id="ex_6_1py--scrape-a-single-page">ex_6_1.py | Scrape a single page</h3>
<pre><code class="language-python">#https://www.udemy.com/course/web-scraping-course-in-python-bs4-selenium-and-scrapy/learn/lecture/27676578#learning-tools
#udemy 강의 예제 코드

from bs4 import BeautifulSoup
import requests

website = &#39;https://subslikescript.com/movie/Titanic-120338&#39;
result = requests.get(website)
content = result.text

soup = BeautifulSoup(content, &#39;lxml&#39;)
# print(soup)

box = soup.find(&#39;article&#39;, class_=&quot;main-article&quot;) 
#underscore is used to avoid conflict with Python&#39;s built-in keyword &#39;class&#39;

title = box.find(&#39;h1&#39;).get_text()  # get_text() is used to extract text from the HTML element
transcription = box.find(&#39;div&#39;, class_=&quot;full-script&quot;).get_text(strip=True, separator=&#39; &#39;)  # strip=True removes leading and trailing whitespace
print(title)
print(transcription)</code></pre>
<ul>
<li><code>strip=True</code> : 앞 뒤 공백 제거</li>
<li><code>separator=&#39; &#39;</code>: 구분자 Space</li>
</ul>
<h3 id="ex_6_2py--exporting-data-to-a-txt-file">ex_6_2.py | Exporting data to a txt file</h3>
<pre><code class="language-python">
from bs4 import BeautifulSoup
import requests

website = &#39;https://subslikescript.com/movie/Titanic-120338&#39;
result = requests.get(website)
content = result.text

soup = BeautifulSoup(content, &#39;lxml&#39;)
# print(soup)

box = soup.find(&#39;article&#39;, class_=&quot;main-article&quot;) 
#underscore is used to avoid conflict with Python&#39;s built-in keyword &#39;class&#39;

title = box.find(&#39;h1&#39;).get_text()  # get_text() is used to extract text from the HTML element
transcription = box.find(&#39;div&#39;, class_=&quot;full-script&quot;).get_text(strip=True, separator=&#39; &#39;)  # strip=True removes leading and trailing whitespace

with open(f&#39;{title}.txt&#39;, &#39;w&#39;) as file: #title + .txt
    file.write(transcription)</code></pre>
<h3 id="ex_6_2py--실행-결과">ex_6_2.py | 실행 결과</h3>
<p><img src="https://velog.velcdn.com/images/woojin-devv/post/a6596e43-6ea1-4ac5-932b-315728b54196/image.png" alt=""></p>
<h2 id="끝으로">끝으로..</h2>
<blockquote>
<p>인강 완강을 목표로..</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[웹 크롤링 뜯어먹기 #3 | 자주 쓰는 Python 문법 모음 | with open(), Dataframe, try-except]]></title>
            <link>https://velog.io/@woojin-devv/%EC%9B%B9-%ED%81%AC%EB%A1%A4%EB%A7%81-%EB%9C%AF%EC%96%B4%EB%A8%B9%EA%B8%B0-3-%EC%9E%90%EC%A3%BC-%EC%93%B0%EB%8A%94-Python-%EB%AC%B8%EB%B2%95-%EB%AA%A8%EC%9D%8C-with-open-Dataframe-try-except</link>
            <guid>https://velog.io/@woojin-devv/%EC%9B%B9-%ED%81%AC%EB%A1%A4%EB%A7%81-%EB%9C%AF%EC%96%B4%EB%A8%B9%EA%B8%B0-3-%EC%9E%90%EC%A3%BC-%EC%93%B0%EB%8A%94-Python-%EB%AC%B8%EB%B2%95-%EB%AA%A8%EC%9D%8C-with-open-Dataframe-try-except</guid>
            <pubDate>Mon, 02 Jun 2025 16:33:16 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p> <strong>&lt; 이전 글</strong></p>
</blockquote>
<ul>
<li><a href="https://velog.io/@woojin-devv/%EC%9B%B9-%ED%81%AC%EB%A1%A4%EB%A7%81-%EA%B8%B0%EC%B4%88-%EA%B3%B5%EB%B6%80%ED%95%98%EA%B8%B0-bs4-requests%EC%9A%94%EC%86%8C-%EC%B6%94%EC%B6%9C-1">웹크롤링 뜯어먹기 #1 (bs4, requests) 요소 추출</a></li>
<li><a href="https://velog.io/@woojin-devv/%EC%9B%B9-%ED%81%AC%EB%A1%A4%EB%A7%81-%EB%9C%AF%EC%96%B4%EB%A8%B9%EA%B8%B0-2-%EC%8A%A4%ED%81%AC%EB%9E%98%ED%95%91-%EB%8F%84%EA%B5%AC-%EB%B9%84%EA%B5%90-bs4-selenium-scrapy">웹크롤링 뜯어먹기 #2 스크래핑 도구 비교 (bs4, selenium, scrapy)</a></li>
</ul>
<hr>
<p><img src="https://velog.velcdn.com/images/woojin-devv/post/6682f1d1-8df0-4f42-81b2-a81fd8c532ec/image.png" alt=""></p>
<h2 id="1-with-open-읽기-쓰기-추가하기">1. with open() 읽기, 쓰기, 추가하기</h2>
<h3 id="📁-ex_3py--실행-전-프로젝트-구조">📁 ex_3.py | 실행 전 프로젝트 구조</h3>
<pre><code>.
├── ex_1.py
├── ex_2.py
├── ex_3.py
└── txt_examples</code></pre><h3 id="📄-ex_3py--파일-생성-예제-코드">📄 ex_3.py | 파일 생성 예제 코드</h3>
<pre><code class="language-python">with open(&#39;txt_examples/ex_3.txt&#39;, &#39;w&#39;) as f:
    f.write(&#39;This is a test file.\n&#39;)
    f.write(&#39;It contains some text.\n&#39;)
    f.write(&#39;This is the third line.\n&#39;)</code></pre>
<h3 id="📁-ex_3py--실행-후-프로젝트-구조">📁 ex_3.py | 실행 후 프로젝트 구조</h3>
<pre><code>.
├── ex_1.py
├── ex_2.py
├── ex_3.py
└── txt_examples
    └── ex_3.txt</code></pre><ul>
<li><code>txt_examples/ex_3.txt</code> 경로에 <code>ex_3.txt</code> 파일이 생성됨을 확인할 수 있다.</li>
<li>※ 주의: 디렉토리는 자동 생성되지 않으므로, <strong>사전에 수동으로 생성</strong>해야 한다.</li>
</ul>
<hr>
<h3 id="✏️-with-open파일경로-mode-기본-문법">✏️ <code>with open(&#39;파일경로&#39;, mode)</code> 기본 문법</h3>
<p><code>mode</code>는 파일을 어떤 용도로 열 것인지 지정하는 옵션이다.</p>
<table>
<thead>
<tr>
<th>모드 (<code>mode</code>)</th>
<th>의미</th>
<th>파일이 없을 때</th>
<th>파일이 있을 때</th>
</tr>
</thead>
<tbody><tr>
<td><code>&#39;r&#39;</code></td>
<td>읽기 (read)</td>
<td>에러 발생</td>
<td>내용 읽기 가능</td>
</tr>
<tr>
<td><code>&#39;w&#39;</code></td>
<td>쓰기 (write)</td>
<td>새로 생성</td>
<td><strong>내용 덮어씀</strong></td>
</tr>
<tr>
<td><code>&#39;a&#39;</code></td>
<td>추가 (append)</td>
<td>새로 생성</td>
<td><strong>맨 뒤에 덧붙임</strong></td>
</tr>
</tbody></table>
<hr>
<h3 id="✏️-파일-입출력-예제-코드">✏️ 파일 입출력 예제 코드</h3>
<pre><code class="language-python"># 1. 파일 쓰기 예제
# - 기존 내용을 덮어쓰고 새 내용 저장
with open(&#39;txt_examples/ex_3.txt&#39;, &#39;w&#39;) as f:
    f.write(&#39;This is a test file.\n&#39;)
    f.write(&#39;It contains some text.\n&#39;)
    f.write(&#39;This is the third line.\n&#39;)

# 2. 파일 읽기 예제
# - 파일 전체 내용을 읽어 출력
with open(&#39;txt_examples/ex_3.txt&#39;, &#39;r&#39;) as f:
    content = f.read()
    print(&quot;File content:&quot;)
    print(content)

# 3. 파일 추가 예제
# - 기존 파일 끝에 새 줄을 추가
with open(&#39;txt_examples/ex_3.txt&#39;, &#39;a&#39;) as f:
    f.write(&#39;This line is added at the end of the file.\n&#39;)

# 추가 후 다시 읽어 확인
with open(&#39;txt_examples/ex_3.txt&#39;, &#39;r&#39;) as f:
    content = f.read()
    print(&quot;File content (with line added):&quot;)
    print(content)</code></pre>
<h3 id="💬-출력-예시">💬 출력 예시</h3>
<pre><code>File content:
This is a test file.
It contains some text.
This is the third line.

File content (with line added):
This is a test file.
It contains some text.
This is the third line.
This line is added at the end of the file.</code></pre><h2 id="2-pandas--dataframe">2. Pandas | Dataframe</h2>
<h3 id="📁-ex_4py--실행-전-프로젝트-구조">📁 ex_4.py | 실행 전 프로젝트 구조</h3>
<pre><code class="language-bash">.
├── csv_examples
├── ex_1.py
├── ex_2.py
├── ex_3.py
├── ex_4.py
└── txt_examples
    └── ex_3.txt</code></pre>
<h3 id="📄-ex_4py--dataframe-예제-코드">📄 ex_4.py | Dataframe 예제 코드</h3>
<pre><code class="language-python">import pandas as pd

states = [&#39;California&#39;, &#39;Texas&#39;, &#39;Florida&#39;, &#39;New York&#39;, &#39;Illinois&#39;]
population = [39538223, 29145505, 21538187, 20201249, 12812508]

dict_states = {&#39;State&#39;: states, &#39;Population&#39;: population}
df_states = pd.DataFrame.from_dict(dict_states)
df_csv = df_states.to_csv(&#39;csv_examples/states_population.csv&#39;, index=False)

print(df_states)</code></pre>
<h3 id="📁-ex_4py--실행-후-프로젝트-구조">📁 ex_4.py | 실행 후 프로젝트 구조</h3>
<pre><code class="language-bash">.
├── csv_examples
│   └── states_population.csv
├── ex_1.py
├── ex_2.py
├── ex_3.py
├── ex_4.py
└── txt_examples
    └── ex_3.txt</code></pre>
<h2 id="3-try-except-구문">3. try-except 구문</h2>
<h3 id="📄-ex_5py--에러발생-예제-코드">📄 ex_5.py | 에러발생 예제 코드</h3>
<pre><code class="language-python">#Handling exceptions errors: Try-Except
#예를 들어, 
#List에 여러 개의 타입 values가 섞여 있을 때
#TypeError가 발생할 수 있음.

example_list = [2,4,6, &quot;california&quot;, 8, 10]

for element in example_list:
    print(element/2)
</code></pre>
<h4 id="ㄴ-💬-출력예시--typeerror-발생">ㄴ 💬 출력예시 | TypeError 발생</h4>
<pre><code>1.0
2.0
3.0
Traceback (most recent call last):
  File &quot;/Users/choiwoojin/Developer/web-crawling-tutorial/ex_5.py&quot;, line 9, in &lt;module&gt;
    print(element/2)
          ~~~~~~~^~
TypeError: unsupported operand type(s) for /: &#39;str&#39; and &#39;int&#39;</code></pre><h3 id="📄-ex_5_exceptionpy--try-except-적용-예제-코드">📄 ex_5_exception.py | try-except 적용 예제 코드</h3>
<pre><code class="language-python">#Handling exceptions errors: Try-Except
#예를 들어, 
#List에 여러 개의 타입 values가 섞여 있을 때
#TypeError가 발생할 수 있음.

example_list = [2,4,6, &quot;california&quot;, 8, 10]

# try-except 구문을 사용하여 예외 처리
#try:
    # 예외가 발생할 수 있는 코드
#except 예외타입:
    # 예외가 발생했을 때 실행할 코드

for element in example_list:
    try: 
        print(element/2)
    except:
        print(f&quot;Error: {element} is not a number, cannot divide by 2.&quot;)
</code></pre>
<h4 id="ㄴ-💬-출력예시">ㄴ 💬 출력예시</h4>
<pre><code>2.0
4.0
6.0
Error: california is not a number, cannot divide by 2.
8.0
10.0</code></pre><hr>
<h3 id="참고">참고</h3>
<ul>
<li><a href="https://www.udemy.com/course/web-scraping-course-in-python-bs4-selenium-and-scrapy/learn/lecture/27676558#learning-tools">Udemy | Web Scraping in Python Selenium, Scrapy + ChatGPT Prize 2024
</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[웹 크롤링 뜯어먹기 #2 | 스크래핑 도구 비교 (bs4, selenium, scrapy)]]></title>
            <link>https://velog.io/@woojin-devv/%EC%9B%B9-%ED%81%AC%EB%A1%A4%EB%A7%81-%EB%9C%AF%EC%96%B4%EB%A8%B9%EA%B8%B0-2-%EC%8A%A4%ED%81%AC%EB%9E%98%ED%95%91-%EB%8F%84%EA%B5%AC-%EB%B9%84%EA%B5%90-bs4-selenium-scrapy</link>
            <guid>https://velog.io/@woojin-devv/%EC%9B%B9-%ED%81%AC%EB%A1%A4%EB%A7%81-%EB%9C%AF%EC%96%B4%EB%A8%B9%EA%B8%B0-2-%EC%8A%A4%ED%81%AC%EB%9E%98%ED%95%91-%EB%8F%84%EA%B5%AC-%EB%B9%84%EA%B5%90-bs4-selenium-scrapy</guid>
            <pubDate>Mon, 02 Jun 2025 15:00:34 GMT</pubDate>
            <description><![CDATA[<img src="https://velog.velcdn.com/images/woojin-devv/post/e41dfd68-700f-44a2-8525-79e59d447caf/image.png">

<h3 id="전체-비교">전체 비교</h3>
<table>
<thead>
<tr>
<th>Tool</th>
<th>특징 요약</th>
<th>장점</th>
<th>단점</th>
</tr>
</thead>
<tbody><tr>
<td><strong>BeautifulSoup</strong></td>
<td>- HTML/XML 파싱<br>- 가장 쉬운 웹 크롤링 도구</td>
<td>배우기 쉬움<br>초보자에게 적합</td>
<td>JavaScript 미지원<br>느림</td>
</tr>
<tr>
<td><strong>Selenium</strong></td>
<td>- 웹 브라우저 자동화 도구<br>- JavaScript 렌더링 처리 가능</td>
<td>JS 기반 사이트도 크롤링 가능<br>배우기 쉬움</td>
<td>느림 (모든 스크립트 실행)<br>무거움</td>
</tr>
<tr>
<td><strong>Scrapy</strong></td>
<td>- 파이썬 기반 프레임워크<br>- 비동기 방식으로 빠르게 데이터 수집 가능</td>
<td>빠름<br>가장 완성도 높은 프레임워크</td>
<td>배우기 어려움<br>설정 복잡할 수 있음</td>
</tr>
</tbody></table>
<hr>
<p><img src="https://velog.velcdn.com/images/woojin-devv/post/30af20c3-2c77-4689-a17c-4ccd237ed221/image.png" alt=""></p>
<h3 id="1-beautiful-soup">1. Beautiful Soup</h3>
<img src="https://velog.velcdn.com/images/woojin-devv/post/d79bde25-b938-468c-ab16-b9efac4334d0/image.png" width=50%>

<h4 id="자주-사용되는-메서드-정리">자주 사용되는 메서드 정리</h4>
<table>
<thead>
<tr>
<th>메서드/속성</th>
<th>설명</th>
<th>반환값 형식</th>
</tr>
</thead>
<tbody><tr>
<td><code>find()</code></td>
<td>첫 번째 해당 요소</td>
<td>Tag</td>
</tr>
<tr>
<td><code>find_all()</code></td>
<td>모든 해당 요소</td>
<td>ResultSet (list 유사)</td>
</tr>
<tr>
<td><code>select()</code></td>
<td>CSS 선택자에 맞는 모든 요소</td>
<td>list</td>
</tr>
<tr>
<td><code>select_one()</code></td>
<td>CSS 선택자에 맞는 첫 번째 요소</td>
<td>Tag or None</td>
</tr>
<tr>
<td><code>get_text()</code></td>
<td>텍스트만 추출</td>
<td>str</td>
</tr>
<tr>
<td><code>get()</code></td>
<td>속성값 추출</td>
<td>str or None</td>
</tr>
<tr>
<td><code>attrs</code></td>
<td>속성 딕셔너리 반환</td>
<td>dict</td>
</tr>
<tr>
<td><code>parent</code></td>
<td>부모 요소 반환</td>
<td>Tag</td>
</tr>
<tr>
<td><code>children</code></td>
<td>자식 요소 반복자 반환</td>
<td>generator</td>
</tr>
<tr>
<td><code>string</code></td>
<td>텍스트 노드 반환 (하위 태그 없을 때만)</td>
<td>str or NavigableString</td>
</tr>
</tbody></table>
<h4 id="자주-사용되는-메서드-코드-예시">자주 사용되는 메서드 코드 예시</h4>
<pre><code class="language-python">from bs4 import BeautifulSoup

html = &quot;&quot;&quot;
&lt;html&gt;
  &lt;head&gt;&lt;title&gt;Example&lt;/title&gt;&lt;/head&gt;
  &lt;body&gt;
    &lt;div class=&quot;content&quot; id=&quot;main&quot;&gt;
      &lt;p class=&quot;text&quot;&gt;First paragraph.&lt;/p&gt;
      &lt;p class=&quot;text&quot;&gt;Second paragraph.&lt;/p&gt;
      &lt;a href=&quot;https://example.com&quot; id=&quot;link&quot;&gt;Visit Example&lt;/a&gt;
    &lt;/div&gt;
  &lt;/body&gt;
&lt;/html&gt;
&quot;&quot;&quot;

soup = BeautifulSoup(html, &#39;html.parser&#39;)

# 1. find(tag_name, attrs)  
# - 조건에 맞는 첫 번째 요소 반환
soup.find(&#39;p&#39;)  # &lt;p class=&quot;text&quot;&gt;First paragraph.&lt;/p&gt;

# 2. find_all(tag_name, attrs)
# - 조건에 맞는 모든 요소 리스트 반환
soup.find_all(&#39;p&#39;)  # [&lt;p&gt;...&lt;/p&gt;, &lt;p&gt;...&lt;/p&gt;]

# 3. select(css_selector)
# - CSS 선택자를 이용해 요소 리스트 반환
soup.select(&#39;div.content p.text&#39;)  # [&lt;p class=&quot;text&quot;&gt;...&lt;/p&gt;, &lt;p class=&quot;text&quot;&gt;...&lt;/p&gt;]

# 4. select_one(css_selector)
# - CSS 선택자를 이용해 첫 번째 요소만 반환
soup.select_one(&#39;a#link&#39;)  # &lt;a href=&quot;...&quot;&gt;Visit Example&lt;/a&gt;

# 5. get_text(strip=False)
# - 태그 내부의 모든 텍스트를 추출 (하위 태그 포함)
soup.find(&#39;p&#39;).get_text()  # &quot;First paragraph.&quot;

# 6. get(attribute_name)
# - 특정 속성(attribute)의 값을 가져옴
soup.find(&#39;a&#39;).get(&#39;href&#39;)  # &quot;https://example.com&quot;

# 7. attrs
# - 태그의 모든 속성 정보를 딕셔너리 형태로 반환
soup.find(&#39;a&#39;).attrs  # {&#39;href&#39;: &#39;https://example.com&#39;, &#39;id&#39;: &#39;link&#39;}

# 8. parent
# - 현재 요소의 부모 요소 반환
soup.find(&#39;a&#39;).parent.name  # &#39;div&#39;

# 9. children
# - 현재 요소의 자식 요소들을 generator로 반환
list(soup.find(&#39;div&#39;).children)  # [\n, &lt;p&gt;...&lt;/p&gt;, \n, &lt;p&gt;...&lt;/p&gt;, \n, &lt;a&gt;...&lt;/a&gt;, \n]

# 10. string
# - 해당 태그의 텍스트를 직접 반환 (자식 태그 없이 텍스트만 있을 경우)
soup.title.string  # &quot;Example&quot;
</code></pre>
<h3 id="2-selenium">2. Selenium</h3>
<img src="https://velog.velcdn.com/images/woojin-devv/post/b0adbc38-b138-4aeb-8c8b-36bf90236e41/image.png" width=30%>

<h4 id="자주-사용되는-메서드-정리-1">자주 사용되는 메서드 정리</h4>
<table>
<thead>
<tr>
<th>메서드</th>
<th>설명</th>
<th>반환값 / 효과</th>
</tr>
</thead>
<tbody><tr>
<td><code>get(url)</code></td>
<td>페이지 열기</td>
<td>None</td>
</tr>
<tr>
<td><code>find_element()</code></td>
<td>첫 요소 찾기</td>
<td>WebElement</td>
</tr>
<tr>
<td><code>find_elements()</code></td>
<td>모든 요소 리스트</td>
<td>list[WebElement]</td>
</tr>
<tr>
<td><code>send_keys(&quot;text&quot;)</code></td>
<td>입력값 전달</td>
<td>None</td>
</tr>
<tr>
<td><code>click()</code></td>
<td>클릭 이벤트</td>
<td>None</td>
</tr>
<tr>
<td><code>get_attribute(&quot;attr&quot;)</code></td>
<td>속성값 가져오기</td>
<td>str</td>
</tr>
<tr>
<td><code>element.text</code></td>
<td>텍스트 가져오기</td>
<td>str</td>
</tr>
<tr>
<td><code>is_displayed()</code></td>
<td>화면에 보이는지 확인</td>
<td>True / False</td>
</tr>
<tr>
<td><code>implicitly_wait(초)</code></td>
<td>요소 기다리기 (전역 설정)</td>
<td>None</td>
</tr>
<tr>
<td><code>close()</code> / <code>quit()</code></td>
<td>탭 종료 / 브라우저 종료</td>
<td>None</td>
</tr>
</tbody></table>
<h4 id="자주-사용되는-메서드-코드-예시-1">자주 사용되는 메서드 코드 예시</h4>
<pre><code class="language-python">from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get(&quot;https://example.com&quot;)

# 1. get(url)
# - 특정 웹 페이지로 이동
driver.get(&quot;https://example.com&quot;)

# 2. find_element(By.~, value)
# - 조건에 맞는 첫 번째 요소 찾기
driver.find_element(By.TAG_NAME, &quot;h1&quot;)

# 3. find_elements(By.~, value)
# - 조건에 맞는 모든 요소 리스트 찾기
driver.find_elements(By.CLASS_NAME, &quot;item&quot;)

# 4. send_keys(text)
# - 입력창에 텍스트 입력
search_box = driver.find_element(By.NAME, &quot;q&quot;)
search_box.send_keys(&quot;Selenium&quot;)

# 5. click()
# - 클릭 이벤트 수행
button = driver.find_element(By.ID, &quot;submit&quot;)
button.click()

# 6. get_attribute(attribute_name)
# - 요소의 속성값 가져오기
link = driver.find_element(By.TAG_NAME, &quot;a&quot;)
href = link.get_attribute(&quot;href&quot;)

# 7. text
# - 요소 내부의 텍스트 추출
header = driver.find_element(By.TAG_NAME, &quot;h1&quot;).text

# 8. is_displayed()
# - 요소가 화면에 보이는 상태인지 확인 (True/False)
driver.find_element(By.ID, &quot;banner&quot;).is_displayed()

# 9. implicitly_wait(seconds)
# - 요소가 로드될 때까지 최대 몇 초 기다릴지 설정 (전역)
driver.implicitly_wait(5)

# 10. close() / quit()
# - close(): 현재 탭 종료, quit(): 전체 브라우저 종료
driver.close()
driver.quit()
</code></pre>
<h3 id="3-scrapy">3. Scrapy</h3>
<img src="https://velog.velcdn.com/images/woojin-devv/post/bb610937-7c39-4f42-960c-cd65a5ee4a9b/image.png" width=50%>

<h4 id="자주-사용되는-메서드-정리-2">자주 사용되는 메서드 정리</h4>
<table>
<thead>
<tr>
<th>메서드/속성</th>
<th>설명</th>
<th>반환값</th>
</tr>
</thead>
<tbody><tr>
<td><code>response.css(selector)</code></td>
<td>CSS 선택자로 요소 찾기</td>
<td>Selector (chainable)</td>
</tr>
<tr>
<td><code>response.xpath(xpath)</code></td>
<td>XPath로 요소 찾기</td>
<td>Selector (chainable)</td>
</tr>
<tr>
<td><code>response.follow(url)</code></td>
<td>상대/절대 URL 기반 새 요청 생성</td>
<td>Request 객체</td>
</tr>
<tr>
<td><code>response.url</code></td>
<td>현재 페이지 URL</td>
<td>str</td>
</tr>
<tr>
<td><code>response.status</code></td>
<td>HTTP 응답 상태 코드</td>
<td>int</td>
</tr>
<tr>
<td><code>response.body</code></td>
<td>페이지 원문 (bytes)</td>
<td>bytes</td>
</tr>
<tr>
<td><code>response.text</code></td>
<td>페이지 원문 (str)</td>
<td>str</td>
</tr>
<tr>
<td><code>response.meta</code></td>
<td>요청 간 데이터 전달용 딕셔너리</td>
<td>dict</td>
</tr>
<tr>
<td><code>scrapy.Request()</code></td>
<td>새 요청 수동 생성 (callback 지정 가능)</td>
<td>Request 객체</td>
</tr>
<tr>
<td><code>.get()</code>, <code>.getall()</code></td>
<td>Selector로 추출한 값 가져오기</td>
<td>str / list[str]</td>
</tr>
</tbody></table>
<h4 id="자주-사용되는-메서드-코드-예시-2">자주 사용되는 메서드 코드 예시</h4>
<pre><code class="language-python">import scrapy

class ExampleSpider(scrapy.Spider):
    name = &quot;example&quot;
    start_urls = [&#39;https://example.com&#39;]

    def parse(self, response):
        # 1. response.css() - CSS 선택자로 요소 찾기
        titles = response.css(&#39;h1::text&#39;).getall()

        # 2. response.xpath() - XPath로 요소 찾기
        paragraphs = response.xpath(&#39;//p/text()&#39;).getall()

        # 3. response.follow() - 상대 URL을 따라 새로운 요청 생성
        for href in response.css(&#39;a::attr(href)&#39;).getall():
            yield response.follow(href, callback=self.parse_detail)

        # 4. response.url - 현재 페이지의 URL
        print(response.url)

        # 5. response.status - HTTP 상태 코드
        print(response.status)

        # 6. response.body - 응답 바디 (bytes 형태)
        raw_html = response.body

        # 7. response.text - 응답 바디 (str 형태)
        raw_html_str = response.text

        # 8. response.meta - 요청 간 데이터 전달용 딕셔너리
        print(response.meta)

        # 9. scrapy.Request() - 커스텀 요청 만들기
        yield scrapy.Request(url=&#39;https://example.com/page&#39;, callback=self.parse_page)

        # 10. get(), getall() - 선택한 요소에서 텍스트 또는 속성 추출
        first_title = response.css(&#39;h1::text&#39;).get()      # 첫 번째
        all_titles = response.css(&#39;h1::text&#39;).getall()    # 전체 리스트
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[웹 크롤링 뜯어먹기 #1 | (bs4, requests) 요소 추출 ]]></title>
            <link>https://velog.io/@woojin-devv/%EC%9B%B9-%ED%81%AC%EB%A1%A4%EB%A7%81-%EA%B8%B0%EC%B4%88-%EA%B3%B5%EB%B6%80%ED%95%98%EA%B8%B0-bs4-requests%EC%9A%94%EC%86%8C-%EC%B6%94%EC%B6%9C-1</link>
            <guid>https://velog.io/@woojin-devv/%EC%9B%B9-%ED%81%AC%EB%A1%A4%EB%A7%81-%EA%B8%B0%EC%B4%88-%EA%B3%B5%EB%B6%80%ED%95%98%EA%B8%B0-bs4-requests%EC%9A%94%EC%86%8C-%EC%B6%94%EC%B6%9C-1</guid>
            <pubDate>Mon, 02 Jun 2025 12:34:39 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/woojin-devv/post/4dc8723a-adb9-46fc-898b-86a613ccdf8f/image.png" alt=""></p>
<blockquote>
<p>현재 수학 및 과학 타이핑 부업을 하고 있는데,, 아무래도 전공과는 무관한 부업이기도 하고 슬슬 웹크롤링으로 넘어가볼까?하는 막연한 생각으로..작년 여름부터 공부하자고 다짐하다가 1년뒤에 시작하는. 그런 인생을 살고 있어요</p>
</blockquote>
<hr>
<h2 id="1-web-crawling이란">1. Web Crawling이란?</h2>
<p>웹사이트의 데이터를 자동으로 수집하는 기술 또는 작업을 의미한다.</p>
<h3 id="📌-예를-들어">📌 예를 들어:</h3>
<ul>
<li>뉴스 사이트의 기사 제목과 날짜를 자동으로 모으고 싶다</li>
<li>쇼핑몰의 상품 가격 변동을 추적하고 싶다</li>
<li>명언 사이트에서 명언들을 수집하고 싶다</li>
</ul>
<p>→ 이럴 때 <strong>웹 크롤러</strong>를 만들어 자동으로 정보를 수집할 수 있다.</p>
<h3 id="📚-용어-정리">📚 용어 정리</h3>
<table>
<thead>
<tr>
<th>용어</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>크롤링 (Crawling)</strong></td>
<td>웹페이지를 방문해서 데이터를 수집하는 것</td>
</tr>
<tr>
<td><strong>스크래핑 (Scraping)</strong></td>
<td>HTML에서 원하는 부분만 &quot;긁어내는&quot; 작업</td>
</tr>
<tr>
<td><strong>파싱 (Parsing)</strong></td>
<td>HTML을 구조화해서 원하는 태그나 정보를 찾는 과정</td>
</tr>
</tbody></table>
<blockquote>
<p>참고: <strong>크롤링 = 방문 + 수집</strong>, <strong>스크래핑 = 필요한 정보만 추출</strong></p>
</blockquote>
<hr>
<h2 id="2-html-요소-이해">2. HTML 요소 이해</h2>
<table>
<thead>
<tr>
<th>요소</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>body 요소</strong></td>
<td><code>&lt;body&gt;</code>로 시작해서 <code>&lt;/body&gt;</code>로 끝나는 모든 HTML 요소</td>
</tr>
<tr>
<td><strong>div 요소</strong></td>
<td><code>&lt;div&gt;</code>로 시작해서 <code>&lt;/div&gt;</code>로 끝나는 블록 단위의 요소</td>
</tr>
</tbody></table>
<hr>
<h2 id="3-사용된-python-모듈">3. 사용된 Python 모듈</h2>
<table>
<thead>
<tr>
<th>모듈</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>requests</code></td>
<td>웹 서버에 HTTP 요청을 보내고 응답을 받아오는 라이브러리</td>
</tr>
<tr>
<td><code>bs4</code> (BeautifulSoup)</td>
<td>HTML/XML 문서를 파싱하고 탐색할 수 있는 라이브러리</td>
</tr>
</tbody></table>
<p>💡 설치가 필요하다면:</p>
<pre><code class="language-bash">pip install requests beautifulsoup4</code></pre>
<hr>
<p><img src="https://velog.velcdn.com/images/woojin-devv/post/37eb2c10-557c-482f-b456-7b7f39cf9313/image.png" alt=""></p>
<h3 id="ex-1-quotetoscrape의-body-요소-추출">ex 1) Quote.toscrape의 <code>&lt;body&gt;</code> 요소 추출</h3>
<pre><code class="language-python">import requests
from bs4 import BeautifulSoup

# 1. 대상 URL
url = &#39;https://quotes.toscrape.com&#39;

# 2. 페이지 요청
response = requests.get(url)
response.raise_for_status()  # 에러 발생 시 예외 처리

# 3. HTML 파싱
soup = BeautifulSoup(response.text, &#39;html.parser&#39;)

# 4. &lt;body&gt; 요소 추출
body = soup.body

# 5. 결과 출력
print(body.prettify())  # 보기 좋게 들여쓰기 출력</code></pre>
<h3 id="출력-결과">출력 결과</h3>
<p><img src="https://velog.velcdn.com/images/woojin-devv/post/e0d6da78-1aa7-44bb-b91b-2c052ce37710/image.png" alt=""></p>
<hr>
<h3 id="ex-2-class-값이-quote인-div-요소들의-텍스트-모두-추출">ex 2) class 값이 &quot;quote&quot;인 div 요소들의 텍스트 모두 추출</h3>
<p><img src="https://velog.velcdn.com/images/woojin-devv/post/fd5c9691-7cdb-44a8-841c-82a2ffd72cf7/image.png" alt=""></p>
<h4 id="copy-용도-예시">copy 용도 예시</h4>
<table>
<thead>
<tr>
<th>메뉴 항목</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Copy element</strong></td>
<td><em>HTML element markup</em><br>선택한 요소 전체의 HTML 마크업</td>
</tr>
<tr>
<td><strong>Copy outerHTML</strong></td>
<td><em>Outer HTML serialization</em><br>요소 자체 + 내부 콘텐츠를 포함한 HTML 문자열</td>
</tr>
<tr>
<td><strong>Copy selector</strong></td>
<td><em>CSS selector path</em><br>해당 요소를 지정할 수 있는 CSS 선택자 경로</td>
</tr>
<tr>
<td><strong>Copy JS path</strong></td>
<td><em>JavaScript DOM query path</em><br>JavaScript로 DOM 요소에 접근할 수 있는 경로</td>
</tr>
<tr>
<td><strong>Copy styles</strong></td>
<td><em>Inline and computed CSS styles</em><br>요소에 적용된 CSS 스타일 속성들</td>
</tr>
<tr>
<td><strong>Copy XPath</strong></td>
<td><em>Relative XPath expression</em><br>선택한 요소에 대한 상대 XPath 경로</td>
</tr>
<tr>
<td><strong>Copy full XPath</strong></td>
<td><em>Absolute XPath expression</em><br>HTML 문서 루트부터의 절대 XPath 경로</td>
</tr>
</tbody></table>
<ul>
<li><code>body &gt; div &gt; div:nth-child(2) &gt; div.col-md-8</code> 요소 안에 클래스 값이 &quot;quote&quot;인 div 요소들의 텍스트 출력</li>
</ul>
<h3 id="소스코드">소스코드</h3>
<pre><code class="language-python">import requests
from bs4 import BeautifulSoup

# 웹페이지 요청
url = &quot;http://quotes.toscrape.com&quot;
response = requests.get(url)
soup = BeautifulSoup(response.text, &quot;html.parser&quot;)

# body &gt; div &gt; div:nth-child(2) &gt; div.col-md-8 안의 div.quote 요소 선택
quote_container = soup.select_one(&quot;body &gt; div &gt; div:nth-child(2) &gt; div.col-md-8&quot;)
quotes = quote_container.find_all(&quot;div&quot;, class_=&quot;quote&quot;)

# 텍스트 출력
for quote in quotes:
    text = quote.find(&quot;span&quot;, class_=&quot;text&quot;).get_text(strip=True)
    author = quote.find(&quot;small&quot;, class_=&quot;author&quot;).get_text(strip=True)
    print(f&quot;{text} — {author}&quot;)
</code></pre>
<h3 id="출력결과">출력결과</h3>
<p><img src="https://velog.velcdn.com/images/woojin-devv/post/61f491d6-fc8f-4adc-abc9-fa65e7dab498/image.png" alt=""></p>
<hr>
<h3 id="🔗-참고자료">🔗 참고자료</h3>
<ul>
<li><a href="https://quotes.toscrape.com/">[예제 사이트]: Quote to Scrape</a>  </li>
<li><a href="https://product.kyobobook.co.kr/detail/S000216249128">[참고도서]: 이게 되네? 챗GPT 미친 크롤링 24제</a>  </li>
</ul>
<h3 id="⚒️-개발자-도구-열기">⚒️ 개발자 도구 열기</h3>
<ul>
<li><strong>macOS</strong>: 우클릭 &gt; 검사  </li>
<li><strong>Windows</strong>: <code>F12</code> 키  </li>
</ul>
<h3 id="🤓-마무리-말">🤓 마무리 말</h3>
<blockquote>
<p>HTML 공부를 손에 놓은지 3년(..?)은 다되어가는 것 같은데 그동안 gpt의 무궁무진한 성장으로 공부해야겠다는 생각을 안하고 있다가.. 전혀 기억이 안나는 그런 사태가 발생했다. 정신체리.🍒🍒🍒</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Figma MCP Plugin & Cursor 설치 방법 (Mac os)]]></title>
            <link>https://velog.io/@woojin-devv/Figma-MCP-Plugin-Cursor-%EC%84%A4%EC%B9%98-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@woojin-devv/Figma-MCP-Plugin-Cursor-%EC%84%A4%EC%B9%98-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Thu, 22 May 2025 07:02:08 GMT</pubDate>
            <description><![CDATA[<h2 id="1-cursor-설치">1. Cursor 설치</h2>
<ul>
<li>공식 웹사이트(<a href="https://www.cursor.com/">https://www.cursor.com/</a>)에 접속해 본인의 운영체제에 맞는 설치 파일을 다운로드합니다.</li>
<li>설치 후 프로그램을 실행하여 기본 환경을 준비합니다.</li>
</ul>
<h2 id="2-figma-설치-optional">2. Figma 설치 (Optional)</h2>
<blockquote>
<p>Figma 데스크탑 앱은 선택 사항이며, 웹 버전만으로도 충분히 작업이 가능합니다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/woojin-devv/post/f648b279-bfe6-45ca-95f0-ae7192af4ec7/image.png" alt=""></p>
<ol>
<li><p>Figma를 실행한 뒤, <code>Plugin</code> 메뉴에서 <code>Cursor Talk to MCP</code>를 검색합니다.
<img src="https://velog.velcdn.com/images/woojin-devv/post/3938f9ba-d6fb-440b-b41b-d157b52d1dd9/image.png" alt=""></p>
</li>
<li><p>해당 플러그인을 클릭하면 MCP 연동 설정 창이 나타납니다.
<img src="https://velog.velcdn.com/images/woojin-devv/post/d3a08282-4bcd-4756-8c4d-d84589294c44/image.png" alt=""></p>
</li>
</ol>
<h2 id="3-cursor에-mcp-서버-설정">3. Cursor에 MCP 서버 설정</h2>
<p><img src="https://velog.velcdn.com/images/woojin-devv/post/09b27817-65b6-42f1-b243-bd03751aae39/image.png" alt=""></p>
<ol>
<li><p>Cursor 앱을 실행하고 우측 상단의 ⚙️ 아이콘을 클릭해 설정 창을 엽니다.
<img src="https://velog.velcdn.com/images/woojin-devv/post/f00d531b-0640-47de-ac06-fd69d3e7066f/image.png" alt=""></p>
</li>
<li><p>좌측 메뉴에서 <code>MCP</code> 탭을 선택합니다.
<img src="https://velog.velcdn.com/images/woojin-devv/post/2409fd09-7782-4bd5-9a94-aacf7b7bd2a9/image.png" alt=""></p>
</li>
<li><p>우측 상단에 있는 <strong><code>Add new global MCP server</code></strong> 버튼을 클릭합니다.
<img src="https://velog.velcdn.com/images/woojin-devv/post/8ecd6425-9720-4666-9eb0-ffc2716fe2a3/image.png" alt=""></p>
</li>
<li><p>Figma에서 복사한 <code>MCP Configuration</code> 내용을 <code>mcp.json</code> 파일에 붙여넣고 저장합니다.
<img src="https://velog.velcdn.com/images/woojin-devv/post/e185cf16-9765-4d75-a641-4c4425673b5c/image.png" alt=""></p>
</li>
<li><p><code>Talk to Figma</code> 서버 항목이 활성화되었다면 설정이 완료된 것입니다.</p>
</li>
</ol>
<h2 id="4-mcp-websocket-서버-클론-및-실행">4. MCP WebSocket 서버 클론 및 실행</h2>
<p>해당 GitHub 저장소는 Cursor에서 사용할 WebSocket 기반 MCP 서버입니다.</p>
<p><a href="https://github.com/sonnylazuardi/cursor-talk-to-figma-mcp">https://github.com/sonnylazuardi/cursor-talk-to-figma-mcp</a></p>
<pre><code class="language-bash">git clone https://github.com/sonnylazuardi/cursor-talk-to-figma-mcp.git</code></pre>
<p>클론한 프로젝트 디렉토리를 Cursor에서 열어줍니다.</p>
<p><img src="https://velog.velcdn.com/images/woojin-devv/post/fffdf259-5cf2-4d07-b11e-8423fb0560e1/image.png" alt="">
<img src="https://velog.velcdn.com/images/woojin-devv/post/b6026392-0d74-4c71-9de6-fcf1d9057742/image.png" alt="">
<img src="https://velog.velcdn.com/images/woojin-devv/post/e9b3fa4f-a669-41ba-83f0-8cf2941aed1a/image.png" alt=""></p>
<p>터미널을 열어 현재 위치가 프로젝트 디렉토리인지 확인합니다.</p>
<h2 id="5-bun-shell-설치-및-실행">5. bun shell 설치 및 실행</h2>
<p><code>bun shell</code>이 설치되어 있지 않다면 아래 명령어로 설치합니다:</p>
<pre><code class="language-bash">curl -fsSL https://bun.sh/install | bash</code></pre>
<p>설치가 완료되면 다음 명령어로 초기 설정을 진행합니다:</p>
<pre><code class="language-bash">bun setup</code></pre>
<p><img src="https://velog.velcdn.com/images/woojin-devv/post/eb7c3c3d-a72d-4a3e-afe2-a1d0dd94f22d/image.png" alt=""></p>
<p>WebSocket 서버를 실행합니다:</p>
<pre><code class="language-bash">bun socket</code></pre>
<p><img src="https://velog.velcdn.com/images/woojin-devv/post/a3d3cbdb-27ca-463e-9d80-dabb0121d6b7/image.png" alt=""></p>
<p><code>3055</code> 포트가 열렸다면 정상적으로 실행된 것입니다.</p>
<p>이제 다시 Figma를 실행합니다.</p>
<p><img src="https://velog.velcdn.com/images/woojin-devv/post/c836e3f0-450a-4850-867c-9bfe203180b6/image.png" alt=""></p>
<p><code>Use localhost</code> 토글을 활성화하고 <code>connect</code> 버튼을 클릭하면, <code>c02el96w</code> 채널이 연결되었다는 메시지가 나타납니다.</p>
<p><img src="https://velog.velcdn.com/images/woojin-devv/post/c604c172-db66-4e79-b3a0-207d0a113ba0/image.png" alt=""></p>
<p>Cursor에서도 동일한 채널 명칭과 함께 JSON 응답이 도착하면, MCP 연동이 성공적으로 완료된 것입니다!</p>
<hr>
<h2 id="주의-">주의 !</h2>
<blockquote>
<p>연결 중에 cursor-talk-to-figma-mcp plugin을 종료하면 연결이 종료됩니다! </p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Medium | 개발자들이 알아야 할 15가지 ChatGPT Prompts 정리 #1]]></title>
            <link>https://velog.io/@woojin-devv/Medium-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%93%A4%EC%9D%B4-%EC%95%8C%EC%95%84%EC%95%BC-%ED%95%A0-15%EA%B0%80%EC%A7%80-ChatGPT-Prompts-%EC%A0%95%EB%A6%AC-1</link>
            <guid>https://velog.io/@woojin-devv/Medium-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%93%A4%EC%9D%B4-%EC%95%8C%EC%95%84%EC%95%BC-%ED%95%A0-15%EA%B0%80%EC%A7%80-ChatGPT-Prompts-%EC%A0%95%EB%A6%AC-1</guid>
            <pubDate>Fri, 16 May 2025 17:20:51 GMT</pubDate>
            <description><![CDATA[<h1 id="☺︎-서론">☺︎ 서론</h1>
<ul>
<li><strong>출처</strong> <del><a href="https://medium.com/gitconnected/15-advanced-chatgpt-prompts-every-developer-must-know-43af9351c0dc">15 Advanced ChatGPT Prompts Every Developer Must Know - Medium</a></del></li>
</ul>
<blockquote>
<p>velog에 어떤 글을 작성하고, 정리하는게 도움이 될지 많이 고민을 하다가,,, 영어 공부도 할 겸 Medium에서 글을 읽고 내용을 정리해보기로 했다!</p>
</blockquote>
<p>#
#
#</p>
<hr>
<h1 id="☺︎-내용정리">☺︎ 내용정리</h1>
<blockquote>
<p>개발자들은 흔히 ChatGPT에게 다음과 같은 단순한 질문을 많이 던진다.</p>
</blockquote>
<pre><code>&quot;이 함수 구현해줘.&quot;
&quot;에러난 거 설명해줘.&quot;</code></pre><p>필자는 이러한 질문을 entry-level stuff라고 표현했다. 실제로 나도 빠른 답변을 원할 때 간단히 질문하는 편인데, 이는 ChatGPT의 능력을 100% 활용하지 못하는 방식이다. </p>
<p><img src="https://velog.velcdn.com/images/woojin-devv/post/862bec15-4e08-4dde-bb80-5d2e8454fe72/image.png" alt=""></p>
<p>뭐 대충 이런식으로..ㅎ</p>
<p>#
#
이번 글에서는 개발자가 <strong>coding</strong>, <strong>debugging</strong>, <strong>optimization</strong>을 효율적으로 할 수 있도록 도와주는 ChatGPT 활용 전략 3가지를 정리했다.
#
#</p>
<hr>
<h2 id="1-advanced-chain-of-thought-prompting">1. Advanced Chain-of-Thought Prompting</h2>
<p><img src="https://velog.velcdn.com/images/woojin-devv/post/19778297-a27e-43ca-899c-b12b7c7bb825/image.png" alt=""></p>
<p>LLM이 논리 추론, 산술 문제에 약하다는 점을 보완하는 전략</p>
<ul>
<li>참조 링크: <del><a href="https://arxiv.org/pdf/2201.11903">Chain-of-Thought Prompting Elicits Reasoning in Large Language Models 논문 (arXiv)</a></del></li>
<li>기술 가이드: <del><a href="https://www.promptingguide.ai/techniques/cot">Prompting Guide - CoT</a></del></li>
</ul>
<p>⠀<strong>Chain-of-Thought (CoT)</strong> 기법은 LLM의 추론 능력을 향상시키기 위한 대표적 전략이다. 문제를 단계적으로 풀도록 유도함으로써, 더 깊이 있는 분석을 유도할 수 있다.</p>
<h3 id="문제-해결-프레임워크-예시">문제 해결 프레임워크 예시</h3>
<pre><code>
1. 문제 정확히 파악하기:
   - 문제를 명확하고 구체적으로 서술

2. 제약 조건 및 요구 사항 정의:
   - 기술적 제약사항을 모두 나열

3. 가능한 전략 탐색:
   - 최소 3가지 접근법 제시

4. 최선의 전략 실행:
   - 주석이 잘 달린 깔끔한 코드로 해결

5. 되돌아보고 학습하기:
   - 효율성 분석, 엣지 케이스 고려, 개선점 제안</code></pre><h2 id="2-interview-preparation-for-faang-or-mncs">2. Interview Preparation for FAANG or MNCs</h2>
<p>FAANG 또는 대형 기술 기업의 면접을 준비할 때 사용할 수 있는 Prompt</p>
<h3 id="step-1-맞춤형-전략-생성을-위한-자기소개">Step 1: 맞춤형 전략 생성을 위한 자기소개</h3>
<pre><code>I&#39;m preparing for technical interviews at top-tier tech companies (FAANG).</code></pre><p>Create a personalized preparation plan and coaching session based on my background:</p>
<ul>
<li>Experience level: Junior</li>
<li>Technical background: Flutter, Kotlin, Mobile Development</li>
<li>Target roles: Mobile App Developer</li>
<li>Target companies: Kakao, Naver, Baemin 등</li>
<li>Interview timeline: 2025년 6~12월 중</li>
<li>Current preparation status: 3개의 포트폴리오 진행 중, CS 공부 병행</li>
<li>Strengths: UI 구현, 문제 해결력</li>
<li>Weaknesses: 코딩테스트, 행동면접</li>
</ul>
<h3 id="step-2-이력서-기반-계획-수립">Step 2: 이력서 기반 계획 수립</h3>
<ol>
<li>3개월 학습 계획 수립 (월간/주간 목표 포함)</li>
<li>반드시 알아야 할 알고리즘 &amp; 자료구조 리스트 정리 (난이도 포함)</li>
<li>목표 기업 스타일에 맞는 실전 문제 3개 생성</li>
<li>시스템 설계 접근 전략 (지원 레벨 기준)</li>
<li>행동면접 시나리오 작성 (STAR 기법 활용)</li>
<li>긴장 완화 전략 및 예상 질문에 대한 대응법</li>
<li>기업 문화에 맞춘 면접 답변 전략</li>
</ol>
<h2 id="3-iterative-refinement-through-targeted-feedback">3. Iterative Refinement Through Targeted Feedback</h2>
<p>대부분의 개발자는 초기 결과물을 그대로 사용하는 경향이 있다. 다음과 같이 피드백 기반 반복 개선을 유도할 수 있다.</p>
<h3 id="prompt-예시">Prompt 예시</h3>
<p>I&#39;ll evaluate your solution based on these specific criteria:</p>
<pre><code>1. Performance: Does it scale efficiently with large data sets?
2. Maintainability: Is the code well-structured and documented?
3. Error handling: Does it gracefully handle edge cases?
4. Security: Are there any potential vulnerabilities?
5. Test coverage: Can this solution be thoroughly tested?
For any areas that don&#39;t meet expectations, I&#39;ll provide specific feedback for improvement.

Initial task: [YOUR DEVELOPMENT TASK]</code></pre><ul>
<li>이런 식으로 응답에 대해 부족한 점을 재서술해서 결과를 보정할 수 있다.</li>
</ul>
<hr>
<h1 id="끝으로">끝으로</h1>
<p> 프롬프트 공부도 시간날 때 해야지..(특: 시간날 때 없음, *시간내서)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] iOS 환경에서 custom font weight < 400 이하 적용 안됨 이슈]]></title>
            <link>https://velog.io/@woojin-devv/flutter-custom-font-weight-400-%EC%9D%B4%ED%95%98-%EC%A0%81%EC%9A%A9-%EC%95%88%EB%90%A8-%EC%9D%B4%EC%8A%88</link>
            <guid>https://velog.io/@woojin-devv/flutter-custom-font-weight-400-%EC%9D%B4%ED%95%98-%EC%A0%81%EC%9A%A9-%EC%95%88%EB%90%A8-%EC%9D%B4%EC%8A%88</guid>
            <pubDate>Thu, 08 May 2025 08:44:30 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/woojin-devv/post/0fda64f1-a4ab-43fe-9184-4de731a7023c/image.png" alt=""></p>
<h4 id="pubspecyaml">pubspec.yaml</h4>
<pre><code>flutter:
  assets:
    - assets/images/
  fonts:
    - family: GyeonggiBatang
      fonts:
        - asset: fonts/GyeonggiBatang_Bold.ttf
        - asset: fonts/GyeonggiBatang_Regular.ttf
    - family: GyeonggiTitle
      fonts:
        - asset: fonts/GyeonggiTitle_Light.ttf
          weight: 300
        - asset: fonts/GyeonggiTitle_Medium.ttf
          weight: 500
        - asset: fonts/GyeonggiTitle_Bold.ttf
          weight: 700
    - family: GyeonggiTitleV
      fonts:
        - asset: fonts/GyeonggiTitleV_Bold.ttf
</code></pre><p>-&gt; 이런식으로 적용해서 </p>
<h4 id="code">code</h4>
<pre><code>Text(
                      &#39;학부모 / 자녀 &#39;,
                      style: TextStyle(
                        fontFamily: &#39;GyeonggiTitle&#39;,
                        fontSize: 14,
                        fontWeight: FontWeight.w300,
                        color: Color(0xff525152),
                      ),
                    ),</code></pre><p>fontweight을 적용했더니, ios에서 적용이 안된다. 
<img src="https://velog.velcdn.com/images/woojin-devv/post/de4248a6-ecef-4d7d-b913-67d5174005ae/image.png" alt=""></p>
<ul>
<li>폰트 파일의 내부 Weight Class (예: 300 = Light, 400 = Regular)가 실제로 어떻게 적용되어있는지 파악하기 위해 <a href="https://fontdrop.info/">https://fontdrop.info/</a> 를 접속해서 확인해봤더니, Weight class가 300으로 지정되어 있었음에도 불구하고 ios 디바이스에서는 font weight이 적용이 되지 않음을 확인 했다. </li>
<li>그래서 안드로이드 에뮬레이터에서도 돌려보니.. 안드로이드 에뮬레이터에서는 font weight이 적용이 되어있었다.</li>
<li>결론 = pubspec.yaml 문제는 아님<ul>
<li>그래서 구글링을 해서 좀 찾아보았다. </li>
<li><a href="https://github.com/flutter/flutter/issues/75832">https://github.com/flutter/flutter/issues/75832</a></li>
<li><blockquote>
<p>Font Weight Light이 weight &lt; 400일 경우, ios 혹은 web 환경에서 렌더가 안되는 이슈가 있었다. </p>
</blockquote>
</li>
</ul>
</li>
</ul>
<blockquote>
<p>어쨌든, 해결은 해야한다. 
결론, 폰트 family 클래스별로 분류하지 말고 폰트 단일 파일별로 분류를 해야했다. </p>
</blockquote>
<h4 id="수정한-pubspecyaml">수정한 pubspec.yaml</h4>
<pre><code>  fonts:
    - family: GyeonggiBatangBold
      fonts:
        - asset: fonts/GyeonggiBatang_Bold.ttf
    - family: GyeonggiBatangRegular
      fonts:
        - asset: fonts/GyeonggiBatang_Regular.ttf
    - family: GyeonggiTitleLight
      fonts:
        - asset: fonts/GyeonggiTitle_Light.ttf
    - family: GyeonggiTitleMedium
      fonts:
        - asset: fonts/GyeonggiTitle_Medium.ttf
    - family: GyeonggiTitleBold
      fonts:
        - asset: fonts/GyeonggiTitle_Bold.ttf
    - family: GyeonggiTitleVBold
      fonts:
        - asset: fonts/GyeonggiTitleV_Bold.ttf
</code></pre><h4 id="code-1">code</h4>
<pre><code>                    Text(
                      &#39;학부모 / 자녀 &#39;,
                      style: TextStyle(
                        fontFamily: &#39;GyeonggiTitleLight&#39;,
                        fontSize: 14,
                        fontWeight: FontWeight.w300,
                        color: Color(0xff525152),
                      ),
                    ),
</code></pre><p><img src="https://velog.velcdn.com/images/woojin-devv/post/f9c5a114-5a7d-49c3-afcf-ca2a1c162245/image.png" alt=""></p>
<p>렌더링이 되긴 한다 ^^
주먹구구식이지만,, 도움은 되셨길.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[API] REST, REST API, RESTful]]></title>
            <link>https://velog.io/@woojin-devv/API-REST-REST-API-RESTful</link>
            <guid>https://velog.io/@woojin-devv/API-REST-REST-API-RESTful</guid>
            <pubDate>Sun, 20 Apr 2025 12:18:00 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/woojin-devv/post/c3e4b25b-0e0b-4cb5-a8cd-5381cab7b51b/image.png" alt=""></p>
<h1 id="개요">개요</h1>
<blockquote>
<p>REST, REST API, RESTful에 대한 정의와 특징</p>
</blockquote>
<hr>
<h1 id="1-api란">1. API란?</h1>
<p><strong>API(Application Programming Interface)</strong>는 소프트웨어 시스템 간에 기능이나 데이터를 주고받을 수 있도록 정해진 규칙, 명령, 프로토콜을 제공하는 인터페이스</p>
<ul>
<li>한 프로그램이 다른 프로그램의 기능이나 데이터를 사용할 수 있도록 해주는 일종의 다리 역할을 한다.</li>
<li>API는 운영체제, 라이브러리, 데이터베이스, 하드웨어 등 다양한 환경에서 동작할 수 있으며, 반드시 네트워크를 사용할 필요는 없다.<ul>
<li>예시 ) 운영체제 API(Windows API, macOS API), 라이브러리 API(파이썬 pandas 등)</li>
</ul>
</li>
</ul>
<table>
<thead>
<tr>
<th>용어</th>
<th>설명</th>
<th>추가정보 / 예시</th>
</tr>
</thead>
<tbody><tr>
<td>Client</td>
<td>웹에서 정보에 액세스하려는 사용자</td>
<td>사용자는 웹 브라우저, 앱 등 다양한 클라이언트로 서버에 요청을 보냄</td>
</tr>
<tr>
<td>Server(서버)</td>
<td>클라이언트에 리소스를 제공하는 시스템</td>
<td>클라이언트 요청을 받아 리소스를 전달하거나 조작하는 역할을 담당</td>
</tr>
<tr>
<td>Resource(리소스)</td>
<td>클라이언트에게 제공하는 정보</td>
<td>REST에서 서버가 관리하는 모든 데이터 또는 객체 (예: 문서, 사진, DB 내용 등)</td>
</tr>
</tbody></table>
<h1 id="2-rest란">2. REST란?</h1>
<p><strong>REST(Representational State Transfer)</strong>는 웹에서 자원의 상태를 표현(Representation)하여 전송(Transfer)하는 아키텍처 스타일</p>
<ul>
<li>REST는 HTTP 프로토콜을 기반으로 하며, 자원(Resource)에 대한 고유 식별자(URI)를 사용한다.</li>
<li>클라이언트는 서버의 자원에 접근하거나 상태를 변경할 때 JSON, XML 등으로 데이터를 주고받는다.</li>
</ul>
<h2 id="21-rest-api와-restful의-차이">2.1 REST API와 RESTful의 차이</h2>
<ul>
<li>REST API: REST 원칙을 따르는 API로, HTTP URI로 자원을 명시하고, HTTP Method(POST, GET, PUT, DELETE 등)로 해당 자원에 대한 CRUD(생성, 조회, 수정, 삭제)를 구현한다.</li>
<li>RESTful: REST의 원칙을 잘 지키도록 설계된 API를 의미한다. RESTful하지 않은 API는 단순히 웹 API 또는 HTTP API로 부른다.</li>
</ul>
<h2 id="22-rest의-주요-특징">2.2 REST의 주요 특징</h2>
<table>
<thead>
<tr>
<th>특징</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>Uniform Interface</td>
<td>URI로 리소스를 식별하고, HTTP 표준 방식으로 조작한다.</td>
</tr>
<tr>
<td>Stateless</td>
<td>서버는 클라이언트의 상태를 따로 저장하지 않고, 각 요청은 독립적으로 처리</td>
</tr>
<tr>
<td>Cacheable</td>
<td>HTTP 표준 캐싱 기능을 활용하여 성능을 높일 수 있음</td>
</tr>
<tr>
<td>Client-Server 구조</td>
<td>클라이언트와 서버의 역할이 명확히 분리되어 의존성이 줄어듦</td>
</tr>
<tr>
<td>Layered System</td>
<td>중간 서버(프록시, 게이트웨이 등)을 둘 수 있어 유연성이 높아짐</td>
</tr>
<tr>
<td>Self-descriptiveness</td>
<td>메시지 자체에 필요한 정보를 포함하기 때문에 별도의 description없이 이해 가능</td>
</tr>
</tbody></table>
<h2 id="23-rest-api-설계-기본-규칙">2.3 REST API 설계 기본 규칙</h2>
<ol>
<li>URI는 자원을 표현한다<ul>
<li>동사 대신 명사 사용 / 대문자 대신 소문자 사용</li>
<li>단수 명사는 객체, 복수 명사는 컬렉션 표현<ul>
<li>예) <code>/members/1</code> (O),  <code>Member/1</code> (X)</li>
</ul>
</li>
</ul>
</li>
<li>행위는 HTTP Method로 표현<ul>
<li><code>GET, POST, PUT, DELETE</code> 등</li>
<li>URI에 동사나 행위가 포함되면 안됨</li>
<li><code>GET /members/1</code> (O),  <code>GET /members/delete/1</code>(X)</li>
</ul>
</li>
<li>슬래시 (/)는 계층 관계 표현, 마지막에 사용하지 않는다.<ul>
<li><code>/users/123/devices</code> (O),  <code>/users/123/devices/</code> (X)</li>
</ul>
</li>
<li>하이픈을 사용하되, 언더스코어는 피한다. <ul>
<li>예: <code>/user-groups</code> (O), <code>/user_groups</code> (X)</li>
</ul>
</li>
<li>소문자 사용, 파일 확장자 사용 금지<ul>
<li>예: <code>/users</code> (O), <code>/Users.json</code> (X)</li>
</ul>
</li>
</ol>
<h2 id="24-restful-api-클라이언트-요청--응답-예시">2.4 <strong>RESTful API 클라이언트 요청 / 응답 예시</strong></h2>
<ul>
<li>가령, 사용자 관리(User Management)를 위한 RESTful API가 존재한다고 가정.<ul>
<li>클라이언트는 이 API를 통해 User를 생성하거나 조회할 수 있음</li>
<li>RESTful API의 설계에 따라 <code>/users</code> 라는 URI로 사용자 리소스를 나타내고, 다양한 HTTP 메서드를 적용 가능함</li>
</ul>
</li>
</ul>
<h3 id="241-restful-api-예시---http-method">2.4.1 RESTful API 예시 - HTTP Method</h3>
<table>
<thead>
<tr>
<th>HTTP Method</th>
<th>URI</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>GET</strong></td>
<td><code>/users</code></td>
<td>클라이언트는 GET을 사용하여 서버의 지정된 URL에 있는 리소스에 액세스한다. (users의 모든 사용자 조회)</td>
</tr>
<tr>
<td><strong>GET</strong></td>
<td><code>/users/{id}</code></td>
<td>특정 사용자 조회한다. (users group의 id값에 따른)</td>
</tr>
<tr>
<td><strong>POST</strong></td>
<td><code>/users</code></td>
<td>클라이언트는 POST를 사용하여 서버에 데이터를 전송(새로운 사용자 추가)</td>
</tr>
<tr>
<td><strong>PUT</strong></td>
<td><code>/users/{id}</code></td>
<td>클라이언트는 PUT을 사용하여 서버의 기존 리소스를 업데이트 (users group의 새로운 사용자 추가)</td>
</tr>
<tr>
<td><strong>DELETE</strong></td>
<td><code>/users/{id}</code></td>
<td>클라이언트는 DELETE 요청을 사용하여 리소스를 제거(사용자 삭제)</td>
</tr>
<tr>
<td>- HTTP Method를 통해 해당 자원에 대하여 CRUD Operation을 적용할 수 있다.</td>
<td></td>
<td></td>
</tr>
</tbody></table>
<h3 id="crud란">CRUD란?</h3>
<blockquote>
<p><strong>C</strong>reate: 데이터 생성 (Post)
<strong>R</strong>ead: 데이터 조회 (GET)
<strong>U</strong>pdate: 데이터 수정 (PUT, PATCH)
<strong>D</strong>elete: 데이터 삭제 (DELETE)</p>
</blockquote>
<h2 id="25-rest-api-설계-규칙">2.5 REST API 설계 규칙</h2>
<ol>
<li>슬래시 구분자 (/)는 계층 관계를 나타내는데 사용한다. <ul>
<li><code>/users/123/devices</code>는 “123번 사용자”의 “devices”라는 하위 리소스를 의미</li>
</ul>
</li>
<li>URI 마지막 문자로 슬래시(/)를 포함하지 않는다.<ul>
<li>REST API에서 URI의 마지막에 슬래시(/)를 붙이지 않는 것은, 혼란을 방지하고 일관성 있는 리소스 식별을 위해 지켜야 할 중요한 설계 규칙이다.</li>
<li>식별자 일관성: URI의 모든 문자는 리소스의 고유 식별자에 포함된다. 즉, <code>/api/items</code>와 <code>/api/items/</code>는 엄밀히 따지면 서로 다른 URI이며, 다르게 인식될 수 있다</li>
<li>웹 서버나 프레임워크에 따라 두 URI를 동일하게 처리하기도 하지만, 명확하고 예측 가능한 API를 제공하기 위해서는 마지막에 슬래시를 붙이지 않는 것이 표준</li>
</ul>
</li>
</ol>
<hr>
<h3 id="참조">참조</h3>
<p><a href="https://aws.amazon.com/ko/what-is/restful-api/">RESTful API란 무엇인가요? - RESTful API 설명 - AWS</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] Container class]]></title>
            <link>https://velog.io/@woojin-devv/flutter-Container-class</link>
            <guid>https://velog.io/@woojin-devv/flutter-Container-class</guid>
            <pubDate>Wed, 16 Apr 2025 14:30:22 GMT</pubDate>
            <description><![CDATA[<h1 id="container-class">Container Class</h1>
<hr>
<ul>
<li>위젯에 배경 스타일, 배경색이나 모양, 크기 제한 등을 하고 싶을 때 사용</li>
<li>하위 요소 위젯 구성, 장식, 위치를 정할 수 있음</li>
</ul>
<blockquote>
<p>예시)</p>
</blockquote>
<pre><code class="language-dart">Text(&#39;Boring&#39;)</code></pre>
<ul>
<li>위와 같은 Text 위젯의 배경스타일, 모양, 크기 제한을 하고 싶을 때</li>
</ul>
<blockquote>
<pre><code class="language-dart">Container(
    child: Text(&#39;Boring&#39;),
    color: Colors.blue,
);</code></pre>
</blockquote>
<pre><code>
- 추가 작업 없이도 container의 크기는 자식 위젯의 크기에 맞춰짐
- padding 추가 가능
- `decoration` property를 통해 container의 shape을 추가할 수 있음


&lt;div align=&quot;center&quot;&gt;
  &lt;img src=&quot;https://velog.velcdn.com/images/woojin-devv/post/0f01f194-ae84-4315-91d2-073578962371/image.png&quot; width=&quot;400&quot;&gt;
&lt;/div&gt;

### 전체 코드

```dart
import &#39;package:flutter/material.dart&#39;;

class ContainerScreen extends StatelessWidget {
  const ContainerScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.white,
        title: const Text(
          &#39;Container Example&#39;,
          style: TextStyle(fontWeight: FontWeight.bold),
        ),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            // 기본 텍스트 위젯 (Container 없음)
            const Text(
              &#39;Boring&#39;,
              style: TextStyle(
                fontSize: 20,
                fontWeight: FontWeight.bold,
              ),
            ),

            // 배경 색만 지정한 Container
            Container(
              color: Colors.blue,
              child: const Text(
                &#39;Boring&#39;,
                style: TextStyle(
                  fontSize: 20,
                  fontWeight: FontWeight.bold,
                ),
              ),
            ),

            // 패딩이 추가된 Container
            Container(
              padding: const EdgeInsets.all(20),
              color: Colors.blue,
              child: const Text(
                &#39;Boring&#39;,
                style: TextStyle(
                  fontSize: 20,
                  fontWeight: FontWeight.bold,
                ),
              ),
            ),

            // 회전(transform) 효과가 추가된 Container
            Container(
              transform: Matrix4.rotationZ(0.1),
              padding: const EdgeInsets.all(20),
              color: Colors.blue,
              child: const Text(
                &#39;Boring&#39;,
                style: TextStyle(
                  fontSize: 20,
                  fontWeight: FontWeight.bold,
                ),
              ),
            ),

            // 원형 모양으로 만들어진 Container
            Container(
              decoration: const BoxDecoration(
                shape: BoxShape.circle,
                color: Colors.blue,
              ),
              padding: const EdgeInsets.all(20),
              child: const Text(
                &#39;Boring&#39;,
                style: TextStyle(
                  fontSize: 20,
                  fontWeight: FontWeight.bold,
                ),
              ),
            )
          ],
        ),
      ),
    );
  }
}
</code></pre><hr>
<h3 id="참조">참조</h3>
<p><a href="https://api.flutter.dev/flutter/widgets/Container-class.html">Container class - widgets library - Dart API</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] Column, Row class 정리]]></title>
            <link>https://velog.io/@woojin-devv/flutter-%EB%A0%88%EC%9D%B4%EC%95%84%EC%9B%83-%EC%9C%84%EC%A0%AFColumn-Row</link>
            <guid>https://velog.io/@woojin-devv/flutter-%EB%A0%88%EC%9D%B4%EC%95%84%EC%9B%83-%EC%9C%84%EC%A0%AFColumn-Row</guid>
            <pubDate>Wed, 16 Apr 2025 06:17:30 GMT</pubDate>
            <description><![CDATA[<h3 id="1-column-class">1. Column class</h3>
<ul>
<li>A widget that displays its children in a vertical array</li>
<li>수직 방향 배열로 자식 위젯들을 나열하는 위젯</li>
<li>기본적으로 <code>Column</code>에 있는 자식 위젯들은 필요한 만큼만 공간을 차지함.</li>
</ul>
<h3 id="2-row-class">2. Row class</h3>
<ul>
<li>A widget that displays its children in a horizontal array</li>
<li>수평 방향 배열로 자식 위젯들을 나열하는 위젯</li>
<li><code>Column</code> class와 마찬가지로 <code>Row</code> 의 자식 위젯들은 필요한 만큼만 공간을 차지함.</li>
</ul>
<h3 id="필요한-만큼-공간을-차지한다는-의미는">필요한 만큼 공간을 차지한다는 의미는?</h3>
<p>예를 들어, </p>
<pre><code class="language-dart">Column(
  children: [
    Text(&quot;Hello&quot;),
    Text(&quot;World&quot;),
  ],
)</code></pre>
<div align="center">
  <img src="https://velog.velcdn.com/images/woojin-devv/post/bf6c0944-9fea-4c51-8a54-7614db3cc6b3/image.png" width="300"/>
</div>


<p><code>Column</code> 위젯의 자식 위젯들은 <code>Text</code> 위젯인데, 이 자식 위젯들은 자기의 고유한 공간 <strong>만큼만</strong> 차지함 (Widget Inspector로 보면 자식 위젯의 고유 크기를 가시적으로 볼 수 있음)</p>
<p>→ Column의 공간을 전부 사용하고 싶다면??</p>
<h3 id="expanded-사용">Expanded 사용</h3>
<ul>
<li>여러 개의 <code>Expanded</code>가 있으면 공간을 <strong>비율대로 나눔</strong></li>
</ul>
<p>→ <code>Expanded</code>는 가능한 남은 공간을 자식 위젯에게 주는 역할을 한다.</p>
<p>아래 코드의 예시는 3개의 Expanded가 존재하기 때문에 Column의 고유 Height 만큼을 각각 1/3 씩 나눠가짐</p>
<p>→ <code>Expanded</code> 가 4개라면?? → 1/4씩 나눠가짐</p>
<pre><code class="language-dart">Column(
        children: [
          Expanded(
            child: Container(color: Colors.red), // 1/3 공간
          ),
          Expanded(
            child: Container(color: Colors.green), // 1/3 공간
          ),
          Expanded(
            child: Container(color: Colors.blue), // 1/3 공간
          ),
        ],
      ),</code></pre>
<pre><code class="language-dart">Row(
        children: [
          Expanded(
            child: Container(color: Colors.red), // 1/3 공간
          ),
          Expanded(
            child: Container(color: Colors.green), // 1/3 공간
          ),
          Expanded(
            child: Container(color: Colors.blue), // 1/3 공간
          ),
        ],
      ),</code></pre>
<pre><code class="language-dart">Row(
        children: [
          Container(
            width: 200,
            height: 200,
            color: Colors.red,
          ),
          Container(
            width: 200,
            height: 200,
            color: Colors.green,
          ),
          Container(
            width: 200,
            height: 200,
            color: Colors.blue,
          )
        ],
      ),</code></pre>
<p><img src="https://velog.velcdn.com/images/woojin-devv/post/02ae954d-47cb-4e5e-a308-6aaf66960b01/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] Splash Screen 구현: Route로 화면 전환하기]]></title>
            <link>https://velog.io/@woojin-devv/Flutter-Splash-Screen-%EA%B5%AC%ED%98%84-Route%EB%A1%9C-%ED%99%94%EB%A9%B4-%EC%A0%84%ED%99%98%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@woojin-devv/Flutter-Splash-Screen-%EA%B5%AC%ED%98%84-Route%EB%A1%9C-%ED%99%94%EB%A9%B4-%EC%A0%84%ED%99%98%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 14 Apr 2025 06:10:39 GMT</pubDate>
            <description><![CDATA[<h2 id="📁-디렉터리-구조">📁 디렉터리 구조</h2>
<pre><code class="language-dart">lib
├── main.dart
└── screens
    ├── main_screen.dart
    └── splash_screen.dart</code></pre>
<h2 id="실행흐름">실행흐름</h2>
<ul>
<li><p>앱 실행 시 &#39;/&#39;에서 시작됨</p>
<ul>
<li><p><code>&#39;/&#39; : (context) =&gt; const SplashScreen()</code> 으로 설정되어 있어, 앱을 실행하면 SplashScreen UI가 첫 화면으로 초기화됨</p>
</li>
<li><p><code>splash_screen.dart</code>에서는 다음과 같이 작성되어 있어, 2초 후에 <code>&#39;/main&#39;</code> 경로로 이동하도록 되어 있음:</p>
<pre><code class="language-dart">  Future.delayed(const Duration(seconds: 2), () {
    Navigator.pushNamed(context, &#39;/main&#39;);
  });</code></pre>
</li>
</ul>
</li>
</ul>
<h3 id="gif">GIF</h3>
<div align="center">
  <img src="https://velog.velcdn.com/images/woojin-devv/post/7029ea58-1c69-4092-85ef-ebe0911d4f51/image.gif" width="200" />
</div>



<h2 id="소스코드">소스코드</h2>
<hr>
<h3 id="maindart">main.dart</h3>
<pre><code class="language-dart">import &#39;package:flutter/material.dart&#39;;

class SplashScreen extends StatelessWidget {
  const SplashScreen({super.key});

  @override
  Widget build(BuildContext context) {
    Future.delayed(const Duration(seconds: 2), () {
      Navigator.pushNamed(context, &#39;/main&#39;);
    });
    return Scaffold(
      appBar: AppBar(),
      body: const Center(
        child: Text(&#39;Splash Screen&#39;),
      ),
    );
  }
}</code></pre>
<h3 id="main_screendart">main_screen.dart</h3>
<pre><code class="language-dart">import &#39;package:flutter/material.dart&#39;;

class MainScreen extends StatefulWidget {
  const MainScreen({super.key});

  @override
  State&lt;MainScreen&gt; createState() =&gt; _MainScreenState();
}

class _MainScreenState extends State&lt;MainScreen&gt; {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text(&#39;MainScreen&#39;),
      ),
      body: const Column(),
    );
  }
}</code></pre>
<h3 id="splash_screendart">splash_screen.dart</h3>
<pre><code class="language-dart">import &#39;package:flutter/material.dart&#39;;

class SplashScreen extends StatelessWidget {
  const SplashScreen({super.key});

  @override
  Widget build(BuildContext context) {
    Future.delayed(const Duration(seconds: 2), () {
      Navigator.pushNamed(context, &#39;/main&#39;);
    });
    return Scaffold(
      appBar: AppBar(),
      body: const Center(
        child: Text(&#39;Splash Screen&#39;),
      ),
    );
  }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] MaterialApp 이란?]]></title>
            <link>https://velog.io/@woojin-devv/flutter-MaterialApp-%EC%9D%B4%EB%9E%80</link>
            <guid>https://velog.io/@woojin-devv/flutter-MaterialApp-%EC%9D%B4%EB%9E%80</guid>
            <pubDate>Mon, 14 Apr 2025 05:34:39 GMT</pubDate>
            <description><![CDATA[<h1 id="📱-materialapp-클래스란">📱 MaterialApp 클래스란?</h1>
<hr>
<ul>
<li>Flutter 앱의 뼈대가 되는 위젯</li>
<li>MaterialApp 위젯을 사용하면 구글의 Material Design 스타일을 쉽게 사용가능</li>
</ul>
<h2 id="주된-역할">주된 역할</h2>
<ol>
<li>앱 전체 테마 설정 (색상, 글꼴등을 설정)</li>
<li>라우팅 (화면 이동) 관리 </li>
<li>홈 화면 지정</li>
<li>앱 이름, 아이콘 등 설정</li>
</ol>
<div style="display: flex; align-items: center; justify-content: center;">
  <!-- 이미지 영역 -->
  <div>
    <img src="https://velog.velcdn.com/images/woojin-devv/post/b4941a8b-c5b3-4053-b56d-a0fb65ef076a/image.png" width="200" />
  </div>

  <!-- 설명글 영역 -->
  <div style="margin-left: 20px; max-width: 400px;">
    <p style="font-size: 16px; line-height: 1.5;">
-> Figma의 Material Design Icons를 사용하면, 
      <br>Google 에서 지원하는 아이콘을 활용하여 UI를 구성할 수 있음
    </p>
  </div>
</div>

<h2 id="속성">속성</h2>
<table>
<thead>
<tr>
<th>속성</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>home</code></td>
<td>앱이 시작할 때 처음 보여줄 화면</td>
</tr>
<tr>
<td><code>theme</code></td>
<td>앱 전체에 적용될 테마 (색상, 글꼴)</td>
</tr>
<tr>
<td><code>routes</code></td>
<td>화면 이름과 실제 위젯을 매핑해주는 딕셔너리</td>
</tr>
<tr>
<td><code>initialRoute</code></td>
<td>앱 시작시 보여줄 경로 (기본 <code>/</code> )</td>
</tr>
<tr>
<td><code>navigatorKey</code></td>
<td>전역에서 화면 이동을 제어할 수 있는 키</td>
</tr>
<tr>
<td><code>debugShowCheckedModeBanner</code></td>
<td>앱 우측 상단 ‘DEBUG’ 배너 표시 여부 (기본은 true)</td>
</tr>
</tbody></table>
<h2 id="예시-코드">예시 코드</h2>
<pre><code class="language-dart">class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: &#39;Flutter Demo&#39;,
      routes: {
        &#39;/&#39;: (context) =&gt; SplashScreen(),
        &#39;/main&#39;: (context) =&gt; MainScreen(),
      }
    );
  }
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[안드로이드 스튜디오] UI 뜯어보기 #1 - 마켓컬리 카드뷰 (figma, android studio)]]></title>
            <link>https://velog.io/@woojin-devv/UI-UI-%EB%9C%AF%EC%96%B4%EB%B3%B4%EA%B8%B0-1-%EB%A7%88%EC%BC%93%EC%BB%AC%EB%A6%AC-%EC%B9%B4%EB%93%9C%EB%B7%B0-figma-android-studio</link>
            <guid>https://velog.io/@woojin-devv/UI-UI-%EB%9C%AF%EC%96%B4%EB%B3%B4%EA%B8%B0-1-%EB%A7%88%EC%BC%93%EC%BB%AC%EB%A6%AC-%EC%B9%B4%EB%93%9C%EB%B7%B0-figma-android-studio</guid>
            <pubDate>Thu, 06 Feb 2025 19:08:30 GMT</pubDate>
            <description><![CDATA[<p>안녕하시렵니까..😄</p>
<p><strong>velog 첫 게시글의 내용을 고민하다가 일단 글이라도 써보자해서 쓰는 UI 뜯어보기</strong>
시작하겠습니다.~</p>
<h2 id="1-레퍼런스-준비">1. 레퍼런스 준비</h2>
<p>일단 figma에서 새 파일을 만들고, frame은 제가 현재 사용중인 폰과 규격이 맞는 프레임을 불러올게요
(왜냐하면 제 폰으로 홈화면을 캡처했기 때문입니다.)</p>
<p>프레임 안에 캡처한 이미지를 넣어주면
<img src="https://velog.velcdn.com/images/woojin-devv/post/e0b0bc95-6fa7-4466-83fd-0cd201c6ad37/image.png" alt=""></p>
<p>위와 같이 카드뷰의 규격을 딸 레퍼런스가 완성 됩니다 ㅎ
&lt;컬리앱 전부를 클론 코딩할 시간은 없어서 카드뷰만 뜯어보도록 하겠습니다.&gt;</p>
<h2 id="2-레이아웃-분해">2. 레이아웃 분해</h2>
<p><img src="https://velog.velcdn.com/images/woojin-devv/post/2ff6f5d2-61b4-4557-9eaa-ecbdab8af7d4/image.png" alt=""></p>
<p>위와 같은 식으로 레이아웃을 크게 세 부분으로 나눌 수 있습니다. (이미지 - 카트 - 가격 및 정보)
레이아웃 딸 때는 보통 채도가 높은 색의 opacity를 줄여서 레이아웃을 따면 더 수월합니다 </p>
<p>##3. 사용할 데이터 명세
하나의 카드뷰에서 필요한 데이터 클래스를 미리 표시해줍니다.</p>
<pre><code>&lt;data class&gt; 

이미지 -&gt; img
담기 버튼 -&gt;그냥 frame레이아웃으로 구성 -&gt; cart
품목이름 -&gt; title
할인 전 금액 -&gt; original_price
할인율 -&gt; discount_rate
가격 -&gt; price</code></pre><p>아래 이미지와 같은 식으로 xml의 id를 네이밍 해줄거예요.
<img src="https://velog.velcdn.com/images/woojin-devv/post/ed438f14-5f43-4fd9-a425-64cb3b6cae97/image.png" alt=""></p>
<p>font는 정확히 뭔지 모르겠어서 가장 비슷한 &#39;inter&#39;로 하도록 합니다. !</p>
<p><img src="https://velog.velcdn.com/images/woojin-devv/post/626286bd-dbae-44bb-906b-18fed83679b8/image.png" alt=""></p>
<p>figma의 스포이드로 자주 쓰일 것 같은 색들을 미리 따줍니다.</p>
<ul>
<li>데이터 클래스 정의하기 위한 네이밍 </li>
<li>반복적으로 사용하는 색상 -&gt; 팔레트 만들기</li>
<li>카드뷰 horizontal 방향으로 스크롤 됨 (카드뷰간 spacing은 8dp임) </li>
</ul>
<p>Q)여기서 알 수 있는 건 카드뷰 리소스를 만들 때, margin값을 얼마를 줘야할까요? </p>
<p>A) 정답은 4dp입니다~~ 
리소스로 등록해둔 뷰가 수평 방향으로 반복적 사용되기 때문입니다.
<img src="https://velog.velcdn.com/images/woojin-devv/post/7b0ce6bc-9217-482d-9b73-27d086f7e96b/image.png" alt=""></p>
<hr>
<h2 id="4-res--values--colorsxml-에-색상-리소스-추가-feat-gpt">4. res &gt; values &gt; colors.xml 에 색상 리소스 추가 (feat. GPT)</h2>
<p><img src="https://velog.velcdn.com/images/woojin-devv/post/76867b27-3655-4a9a-919e-8d59318c6ddf/image.png" alt="">
타이핑하기 매우 귀찮으므로, chatgpt에 팔레트 이미지와 프롬프트를 입력하고, 그걸 복사해서 colors.xml에 리소스로 추가합니다.</p>
<pre><code>&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;resources&gt;
    &lt;!-- 원래 있던 색상 --&gt;
    &lt;color name=&quot;black&quot;&gt;#FF000000&lt;/color&gt;
    &lt;color name=&quot;white&quot;&gt;#FFFFFFFF&lt;/color&gt;

    &lt;!-- 카드뷰에서 사용할 색상 --&gt;
    &lt;color name=&quot;black_replacement_color&quot;&gt;#2A2A2A&lt;/color&gt;
    &lt;color name=&quot;cart_stroke_color&quot;&gt;#E2E7EB&lt;/color&gt;
    &lt;color name=&quot;original_color&quot;&gt;#C0C9D2&lt;/color&gt;
    &lt;color name=&quot;discount_rate_color&quot;&gt;#FE5307&lt;/color&gt;
&lt;/resources&gt;</code></pre><p>일단, recyclerview를 만들기 위한 밑작업은 끝났습니다. </p>
]]></description>
        </item>
    </channel>
</rss>