[혼공분석] 2주차

빡커·2025년 7월 14일
0

데이터 수집하기

학습목표

  1. api로 가져오는 법
  2. 웹스크래핑으로 가져오는법

02-1 API 사용하기

api 줄임말 뜻? 핵심은? 예시는?

  • Application Programming Interface 라는 거창한 명칭이다. 어플리케이션은 프로그래밍 규약인데, 사실 api가 아키텍쳐 설계도는 아니다. 요즘 날에 쓰이는 용어로는, 그니까 이걸 어떻게 사용하면 되고 무슨 기능을 제공할 수 있는데? 라는걸 표현하는 문서지. 세세한 세팅환경까지 전부 명세하는 규약은 아니라는것.

web API란?

  • 웹 앱 API를 말하는거지. 웹앱은 이렇게 만드세요. 가 아니라, 이제는 REST API를 표현하는 말로 변모됨.

HTTP란?

  • HyperText Transfer Protocol
  • 초월문서 전송 규약
  • HTTP는 그런 방법론이자 기술이다. 그런데 이 기술을 반드시 사용하고 아니 요즘은 이제 이거를 전용으로 이 기술이 사용된다. 바로 브라우저와 웹앱 사이에 데이터 전송 방식이 HTTP로 자리 잡았다.

그 두개가 뭔 차이일까? API를 어디까지 API로 봐야될까?

  • HTTP는 말그대로 기술이고, API는 그래서 어떻게 사용하면 되는데? 이다. 웹 API만 있는게 아니다. embed program api 혹은 라이브러리 api 혹은 프레임워크 api 혹은 엔진 api 등 결국 어플리케이션 간에 소통을 위한 방법이 api이기 때문에 둘은 다르다.

REST API란? 일단 REST가 뭐의 줄임말이야? 그게 뭔뜻이야?

  • REpresentational State Transfer
  • 아 계속해서 검색해가며 하는데 확실하게 정리하면 왜 이게 Representational State Transfer냐면, State를 전송할때 대표적인 State만 전송하자 해서 State Transfer인데 Representational 이라는 수식어가 붙는다.
  • 브라우저에서 요청한 주소를 매핑할때 서버에서 어떤 주소를 받아들일때 동작하게 만들지를 설계할때 아주 대표적인 '리소스'만 받아도 충분히 동작할 수 있다는 근거로 등장한다.
  • 왜냐? HTTP에는 method, caching option, links parameter, contents type 등등 서버와 클라이언트간에 소통시 '분기' 조건 역할을 할 정보들을 담을 수 있으니, '리소스 uri'라는 명칭 하나로 온갖 동작을 다 설계할 수 있기 때문. 이는 다형성의 원리를 따라 설계의 복잡도를 줄인 아키텍쳐이다. 주소에 모든 정보가 다 포함되면 서버가 아 대충 이거 하겠구나? 라는 은닉성도 없을뿐더러, 너무 길어지고 알아보기 쉽지 않기때문에 나온 구조조

REST API 구성을 설명하라 가장 간단한 핵심 규칙만 풀어써보자 아는대로로

  • 위에 앵간하면 설명했는데 더 말하자면 http method 방식을 잘 알고 적절하게 사용하자.

HTML이란?

  • HyperText Markup Language
  • 시멘틱 구주로 화면의 요소 구별 세팅 담당

브라우저는 따라서 뭘 렌더링하는 엔진인가?

  • 화면을 구성하는 3개 파일을 렌더링 하지.

그럼 요청해서 나오는 응답의 결과가 HTML이라면 HTTP에는 HTML 데이터가 포함되어 있을까?

  • 당빠 HTTP 바디 부분에 html이 담겨 나온다.

파이썬으로 JSON 데이터 다루기

JSON이란? 무슨 약자인가?

  • JavaScript Object Notation
  • 자바스크립트 문법을 따르는 컨테이너 객체 형태인데, 이 문법 형식과 같은 방식으로 문자열을 입력하여 JSON 해석기로 필요한 데이터를 통신하는 방식으로 JSON이 활용되게 바뀜

JSON은 파이썬의 어떤 객체들이 합쳐진 모습과 유사하지?

  • 딕셔너리와 리스트가 합쳐진 상태이다.

