<8-1주차 수강 클립>
02. 데이터 수집을 위한 Python (Crawling)
10. selenium 모듈 - 01. 사이트에 로그인하여 데이터 크롤링하기
11. selenium 모듈 - 02. selenium 모듈로 웹사이트 크롤링하기
12. selenium 모듈 - 03. 웹사이트의 필요한 데이터가 로딩된 후 크롤링하기
13. selenium 모듈 - 04. 실전 웹 크롤링
이번 주차는 2단원 [파이썬으로 웹 크롤링하기] 를 마무리 짓는 주차였습니다.
requests와 beautifulsoup 모듈에 이어 좀 더 파워풀한 크롤링이 가능한
selenium 모듈의 사용법에 대해 알아봤어요!
>>수강 인증샷<<
10. selenium 모듈 - 01. 사이트에 로그인하여 데이터 크롤링하기
- HTTP 상태 코드
1xx (정보): 요청을 받았으며 프로세스를 계속한다
2xx (성공): 요청을 성공적으로 받았으며 인식했고 수용하였다
3xx (리다이렉션): 요청 완료를 위해 추가 작업 조치가 필요하다
4xx (클라이언트 오류): 요청의 문법이 잘못되었거나 요청을 처리할 수 없다
5xx (서버 오류): 서버가 명백히 유효한 요청에 대해 충족을 실패했다
- 뉴스 댓글 개수 크롤링하기
개발자 도구의 network-XHR(Xml Http Request)
*) XHR: AJAX 비동기적 호출을 말함 (웹사이트를 전체 로딩이 아닌 부분적으로 필요한 데이터를 그때그때 비동기적으로 호출)
Name 탭의 요청을 하나씩 눌러서 Headers, Preview, Response, Timing 탭을 모두 확인 → 필요한 정보가 어디에 있을지는 케바케이기 때문!!
찾았다면 해당 요청의 Headers 탭에서 endpoint(General- Request url) 가져와 호출하기
url = 'https://comment.daum.net/apis/v1/ui/single/main/@20190728165812603'
resp = requests.get(url)
print(resp) #401: 클라이언트 오류_잘못 호출함
: 크롤링 시 원하는 응답 코드를 받지 못한 경우, 다시 개발자 도구의 해당 호출로 가서 response/request header을 dict로 구성해 호출할 때 헤더를 같이 전달해주기! (제대로 동작할 '가능성'이 높아짐)
url = 'https://comment.daum.net/apis/v1/ui/single/main/@20190728165812603'
headers = {
'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb3J1bV9rZXkiOiJuZXdzIiwiZ3JhbnRfdHlwZSI6ImFsZXhfY3JlZGVudGlhbHMiLCJzY29wZSI6W10sImV4cCI6MTYxNzExNzkzMSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9DTElFTlQiXSwianRpIjoiYWYzOGQ0ODAtMWIwMC00N2EyLWIzNDQtNjcxZWM1MGQ4NWEwIiwiZm9ydW1faWQiOi05OSwiY2xpZW50X2lkIjoiMjZCWEF2S255NVdGNVowOWxyNWs3N1k4In0.BNFD2iU4fwLStnu64ZJpCh9SkKmVMHw-z-zhEFtN08Y',
'Origin': 'https://news.v.daum.net',
'Referer': 'https://news.v.daum.net/v/20190728165812603',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36'
}
resp = requests.get(url, headers=headers)
print(resp) #추가 정보(헤더)를 줬을 때 200으로 성공
resp.json() #json형식으로 출력, dict로 받아와짐
resp.json()['post']['commentCount']
- 로그인하여 데이터 크롤링하기 (by. post 방식)
특정한 경우, 로그인을 해서 크롤링을 해야만 하는 경우가 존재
ex) 쇼핑몰에서 주문한 아이템 목록, 마일리지 조회 등
이 경우, 로그인을 자동화하고 로그인에 사용한 세션을 유지하여 크롤링을 진행
- 로그인 후 데이터 크롤링 순서
- endpoint 찾기 (개발자 도구의 network를 활용해 로그인을 관장하는 endpoint 찾기)
- id와 password가 전달되는 form data찾기
- session 객체 생성하여 login 진행
- 이후 session 객체로 원하는 페이지로 이동하여 크롤링
url = 'https://www.yes24.com/Templates/FTLogin.aspx'
: 1. 로그인 endpoint
data = {
'SMemberID': 'test',
'SMemberPassword': 'test1234'
}
: 2. id, pwd로 구성된 form data 생성
s = requests.Session()
resp = s.post(url, data=data)
print(resp) #200 (성공)
: 3. 로그인
endpoint(url)과 data를 구성하여 post 요청 (login의 경우 post로 구성하는 것이 정상적인 웹사이트!)
세션 객체를 이용해 로그인을 함 → 로그인한 세션을 그대로 이용해서 다음 요청에 사용할 것이기 때문
my_page = 'http://www.yes24.com/Templates/FTMyAccount_1YESPoint.aspx' #마이페이지의 url
resp = s.get(my_page) #html 페이지를 요청하고 받아온 것->bs객체 만들기
soup = BeautifulSoup(resp.text)
soup.select('span#lYESPoint')
: 4. crawling
로그인시 사용했던 session을 다시 사용하여 요청
11. selenium 모듈 - 02. selenium 모듈로 웹사이트 크롤링하기
- selenium
웹페이지 테스트 자동화용 모듈
개발/테스트용 드라이버(웹 브라우저)를 사용해 실제 사용자가 브라우저를 사용하는 것처럼 동작시킬 수 있음 (id, pwd로 로그인, 검색창에 검색 etc..)
*) 아나콘다 내비게이터에서 selenium 모듈 설치 & 크롬 드라이버 다운로드 필요
(reques + bs 조합으로 크롤링에 실패한 경우 selenium으로 돌아가게 돼있음)
- selenium 사용 예제
python.org 사이트로 이동해 검색 자동화하기
1. 사이트 오픈
2. input 필드를 검색하여 Key 이벤트 전달
#크롬 드라이버 설치 경로
chrome_driver = 'C:\\Users\\user\\Downloads\\chromedriver_win32\\chromedriver'
#웹드라이버 객체 생성
driver = webdriver.Chrome(chrome_driver)
driver.get('https://www.python.org') #이동하고자 할 사이트 (자동)
#검색 자동화 (검색창의의 html 태그_id: id-search-field)
search = driver.find_element_by_id('id-search-field')
search.clear() #원래 있던 내용 지우기
time.sleep(3) #3초간 딜레이
search.send_keys('lambda')
time.sleep(3)
search.send_keys(Keys.RETURN) #enter키 눌러 검색
time.sleep(3)
driver.close() #사이트 닫기 (자동)
: SessionNotCreatedException 에러: chrome_driver의 설치 경로 입력 시 \ 대신 \\ 혹은 / 으로 변경하기
근데도 난 에러가 난다
- selenium을 이용한 다음뉴스 웹사이트 크롤링
driver 객체의 find_xxx_by 함수 활용
브라우저를 이용한다 → 요청이 한 번에 오는게 아니라 페이지에 담긴 소스 그대로 사용
따라서 selenium을 이용하면 좀 더 높은 확률로 데이터 크롤링 성공 가능
*) 차이점
1. request + bs: network탭을 이용해 추가적인 요청을 파악한 후 크롤링
2. selenium: 위 단계를 생략하고 한 번에 가능
chrome_driver = 'C:\\Users\\user\\Downloads\\chromedriver_win32\\chromedriver'
driver = webdriver.Chrome(chrome_driver)
url = 'https://news.v.daum.net/v/20190728165812603'
#브라우저 열기
driver.get(url)
src = driver.page_source
soup = BeautifulSoup(src) #bs객체에 html.text 넘겨서 객체 생성&초기화
#브라우저 닫기
driver.close()
comment = soup.select_one('span.alex-count-area')
comment.get_text()
: 페이지에 담긴 html 소스 그대로 사용 (driver.page_source)
12. selenium 모듈 - 03. 웹사이트의 필요한 데이터가 로딩된 후 크롤링하기
- selenium을 활용하여 특정 element의 로딩 대기
WebDriverWait 객체를 이용해 해당 element가 로딩되는 것을 대기
실제로 해당 기능을 활용하여 거의 모든 사이트의 크롤링이 가능
WebDriverWait(driver, 시간(초)).until(EC.presence_of_element_located((By.CSS_SELECTOR, 'CSS_RULE')))
chrome_driver = 'C:\\Users\\ydj89\\Downloads\\chromedriver_win32\\chromedriver'
driver = webdriver.Chrome(chrome_driver)
url = 'https://nownews.seoul.co.kr/news/newsView.php?id=20190730601010&wlog_tag3=naver'
#브라우저 열기
driver.get(url)
src = driver.page_source
soup = BeautifulSoup(src) #bs객체에 html.text 넘겨서 객체 생성&초기화
#브라우저 닫기
driver.close()
comment = soup.select_one('span.u_cbox_count') #아직 이 태그가 로딩되지x, 크롤링 안됨 (타이밍의 문제)
comment.get_text()
: 태그(찾고자 하는 element)가 로딩되지 않음, 크롤링 실패 → 타이밍의 문제!
#driver에게 최대 10초간(general) 기다리게 하기 (WebDriverWait)
#언제까지(until): element가 로딩될 때까지 (EC.presence~)
#어떤 밥법으로 찾을지(By.): CSS_SELECTOR로
#어떤 태그를 찾을지: 태그명.id
driver.get(url)
WebDriverWait(driver, 10).until(EC.presence_of_element_located(By.CSS_SELECTOR, 'span.u_cbox_count'))
: *)# 때 10초동안에도 로딩이 안되면 에러가 뜨지만 대부분의 웹페이지는 10초내에 로딩 되기 때문에 모든 element가 로딩된다 가정
13. selenium 모듈 - 04. 실전 웹 크롤링
- 뉴스 제목 크롤링 함수
def get_daum_news_title(news_id):
url = 'https://news.v.daum.net/v/{}'.format(news_id)
resp = requests.get(url)
soup = BeautifulSoup(resp.text)
title_tag = soup.select_one('h3.tit_view')
if title_tag: #존재한다면(=제목 크롤링 성공) 출력
return title_tag.get_text()
return ""
get_daum_news_content('20190728165812603')
- 뉴스 댓글 크롤링 함수
#댓글 '더보기' 클릭시 network 로그에 찍히는 추가 요청 endpoint
#offset: 원래 보여지던 댓글 개수 + limit
#limit: 댓글이 한 번에 10개씩 가져와짐
url = 'https://comment.daum.net/apis/v1/posts/133493400/comments?parentId=0&offset=13&limit=10&sort=POPULAR&isInitial=false&hasNext=true&randomSeed=1617363477'
#요청시 응답코드 4xx: 해당 endpoint를 호출할 권한이 없음
#->헤더의 값들을 dict로 묶어 같이 전달해주자!
headers = {
'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb3J1bV9rZXkiOiJuZXdzIiwiZ3JhbnRfdHlwZSI6ImFsZXhfY3JlZGVudGlhbHMiLCJzY29wZSI6W10sImV4cCI6MTYxNzQwNTQwNCwiYXV0aG9yaXRpZXMiOlsiUk9MRV9DTElFTlQiXSwianRpIjoiMWQ3NjRjYTMtNjM1Yy00MmFkLWJlYTgtMjJlYTQ2NzI4NDI1IiwiZm9ydW1faWQiOi05OSwiY2xpZW50X2lkIjoiMjZCWEF2S255NVdGNVowOWxyNWs3N1k4In0.4w62K52dSzi1FDwIljP4gz_IiD_RRwNXCX_WQmmdcj8',
'Origin': 'https://news.v.daum.net',
'Referer': 'https://news.v.daum.net/v/20190728165812603',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36'
}
resp = requests.get(url, headers=headers)
resp.json()
: endpoint(url)을 request.get으로 요청 시 response 코드가 4xx라면 호출 권한이 없는 것 → response header의 값들을 dict로 묶어 같이 전달해주면 성공할 확률 높아짐 !
: limit: '더보기'버튼 클릭 시 추가적으로 보여질 댓글 개수 (10개)
: offset: 원래 보여지던 댓글 개수 + limit (ex) 원래 3개 + 더보기 = 13개)
#offset을 적절히 활용해 긁어오기
#endpoint의 offset값을 43으로 주면 빈 리스트를 반환->거리 43 이후에는 '더이상 댓글이 없음'을 의미
def get_daum_news_comment(news_id):
headers = {
'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb3J1bV9rZXkiOiJuZXdzIiwiZ3JhbnRfdHlwZSI6ImFsZXhfY3JlZGVudGlhbHMiLCJzY29wZSI6W10sImV4cCI6MTYxNzQwNTQwNCwiYXV0aG9yaXRpZXMiOlsiUk9MRV9DTElFTlQiXSwianRpIjoiMWQ3NjRjYTMtNjM1Yy00MmFkLWJlYTgtMjJlYTQ2NzI4NDI1IiwiZm9ydW1faWQiOi05OSwiY2xpZW50X2lkIjoiMjZCWEF2S255NVdGNVowOWxyNWs3N1k4In0.4w62K52dSzi1FDwIljP4gz_IiD_RRwNXCX_WQmmdcj8',
'Origin': 'https://news.v.daum.net',
#일단 news_id를 referer값에 전달하기로 함,,
'Referer': 'https://news.v.daum.net/v/{}'.format(news_id),
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36'
}
#전체 url이 아니라 offset값을 다르게 주기 위함
url_template = 'https://comment.daum.net/apis/v1/posts/133493400/comments?parentId=0&offset={}&limit=10&sort=POPULAR&isInitial=false&hasNext=true&randomSeed=1617363477'
#예제랑 url_template 다름...
#url_template = 'https://comment.daum.net/apis/v1/posts/@{다름,,}/comments?parentId=0&offset={}&limit=10&sort=RECOMMEND&isInitial=false'
offset = 0 #오프셋 증가->더보기 버튼을 누른다는 뜻
comments = [] #댓글을 담을 리스트
while True:
url = url_template.format(offset)
resp = requests.get(url, headers=headers)
data = resp.json()
if not data: #긁어온 댓글이 없는 경우 while loop 탈출
break
comments.extend(data) #리스트에 긁어온 댓글(data) 추가
offset += 10 #limit이 10이기 때문!_댓글을 10개씩 가져옴, 루프는 그 다음 10개부터 시작돼야하기 때문
return comments
: [더보기] 클릭 시 요청의 endpoint는 offset값만 달라짐을 활용, format함수를 이용해 url 구성하기
: 댓글 요청의 headers 구성은 동일 (requests.get 사용 시 headers=headers 동일하게 사용)
comment = []
for info in get_daum_news_comment('20190728165812603'):
print(info['content'], '\n')
: 댓글 내용(content)만 출력
강의 링크: https://bit.ly/3cB3C8y
'코딩 > 머신러닝&데이터 분석 강의' 카테고리의 다른 글
[머신러닝 인강] 9-1주차: ndarray 인덱싱, 슬라이싱, 기본 함수 (0) | 2021.04.18 |
---|---|
[머신러닝 인강] 8-2주차: numpy 모듈과 ndarray 이해하기 (0) | 2021.04.10 |
[머신러닝 인강] 7-2주차: beautifulsoup 모듈 (0) | 2021.04.02 |
[머신러닝 인강] 7-1주차: API의 활용 (0) | 2021.03.30 |
[머신러닝 인강] 6-2주차: 웹 기본 지식 이해하기 (0) | 2021.03.28 |