7~10주차 — 웹 스크래핑 · NumPy / Pandas 입문 · 패키징과 배포 · 고급 종합 실습
requests 로 HTTP 요청, BeautifulSoup 로 HTML 파싱, 세션·쿠키 관리, 그리고 robots.txt 와 매너까지 — 윤리적 스크래핑의 기본을 익힙니다. NumPy 배열 연산과 인덱싱, Pandas DataFrame 의 필터·정렬·groupby, 그리고 CSV 읽고 분석하기까지 — 데이터 처리의 두 거인을 만납니다. 가상환경, pyproject.toml 의 구조, console_scripts 로 CLI 도구 제공, 그리고 TestPyPI 업로드까지 — 자체 패키지를 만들어 배포하는 전체 흐름. 비동기·외부 라이브러리·테스트·배포를 통합한 실전 프로젝트. 패키지 구조 + CI 적용까지 포함합니다.
이 강의에서 배우는 것
- 1requests 로 HTTP GET/POST 요청을 한다
- 2BeautifulSoup 로 HTML 을 파싱한다
- 3헤더·세션·쿠키를 다룬다
- 4robots.txt 와 매너를 지킨다
- 5NumPy 배열 생성·연산·인덱싱을 한다
- 6Pandas DataFrame 의 기본 조작을 익힌다
- 7CSV 를 읽고 필터·집계한다
- 8groupby 와 피벗 테이블을 사용한다
- 9pyproject.toml 의 구조를 이해한다
- 10자체 패키지를 빌드해서 설치한다
- 11console_scripts 로 CLI 도구를 제공한다
- 12TestPyPI 에 업로드한다
- 13비동기·외부 라이브러리·테스트·배포를 통합한 실전 프로젝트를 완성한다
- 14패키지 구조 + CI 를 적용한다
7주차 — 웹 스크래핑
requests 로 HTTP 요청, BeautifulSoup 로 HTML 파싱, 세션·쿠키 관리, 그리고 robots.txt 와 매너까지 — 윤리적 스크래핑의 기본을 익힙니다.
1. requests
import requests
r = requests.get("https://example.com")
print(r.status_code) # 200
print(r.text[:200])
print(r.headers["Content-Type"])
# JSON API
r = requests.get("https://api.github.com/users/python")
data = r.json()
print(data["public_repos"])2. POST 요청
r = requests.post("https://httpbin.org/post",
data={"key": "value"},
headers={"User-Agent": "MyBot 1.0"},
)3. BeautifulSoup
pip install beautifulsoup4 lxmlfrom bs4 import BeautifulSoup
html = """<html><body>
<h1>제목</h1>
<ul>
<li class="item">A</li>
<li class="item">B</li>
</ul>
</body></html>"""
soup = BeautifulSoup(html, "html.parser")
print(soup.h1.text)
print([li.text for li in soup.find_all("li")])
print([li.text for li in soup.select("li.item")]) # CSS 선택자4. 세션과 쿠키
session = requests.Session()
session.headers.update({"User-Agent": "Mozilla/5.0"})
# 로그인
session.post("https://example.com/login", data={"user": "x", "pass": "y"})
# 같은 세션 → 쿠키 유지
r = session.get("https://example.com/me")5. 매너와 윤리
- robots.txt 확인: https://example.com/robots.txt
- 요청 간격: time.sleep(1) 정도 권장
- 사용자 에이전트: 정직하게 표시
- 저작권·약관 준수
스크래핑은 합법성과 윤리가 중요한 영역입니다. 사이트의 이용 약관과 robots.txt 를 반드시 확인하세요.
6. 실전 패턴
import requests
from bs4 import BeautifulSoup
URL = "https://news.ycombinator.com"
HEADERS = {"User-Agent": "Mozilla/5.0 (study)"}
def fetch_titles():
r = requests.get(URL, headers=HEADERS, timeout=10)
r.raise_for_status()
soup = BeautifulSoup(r.text, "html.parser")
titles = [a.get_text() for a in soup.select(".titleline > a")]
return titles[:10]
for i, title in enumerate(fetch_titles(), 1):
print(f"{i}. {title}")자주 하는 실수
- timeout 누락 — 무한 대기. 항상 timeout=10
- raise_for_status() 안 함 — 404 못 알아챔
- HTML 파서 미지정 — 명시적으로 'html.parser'
- 선택자 깨지기 쉬움 — id/class 변동에 취약
- 차단당함 — 너무 빠른 요청. time.sleep 으로 간격
FAQ
Q1. JS 렌더링 페이지? — Selenium, Playwright 사용.
Q2. 동적 사이트? — 보통 내부 API 직접 호출(개발자 도구 → 네트워크 탭).
Q3. 로그인 후 페이지? — Session 사용. CSRF 토큰까지.
8주차 — NumPy / Pandas 입문
NumPy 배열 연산과 인덱싱, Pandas DataFrame 의 필터·정렬·groupby, 그리고 CSV 읽고 분석하기까지 — 데이터 처리의 두 거인을 만납니다.
0. 설치
pip install numpy pandas matplotlib1. NumPy 기본
import numpy as np
a = np.array([1, 2, 3, 4, 5])
print(a.mean(), a.std(), a.sum())
# 벡터 연산 (Python list보다 훨씬 빠름)
print(a * 2) # [2 4 6 8 10]
print(a + a)
print(a > 2) # 불리언 배열
# 2D
m = np.arange(12).reshape(3, 4)
print(m)
print(m.shape)
print(m.sum(axis=0)) # 컬럼 합
print(m.sum(axis=1)) # 행 합2. NumPy 인덱싱
a = np.arange(10)
print(a[3])
print(a[2:6])
print(a[a > 5]) # 불리언 인덱싱
m = np.arange(12).reshape(3, 4)
print(m[1, 2]) # 행 1, 열 2
print(m[:, 1]) # 모든 행, 열 1
print(m[1, :]) # 행 13. Pandas DataFrame
import pandas as pd
df = pd.DataFrame({
"name": ["Alice", "Bob", "Charlie", "Dave"],
"score": [92, 78, 85, 67],
"dept": ["A", "B", "A", "B"],
})
print(df)
print(df.describe())
print(df.shape, df.columns.tolist())4. 필터·정렬·집계
# 필터
print(df[df.score >= 80])
# 정렬
print(df.sort_values("score", ascending=False))
# 그룹 집계
print(df.groupby("dept")["score"].mean())
print(df.groupby("dept").agg({"score": ["mean", "max", "count"]}))5. CSV 읽기
df = pd.read_csv("sales.csv")
df["date"] = pd.to_datetime(df["date"])
monthly = df.groupby(df["date"].dt.to_period("M"))["amount"].sum()
print(monthly)6. 시각화 (matplotlib)
import matplotlib.pyplot as plt
monthly.plot(kind="bar")
plt.title("월별 매출")
plt.tight_layout()
plt.savefig("monthly.png")자주 하는 실수
- for 루프로 NumPy 배열 처리 — 벡터 연산 사용해야 빠름
- SettingWithCopyWarning — df[df.x > 0]['y'] = 5 대신 .loc[]
- 인덱스 vs 컬럼 혼동 — df['a'] 는 컬럼, df.loc[a] 는 인덱스
- NaN 무시 — dropna(), fillna() 로 명시적 처리
FAQ
Q1. NumPy vs list? — NumPy는 동일 타입, 메모리 연속, 벡터 연산. 수치 계산에서 100배 이상 빠름.
Q2. Pandas vs SQL? — Pandas는 메모리 내. SQL은 영구 저장 + 큰 데이터.
Q3. polars 가 더 빠르다는데? — 맞음. 큰 데이터에서 좋음. 다만 생태계는 pandas 압도적.
9주차 — 패키징과 배포
가상환경, pyproject.toml 의 구조, console_scripts 로 CLI 도구 제공, 그리고 TestPyPI 업로드까지 — 자체 패키지를 만들어 배포하는 전체 흐름.
1. 가상환경
프로젝트마다 라이브러리 버전을 분리합니다.
python -m venv .venv
# 활성화
source .venv/bin/activate # macOS/Linux
.venv\Scripts\activate # Windows
# 종료
deactivate2. pyproject.toml
현대 파이썬 패키지의 표준 설정 파일.
[project]
name = "mytool"
version = "0.1.0"
description = "내 멋진 CLI 도구"
authors = [{name = "홍길동", email = "hong@example.com"}]
requires-python = ">=3.10"
dependencies = [
"requests>=2.28",
"rich>=13.0",
]
[project.scripts]
mytool = "mytool.cli:main"
[build-system]
requires = ["setuptools>=61"]
build-backend = "setuptools.build_meta"3. 패키지 구조
mytool_project/
├── pyproject.toml
├── README.md
├── LICENSE
├── src/
│ └── mytool/
│ ├── __init__.py
│ └── cli.py
└── tests/
└── test_cli.py# src/mytool/cli.py
def main():
print("Hello from mytool!")설치:
pip install -e . # 개발 모드 (수정이 즉시 반영)
mytool # CLI 실행4. 빌드
pip install build
python -m build # dist/ 에 .whl, .tar.gz 생성5. TestPyPI 업로드
pip install twine
twine upload --repository testpypi dist/*다른 환경에서 설치:
pip install --index-url https://test.pypi.org/simple/ mytool6. requirements.txt vs pyproject.toml
| requirements.txt | pyproject.toml | |
|---|---|---|
| 용도 | 환경 고정 (deploy) | 라이브러리 메타데이터 |
| 버전 | 정확한 핀 | 범위 |
| 빌드 | 안 됨 | 됨 |
pip freeze > requirements.txt # 현재 환경 내보내기
pip install -r requirements.txt # 재현자주 하는 실수
- 가상환경 활성화 안 함 — 시스템 파이썬에 설치됨
- __init__.py 빠뜨림 — 패키지로 인식 안 됨
- src/ 레이아웃 안 씀 — import 충돌 가능
- 버전 안 올리고 재배포 — PyPI는 같은 버전 거부
FAQ
Q1. setup.py 는? — 신규는 pyproject.toml. 레거시 호환만 setup.py.
Q2. poetry vs pip+venv? — poetry는 통합 도구. 신규는 poetry나 uv 권장.
Q3. PyPI vs TestPyPI? — PyPI는 진짜 공개. TestPyPI는 연습용.
10주차 — 고급 종합 실습
비동기·외부 라이브러리·테스트·배포를 통합한 실전 프로젝트. 패키지 구조 + CI 적용까지 포함합니다.
1. 프로젝트 구조 (권장)
my_project/
├── pyproject.toml
├── README.md
├── src/my_project/
│ ├── __init__.py
│ ├── models.py (dataclass)
│ ├── client.py (httpx 비동기)
│ ├── analyzer.py (Pandas)
│ └── cli.py (argparse 진입점)
├── tests/
│ ├── test_models.py
│ ├── test_client.py
│ └── test_analyzer.py
└── .github/workflows/ci.yml (선택)2. 골격 — 비동기 크롤러 + 분석
models.py: dataclass 로 도메인 정의 → client.py: httpx 비동기 fetch → analyzer.py: Pandas 집계 → cli.py: argparse 로 진입점.
3. 체크리스트
- 타입 힌트 + mypy --strict
- pytest 테스트 (커버리지 70%+)
- CLI (argparse 또는 click)
- 비동기 또는 병렬 처리
- 외부 라이브러리 (requests/httpx, pandas 등)
- pyproject.toml 패키지화
- README (설치법, 사용법)
자주 하는 실수
- 모놀리식 한 파일 — 테스트도 어렵고 재사용도 어려움
- 타입 힌트 누락 — IDE 지원 안 받음
- 테스트 없이 시작 — 나중에 더 어려움
- README 없음 — 본인도 며칠 후 잊어버림
FAQ
Q1. CI 가 뭔가요? — Continuous Integration. push마다 자동 테스트(GitHub Actions).
Q2. 어떤 도메인? — 본인이 진짜 쓸 만한 것. 뉴스 요약, 공부 시간 추적, 시세 알림 등.
최종 과제 — 4개 중 1개 선택
- 비동기 크롤러 + 분석
- CLI 도구 패키지 (PyPI 배포)
- 데이터 파이프라인 (CSV → 정제 → 시각화)
- 자동화 봇 (Slack/Discord 웹훅)
다음 단계
고급 과정 완주 🎉. 이후 도메인별 학습:
- 백엔드: FastAPI / Django REST framework
- 데이터·ML: PyTorch / scikit-learn / Hugging Face
- 자동화·DevOps: Ansible / Airflow / Docker
- 웹 스크래핑: Scrapy / Playwright
💻 예제 (examples)
실제로 실행해 결과를 확인할 수 있는 예제 코드입니다.
import requests
r = requests.get("https://httpbin.org/get", params={"q": "python"}, timeout=10)
print(r.status_code)
print(r.json()["args"])200
{'q': 'python'}from bs4 import BeautifulSoup
html = """<html><body>
<h1>제목</h1>
<ul>
<li class="item">A</li>
<li class="item">B</li>
<li class="item">C</li>
</ul>
</body></html>"""
soup = BeautifulSoup(html, "html.parser")
print(soup.h1.text)
print([li.text for li in soup.select("li.item")])제목
['A', 'B', 'C']import requests
s = requests.Session()
s.headers.update({"User-Agent": "study/1.0"})
# httpbin이 보낸 쿠키를 받아서 그대로 사용
s.get("https://httpbin.org/cookies/set/sessionid/abc123")
r = s.get("https://httpbin.org/cookies")
print(r.json()){'cookies': {'sessionid': 'abc123'}}import requests
from bs4 import BeautifulSoup
r = requests.get("https://httpbin.org/html", timeout=10)
r.raise_for_status()
soup = BeautifulSoup(r.text, "html.parser")
print("h1:", soup.h1.get_text(strip=True))
print("p 개수:", len(soup.find_all("p")))h1: Herman Melville - Moby-Dick
p 개수: 1import numpy as np
a = np.array([1, 2, 3, 4, 5])
print("평균:", a.mean())
print("표준편차:", round(a.std(), 4))
print("합계:", a.sum())
print("배수:", a * 2)
print("불리언:", a > 2)평균: 3.0
표준편차: 1.4142
합계: 15
배수: [ 2 4 6 8 10]
불리언: [False False False True True]import numpy as np
m = np.arange(12).reshape(3, 4)
print(m)
print("m[1, 2] =", m[1, 2])
print("m[:, 1] =", m[:, 1])
print("m[1, :] =", m[1, :])
print("axis=0 합:", m.sum(axis=0))
print("axis=1 합:", m.sum(axis=1))[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
m[1, 2] = 6
m[:, 1] = [1 5 9]
m[1, :] = [4 5 6 7]
axis=0 합: [12 15 18 21]
axis=1 합: [ 6 22 38]import pandas as pd
df = pd.DataFrame({
"name": ["Alice", "Bob", "Charlie"],
"score": [92, 78, 85],
})
print(df)
print("---")
print("평균:", df["score"].mean())
print("최대:", df["score"].max()) name score
0 Alice 92
1 Bob 78
2 Charlie 85
---
평균: 85.0
최대: 92import pandas as pd
df = pd.DataFrame({
"name": ["Alice", "Bob", "Charlie", "Dave"],
"score": [92, 78, 85, 67],
"dept": ["A", "B", "A", "B"],
})
print(df[df.score >= 80])
print("---")
print(df.groupby("dept")["score"].mean()) name score dept
0 Alice 92 A
2 Charlie 85 A
---
dept
A 88.5
B 72.5
Name: score, dtype: float64[project]
name = "mytool"
version = "0.1.0"
description = "내 멋진 CLI 도구"
authors = [{name = "홍길동", email = "hong@example.com"}]
requires-python = ">=3.10"
dependencies = ["requests>=2.28"]
[project.scripts]
mytool = "mytool.cli:main"
[build-system]
requires = ["setuptools>=61"]
build-backend = "setuptools.build_meta"(설정 파일 — 직접 출력은 없음)__version__ = "0.1.0"(import 시 활성화)import argparse
def main():
parser = argparse.ArgumentParser(prog="mytool")
parser.add_argument("name", help="이름")
parser.add_argument("--upper", action="store_true")
args = parser.parse_args()
msg = f"Hello, {args.name}!"
print(msg.upper() if args.upper else msg)
if __name__ == "__main__":
main()$ mytool 홍길동
Hello, 홍길동!
$ mytool 홍길동 --upper
HELLO, 홍길동!# 가상환경
python -m venv .venv
source .venv/bin/activate
# 개발 설치
pip install -e .
# 실행
mytool 세상
# 빌드
pip install build
python -m buildHello, 세상!
Successfully built mytool-0.1.0.tar.gz mytool-0.1.0-py3-none-any.whlfrom dataclasses import dataclass, field
@dataclass
class Article:
title: str
url: str
score: int = 0
tags: list[str] = field(default_factory=list)
a = Article("파이썬 비동기", "https://x", score=120, tags=["python", "async"])
print(a)Article(title='파이썬 비동기', url='https://x', score=120, tags=['python', 'async'])import asyncio
import httpx
async def fetch_one(client, url):
r = await client.get(url, timeout=10)
return r.status_code, len(r.text)
async def fetch_all(urls):
async with httpx.AsyncClient() as client:
return await asyncio.gather(*(fetch_one(client, u) for u in urls))
if __name__ == "__main__":
urls = ["https://example.com"] * 3
print(asyncio.run(fetch_all(urls)))[(200, 1256), (200, 1256), (200, 1256)]import pandas as pd
def by_dept(df: pd.DataFrame) -> pd.Series:
return df.groupby("dept")["score"].mean()
if __name__ == "__main__":
df = pd.DataFrame({"name": ["A", "B", "C", "D"], "score": [90, 70, 80, 60], "dept": ["X", "Y", "X", "Y"]})
print(by_dept(df))dept
X 85.0
Y 65.0
Name: score, dtype: float64import argparse
def main():
p = argparse.ArgumentParser(prog="myapp")
sub = p.add_subparsers(dest="cmd", required=True)
sub.add_parser("crawl")
a = sub.add_parser("analyze")
a.add_argument("path")
args = p.parse_args()
if args.cmd == "crawl":
print("크롤 시작")
elif args.cmd == "analyze":
print(f"분석: {args.path}")
if __name__ == "__main__":
main()$ myapp crawl
크롤 시작
$ myapp analyze data.csv
분석: data.csv📝 과제 (exercises)
직접 풀어보고, 막힐 때 정답을 펼쳐 비교해보세요.
JSON API 호출
목표: GitHub API 로 사용자 저장소 정보를 가져온다.
- username 입력 받기
- public_repos 개수 출력
- 예외 처리 (timeout, raise_for_status)
https://api.github.com/users/{username}
사용자: torvalds
공개 저장소: 7- · timeout 사용
- · raise_for_status 사용
▶정답 코드 펼치기 / 접기
import requests
def info(username):
url = f"https://api.github.com/users/{username}"
r = requests.get(url, timeout=10)
r.raise_for_status()
data = r.json()
return data.get("public_repos", 0)
name = "torvalds"
print(f"사용자: {name}")
print(f"공개 저장소: {info(name)}")사용자: torvalds
공개 저장소: 7HTML 헤딩 추출
목표: 주어진 URL 의 모든 h1, h2 텍스트를 추출한다.
- BeautifulSoup 사용
- h1, h2 모두
soup.find_all('h1')
H1: ['Herman Melville - Moby-Dick']
H2: []- · 파서 명시
- · 결과 list
▶정답 코드 펼치기 / 접기
import requests
from bs4 import BeautifulSoup
r = requests.get("https://httpbin.org/html", timeout=10)
soup = BeautifulSoup(r.text, "html.parser")
h1s = [t.get_text(strip=True) for t in soup.find_all("h1")]
h2s = [t.get_text(strip=True) for t in soup.find_all("h2")]
print(f"H1: {h1s}")
print(f"H2: {h2s}")H1: ['Herman Melville - Moby-Dick']
H2: []robots.txt 확인
목표: 주어진 도메인의 robots.txt 를 가져와 출력한다.
- https://{domain}/robots.txt
- 200 이 아니면 안내 메시지
status_code 확인
User-agent: *
Disallow: ...- · timeout 사용
- · 비정상 응답 처리
▶정답 코드 펼치기 / 접기
import requests
def show_robots(domain):
url = f"https://{domain}/robots.txt"
r = requests.get(url, timeout=10)
if r.status_code != 200:
print(f"robots.txt 없음 (status {r.status_code})")
return
print(r.text[:200])
show_robots("github.com")# If you would like to crawl GitHub contact us via https://support.github.com/contact?tags=dotcom-robots
# We also provide an extensive API: https://docs.github.com
User-agent: *
Disallow: /*/pulls
Disallow: /*/pull/NumPy 배열 통계
목표: 1~100 배열의 평균/최대/짝수 합을 구한다.
- np.arange 사용
- 평균, 최대, 짝수 합 출력
a[a % 2 == 0].sum()
평균: 50.5
최대: 100
짝수 합: 2550- · 불리언 인덱싱 사용
▶정답 코드 펼치기 / 접기
import numpy as np
a = np.arange(1, 101)
print(f"평균: {a.mean()}")
print(f"최대: {a.max()}")
print(f"짝수 합: {a[a % 2 == 0].sum()}")평균: 50.5
최대: 100
짝수 합: 2550DataFrame 학과별 평균
목표: 학생 점수 DataFrame 에서 학과별 평균을 구한다.
- pandas.DataFrame 직접 작성
- groupby + mean
df.groupby('dept')['score'].mean()
dept
A 88.5
B 72.5
Name: score, dtype: float64- · DataFrame 사용
- · groupby 결과 일치
▶정답 코드 펼치기 / 접기
import pandas as pd
df = pd.DataFrame({
"name": ["Alice", "Bob", "Charlie", "Dave"],
"score": [92, 78, 85, 67],
"dept": ["A", "B", "A", "B"],
})
print(df.groupby("dept")["score"].mean())dept
A 88.5
B 72.5
Name: score, dtype: float64BMI 시뮬레이션
목표: 키/몸무게 분포에서 BMI 통계를 구한다.
- np.random.normal 로 키 1.7m, 몸무게 65kg 분포 100개
- BMI = 몸무게 / 키^2
- 평균 BMI 출력
seed 고정으로 재현 가능
평균 BMI: 22.x- · 벡터 연산
- · 통계 출력
▶정답 코드 펼치기 / 접기
import numpy as np
rng = np.random.default_rng(42)
heights = rng.normal(1.7, 0.05, 100)
weights = rng.normal(65, 5, 100)
bmi = weights / heights ** 2
print(f"평균 BMI: {bmi.mean():.2f}")
print(f"최저 BMI: {bmi.min():.2f}")
print(f"최고 BMI: {bmi.max():.2f}")평균 BMI: 22.66
최저 BMI: 17.95
최고 BMI: 28.14최소 패키지
목표: Hello World 를 출력하는 CLI 패키지를 만든다.
- src/hello/__init__.py + cli.py
- pyproject.toml + console_scripts
- pip install -e . 후 hello 명령 실행 가능
[project.scripts] hello = 'hello.cli:main'
Hello, 패키지!- · 패키지 구조
- · CLI 등록
▶정답 코드 펼치기 / 접기
# pyproject.toml
[project]
name = "hello"
version = "0.1.0"
[project.scripts]
hello = "hello.cli:main"
[build-system]
requires = ["setuptools>=61"]
build-backend = "setuptools.build_meta"
# src/hello/__init__.py
__version__ = "0.1.0"
# src/hello/cli.py
def main():
print("Hello, 패키지!")
# 설치 + 실행
# pip install -e .
# helloHello, 패키지!argparse 가산기
목표: 두 수를 더하는 CLI 패키지를 만든다.
- addr 1 2 → 3 출력
- argparse 사용
type=int 지정
3- · argparse 사용
- · 정상 출력
▶정답 코드 펼치기 / 접기
# src/addr/cli.py
import argparse
def main():
p = argparse.ArgumentParser(prog="addr")
p.add_argument("a", type=int)
p.add_argument("b", type=int)
args = p.parse_args()
print(args.a + args.b)
# pyproject.toml: [project.scripts] addr = "addr.cli:main"
# 실행: addr 1 23버전 출력 옵션
목표: --version 옵션으로 패키지 버전을 출력한다.
- argparse --version action
- __init__.py 의 __version__ 사용
action='version', version=f'%(prog)s {__version__}'
mytool 0.1.0- · --version 동작
- · 버전 일치
▶정답 코드 펼치기 / 접기
# src/mytool/__init__.py
__version__ = "0.1.0"
# src/mytool/cli.py
import argparse
from mytool import __version__
def main():
p = argparse.ArgumentParser(prog="mytool")
p.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
p.parse_args()
# mytool --versionmytool 0.1.0최종 1 — 비동기 크롤러
목표: 여러 URL 을 동시 fetch 하고 길이/응답시간을 출력한다.
- httpx + asyncio.gather
- 각 URL 의 (url, status, ms, bytes) 출력
- 타임아웃 10초
time.perf_counter()
asyncio.gather
https://example.com 200 305ms 1256B
https://httpbin.org/get 200 412ms 380B- · 비동기 동시 실행
- · 에러 처리
▶정답 코드 펼치기 / 접기
import asyncio
import time
import httpx
async def fetch(client, url):
start = time.perf_counter()
try:
r = await client.get(url, timeout=10)
ms = int((time.perf_counter() - start) * 1000)
return f"{url} {r.status_code} {ms}ms {len(r.text)}B"
except Exception as e:
return f"{url} ERROR {e}"
async def main(urls):
async with httpx.AsyncClient() as client:
results = await asyncio.gather(*(fetch(client, u) for u in urls))
for line in results:
print(line)
asyncio.run(main(["https://example.com", "https://httpbin.org/get"]))https://example.com 200 305ms 1256B
https://httpbin.org/get 200 412ms 380B최종 2 — CLI 패키지
목표: argparse 기반 CLI 패키지를 만들어 로컬 설치한다.
- src/myapp/cli.py + pyproject.toml
- myapp greet --name=홍길동 → 인사 출력
[project.scripts] myapp = 'myapp.cli:main'
안녕, 홍길동!- · pip install -e . 동작
- · CLI 출력 일치
▶정답 코드 펼치기 / 접기
# pyproject.toml
[project]
name = "myapp"
version = "0.1.0"
[project.scripts]
myapp = "myapp.cli:main"
[build-system]
requires = ["setuptools>=61"]
build-backend = "setuptools.build_meta"
# src/myapp/cli.py
import argparse
def main():
p = argparse.ArgumentParser(prog="myapp")
sub = p.add_subparsers(dest="cmd", required=True)
g = sub.add_parser("greet")
g.add_argument("--name", required=True)
args = p.parse_args()
if args.cmd == "greet":
print(f"안녕, {args.name}!")
# pip install -e .
# myapp greet --name=홍길동안녕, 홍길동!최종 3 — 데이터 파이프라인
목표: CSV → 필터 → 집계 → 출력 흐름을 짠다.
- pandas 로 read_csv
- score >= 80 만 필터
- dept 별 mean 출력
StringIO 로 인라인 CSV
dept
A 88.5
Name: score, dtype: float64- · 필터
- · groupby + mean
▶정답 코드 펼치기 / 접기
import pandas as pd
from io import StringIO
csv = """name,score,dept
Alice,92,A
Bob,78,B
Charlie,85,A
Dave,67,B"""
df = pd.read_csv(StringIO(csv))
high = df[df.score >= 80]
print(high.groupby("dept")["score"].mean())dept
A 88.5
Name: score, dtype: float64댓글 0
아직 댓글이 없습니다. 첫 댓글을 남겨 주세요.
댓글은 회원만 쓸 수 있습니다.
광고·욕설·비방·개인정보·불법 내용이 담긴 댓글은 숨기거나 지웁니다. 댓글은 닉네임과 함께 공개됩니다. 개인정보처리방침