문법으로 주의할 사항 항상 뭐로 요소를 감싸는가? 왜 그럴까? 좀만 더 생각해봐

  • "" 큰따옴표로 감싸줘야 한다. 왜그러냐? 어떤 언어에서든 통용하여 사용하기에 요소 구분을 ""로 파싱하여 해석하기 때문이다.

파이썬을 웹이 이해하는 형태로 바꿀려면? 무슨함수? 걔가 뭘 반환하길래?

import json
jsonStr = json.dumps(dic, ensure_ascii=False)
  • JSon 형식을 따르는 문자열 형태로 바꾼다.

그 함수에서 한글이 포함되어 있다면? 왜? 그렇게 매개변수를 넘기지?

  • ensure_ascii=False
  • 아스키가 영어를 전달하기에 효율적이기 때문에 일반적으로 ascii로 전달하고 해석하려한다. 유니코드 형태로 전달하려면 ensure_ascii 옵션을 false로

자 웹에서 온걸 파이썬이 이해하기 쉽게 하려면? 뭘해야될까? 그걸 해주는 함수는?

import json
dic = json.loads(jsonStr)

프로그램밍언어가 프로그래밍 언어가 아닌 다른 녀석이 이해할 수 있는 포맷으로 바꿔주는걸 뭐라고 하고, 그 반대는 뭘까?

  • 다른 녀석이 이해 -> 직렬화
  • 다시 직렬화의 반대 -> 역직렬화

역직렬화의 결과는??

  • 딕셔너리 혹은 딕셔너리 리스트

JSON 응답을 데이터프레임으로 바꾸면 장점? 어떻게 바꾸나?

  • 판다스 패키지의 데이터프레임은 행렬형태의 자료를 효과적으로 다루는 함수들이 다량 정의되어 있어서 장점이 크다
import pandas as pd
# pd.read_json(jsonStr) 문법적으로 전혀 오류 없어보이지만 주의해야한다. 매개변수로 스트링이긴 한데 파일 경로 타입만 받는다.
from io import StringIO
pd.read_json(StringIO(jsonStr)) #jsonStr을 읽을 수 있는 파일패스로 변경해주는 함수로 감싸서 동작작

파이썬에서 XML 다루기

XML 무슨 약자? 자 HTML과 비교하여 XML이 좀 더 우위에 있는게 어떤게 우위에 있을까?

  • eXtensible Markup Language
  • HTML은 브라우저가 이해하는 문법이지 사람이 이해하기엔 벅차다.
  • 기계도 이해하고 사람도 이해하면서 단순히 데이터 깊이를 나눠서 전달할 수 있는 문법이 있다면? -> 그게 XML
  • HTML처럼 태그 문법으로 구조를 나눈다.

문법적 요소를 설명하라. 뭐로 감싸지고 그 대상은 뭔지만?

  • 모든 태그는 엘리먼트 요소임을 표현한다. 엘리먼트는 내부적으로 값을 가진다.
  • 엘리먼트가 엘리먼트를 값으로 가질 수도 있고 엘리먼트 여러개를 값으로 가질 수도 있다. 다만 XML은 반드시 시작노드에는 시작 엘리먼트 하나만 존재할 수 있다.
  • 즉 같은 레벨의 태그를 연달아 올리수 없고 같은 레벨의 태그를 연달아 나열하려면 반드시 상위 태그의 엘리먼트로 감싼다. 즉 트리 구조를 무조건 따라야 한다는 강제성이 존재재

역직렬화한 결과는? 그리고 그 새끼를 다루는 가장 기본적인거? 만약 부모 엘리 자식엘리가 존재할때 자식엘리 접근은? 아주 ㅈ같네 이거? 왜 그렇게? 위험한 방법은 왜 위험한건가?

  • 역직렬화한 결과는 딕셔너리 리스트가 되었으면 참 좋겠는다 그 새끼는 그게 아니다. XML파서가 전문적으로 존재하면 래퍼 클래스이다. 데이터 프레임 같은건데 아주 ㅈ같다.
