스타트코딩
@startcoding
수강생
26,690
수강평
1,302
강의 평점
4.9
강사 약력
유튜브 스타트코딩 채널운영 (구독자 3만명)
삼성전자 메인 강사
에스오일, KT, 한국능률협회 등 출강
'파이썬을 제대로 활용해보려고 해' 저자
알파코(Alpaco) K-디지털트레이닝 강사
동신대, 대구한의대, 초등학교,중학교 코딩 특강
프로그래머스 파이썬 PCCE 자격증 강의
스타트코딩 유튜브 채널
https://www.youtube.com/channel/UCHwhZ7HPBhUh2IscPSL0pHA
스타트코딩 교육 센터
강의
수강평
- [신규 개정판] 이것이 진짜 크롤링이다 - 기본편
- [신규 개정판] 이것이 진짜 크롤링이다 - 기본편
- [신규 개정판] 이것이 진짜 크롤링이다 - 실전편 (인공지능 수익화)
게시글
질문&답변
안녕하세요 셀레니움에 대해서 질문
안녕하세요! 좋은 질문입니다.Playwright가 Selenium보다 여러 면에서 개선된 도구인 건 맞습니다. 속도도 빠르고, 비동기 처리나 자동 대기(auto-wait) 기능도 편리하죠.다만 강의에서 Selenium을 계속 가르치는 이유가 있어요.학습 자료와 커뮤니티가 압도적으로 많습니다. 막혔을 때 검색해서 해결하기가 훨씬 쉬워요.진입장벽이 상대적으로 낮습니다. Selenium이 배우기 상대적으로 훨씬 쉬워요. 기본 개념은 동일합니다. Selenium을 제대로 이해하면 Playwright는 금방 적응할 수 있어요.Playwright 별도 강의는 준비되어있지 않지만, Selenium으로 동적 페이지 크롤링 원리를 완전히 익히신 후에 Playwright 공식 문서나 간단한 튜토리얼로 넘어가시면 어렵지 않게 전환하실 수 있습니다.나중에 Playwright에 대해서 유튜브나 실전편 컨텐츠로 한번 다뤄 볼게요.
- 0
- 1
- 37
질문&답변
Live server 를 이용 해보고 싶은데 확장메뉴 설치가 막혀 있어요
AI 인턴이 말한대로 주피터 웹 버전에서는 Live Server 를 사용하기 어렵습니다 ㅜ대안으로 사용할 수 있는 툴을 찾아보는걸 추천드려요~
- 0
- 2
- 42
질문&답변
import requests from bs4 import BeatifulSoup 이 단계에서 안 되네요
AI 인턴 말대로 오타때문에 발생한 오류입니다 ^^
- 0
- 2
- 50
질문&답변
크롤링 연습사이트 문의
안녕하세요 어제 확인해 봤는데,호스팅 사이트에서 업데이트가 있었네요서버 재시작해서 복구 되었으니 지금 정상 접속될겁니다!
- 0
- 2
- 61
질문&답변
선택자 질문
안녕하세요 남구님!AI 인턴이 말해준 것 처럼 클래스나 아이디값을 이용하는게 일반적입니다. 다만 클래스나 아이디 값이 없을 경우에는 부모태그를 찾아준 다음 내려오는 방식을 사용하시면 돼요. 선택자를 지정하는 방법은 여러가지를 사용할 수 있기에 정답이 하나만 있는게 아닙니다.다양하게 만들어 보시면서 선택자에 익숙해 진다면 자연스럽게 지금의 고민이 해결되실 거예요 :)
- 0
- 2
- 49
질문&답변
'특정 요소가 나타날 때까지 스크롤' 부분 에러
안녕하세요 답변이 조금 늦었습니다.find_elements 대신 find_element 를 사용하신 것 아닐까요?
- 0
- 2
- 58
질문&답변
자동 로그인 질문
본인의 아이디와 비밀번호를 직접 입력해서 사용하시면 됩니다 ^^
- 0
- 2
- 63
질문&답변
원하는 값이 없을 때
저는 보통 try except 나 if 를 이용해서 간단하게 처리해서 pydantic 한 처리방법에 대해서는 잘모르겠네요 🤔 대신 제미나이 답변을 첨부해 드립니다!안녕하세요, 수강생님. 질문 감사합니다. 코드까지 깔끔하게 첨부해주셔서 질문의 의도를 정확히 파악하는 데 큰 도움이 되었습니다. Pydantic을 활용한 접근 방식, 아주 훌륭합니다.결론부터 말씀드리면, 수강생님이 작성하신 코드는 '오버 엔지니어링'이 전혀 아닙니다. 오히려 실무에서 적극 권장되는 '견고한(robust) 엔지니어링'에 가깝습니다. 제가 강의에서 (아마도) 간단한 딕셔너리(dict)로 처리한 것은 강의의 핵심 주제(크롤링)에 집중하기 위함이었을 겁니다.수강생님의 접근 방식(Pydantic)이 왜 좋은지, 그리고 '유연한 처리'란 무엇인지 설명해 드릴게요. 🧐 Pydantic을 쓰는 것이 왜 좋은가? 명확한 데이터 구조 (Schema):NewsItem 모델을 보는 것만으로도 "아, 이 크롤러는 5개의 필드를 수집하고, 각 필드는 이런 타입(str, HttpUrl)이구나"라고 즉시 알 수 있습니다.관심사의 분리 (SoC):crawl_... 함수는 HTML에서 데이터를 '추출(Extract)'하는 책임만 집니다.NewsItem 모델은 추출된 데이터가 '올바른지 검증(Validate)'하는 책임을 집니다.코드가 훨씬 깨끗해지고 유지보수가 쉬워집니다.조기 발견 (Fail-fast): 크롤링 도중 subject가 비어있거나 detail_article_url이 잘못된 형식이라면, Pydantic이 즉시 ValidationError를 발생시킵니다. 데이터가 한참 뒤에 DB에 저장되거나 다른 API로 전송될 때 에러가 나는 것보다 훨씬 좋습니다. 🎛 "유연한 처리" vs "견고한 처리" '유연하게 처리한다'는 것이 에러를 무시하고 대충 넘어가는 것을 의미하지는 않습니다.1. "간단하지만 지저분해지기 쉬운" 유연한 처리 (강의에서 쓸 법한 방식)Pydantic 없이 crawl_news_per_page 함수 내에서 모든 것을 처리하는 방식입니다.Pythondef crawl_news_per_page(soup: BeautifulSoup): result = [] news_per_page = soup.select(".block1") for article_card in news_per_page: try: subject = article_card.select_one(".articleSubject > a").text detail_article_url = get_detail_news_url(article_card.select_one(".articleSubject > a").get("href")) content_tag = article_card.select_one(".articleSummary") content = content_tag.contents[0].strip() press = content_tag.select_one(".press").text.strip() article_date = content_tag.select_one(".wdate").text.strip() # 여기서 직접 유효성 검사 (점점 지저분해짐) if not subject or not content: print("경고: 빈 제목이나 내용이 있습니다. 이 항목은 건너뜁니다.") continue # 이 항목을 스킵 result.append({ "subject": subject, "detail_article_url": detail_article_url, "content": content, "press": press, "article_date": article_date }) except AttributeError as e: # .text나 .get('href')를 시도할 때 태그가 None이면 발생하는 에러 print(f"파싱 에러 발생: {e}. 이 항목은 건너뜁니다.") continue # 이 항목을 스킵 except Exception as e: # 기타 예외 print(f"알 수 없는 에러: {e}") return result 문제점:try...except 블록이 거대해지고, 파싱 로직과 검증 로직, 에러 처리 로직이 뒤섞여 '스파게티 코드'가 되기 쉽습니다.2. 수강생님이 선택한 "견고한" 처리 (Pydantic)이 방식이 훨씬 낫습니다. 다만, 수강생님의 코드에서 두 가지 치명적인 문제를 수정해야 합니다. 💡 Pydantic 접근법 실전 꿀팁 (코드 개선) 수강생님의 코드는 90% 훌륭합니다. 하지만 valid_url 검증자와 파싱 부분에 실무에서 문제가 될 수 있는 부분이 있습니다.1. Validator는 I/O 작업을 해선 안 됩니다.문제:valid_url 함수 내부에서 requests.get(url)를 호출하고 계십니다.이유: Pydantic 모델을 생성(초기화)할 때마다 매번 HTTP 요청이 발생합니다. 뉴스 100개를 크롤링하면, 모델을 만들 때 100번, 나중에 실제 데이터를 쓸 때 100번, 총 200번의 요청이 나갈 수 있습니다. Validator는 데이터의 형식(format)이 유효한지만 빠르고 가볍게 검사해야 합니다. URL이 실제로 연결 가능한지(reachability)는 나중에 그 URL을 사용할 때 검사하는 것이 맞습니다.해결: Pydantic이 제공하는 HttpUrl 타입을 사용하면 requests 호출 없이도 URL 형식을 검증해 줍니다.2. 파싱(Parsing) 단계는 AttributeError에 방어적이어야 합니다.문제:subject = article_card.select_one(".articleSubject > a").text이유: 만약 .articleSubject > a 셀렉터에 해당하는 태그가 없다면select_one은 None을 반환합니다. None.text를 호출하는 순간 AttributeError가 발생하고, Pydantic 모델은 구경도 못하고 프로그램이 죽습니다.해결: 파싱 단계에서는 None을 안전하게 처리하고, None 또는 빈 문자열을 Pydantic 모델에 전달해야 합니다. 그 뒤는 Pydantic의 AfterValidator(non_empty_str)가 알아서 처리해 줄 것입니다. 🔧 개선된 코드 예시 제가 수강생님의 코드를 개선해 보겠습니다.Python# ... (requests, BeautifulSoup, datetime 등 import는 동일) from typing import Annotated, Optional from pydantic import BaseModel, HttpUrl, field_validator, ValidationInfo # --- Pydantic 모델 정의 --- def non_empty_str(v: str) -> str: """공백 제거 후 빈 문자열인지 검사하는 Validator""" if not v or not v.strip(): raise ValueError("필수 필드가 비어있습니다.") return v.strip() def valid_date_format(v: str) -> str: """날짜 형식이 'YYYY-MM-DD HH:MM:SS'인지 검사하는 Validator""" v = v.strip() try: datetime.strptime(v, "%Y-%m-%d %H:%M:%S") return v except ValueError: raise ValueError("날짜 형식이 'YYYY-MM-DD HH:MM:SS'와 다릅니다.") class NewsItem(BaseModel): # str 타입에 non_empty_str 검증을 적용합니다. subject: Annotated[str, AfterValidator(non_empty_str)] # HttpUrl 타입을 사용해 URL 형식을 검증합니다. (I/O 없음!) detail_article_url: HttpUrl content: Annotated[str, AfterValidator(non_empty_str)] press: Annotated[str, AfterValidator(non_empty_str)] article_date: Annotated[str, AfterValidator(valid_date_format)] # --- 크롤링 함수 정의 --- ROOT = "https://finance.naver.com/" PATH = "news/mainnews.naver" def get_detail_news_url(path): return urljoin(ROOT, path) def safe_get_text(tag, selector): """안전하게 .text를 추출하는 헬퍼 함수""" selected = tag.select_one(selector) return selected.text.strip() if selected else "" # None 대신 빈 문자열 반환 def safe_get_content(tag, selector): """본문 내용(press, wdate 제외)을 안전하게 추출하는 헬퍼 함수""" selected = tag.select_one(selector) if not selected: return "" # .press, .wdate 태그를 제외한 순수 텍스트 노드를 찾습니다. content_text = "" for child in selected.contents: if child.name not in ['span', 'a']: # press/wdate가 span/a 태그일 경우 content_text += str(child).strip() return content_text.strip() if content_text else "" def crawl_news_per_page(soup: BeautifulSoup): result = [] news_per_page = soup.select(".block1") for article_card in news_per_page: # 1. 안전하게 데이터 추출 (AttributeError 방지) # 태그가 없으면 None이나 ""이 subject_raw 등에 할당됩니다. subject_raw = safe_get_text(article_card, ".articleSubject > a") url_raw = article_card.select_one(".articleSubject > a") detail_article_url_raw = get_detail_news_url(url_raw.get("href")) if url_raw else "" content_raw = safe_get_content(article_card, ".articleSummary") press_raw = safe_get_text(article_card, ".articleSummary .press") article_date_raw = safe_get_text(article_card, ".articleSummary .wdate") try: # 2. Pydantic 모델로 검증 # 여기서 non_empty_str, HttpUrl, valid_date_format 등이 작동합니다. news_item = NewsItem( subject=subject_raw, detail_article_url=detail_article_url_raw, content=content_raw, press=press_raw, article_date=article_date_raw ) # 검증 성공 시 결과에 추가 result.append(news_item.model_dump()) except Exception as e: # 3. Pydantic 검증 실패 시 에러 처리 # (예: subject가 비어있거나, URL 형식이 아닐 때) print(f"--- 데이터 검증 실패 (항목 스킵) ---") print(f"에러: {e}") print(f"원본 데이터: [제목: {subject_raw[:20]}...], [URL: {detail_article_url_raw}]") print("---------------------------------") return result # ... (crawl_all_news, get_news_page_url 등 나머지 코드는 동일) 요약 수강생님의 Pydantic 접근은 '오버 엔지니어링'이 아니라 '베스트 프랙티스'입니다.'유연한 처리'란 try...except로 대충 넘기는 것이 아니라, 파싱(추출)과 검증(Validation)의 책임을 분리하고, Pydantic 같은 도구를 사용해 데이터 무결성을 '견고하게' 보장하는 것입니다.Validator 내부에서 requests.get 같은 I/O 작업은 절대 금물입니다. HttpUrl 타입을 활용하세요.파싱 로직은 AttributeError가 나지 않도록 방어적으로 작성하고 (예: safe_get_text), 검증은 Pydantic에 맡기세요.
- 0
- 2
- 71
질문&답변
44강 제목, 링크
여행다녀오느라 답변이 조금 늦었습니다.똑같이 not 선택자를 사용하면 되어서, 강의에 추가로 업데이트하진 않았는데요!아래와 같이 선택자를 만드시면 잘 동작할겁니다 ㅎㅎ posts = driver.find_elements(By.CSS_SELECTOR, "tbody > tr:not(.board-notice) .article") for post in posts: title = post.text link = post.get_attribute('href') print(title, link) html 구조를 정확히 파악하고선택자를 이용하면 됩니다 선택자 만드는게 까다로울 때는, AI에게 HTML 구조 (ex. table 태그)를 복사한다음 질문해보시는 것도 좋아요! (사진)
- 0
- 1
- 85
질문&답변
5번 강의 실습문제에서 질문있습니다.
그렇게 하셔도 됩니다!다만 print("이름-",name) 이렇게 썼을때 띄어쓰기가 하나 들어간다는 특징이 있어요 ㅎㅎ
- 0
- 1
- 59





![Thumbnail image of the [신규 개정판] 이것이 진짜 크롤링이다 - 기본편](https://cdn.inflearn.com/public/courses/327774/cover/e25c692a-f39a-41ec-9da7-bdd7922e6553/327774.png?w=148)
![Thumbnail image of the [신규 개정판] 이것이 진짜 엑셀자동화다 - 기본편](https://cdn.inflearn.com/public/courses/330111/cover/d1d1754d-deea-4b3d-b14b-7e20cf602e5e/330111.png?w=148)
![Thumbnail image of the [신규 개정판] 이것이 진짜 크롤링이다 - 실전편 (인공지능 수익화)](https://cdn.inflearn.com/public/courses/328045/cover/aec69a0c-6a3c-4602-b4a0-62d492398c12/328045.png?w=148)