혼공단

[혼공분석] 2주차_Ch2. 데이터 수집

tisteryun 2025. 7. 12. 23:29

 

이번 주에는... 일요일에 개인적으로 큰 프로젝트이 있어 정신이 없었다.

오늘 겨우 책을 폈는데, 저번 주처럼 시간을 쓸 수 있을지는...^^

 

사실은 반 포기했었는데...어제 도착한 우수혼공족 소식에

기분이 좋아져서 열심히 적어본닿ㅎㅎㅎ

 

오늘 이런저런 정보를 채워넣을 수 없다면 다음 주에 더 보완하길 바라며...

 

이번 주의 주제는 데이터 수집!

사실 이 주제는 잘 모르는 분야라서, 더욱 흥미롭다.

 

그럼 빠르게 시작!


Ch2. 데이터 수집하기

02-1. API 사용하기

 

API라는 단어 자체는 나에게 익숙한 단어이다. 

다양한 프로젝트에서 앱서비스를 기획하면서, API를 연동하여 만들겠다는 말을 많이 했고, 들었다.

(물론 나는 기획자였기에... 정확한 이해를 하지 못했다)

그래서 여기 API 설명이 있다는 게 반갑기도 하다. 

 

API를 이용하면 어쨌든 데이터에 접근할 수 있다.

어떠한 데이터베이스에 매번 직접 접근하기 어렵기 때문에, 인증된 URL을 통해 접근할 수 있는 방식.

 

API는 두 프로그램이 대화, 즉 통신할 수 있도록 하는 매커니즘이라고 한다. 

 

흠...

이 설명에도 뭔가 선명하게 머릿속에 API라는 개념이 들어오지는 않는 것 같아 추가적으로 검색을 해봤는데,

주로 식당에서 '메뉴판'/'점원'의 역할을 API에 많이 빗대어 설명하고 있었다. 

 

손님 ------->메뉴판을 보고 음식 선택, 점원이 전달 -------> 요리사

 

여기서 손님과 요리사가 각각의 프로그램이고,

서로 직접 대화하지 않고 중간에 매개체를 두고 소통한다는 의미로 이런 예시를 들고 있는 것 같다. 

 

실제 예시로는 쇼핑몰에서 카드결제를 하는 것을 많이 들었다.

 

쇼핑몰이 있고, 결제시스템이 있다. 

둘다 각각 자신의 프로그램을 만드는 것이지만, 쇼핑몰에서는 카드결제로 바로 연결되어야 편리하다.

이럴 때 결제 시스템의 API를 이용해서 두 프로그램이 언제든지 통신, 연동될 수 있도록 해 두면

쇼핑몰을 이용하는 사람들이 언제든지 결제시스템을 불러와서 결제까지 할 수 있는 것이다.

 

이걸 좀 더 책에 있는 예시와 관련해서 살펴보면,

내가 만든 어플에 실시간 날씨와 관련된 정보를 작게 띄워 두고 싶은 경우,

기상청이 제공하는 API를 사용하여 날씨 정보를 받아 띄워둘 수 있다는 것이다.

 

이런 API는 다양한 형식을 통해 구현할 수 있는데, 어플 간 통신은 웹 기반으로 많이 사용한다고 한다. 

웹 기반 API는 웹 페이지의 기본 통신 프로토콜인 HTTP를 사용하여 만들어진다. 

 

따라서 HTTP를 이용한 API로 두 프로그램을 연결하면, CSV, JSON, XML 형식으로 웹 데이터를 주고 받을 수 있는 것.

웹페이지의 표준언어인 HTML은 너무 복잡해서 이걸로 데이터를 주고 받지 않는다...

 

어쨋든 API는 어떤 프로그램/데이터베이스의 데이터를 편리하게 소통하고 이용하게 하는 인터페이스이고 

이 API를 어떤 형식으로 구현했느냐에 따라 구체적인 소통 방식이 결정되는 것 같다. 

 

 

 

 

그럼 이제, 웹 API를 통해 받은 데이터를 파이썬을 통해 다루어야 할텐데

CSV는 저번주에 많이 다루었으니 JSON, XML을 다루는 법을 알아보자.

 

-JSON (JavaScript Object Notation)

파이썬의 딕셔너리와 리스트를 중첩해 놓은 것과 비슷

도서명을 json객체로 나타내면
{"name":"혼자 공부하는 데이터 분석"}
이런 식이 된다.