import xml.etree.ElementTree as et
book = et.fromstring(xmlStr)
# et.Element 객체가 된다. 내부적으로 tag필드로 tag 값을 가지고 있고 text필드로 요소값이 저장되어 있다.

직렬화 역직렬화 아주 개 ㅈ같은거의 핵심은? JSON과 달리 반드시 강제되는 문법적 요소가 있네 보니까

  • 위에 언급했다. HTML마냥 저주받았다.

여러개의 키값을 확인하는 findall() 문법 사용시 중의할점. 그 원리 설명. 왜 개 ㅈ같은지 시발 욕밖에 안나오네. 이딴걸 왜씀? 뭐 강점이 아예 없진 않지

# 자 xmlStr이 "<book>혼공분석</book>" 이러면 너무나도 해피하지. 
import xml.etree.ElementTree as et
book = et.fromString(xmlStr)
print(book.tag) #book
print(book.text) #혼공분석

#만약 태그 내에 여러 태그가 존재하는 상황? "<book><e>혼공분석</e><e>혼공만파파</e><e>혼공러닝닝</e></book>"
book2 = et.fromString(xmlStr) #여전히 Element객체 안에 뭐가 들었는지 이것만으로 알기 어려워서 ㅈ같은거
print(book.tag) #book
print(book2.text)  # 출력: None ㅈ같다.
# 자식 엘리먼트들의 내용을 출력하기 위해 반복문을 사용한다.
for child in book2:
    print("Child tag:", child.tag, ", text:", child.text)
# => 이렇게 하면 각 <e> 태그의 내용을 확인할 수 있다.


# 예제 XML 데이터: <books> 안에 여러 <book> 요소가 존재하며,
# 각 <book> 내부에 책 제목, 저자, 출간일을 나타내는 태그들이 있습니다.
xmlStr = """
<books>
   <book>
      <name>혼공분석</name>
      <author>홍길동</author>
      <date>2025-05-29</date>
   </book>
   <book>
      <name>혼공알고</name>
      <author>임꺽정</author>
      <date>2025-05-28</date>
   </book>
   <book>
      <name>혼공만파</name>
      <author>유관순</author>
      <date>2025-05-27</date>
   </book>
</books>
"""

# XML 문자열을 파싱하여 최상위 요소(여기서는 <books> 객체)를 얻습니다.
root = et.fromstring(xmlStr)
print("최상위 태그:", root.tag)  # 결과: books

# findall()을 이용해 <books> 요소 내의 모든 <book> 자식 요소들을 찾습니다.
for book in root.findall('book'):
    # findtext()는 자식 요소의 텍스트 값을 바로 반환합니다.
    name = book.findtext('name')
    author = book.findtext('author')
    date = book.findtext('date')
    
    print("-----------")
    print("책 제목:", name)   # 예: 혼공분석 등
    print("저자:", author)    # 예: 홍길동 등
    print("출간일:", date)    # 결과: 2025-05-29 등

API로 20대가 가장 좋아하는 도서 찾기

가장 먼저 해야할 것

  • api, 데이터셋 찾아보기기

API를 호출하는 URL 작성하기. 호출URL과 파라미터로 구분하여 분석 도서관 정보나루 공개 API 사용하는 코드를 분석 ㄱㄱㄱ

  • api문서를 보면서 각 파라미터 한번 읽어보고 -> 분석하여 -> 필요한 데이터만 사용하기 -> 쓰잘데기 없는거 넣어봤자 길이만 냅다 늘어남.
  • 만약 api 업데이트로 파라미터가 달라지면? -> 그럴수 있다. 다만 호출용 파라미터가 자주 일어나면 아주 ㅈ같은 api. 원래 api URL은 자주 바뀌면 안된다.

바로 손코딩 api 호출 ㄱㄱ

# 리퀘스트 요청 바로 때릴 수 있는 모듈
import requests

# 키 변수, url 변수 나누고 key를 URL 변수에 담아서 저장해보기
key = "4e39afc515253ef8c660ac7c5cb3e221590398f21d9d911ce5505ae5907ba2da"

url = f"http://data4library.kr/api/loanItemSrch?format=json&startDt=2021-04-01&endDt=2021-04-30&age=20&authKey={key}"


# get 방식으로 url 날려서 응답 저장
response = requests.get(url.format(isbn))

