inflearn logo
강의

강의

N
챌린지

챌린지

멘토링

멘토링

N
클립

클립

로드맵

로드맵

지식공유

스타트코딩님의 게시글

스타트코딩 스타트코딩

@startcoding

수강생
27,075
수강평
1,345
강의 평점
4.9

게시글 583

질문&답변

셀레니움 환경설정 오류

안녕하세요! Edge 관련 에러는 msedgedriver를 자동으로 찾지 못해서 발생하는 문제입니다. 방법 1) selenium과 webdriver-manager 업데이트 후 재시도 pip install --upgrade selenium selenium을 최신 버전으로 업데이트하면 SeleniumManager가 Edge 드라이버도 자동 관리해주는 경우가 많습니다. 업데이트 후 다시 webdriver.Edge() 실행해보세요. 방법 2) 그래도 안 되면 webdriver-manager 패키지 사용 python from selenium import webdriver from selenium.webdriver.edge.service import Service from webdriver_manager.microsoft import EdgeChromiumDriverManager service = Service(EdgeChromiumDriverManager().install()) driver = webdriver.Edge(service=service) webdriver-manager가 없다면 먼저 설치해주세요: pip install webdriver-manager 방법 3) Edge 브라우저가 설치되어 있는지 확인 혹시 PC에 Edge 브라우저 자체가 삭제되어 있으면 드라이버를 찾을 수 없으니, Edge가 정상 설치되어 있는지도 확인해주세요. 대부분 방법 1로 해결되고, 안 되면 방법 2로 하시면 됩니다!

좋아요수
0
댓글수
2
조회수
80

질문&답변

네이버 로그인 관련

안녕하세요 수강생님! 네이버 로그인 페이지가 최근에 업데이트 된 것 같네요. '셀레니움으로 접속' 해서 로그인 한건지 감지하는 로직을 넣어놓은 것 같습니다. undetected_chromedriver 라이브러리를 이용하면 우회가능하니 아래 코드를 활용해 보세요 라이브러리 설치 pip install undetected_chromedriver import undetected_chromedriver as uc from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys import time import pyperclip # 크롬 드라이버 생성 driver = uc.Chrome() # webdriver 플래그 자동 우회 # 페이지 이동 driver.get("https://nid.naver.com/nidlogin.login?mode=form&url=https://www.naver.com/") time.sleep(1) # 아이디 입력 id = driver.find_element(By.CSS_SELECTOR, "#id") pyperclip.copy(user_id) id.send_keys(Keys.CONTROL, 'v') time.sleep(1) # 비밀번호 입력 pw = driver.find_element(By.CSS_SELECTOR, "#pw") pyperclip.copy(user_pw) pw.send_keys(Keys.CONTROL, 'v') time.sleep(1) # 로그인 버튼 클릭 driver.find_element(By.CSS_SELECTOR, "#log\\.login").click()

좋아요수
0
댓글수
2
조회수
374

질문&답변

안녕하세요 셀레니움에 대해서 질문

안녕하세요! 좋은 질문입니다. Playwright가 Selenium보다 여러 면에서 개선된 도구인 건 맞습니다. 속도도 빠르고, 비동기 처리나 자동 대기(auto-wait) 기능도 편리하죠. 다만 강의에서 Selenium을 계속 가르치는 이유가 있어요. 학습 자료와 커뮤니티가 압도적으로 많습니다. 막혔을 때 검색해서 해결하기가 훨씬 쉬워요. 진입장벽이 상대적으로 낮습니다. Selenium이 배우기 상대적으로 훨씬 쉬워요. 기본 개념은 동일합니다. Selenium을 제대로 이해하면 Playwright는 금방 적응할 수 있어요. Playwright 별도 강의는 준비되어있지 않지만, Selenium으로 동적 페이지 크롤링 원리를 완전히 익히신 후에 Playwright 공식 문서나 간단한 튜토리얼로 넘어가시면 어렵지 않게 전환하실 수 있습니다. 나중에 Playwright에 대해서 유튜브나 실전편 컨텐츠로 한번 다뤄 볼게요.

좋아요수
0
댓글수
1
조회수
106

질문&답변

크롤링 연습사이트 문의

안녕하세요 어제 확인해 봤는데, 호스팅 사이트에서 업데이트가 있었네요 서버 재시작해서 복구 되었으니 지금 정상 접속될겁니다!

좋아요수
0
댓글수
2
조회수
123

질문&답변

선택자 질문

안녕하세요 남구님! AI 인턴이 말해준 것 처럼 클래스나 아이디값을 이용하는게 일반적입니다. 다만 클래스나 아이디 값이 없을 경우에는 부모태그를 찾아준 다음 내려오는 방식을 사용하시면 돼요. 선택자를 지정하는 방법은 여러가지를 사용할 수 있기에 정답이 하나만 있는게 아닙니다. 다양하게 만들어 보시면서 선택자에 익숙해 진다면 자연스럽게 지금의 고민이 해결되실 거예요 :)

좋아요수
0
댓글수
2
조회수
89

질문&답변

자동 로그인 질문

본인의 아이디와 비밀번호를 직접 입력해서 사용하시면 됩니다 ^^

좋아요수
0
댓글수
2
조회수
107

질문&답변

원하는 값이 없을 때

저는 보통 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 함수 내에서 모든 것을 처리하는 방식입니다. Python def 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
조회수
107