JSON 객체는 꼭 키와 값이 문자열을 쓸 경우 큰 따옴표(")로 감싸주어야 한다. 

 

그러나 API로 데이터를 전달할 때는 파이썬의 딕셔너리가 아니라 Json 텍스트로 전달해야 한다.

따라서 파이썬에서 딕셔너리를 만든 후, 변환이 필요함.

 

json.dumps() : 파이썬 객체 -> json 형식의 텍스트로 변환하는 함수

import json

d={"name":"혼자 공부하는 데이터 분석"}
d_str=json.dumps(d, ensure_ascii=False)
print(d_str)
ㄴ{"name":"혼자 공부하는 데이터 분석"}
print(type(d_str))
ㄴ<class 'str'>

 

*ensure_ascii

: json.dumps()함수가 ascii문자 이외의 다른 문자를 16진수로 출력함. 한글이 있기에 False로 설정해야 제대로 나옴. 기본세팅이 True.

 

그러나 json 형식의 텍스트로는 파이썬에서 자유로운 이용이 불가하다.

 

json.load() : json 형식의 텍스트 -> 파이썬 객체

d2 = json.loads(d_str)
#d_str 형식은 json형식이었는데, 함수를 사용함으로써
print(d2['name'])
ㄴ 혼자 공부하는 데이터 분석
#딕셔너리를 불러오는 파이썬 함수로 불러올 수 있게 됨 (딕셔너리가 됨)

 

-json 객체의 더 복잡한 구조

{"name": "혼자 공부하는 데이터 분석", "author": "박해선", "year": 2022}
#여러개의 key와 값이 있는 형태

{"name": "혼자 공부하는 데이터 분석", "author": ["박해선","홍길동"],"year": 2022}
#값에 list가 있는 형태

[
  {"name": "혼자 공부하는 데이터 분석", "author": "박해선", "year": 2022},
  {"name": "혼자 공부하는 머신러닝+딥러닝", "author": "박해선", "year": 2020}
]
#여러 json 객체를 리스트로 묶어 놓어 Json 배열을 만든 형태

 

-json 배열을 파이썬에서 다루는 법

d4_str = """
[
  {"name": "혼자 공부하는 데이터 분석", "author": "박해선", "year": 2022},
  {"name": "혼자 공부하는 머신러닝+딥러닝", "author": "박해선", "year": 2020}
]
"""
#문자열이 길기 때문에 """세겹 따옴표로 문자열을 묶음

d4 = json.loads(d4_str)

print(d4[0]['name'])
#2차원 리스트처럼 프린트하면 됨

*딕셔너리 구조를 바로 json.loads에 넣으려면 ('') 따옴표 안에 넣으면 된다

 

read.json() : json 문자열 -> 데이터프레임

from io import StringIO
import pandas as pd

pd.read_json(StringIO(d4_str))

pd.DataFrame(d4)
앞서 파이썬 객체의 리스트로 변환한 d4를 바로 df로 변환 가능

*StringIO : read.json() 함수에 파일 경로나 파일 외의 다른 것을 넣었을 때 오류를 방지하기 위함

** 판다스는 리스트, 딕셔너리를 받아서 df를 만들기 때문에 딕셔너리로 구성된 json->파이썬 객체를 바로 변환 가능.

 

 

-XML(eXensible Markup Language)

컴퓨터와 사람 모두가 읽고 쓰기 편한 문서 포맷을 위해 고안

도서명을 xml문자열로 표현하면 
<book>
    <name>혼자 공부하는 데이터 분석</name>
</book>

html과 비슷한 듯 하다.

 

fromstring() : xml 문자열 -> 파이썬 객체

x_str = """
<book>  #루트(하위항목이 있어서)
    <name>혼자 공부하는 데이터 분석</name> #태그 이름을 다 설정.
    <author>박해선</author> 
    <year>2022</year>
</book>
"""
태그 열고 - 닫는 한 줄을 엘리먼트라고 함.

import xml.etree.ElementTree as et
bk = et.fromstring(x_str)
#이 함수를 통해 변환하면 단순 파이썬 객체가 아니라 ElemenTree 모듈 아래 정의된 Element 클래스의 객체가 됨.

print(bk.tag)
ㄴbook 
#가장 상위의 태그 이름 출력

 

findtext() : 하위 엘리먼트 확인하여 자동으로 텍스트 반환

book_childs = list(bk)

print(book_childs)
ㄴ[<Element 'name' at 0x7f3f73333470>, /
<Element 'author' at 0x7f3f733334c0>, /
<Element 'year' at 0x7f3f73333510>]

*하위의 하위 엘리먼트는 확인 불가

** Element 클래스의 객체는 저런 식으로 보여짐

name, author, year = book_childs
#요소가 3개인 리스트에 변수를 각각 할당
print(name.text)
print(author.text)
print(year.text)

또는

name = book.findtext('name')
author = book.findtext('author')
year = book.findtext('year')
print(name)
print(author)
print(year)

혼자 공부하는 데이터 분석
박해선
2022

 

 

-복잡한 xml

x2_str = """
<books>
    <book>
        <name>혼자 공부하는 데이터 분석</name>
        <author>박해선</author>
        <year>2022</year>
    </book>
    <book>
        <name>혼자 공부하는 머신러닝+딥러닝</name>
        <author>박해선</author>
        <year>2020</year>
    </book>
</books>
"""
#얘도 문자열이 복잡해서 따옴표 3개씩
#3단계의 상,하위 항목 존재

 

findall() : 여러개의 자식 엘리먼트 확인

*엘리먼트가 동일한 이름을 가지고 있으면 for문을 함께 사용

for book in books.findall('book'):
    name = book.findtext('name')
    author = book.findtext('author')
    year = book.findtext('year')

    print(name)
    print(author)
    print(year)
    
혼자 공부하는 데이터 분석
박해선
2022
혼자 공부하는 머신러닝+딥러닝
박해선
2020

 

read_xml(): xml -> 데이터프레임

import pandas as pd
pd.read_xml(x2_str)

 

 

 

- 파이썬으로 API 호출하기

request 패키지를 주로 이용한다. 

import request
url='open api link(http get 방식)'

r= request.get(url)
#r은 response 클래스 객체로, 프린트하면 <Response [200]> 이렇게 대답한다.

data = r.json()
#그래서 이걸 파이썬 객체로 바꾸어줘야함.
json() : json문자열을 파이썬 객체로 변환

print(data)
ㄴ 딕셔너리 형태로 출력

*r은 response 객체이기 때문에 json 객체를 파이썬 형태로 바꾸어주는 json.load()함수를 쓸 수 없음.

 

대부분 호출된 json 형식의 데이터는 

{'response': {'request': {'startDt': '2021-04-01',
   'endDt': '2021-04-30',
   'age': '20',
   'pageNo': 1,
   'pageSize': 200},
  'resultNum': 200,
  'numFound': 5000,
  'docs': [{'doc': {'no': 1,
     'ranking': '1',
     'bookname': '우리가 빛의 속도로 갈 수 없다면 :김초엽 소설 ',
     'authors': '지은이: 김초엽',
     'publisher': '허블',
     'publication_year': '2019',

이런 식으로 맨 앞에 붙는 key 값이 복잡하게 많다. (어떤 조건의 자료를 뽑았는지에 대한 내용이지만...)

 

우리가 필요한 자료는 'docs' 딕셔너리에 있는 키 값에 있는 리스트 안에 있는 딕셔너리의 'doc'이라는 키의 값이기 때문에

빈 리스트를 만들어서 우리가 필요한 값만 뽑아줘야 유의미한 df을 만들 수 있다. 

빈 리스트에 값을 넣는 두가지 방법

1.빈 리스트를 만들고 for문과 append함수를 사용하기
books = []
for d in data['response']['docs']:
    books.append(d['doc'])
    
2. 리스트 하나 안에서 해결
books = [d['doc'] for d in data['response']['docs']]


#딕셔너리의 값 찾기
딕셔너리이름['key']
#n차원의 딕셔너리 값 찾기
딕셔너리이름['key']['key']

*df를 json으로 저장하려면 

df이름.to_json('파일이름')

 

02-2 웹 스크래핑 사용하기

 

-HTML 불러오기

import requests

isbn = 9791190090018      # '우리가 빛의 속도로 갈 수 없다면'의 ISBN
url = 'http://www.yes24.com/Product/Search?domain=BOOK&query={}'

r = requests.get(url.format(isbn))
#format()는 url의 {}을 isbn으로 채움

print(r.text)
-> html 출력

*여기서 .text는 response객체를 html형태로 읽어줌

(전에는 xml에서 엘리먼트의 텍스트를 읽음)

 

-뷰티풀수프

이름 참 특이하다...

html 구조가 복잡하고, 코드가 너무 많기 때문에 우리가 원하는 부분만을 찾기가 어려움. 그럴때 얘를 사용한다. 

from bs4 import BeautifulSoup
soup = BeautifulSoup(r.text, 'html.parser')

r.text는 검색페이지를 request해서 읽은 것(파싱할 html 문서)
'html.parser'는 파싱에서 사용할 파서

*파서는 입력 데이터를 받아 데이터 구조를 만드는 소프트웨어 라이브러리. json,xml패키지도 다 파서이다.

**파싱은 이런 과정이다.

 

크롬에서 내가 원하는 위치의 태그를 찾아 class의 속성을 알게 되면, 

soup에게 태그의 위치를 찾으라고 지시할 수 있다.

 

find() 

prd_link = soup.find('a'(태그이름), attrs={'class':'gd_name'}(태그 속성을 딕셔너리형태로))
#attrs는 선택사항

print(prd_link)
ㄴ 지정한 태그의 html코드 출력

html코드는 딕셔너리가 아니지만, 같은 맥락으로 활용 가능
print(prd_link['href'])
ㄴ html 코드에서 href='' 부분 값 출력. (출력값 = 문자열)

*만약 찾은 태그의 속성이 id='' 라면 딕셔너리를 {'id':''} 라고 지정하면 된다. 

** 같은 class 속성 이름이 여러개이거나, 같은 id 속성이 여러개라면 원하는 코드가 출력되지 않을 수 있으므로 범위가 넓더라도 정확히 그 코드를 출력할 만한 속성을 사용하는 것이 좋다. (지정 속성의 첫 태그 출력)

 

find_all() : 특정 html 태그를 모두 찾아서 리스트로 반환

 

get_text() : 태그 안의 텍스트 가져오기

for tr in prd_tr_list:
    if tr.find('th').get_text() == '쪽수, 무게, 크기':
        page_td = tr.find('td').get_text()
        break
        
#같다고 설정할 텍스트 오류나지 않게 주의
#html 구조를 잘 보고 태그를 잘 구별해서 사용 'tr','th','td'의 구별처럼...

print(page_td)
ㄴ 330쪽 | 496g | 130*198*30mm
#split은 문자열을 공백을 기준으로 나눔. 인덱스만 잘 적으면 됨!
print(page_td.split()[0])
ㄴ 330쪽

 

- 웹 스크래핑 시 주의점

아무 웹이나 마음대로 스크래핑하다보면 웹사이트에 지장이 갈 수 있기 때문에

1. 웹사이트에서 스크래핑을 허락했는지 확인

-> 웹사이트의 robots.txt 파일을 확인. (~.com/robots.txt 으로 링크 들어가기)

 

2. html 태그를 특정할 수 있는지 확인하기

-> 특정이 되지않는다면 데이터를 가져오기 어렵기 때문에... 웹 스크래핑은 최후의 수단으로 사용하기

-> 또한 일부 웹페이지는 자바스크립트로 데이터를 표현한다. 이 경우 셀레니움(selenium)을 사용해야 함

 

 

*df에서 원하는 행,렬만 출력해서 보기

#열 이름 리스트로 나열
books = books_df[['no','ranking','bookname','authors','publisher',
                 'publication_year','isbn13']]
                 
#loc메서드를 사용하여 출력을 원하는 행,렬 리스트로 넣기
books_df.loc[[0,1], ['bookname','authors']

#슬라이싱으로 넣는 것도 가능
**다만 파이썬과 다르게 마지막 열도 추가!!
books_df.loc[0:1, 'bookname':'authors']

#iloc메서드를 사용하여 출력을 원하는 행,렬 리스트로 넣기
** 행,렬 모두 인덱스를 사용함
books_df.loc[[0,1], [1,2,3,4]]

** ::2 슬라이싱을 이렇게 표현하면, 하나씩 건너뛰고 선택

 

**merge() 함수에 관해 정리하기... -> 다음주에...

 

 

[기본숙제]

본문에는 [True, True]는 없었던 것 같은데 이 방법도 같은 역할인가 보네요

 

[추가숙제]

나미야잡화점의 기적이 개정판이 나오면서 이전 버전의 상세페이지가 삭제되어서 페이지수 조회가 안되네요ㅜ

 

 

 

오늘 하루에 방대한 양을 모두 정리하는 것은 불가능일 것 같아 몇가지 일들을 미룹니다...

다음주에 꼭 137페이지 손코딩을 한번 더 하면서 사용한 함수들을 다시 정리하고, merge() 함수에 대해 정리할게요ㅎ..

 

그래도 오늘 이만큼을 이해한 것이 다행입니다.

이전에 급하게 html을 사용해서 간단한 페이지를 제작해야했던 적이 있는데, 

그 때 html을 조금 만져봐서 그나마 이렇게 빠른 시간에 웹 스크래핑을 이해할 수 있었던 것 같아요

오늘을 위한 배움이엇나 봅니다ㅎㅎ

 

1주차에 비해 내용이 많아지면서 블로그도 길어지고, 두서도 없어지는 느낌인데

좀 더 보기 편하려면 어떻게 정리하는 것이 좋을지 고민해봐도 좋을 것 같네요

 

그럼, 다음주에 봬요!