# 응답을 역직렬화
data = response.json()

#복잡한 리스폰스 객체를 확인한뒤에 어떤 방식으로 for문을 돌려야 책 제목을 리스트에 담을 수 있을까?
print(data)

공개 API로 웹에서 데이터 가져오기 <문제>

문제 해결과정을 말로 풀어 설명하면 끝

  1. 필요한 데이터가 있다. -> 서치 -> api찾기
  2. api 사용방법을 읽어보기 혹은 print찍어보기 -> 분석 -> 어떤 데이터를 사용할지 감잡기
  3. dataframe을 만들어서 정형화하기 -> csv파일로 저장하기 -> 분석결과 공유

확인 문제

  1. 2
  2. 1
  3. 2
  4. json string
  5. 3
  6. 1

02-2 웹 스크래핑

도서 쪽수를 찾아서 (시나리오)

API에서 제공하지 않는 도서 쪽수를 가져와서 만들고 싶다면 어떻게 해야될까? 시작과정만

  • 도서 쪽수가 나와있는 사이트에서 스크래핑. 해당 도서 페이지에 접근하기 위한 파라미터가 뭔지 파악후 접근. 스크래핑을 위해선 requests beautifulsoup 이라는 라이브러리 사용할 것것

yes24 사이트의 파라미터가 당황스럽다. 저런 숫자 데이터는 무엇인지 분석 https://www.yes24.com/product/goods/116253011 뒤에 있는 숫자는 ISBN이 아니다.

결과적으로 그러면 저 숫자를 이용한 URL을 만들어서 호출해야되는데 그 과정을 논리적으로 순서화해봐라 ISBN을 어떻게 활용할수 있을지 검색창에 ISBN을 입력한 결과 URL은 https://www.yes24.com/product/search?domain=ALL&query=9791162243664

  • 위에 다 적은듯듯

검색 결과 페이지 가져오기(코드)

#### 가장 먼저 할일? -> 20대가 가장 좋아하는 도서 목록을 만들기 위한 데이터 가져오기 gdown 코드 검색
gdownFilePath = 'https://bit.ly/3q9SZix'
gdownOutputFilePath =  './20s_best_books.json'

# !pip install gdown
# import gdown
# gdown.download(gdownFilePath, gdownOutputFilePath)

#### 우리가 원하는거 뭐? "쪽수" 그러기 위해 필요한게 뭐? "검색어" "ISBN" 혹은 그외 도움되는 데이터를 담은 데이터프레임으로 전처리 ㄱㄱ
# !pip install pandas
import pandas as pd
bookDF = pd.read_json('20s_best_books.json') # 여기까지가 전체 데이터를 담음 데이터 프레임
bookDF.head(20) #무슨 칼럼만 필요할지 일단 호출로 확인인


#### 데이터프레임이 있을때 행인덱스(숫자겠지) 열인덱스(숫자도 가능하지만 속성키워드도 있지)를 이용하여 원하는 방식으로 dataframe을 짜는 함수, 그리고 유용한 슬라이싱 개념
# 확인 결과 no ranking bookname authors publisher publication_year isbn13 까지 정도만 그 중 일부여도 상관은 없을 듯듯
bookDF1 = bookDF[['no', 'bookname', 'isbn13']] # 가장 일반적인 필요한 열만 찝어서 직접 만드는 방식
# bookDF1.head(20)
bookDF2 = bookDF.loc[[0,1],['no', 'bookname', 'isbn13']] #loc 사용법법
bookDF2 = bookDF.loc[0:20, 'no':'isbn13'] #loc 와 슬라이싱싱
# bookDF2.head(20)
bookDF3 = bookDF.iloc[0:20, [0,2,6]] # iloc와 슬라이싱
bookDF3.head(20)
# print(bookDF3.get('isbn13')[0])

#### 왜 loc는 함수면서 [] 대괄호를 사용하는거지? -> 이유 설명 ㄱㄱ -> 내부적으로 무엇이 돌고 있고, 어떤 작업을 해놨기에 이런게 가능한지 컨셉츄얼하게
# 모든 컨테이너 클래스는 []내부 요소 탐색 연산자를 지원하며 []연산자가 올때 호출되는 함수는 __getitem__ 이라고 정의된 함수이다. 이 함수에 [] 안에 들어가는 매개변수를 넣으면 적절하게 동작하도록 정의가 되어 있다. loc 클래스도 마찬가지로 내부적으로 getitem 함수가 정의되어 있기때문에 []로 동작하는 것것

