Webtoon Crawler - 1
예제에 나와있는 표현을 이해하지 못해서 멘탈 바사삭.
해당 문장이 정확히 어떤 의미인지를 알아야 코드 적용이 가능하기 때문에
최대한 내가 이해할수 있는 정도로 풀어서 정리.
(내 생각은 ##으로 표시.)
- 예제
# 1. HTML받아와서 html변수에 문자열을 할당
# 1-1. 만약 'data/episode_list.html'이 없다면
# -> 내장모듈 os의 'exists'함수를 사용해본다
# -> 파이썬 공식문서 확인
# http://comic.naver.com/webtoon/list.nhn?titleId=703845&weekday=wed
# 죽음에 관하여 (재) 페이지를
# requests를 사용해서 data/episode_list.html에 저장
# list.nhn뒤 ?부터는 url에 넣지 말고 GET parameters로 처리
# -> requests문서의 'Passing Parameters In URLs'
# 저장 후에는 파일을 불러와 html변수에 할당
# 1-2. 이미 'data/episode_list.html'이 있다면
# html변수에 파일을 불러와 할당
import os
import requests
# html을 받아온다는 의미인듯 함
file_path = 'data/episode_list.html'
# HTTP 요청을 보낼주소
## 아래 else문에 HTTP GET요청할때 .get()부분에 들어가는것
url_episode_list = 'http://comic.naver.com/webtoon/list.nhn'
params = {
'titleId': 703845,
}
# HTML파일이 로컬에 저장되어 있는지 검사시작
# HTML파일이 저장되어 있다면
if os.path.exists(file_path):
# 저장되어 있다면, 해당 파일을 읽어서 html변수에 할당
## 해당 파일을 읽어서 -> open(file_path, 'rt').read()
## html 변수에 할당 -> (html = ~~~)
html = open(file_path, 'rt').read()
# HTML파일이 저장되어 있지 않다면
else:
# requests를 사용해 HTTP GET요청
## 객체(코드 문맥에서 벗어나지 않게 마음대로 설정) = requests.get(url주소)
response = requests.get(url_episode_list, params)
# 요청 응답객체의 text속성값을 html변수에 할당
## 요청 응답객체 -> response(꼭 이 이름이 아니어도 됨)
## text속성값 -> 객체.text
html = response.text
# 받은 텍스트 데이터를 HTML 파일로 저장
open(file_path, 'wt').write(html)
os.path.exists 함수 관련 참조 -> ctrl + f -> exists
params 참조 -> Passing Parameters In URLs
# 2. 제목, 저자, 웹툰정보 탐색하기
# html변수를 사용해 soup변수에 BeautifulSoup객체를 생성
# soup객체에서
# - 제목: 죽음에 관하여 (재)
# - 작가: 시니/혀노
# - 설명: 삶과 죽음의 경계선, 그 곳엔 누가 있을까.
# 의 내용을 가져와 title, author, description변수에 할당
# BeautifulSoup클래스형 객체 생성 및 soup변수에 할당
## soup변수->soup
## 할당 -> "="
## BeautifulSoup(html, 'lxml') -> BeautifulSoup클래스형 객체 생성
soup = BeautifulSoup(html, 'lxml')
# div.detail > h2 (제목, 작가)의
# 0번째 자식: 제목 텍스트
# 1번째 자식: 작가정보 span Tag
# Tag로 부터 문자열을 가져올때는 get_text()
## select_one은 해당 선택자에 대해서 한개만 가져온다.
h2_title = soup.select_one('div.detail > h2')
# 0번째 자식: 제목 텍스트
## 제목이니까 변수 이름을 title로
## .은 CSS에서 클래스 선택자를 의미함
## .contents는 해당 태그의 컨텐츠 목록을 출력한다.
## .strip()은 문자열의 처음과 끝에서 ()안에 있는 글자만 출력시킴.
## 이를테면 \n, \t와 같은 글자들은 없어진다고 생각하면 됨.(태그 없는 글자에 쓸것)
title = h2_title.contents[0].strip()
# 1번째 자식: 작가정보 span Tag
## strip=True 란 문자의 양쪽 공백을 다 없애주는거.
## .get_text() -> 문서나 태그의 텍스트 부분만 원할경우(태그 있는 글자에 쓸것)
author = h2_title.contents[1].get_text(strip=True)
# div.detail > p (설명)
# Tag로 부터 문자열을 가져올때는 get_text()
## div.detail > p -> div클래스의 detail의 자식 요소인 p
description = soup.select_one('div.detail > p').get_text(strip=True)
print(title)
print(author)
print(description)
CSS selector에 대해서 이해필수 -> 수업시간에 받은 pdf 파일 활용 or 구글링
크롤링하려는 페이지에서 개발자 도구를 켜서 해당 태그의 클래스 명 등을 확인해야 한다.
# 3. 에피소드 정보 목록을 가져오기
# url_thumbnail: 썸네일 URL
# title: 제목
# rating: 별점
# created_date: 등록일
# no: 에피소드 상세페이지의 고유 번호
# 각 에피소드들은 하나의 dict데이터
# 모든 에피소드들을 list에 넣는다
# 에피소드 목록을 담고 있는 table
## 크롤링 하려는게
table = soup.select_one('table.viewList')
print(table)
# table내의 모든 tr요소 목록
## table'.'(클래스)
## select('tr') -> <tr> 내용 </tr> 이런식으로 되어있는 내용 전체 출력. / 리스트 값으로 반환하기 때문에 list 변수 할당
tr_list = table.select('tr')
# 첫 번째 tr은 thead의 tr이므로 제외, tr_list의 [1:] 부터 순회
## enumerate -> 반복되는 구간에서 객체가 어느 위치에 있는지 알려주는 인덱스 값이 필요할때 사용
## 이번 경우 table.select('tr')로 잘 출력되는지 구분하기가 힘들어서 아래처럼 for 문을 돌림
for index, tr in enumerate(tr_list[1:]):
# 에피소드에 해당하는 tr은 클래스가 없으므로,
# 현재 순회중인 tr요소가 클래스 속성값을 가진다면 continue
## 만약 tr요소중에 class가 있다면 continue
## continue는 반복문의 나머지 부분을 안보고 반복문의 처음으로 돌아감
## 지금은 제외하는 경우를 첫번째에 처리함.
if tr.get('class'):
continue
# 현재 tr의 첫 번째 td요소의 하위 img태그의 'src'속성값
## tr이 가지고 있는 td가 가지고 있는 nth-of-type의 첫번째거가 가지고 있는 img태그의 src속성
url_thumbnail = tr.select_one('td:nth-of-type(1) img').get('src')
# 현재 tr의 첫 번째 td요소의 자식 a 태그에 'href'속성값
### url을 주소와 + params 형태로 만드는것 -> 아래 사이트 참조
url_detail = tr.select_one('td:nth-of-type(1) > a').get('href')
# 현재 tr의 두 번째 td요소의 자식 a요소의 내용
title = tr.select one('td:nth-of-type(2) > a').get_text(strip=True)
# 현재 tr의 세 번째 td요소의 하위 strong태그의 내용
## 하위 선택자는 띄어쓰기로 함.
rating = tr.select_one('td:nth-of-type(3) strong').get_text(strip=True)
# 현재 tr의 네 번째 td요소의 내용
created_date = tr.select_one('td:nth-of-type(4)').get_text(strip=True)
### url_detail을 먼저 할당
### parse.urlsplit(url_detail) -> SplitResult 목록 출력
### .query -> 그 중 query 속성출력
query_string = parse.urlsplit(url_detail).query
### query_string을 파싱해줘 -> 딕셔너리 형태로 출력됨
query_dict = parse.parse_qs(query_string)
### 단순 query_dict['no']로 하면 딕셔너리 형태로 출력됨
### 따라서 [0]을 통해 딕셔너리 형태를 없애주기 위해 0번째 값을 꺼내줌.
no = query_dict['no'][0]
print(url_thumbnail)
print(title)
print(rating)
print(created_date)
print(no)
로드하지 않은 모듈을 임포트시
- Pycharm에서 해당 명령어에 빨간줄 -> Alt + Enter -> 임포트할 모듈 선택
타이핑을 입력하다가 오타가 날수 있으니 되도록 이면 자동완성 기능 사용할것.
한번에 코드를 짤순 없다! 그러니까 print를 자주 써서 제대로 내용을 작성하는지 수시로 확인할것.
Comments