※ 스파르타 코딩클럽의 웹개발 종합반(5주)을 공부하며 기억하고 싶은 특이점들만 기록, 정리해 놓은 노트이다.
※ 노션에 필기한 내용을 복사해오다 보니 식과 포맷이 엉성한 곳이 있다.
let star_image = "⭐".repeat(star);
에러 기록
(인터프리터를 찾을 수 없어…) 가상환경 생성에 실패했습니다.
PyCharm에서 파이썬 새 프로젝트를 실행하려는데 ‘인터프리터를 찾을 수 없고’ 그래서 ‘가상환경 생성에 실패했다’는 메세지만 받기를 십 수번… 여기저기 검색도 해보았지만 내 상황엔 적용되지 않는 것 같아보이는 조언들만 읽다가 지치고 읽다가 지치고 하기를 어언 1시간 반… 그래 가상환경 폴더(venv)는 안 만들어져어도 일단 파이썬 파일이 실행은 되니까 진행하자 하고 진행하던 중, 패키지를 설치하라는 강의를 만나게 되었다. 그런데 아까 한참 삽질을 하던 그 익숙한 창, 설정>프로젝트>Python 인터프리터로 이끌 때부터 쎄하더라니….여기서도 똑같은 에러 창을 띄우며 막혀버렸다. 하는 수 없이 다시 검색에 들어갔는데 웬걸, 다른 키워드로 검색을 해서 그런지 제일 처음 찾은 즉문즉답 페이지에서 해결법을 찾아냈다..!
내가 막힌 부분:
3-3강 (새 파이썬 프로젝트를 만드는 부분),
3-5강 (파이썬 패키지를 설치하는 부분).
둘이 같은 문제인 듯 했다.
내가 한 삽질:
- 파이썬 최신 버전 3.9이라 에러가 있을 수 있다 ⇒ 그래서 버전 3.8을 다시 깜.
- 환경변수 지정의 문제일지도 모른다고 해서 이미 잘 지정되어 있던 환경변수를 괜히 전체 계정(System)용으로 다시 지정해줌.
- 그 밖에 python의 interpreter인지 뭔지를 찾기 위해 숨은 appdata 폴더를 찾기 위한 헛된 노력들…
해결:
https://spartacodingclub.kr/community/fastqna/all/6350ff76a9575d8542404cbc/파이썬 패키지를 설치를 하려는데 못 찾겠어요.
위의 글 내용을 정리하자면, 한마디로 내가 설치한 게 너무 최신 PyCharm 버전이라 문제였다~ 이런 얘기다. 파이썬 버전이 너무 최신인 게 문제라는 말을 듣고 3.9있던 것에 더해 3.8까지 설치했건만… 어쨌든 너무나 간단명료하게 정리된 답변에 따라 지금 버전(2022.3.뭐시기)를 지우고 옛버전(2021.3.3)을 깔았다. (근데 나 유료 회원인거 따로 인증하는 절차가 없던데, Professional 버전이 잘 깔리더란 말이지…? 확인해보니 내 라이센스 계정에 알아서 잘 연결되어 있다.)
그리고 마침내 강의에 나온 대로 모든 과정이 매끄럽게 진행되더라는 결말. 감격이었다. 깔끔하게 설명해주신 튜터님 정말 고마워요!
가상환경(Virtual Environment)
가상환경(VE)은, 쉽게 말해 프로젝트별 공구함이다. 같은 패키지를 프로젝트별로 다른 버전으로 쓸 수 있게 된다. 격리된 실행 환경.
파이썬 프로젝트별 다른 패키지 설치하기
1. 먼저 독립된 가상환경을 가진 새 파이썬 프로젝트를 생성한다:파이참 '파일'메뉴 → ‘new project’→ 프로젝트 폴더 위치 지정, ‘New environment using Virtualenv’로 위치가 ‘\venv’로 생성되는지 확인, ‘Base interpreter’로는 파이썬39로 선택 → 만들기
2. 만들어진 프로젝트에 들어와서 ‘옵션Settings’ → ‘Project: ~~’ → ‘Python Interpreter’ → ‘+’ 클릭 → 필요한 패키지명 검색해서 다운로드 → 그러면 알아서 ‘venv’폴더에 저장되고 버전 컨트롤이 이루어진다.
에러 기록
AttributeError: partially initialized module 'requests' has no attribute 'get' (most likely due to a circular import)
python에서 파일명과 모듈명이 같을 때 발생하는 에러라고 한다.
파일명을 requests.py에서 다른 걸로 바꿔주니 해결됨.
크롤링 기본 세팅
requests 패키지, bueatifulSoup 패키지
import requests
from bs4 import BeautifulSoup
# 타겟 URL을 읽어서 HTML를 받아옴
headers = {'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36'}
data = requests.get('<https://movie.naver.com/movie/sdb/rank/rmovie.naver?sel=pnt&date=20210829>',headers=headers)
# HTML을 BeautifulSoup이라는 라이브러리를 활용해 검색하기 용이한 상태로 만듦
# soup이라는 변수에 "파싱 용이해진 html"이 담긴 상태가 됨
# 이제 코딩을 통해 필요한 부분을 추출하면 된다.
soup = BeautifulSoup(data.text, 'html.parser')
...
type(data) = <class 'requests.models.Response'>
type(data.json()) = <class 'dict'> → 이건 JSON 형식이니까 당연
type(soup) = <class 'bs4.BeautifulSoup'>
BeautifulSoup 기초 1탄
select_one(요소 하나만 선택)과 select(해당하는 요소 모두 선택):
select_one은 태그 타입을 반환한다.
# 브라우저 -> 요소 '검색' -> 해당 요소 우클릭 -> 복사 -> selector 복사
#old_content > table > tbody > tr:nth-child(2) > td.title > div > a
#old_content > table > tbody > tr:nth-child(4) > td.title > div > a
# 이런 주소가 복사됐을 때, 공통부분:
#old_content > table > tbody > tr
movies = soup.select('#old_content > table > tbody > tr')
for movie in movies:
a_tag = movie.select_one('td.title > div > a')
# movie 안에 a 태그가 있으면:
if a_tag is not None:
print(a_tag)
print(a_tag.text)
print(a_tag['href'])
- 선택된 태그 안의 텍스트 추출: 태그.text
- 선택된 태그 안의 속성 추출: 태그[’속성’] ⇒ 얘도 딕셔너리처럼 추출하네.
- select_one이 반환하는 타입은 = <class 'bs4.element.Tag'>
- select가 반환하는 리스트 타입은 = <class 'bs4.element.ResultSet'>
BeautifulSoup 요소 선택자:
# 선택자를 사용하는 방법 (copy selector)
soup.select('태그명')
soup.select('.클래스명')
soup.select('#아이디명')
soup.select('상위태그명 > 하위태그명 > 하위태그명')
soup.select('상위태그명.클래스명 > 하위태그명.클래스명')
# 태그와 속성값으로 찾는 방법
soup.select('태그명[속성="값"]')
# 한 개만 가져오고 싶은 경우
soup.select_one('위와 동일')
웹스크래핑(크롤링)
네이버 영화 순위, 제목, 평점 가져오기
import requests
from bs4 import BeautifulSoup
headers = {'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36'}
data = requests.get('<https://movie.naver.com/movie/sdb/rank/rmovie.naver?sel=pnt&date=20210829>',headers=headers)
soup = BeautifulSoup(data.text, 'html.parser')
# 순위
#old_content > table > tbody > tr:nth-child(2) > td:nth-child(1) > img
# 제목
#old_content > table > tbody > tr:nth-child(2) > td.title > div > a
# 평점
#old_content > table > tbody > tr:nth-child(2) > td.point
# 공통부분:
#old_content > table > tbody > tr
movies = soup.select("#old_content > table > tbody > tr")
for movie in movies:
a = movie.select_one("td.title > div > a")
if a is not None:
rank = movie.select_one("td:nth-child(1) > img")['alt']
title = a['title']
rate = movie.select_one("td.point").text
print(rank, title, rate)
# 왜 '타이틀' 태그만 none인지 아닌지를 체크하는 것인지는,
# 이미 생긴 모양새를 알고 있다는 제 하에 이루어지는 것!
# 즉, 어떤 자리는 영화제목 대신 ----가 온다는 것을 알고 있다.
# 그래서 그 부분(제목 태그)에 대해서만 if로 검사를 해주었다.
데이터베이스
DB의 두 가지 종류: SQL(=RDBMS)
열이 정해진 테이블 형식으로 데이터 저장. 갑자기 속성(열)을 하나 더하기는 어려움. 정형화 되어있음. 유연성이 없는 대신 찾는데 걸리는 시간이 적다.
MySQL, MS-SQL 등
DB의 두 가지 종류: NoSQL
딕셔너리 형태로 데이터를 저장. 스타트업 같이 데이터 체계가 수시로 바뀌는 곳에서 많이 사용함. 자유로운 데이터 적재가 가능하나 일관성이 부족할 수 있다.
MongoDB 등
DB는 컴퓨터인가?
DB는 게임이나 엑셀과 같이 컴퓨터에 설치할 수 있는 프로그램일 뿐이다.
요샌 서버 컴퓨터에 직접 설치하지 않고 Cloud 형태로 제공해주는 서비스를 사용하는 쪽으로 옮겨가는 추세. 왜냐면
- 유저가 몰리거나
- DB를 백업해야 할 때,
- 모니터링 등등
에 아주 용이하기 때문에. 최신 클라우드 서비스로는 mongoDB Atlas가 있다.
MongoDB Atlas 사용하기
가입:
회원가입 → 회사명(Organization) 생성 → 프로젝트 생성 → 클러스터(Cluster) 생성 → (클라우드 컴퓨터에 접속할 유저)아이디, 비밀번호 만들기
파이참에서 MongoDB에 연결하고 자료 올리기:
파이참에 pymongo, dnspython 패키지 설치
// 템플릿
client = MongoClient('mongodb+srv://<ID>:<password>@cluster0.ecmqblf.mongodb.net/?retryWrites=true&w=majority')
db = client.dbsparta
doc = {
'name':'Bob Rogers',
'age': 28,
'height': 182,
}
db.users.insert_one(doc)
# => ...안의 Cluster0 안의 dbsparta라는 데이터베이스 안의 users라는 컬렉션에 doc이 올려짐.
클라우드에서 데이터베이스 확인하기:
MongoDB Atlas → HAN'S ORG - 2022-10-21 > PROJECT 0 → Cluster0의 ‘Browse Collections’ 클릭!
pymongo 기초 1탄
# 저장 - 예시
doc = {'name':'bobby','age':21}
db.users.insert_one(doc)
# 한 개 찾기 - 예시
user = db.users.find_one({'name':'bobby'})
# 여러개 찾기 - 예시 ( _id 값은 제외하고 출력)
all_users = list(db.users.find({},{'_id':False}))
# 바꾸기 - 예시
db.users.update_one({'name':'bobby'},{'$set':{'age':19}})
# 지우기 - 예시
db.users.delete_one({'name':'bobby'})
insert_one(), find_one(), update_one(), delete_one()
아주 충실한 CRUD로구만.
JSON은 딕셔너리 형식으로 데이터가 쓰여진 파일이다. NoSQL의 일종인 MongoDB도 딕셔너리 형태로 데이터를 저장한다고 했다. 즉, 둘 모두
- print( list(db.users.find({})[0]['name'] ) (MongoDB)
- response['RealtimeCityAir']['row'] (JSON)
와 같이 딕셔너리[’키’] 방식으로 값을 뽑는다는 것.
requests와 pymongo 한방에 정리 템플릿
## 웹 크롤링에 필요한 세팅: requests와 bs4 패키지
import requests
from bs4 import BeautifulSoup
headers = {'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36'}
data = requests.get('<https://movie.naver.com/movie/sdb/rank/rmovie.naver?sel=pnt&date=20210829>',headers=headers)
soup = BeautifulSoup(data.text, 'html.parser')
## 클라우드 데이터베이스로의 연결 세팅: pymongo와 dnspython 패키지
from pymongo import MongoClient
# mongoDB 클라우드에 배정받은 내 컴퓨터(?)에 접속할 아이디 & 패스워드
client = MongoClient('mongodb+srv://:@cluster0.ecmqblf.mongodb.net/?retryWrites=true&w=majority')
db = client.dbsparta
# 순위
#old_content > table > tbody > tr:nth-child(2) > td:nth-child(1) > img
# 제목
#old_content > table > tbody > tr:nth-child(2) > td.title > div > a
# 평점
#old_content > table > tbody > tr:nth-child(2) > td.point
# 공통부분
#old_content > table > tbody > tr
movies = soup.select("#old_content > table > tbody > tr")
for movie in movies:
a = movie.select_one("td.title > div > a")
if a is not None:
rank = movie.select_one("td:nth-child(1) > img")['alt']
title = a['title']
rate = movie.select_one("td.point").text
doc = {
'rank': rank,
'title': title,
'rate': rate,
}
# 왜 '타이틀' 태그만 none인지 아닌지를 체크하는 것인지는,
# 이미 생긴 모양새를 알고 있다는 저제 하에 이루어지는 것!
# 즉, 어떤 자리는 영화제목 대신 ----가 온다는 것을 알고 있다.
# 그래서 그 부분(제목 태그)에 대해서만 if로 검사를 해주었다.
# DB에 올리기
db.movies.insert_one(doc)
과제 비교
왼쪽이 정답코드, 오른쪽이 내가 한 것. (정답과 똑같으니 보지 않아도 됨
⇒ 크게 차이나는 것은 없다.
⇒ 다만 soup.select() 으로 반환받은 게 어차피 iterable이면 for문에서 그대로 사용 가능할 것이므로, 굳이 list()로 감싸주지 않아도 되겠다(정답 코드 참조).
참고:
스파르타 코딩클럽 > 웹개발 종합반(5주)의 영상 강의와 강의노트를 주로 참고함
가끔 구글링