#### 이제 ISBN을 알았으니 실제 호출해보기 yes24 호출해보기 -> URL에 변수가 들어가야할 곳에 적절히 넣어 response로 HTML 받아오기 url="변수 받기위한 준비" url.format(변수) 이런식으로 해보기
# yes24URL = "https://www.yes24.com/product/search?domain=ALL&query=9791169210287"
# 우리가 빛의 속도로 갈 수 없다면 isbn 9791190090018
import requests
url = "https://www.yes24.com/product/search?domain=BOOK&query={}" #빈칸 url
isbn = bookDF3.get('isbn13')[0]
res = requests.get(url=url.format(isbn))
print(res.text)

HTML에서 데이터 추출하기 : 뷰티풀수프 : HTML파서

뷰티풀수프 뭔지 간단 요약(목적위주), requests와 뷰티풀 수프를 합친 패키지가 있다 그게 뭔지 이름만

  • 뷰티풀수프는 html 요소를 파싱하여 정보를 가져오는데 특화된 함수들이 정의된 라이브러리 패키지이다. scrapy 스크라피라는 패키지가 있음.

뷰티풀수프를 사용하기 전에 뭐부터 할까? 그니까 HTML을 받아온것까지 했다면 뭘해야할까? 분석한 결과까지 써보기기

  • 실제 우리가 원하는 링크값이나 텍스트값이 어느 요소에 있는지를 파악해야한다.
  • 첫째 검색결과 페이지에서 링크값을 가져온다.
  • 둘째 링크값으로 들어간 상세페이지에서 쪽수 값을 가져온다.

실습

#### beautiful soup 을 사용하기 위한 모든 코드들 -> 라이브러리 -> HTML이라고 해석할 수 있는 상태만들기
# !pip install bs4
from bs4 import BeautifulSoup
#a 태그 class='gd_name' 의 href값이 필요한 값이다.
bs = BeautifulSoup(res.text, 'html.parser') #뷰티풀숩 객체 생성
aElement = bs.find(name='a', attrs={'class':"gd_name"}) # a 태그 중 속성으로ㅗ class gd_name 가져오기기
# print(aElement)
print(aElement.attrs.get('href')) #isbn이 아닌 진짜 상세 페이지로 접근 가능한 키 url

#### 이제 어디로 가야될지 알았으니 가서 진짜 HTML 가져오기 -> 어느 블럭으로 가야 쪽수를 가져올수 있을까?
import requests
url1 = "https://www.yes24.com/product/search?domain=BOOK&query={}" #빈칸 url
isbn = bookDF3.get('isbn13')[0] #첫번째 도서의 isbn값값
res = requests.get(url=url.format(isbn))

bs1 = BeautifulSoup(res.text, 'html.parser') #뷰티풀숩 객체 생성
aElement1 = bs.find(name='a', attrs={'class':"gd_name"}) # a 태그 중 속성으로ㅗ class gd_name 가져오기기
pkey = aElement.attrs.get('href')
url2 = "https://www.yes24.com/{}"
res2 = requests.get(url=url2.format(pkey))#진짜 상세 페이지 이동

#테이블이 여러개 있을 수 있다. 그중 쪽수는 품목 정보에 있으니 일단 품목 정보 요소를 전부 가져오기
# <div id="infoset_specific" class="gd_infoSet infoSet_noLine">
#                                                                             <tr>
#                                 <th class="txt" scope="row">쪽수, 무게, 크기</th>
#                                 <td class="txt lastCol">344쪽 | 496g | 130*198*30mm</td>
#                             </tr>
# </div>
# id는 고유한 값. 따라서 infoset_specific은 유일. 거기 안에서 tr을 전부 찾고. tr.th 를 탐색해보다 쪽수, 무게, 크기인 녀석을 찾아서 그 tr의 tr.td의 344쪽을 가져온다.


#### 요소를 찾았다면 해당 요소를 어떻게 가져오나?
bs2 = BeautifulSoup(res2.text, 'html.parser')
aElement2 = bs2.find(name='div', attrs={'id':'infoset_specific'}) #품목정보를 담은 테이블 전체
trList = aElement2.find_all(name='tr') #tr 요소로 리스트 만들기기

answer = ''
for tr in trList:
    # print(f"0 {tr}")
    if tr.find(name='th').text == '쪽수, 무게, 크기':
        # print(tr.find(name='td').text)
        # print(tr.find(name='td').text[:4])
        answer = tr.find(name='td').text.split()[0]
        break


#### 해당요소를 가져왔다면 거기서 특정 태그를 다 가져오고 싶다면?
# find_all(name = '태그명')

#### 특정 태그들을 가져왔다면 태그안에서 특정 값으로 요소를 한정하고 그 요소를 기반으로 원하는 요소의 변하는 쪽수값 가져오기
# find() == '원하는 값'

전체 도서의 쪽수 구하기

for문으로 데이터 프레임을 감싸는건 비효율적이다. 그래서 데이터 프레임은 내부적으로 반복하여 꺼내올수 있게 함수를 만들어 놨다.

#### 데이터 프레임의 반복문을 사용하기 위해선 적용할 함수객체화 해야한다. 그래서 위의 과정을 하나의 함수화로 바꾸기
# get_page_cnt(isbn) isbn을 넣어주면 쪽수값을 가져온다.
def get_page_cnt(isbn):
    url = "https://www.yes24.com/product/search?domain=BOOK&query={}"
    res = requests.get(url=url.format(isbn))
    bs = BeautifulSoup(res.text, 'html.parser')
    a_element = bs.find(name='a', attrs={'class':'gd_name'})
    p_key = a_element.attrs.get('href')
    if not p_key: # href 속성을 찾지 못했을 경우
        print(f"[{isbn}] a 태그에서 'href' 속성을 찾을 수 없습니다.")
        return ''
    url2 = "https://www.yes24.com/{}"
    res2 = requests.get(url2.format(p_key))
    bs2 = BeautifulSoup(res2.text, 'html.parser')
    a_element2 = bs2.find(name='div', attrs={'id':'infoset_specific'})
    tr_list = a_element2.find_all(name='tr')
    for tr in tr_list:
        if tr.find(name='th').text == '쪽수, 무게, 크기':
            return tr.find(name='td').text.split()[0]
    return ''

#### n개 행 가져오기
def get_page_cnt2(row):
    isbn = row['isbn13']
    return get_page_cnt(isbn)

ser_page_cnt = bookDF3.apply(get_page_cnt2, axis=1)
print(ser_page_cnt)

#### apply()란 무엇이고 언제 어떻게 사용하나?
#데이터 프레임에 사용하며 행에 적용할지 열에 적용할지 선택하며 첫번째 매개변수인 함수명을 적용하는 함수 적용하여 만들어진 값으로 시리즈를 반환함.

#### 람다를 이용하여 함수 축약하기 왜 이지랄? 이렇게 하니까 다른 놈들이. 왜 그렇게 하는데? 함수를 일급객체로 사용하는 것의 장점이겠지지
# 함수명이니까 실제 함수 구현 없이 익명함수를 이용하여 만들 수 있다는 것.

#### 데이터 프레임과 시리즈형을 합치는 함수 사용하여 찹쳐버리기, left_index, right_index 설명하기
#merge 함수이다.

웹 스크래핑 주의점

robots.txt란?

  • robots.txt에 명시된 내용은 스크래핑하면 안된다.

HTML 태그를 특정할 수 없다면? 그 대안은?

  • 셀레니움을 이용하라는 것 같다.

여기까지 과정을 간략히 설명하라.

  • 찾고자하는 페이지에 접근하기 위한 과정
  • 찾고자하는 페이지 속에서 요소를 찾는 과정
  • 그렇게 함수를 만들고 나면 원하는 데이터 프레임에 apply 함수의 매개변수로 추가

확인 문제

  1. 4
  2. 4
  3. 1
  4. 2
  5. 3
profile
난민

0개의 댓글