본문 바로가기
멋쟁이사자처럼 AI스쿨

멋쟁이사자처럼 AI스쿨 5주차 회고

by 헬푸밍 2023. 1. 19.

이번주는 정말 많은 것을 배운 것 같습니다...

 

시간도 참 빠르게 느껴지네요...

 

그럼 이번주 배운 것들을 정리해봅시다!


가장 먼저... 

행정안전부 대통령 기록관에서 대통령 연설기록을 수집했다!

 

검사 창에서 확인할 것들을 먼저 아래와 같이 확인해준다!

확인 했으면...

 

필요한 라이브러리와 url을 적고 요청을 해보자!

# requests, bs4, pandas 불러오기
import requests
from bs4 import BeautifulSoup as bs
import pandas as pd

page_no = 70
url = "https://www.pa.go.kr/research/contents/speech/index.jsp"
params = f"spMode=&artid=&catid=&pageIndex={page_no}&searchHistoryCount=0&damPst=&mediaType=&speechEvent=&searchKeyword=&boxname=&searchStartDate=&searchEndDate=&board-items4=%EC%97%B0%EC%84%A4%EC%9D%BC%EC%9E%90%28%EA%B3%BC%EA%B1%B0%EC%88%9C%29&cnt=20%EA%B1%B4&orderColumn=1&pageUnit=20"
# params에서 필요 없는 부분을 줄일 수 있다

requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)
# 경고 메시지 않뜨게 하기
response = requests.post(url, params=params, verify = False) # verify = False를 해야한다.
response

성공!

 

그럼 페이지를 70페이지로 지정했으니 70페이지의 연설문 데이터를 확인해보자!

pd.read_html(response.text)[0]

이렇게 70페이지에는 박정희 대통령의 연설문으로 가득하다.

 

우리는 모든 페이지를 수집할 것이기 때문에...

BeautifulSoup로 html을 파싱해보자!

검사 => 요소 에서 마지막페이지 정보를 찾아 셀렉터 복사를 해준다!

 

그리고 찾는 코드를 작성...

# frm > nav.board-paging.space5 > ul > li:nth-child(14) > a
# select 로 찾기
# last_page
html = bs(response.text)
last_page = int(html.select('nav > ul > li > a')[-1]['href'].split('=')[-1])
last_page

이렇게 작성해보면 마지막페이지가...

페이지 인것을 확인할 수 있다!

 

우리의 목표는 연설문 내용까지 수집하는 것이기 때문에...

일단 내용링크도 셀렉터 복사로 찾아서 찾아보자!

a_href = [link['href'] for link in html.select('tr > td.subject > a')]
a_href

리스트 컴프리헨션으로 링크를 간단히 찾을 수 있다!

앞의 주소가 빠져있는 것을 확인해두고 앞에 URL을 더해주자!

그럼 대통령 연설문페이지 데이터와 내용링크까지 얻을 수 있는 코드를 함수로 작성해보면...

import requests
from bs4 import BeautifulSoup as bs
import pandas as pd

def page_scrapping(page_no):
    """
    1) page 번호로 URL 만들기
    2) requests.post()로 요청하기
    3) bs 적용
    4) 테이블 찾기
    5) a 태그 목록 찾기
    6) 내용링크에 a 태그 주소 추가하기
    7) 데이터프레임 반환
    """
    url = "https://www.pa.go.kr/research/contents/speech/index.jsp"
    params = f'spMode=&artid=&catid=&pageIndex={page_no}&searchHistoryCount=0'
    parmas = f'{params}&pageUnit=20'
    response = requests.post(url, params=params, verify=False)
    html = bs(response.text)
    df = pd.read_html(response.text)[0]

    a_href = [url + link['href'] for link in html.select('tr > td.subject > a')]

    df["내용링크"] = a_href
    
    return df

이렇게 된다!

한번 아까 해봤던 70페이지를 확인해보면...

페이지 데이터와 내용링크까지 얻을 수 있다...

 

이제 전체 페이지를 수집해보면...

from tqdm import trange
import time


# 경고메시지가 있으면 tqdm 로그가 너무 많이 찍히기 때문에 경고메시지를 제거합니다.
requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)

page_list = []
for page_no in trange(1, last_page+1):
    page_list.append(page_scrapping(page_no))

page_list

이렇게 449페이지의 데이터가 수집이 되고...

 

concat으로 데이터프레임을 합친 뒤...

대통령연설기록이라는 이름으로 저장하고 다시 불러와보면...

df = pd.concat(page_list)
file_name = "대통령연설기록.csv"
df.to_csv(file_name, index=False)
df = pd.read_csv(file_name)
df

총 8980개의 연설문 데이터를 수집한 것을 알 수 있다!

 

이제 이 수집된 데이터의 내용까지 수집해보자!

 

데이터 내용링크 컬럼의 첫번째 데이터를 가져와보면...

sub_url = df.iloc[0]["내용링크"]
sub_url

이렇게 첫번째 글의 내용링크가 나옵니다.

 

이 URL을 가지고 검사 페이지에서 데이터 파싱을 통해 내용을 찾아보면...

response = requests.post(sub_url, verify=False)
html = bs(response.text)
# find로 찾기
content = html.find('td', {'class':'content'}).text
content

첫 글의 내용이 나온다!

 

이제 내용링크에 따라 내용을 가져오는 함수를 작성해보면...

def get_content(sub_url):
    """내용수집 함수
    1) 수집할 URL 만들기
    2) requests로 HTTP 요청하기
    3) response.text에 BeautifulSoup 적용하기
    4) 내용 가져오기
    5) time.sleep()
    6) 내용 반환하기
    """
    response = requests.post(sub_url, verify=False)
    html = bs(response.text)
    content = content = html.find('td', {'class':'content'}).text
    time.sleep(0.001)
    return content

sub_url = df.iloc[0]["내용링크"]

이렇게 작성할 수 있다!

 

그럼... 모든 내용을 뽑기!

3시간 넘게 걸리는거 실화...?

 

그래서 데이터를 100개만 뽑아서 해보기로...하자...

from tqdm.notebook import tqdm
tqdm.pandas()
df = df.head(100)
view = df['내용링크'].progress_map(get_content)
view

이렇게 상위 100개의 내옹만 뽑았다!

 

이제 내용컬럼 추가 해주고!
내용링크 컬럼 삭제하고...

컬럼순서 정렬해서...

데이터 저장뒤 불러오면...

df['내용'] = view
df = df.drop('내용링크', axis = 1)
cols = ['번호', '대통령', '형태', '유형', '제목', '연설일자', '내용']
df.columns = cols
df.to_csv('내용포함대통령연설기록.csv', index=False)
pd.read_csv('내용포함대통령연설기록.csv')

총 100개의 연설문 데이터가 저장되는데...

초기 100개는 모두 이승만 대통령의 연설이다!


다음으로는...

서울특별시 다산콜센터의 주요 민원...을 수집하는 것!

주요질문의 특정 페이지 목록을 수집하는데...

 

먼저 내용 분류를 포함하지 않는 데이터 스크래핑 순서는 이렇다!

    1) page_no 마다 url이 변경되게 f-string 을 사용해 만든다.
    2) requests 를 사용해서 요청을 보내고 응답을 받는다.
    3) pd.read_html 을 사용해서 table tag로 게시물을 읽어온다.
    4) 3번 결과에서 0번 인덱스를 가져와 데이터프레임으로 목록의 내용을 만든다.
    5) html tag를 parsing할 수 있게 bs 형태로 만든다.
    6) 목록 안에 있는 a tag를 찾는다.
    7) a tag 안에서 string 을 분리해서 내용번호만 리스트 형태로 만든다.
    8) 4)의 결과에 "내용번호"라는 컬럼을 만들고 a tag의 리스트를 추가한다.
    9) 없는 페이지번호가 들어왔을 때의 오류에 대처한다.
    10) 반환값을 지정한다.

 

일단 필요한 라이브러리를 불러오고...

1페이지의 테이블 태그의 첫 인덱스를 불러와보면(글 50개씩 불러온다)!

import time
import pandas as pd
import numpy as np
import requests
from bs4 import BeautifulSoup as bs

page_no = 1
base_url = f"https://opengov.seoul.go.kr/civilappeal/list?items_per_page=50&page={page_no}"

table = pd.read_html(base_url, encoding='utf-8')[0]
table

...

총 50개의 민원을 볼 수 있다!

 

이제 내용과 분류를 수집하기 위해...

내용링크에서 내용번호를  수집해보자!

요청 후에...

셀렉터 복사로 링크있는 부분을 찾고...

내용번호를 찾으면...

response = requests.get(base_url)
html = bs(response.text)
link_list = html.select('#content > div > div > div > table > tbody > tr > td > a')
a_link_no = []
for link in link_list:
    a_link_no.append(link['href'].split('/')[-1])
a_link_no

이렇게 쭈욱 내용번호가 나오게 된다.

 

이 내용번호를 아까 테이블에 넣어주게 되면...

내용번호까지 포함한 민원들이 된다!

 

이제 base_url에 내용번호를 추가하면 내용을 볼 수 있는 url이 된다!

content_url = 'https://opengov.seoul.go.kr/civilappeal/'
no = 5
print(table['제목'][no])
print(content_url + table['내용번호'][no])

이렇게 다섯번째 제목과 내용의 url이 일치하는지 확인해보면...

일치한다!

 

그럼 이 과정을 종합해 한 페이지를 얻는 함수를 만들어보면!

def get_one_page(page_no):
    """
    120 주요질문의 특정 페이지 목록을 수집
    1) page_no 마다 url이 변경되게 f-string 을 사용해 만든다.
    2) requests 를 사용해서 요청을 보내고 응답을 받는다.
    3) pd.read_html 을 사용해서 table tag로 게시물을 읽어온다.
    4) 3번 결과에서 0번 인덱스를 가져와 데이터프레임으로 목록의 내용을 만든다.
    5) html tag를 parsing할 수 있게 bs 형태로 만든다.
    6) 목록 안에 있는 a tag를 찾는다.
    7) a tag 안에서 string 을 분리해서 내용번호만 리스트 형태로 만든다.
    8) 4)의 결과에 "내용번호"라는 컬럼을 만들고 a tag의 리스트를 추가한다.
    9) 없는 페이지번호가 들어왔을 때의 오류에 대처한다.
    10) 반환값을 지정한다.
    """
    try:
        base_url = f'https://opengov.seoul.go.kr/civilappeal/list?items_per_page=50&page={page_no}'
        response = requests.get(base_url)
        table = pd.read_html(response.text)[0]
        html = bs(response.text)
        link_list = html.select('#content > div > div > div > table > tbody > tr > td > a')
        a_link_no = [link['href'].split('/')[-1] for link in link_list]
        table['내용번호'] = a_link_no
        return table
    except Exception as e:
        print(f'없는 페이지 입니다. {e}')

이렇게 된다.

함수에 없는 페이지를 넣으면 오류가 발생하게 처리해줬다!

 

1페이지를 넣으면...

get_one_page(1)

1페이지가 나오고...

 

10000페이지를 넣으면

# 없는 페이지도 확인
get_one_page(10000)

오류가 뜬다!

 

이제 모든 페이지를 수집하면(45페이지까지 있음)

page_no = 1
table_list = []

while True:
    df_one = get_one_page(page_no)
    if df_one is None or df_one.shape[0] == 0:
        break
    table_list.append(df_one)
    print(page_no, end='')
    page_no += 1
    time.sleep(0.001)

이렇게 45페이지까지 수집된다!

 

이제 페이지들을 합치고 데이터를 저장하고 불러오면...

df = pd.concat(table_list, ignore_index=True)
file_name = "data/seoul-120-list.csv"
df.to_csv(file_name, index=False)
df = pd.read_csv(file_name)
df

총 2243개의 민원들이 데이터에 있는 것을 확인할 수 있다!

 

이제 내용과 분류를 수집해야 하는데..

 

먼저 분류를 수집해보자!

링크를 타고들어가면...

이곳에서 분류를 찾을 수 있다!

 

먼저 문서 정보 첫페이지에서 수집해 보면...

 

no = df['내용번호'][0]
url = f'https://opengov.seoul.go.kr/civilappeal/{no}'
response = requests.get(url)
df_desc = pd.read_html(response.text)[-1]
df_desc

아래처럼 약간 변형된 데이터 프레임이 반환된다!

 

이것을 전치행렬을 통해 잘 정돈하면...

tb01 = df_desc[[0,1]].set_index(0).T
tb02 = df_desc[[2,3]].set_index(2).T
tb02.index = tb01.index
pd.concat([tb01, tb02], axis=1)

원하는 형식의 데이터프레임을 얻을 수 있다!

 

이제 분류를 수집하는 것을 함수로 만들어보고 확인하면...

def get_desc(response):
    """ 분류 수집하기 
    1) 테이블의 0, 1 번째 데이터 가져와서 0번째 컬럼을 인덱스로 만들고 전치행렬 만들기
    2) 테이블의 2, 3 번째 데이터 가져와서 2번째 컬럼을 인덱스로 만들고 전치행렬 만들기
    3) 1), 2) 번에서 만든 값을 concat으로 병합하기
    4) 병합한 값 반환하기
    """
    df_desc = pd.read_html(response.text)[-1]
    tb01 = df_desc[[0,1]].set_index(0).T
    tb02 = df_desc[[2,3]].set_index(2).T
    tb02.index = tb01.index
    return pd.concat([tb01, tb02], axis=1)
    
    get_desc(response)
    # 아까 앞에서 지정한 첫 민원의 분류의 response

함수가 잘 만들어진 것을 알 수 있다!

 

이제 내용을 수집해야하는데...

 

링크타고 들어가서

이부분이다!

 

셀렉트를 통해 찾아보면...

html.select('div > div.view-content.view-content-article > div > div')[0].text.strip()

이렇게 찾을 수 있다!

 

이제 내용번호를 통해 내용을 수집하는 함수를 만들면(분류수집함수 포함)

def get_view_page(view_no):
    """ 
    내용과 분류를 수집하는 함수 만들기
    1) url을 만들어 줍니다.
    2) requests 로 요청을 보냅니다.
    3) 분류를 수집하는 함수에 response 값을 넘겨 분류와 제공부서 등이 들어있는 데이터프레임을 반환 받습니다.
    4) 내용을 bs 으로 텍스트만 추출합니다.
    5) 3)번 내용에 내용, 내용번호를 함께 데이터프레임에 추가합니다.
    6) 반복문 대신 map이나 apply를 사용할 것이기 때문에 time.sleep으로 쉬도록 합니다.
    7) 5번까지 수집한 내용을 반환하도록 합니다.
    """
    try:
        url = f'https://opengov.seoul.go.kr/civilappeal/{view_no}'
        response = requests.get(url)
        desc = get_desc(response)
        html = bs(response.text)
        html = html.select('div > div.view-content.view-content-article > div > div')[0].text.strip()
        desc['내용'] = html
        desc['내용번호'] = view_no
        return desc
    except Exception as e:
        print(f'없는 내용번호 입니다. {e}')

이렇게 되고 없는 내용번호를 넣어주게 되면 오류가 나오게 처리했다!

 

없는 내용번호를 넣으면

get_view_page(view_no=22904492)

이렇게 출력되고...

 

첫 민원의 내용번호를 넣으면...

get_view_page(view_no=27328170)

첫 페이지의 내용이 나오게 된다!

 

이제 모두 수집해야 하지만...

시간관계상 100개의 민원의 내용까지만 수집해보자!

 

아래와 같이 민원 상위 100개만 df에 넣어준 뒤

df = df.head(100)

수집하면...

# tqdm.notebook 의 tqdm 을 통해 수집 진행상태를 확인합니다.
# progress_apply 를 사용하면 진행상태를 확인하며 데이터를 가져올 수 있습니다.
from tqdm.notebook import tqdm
tqdm.pandas()

df = df.head(100)
view_detail = df['내용번호'].progress_map(get_view_page)
view_detail

 

이렇게 100개가 수집된다!

 

수집한 내용데이터를 하나의 데이터프레임으로 병합한뒤...

df와 merge()함수를 통해 병합하면...

df_view = pd.concat(view_detail.tolist(), ignore_index=True)
df_detail = df.merge(df_view, on=['내용번호', '생산일'], how='left')
df_detail

이렇게 병합이 되고...

 

원하는 컬럼만 남기고 파일로 저장한 뒤 불러오자!

cols = ['번호', '분류', '제목', '내용', '내용번호']
df_detail = df_detail[cols]
file_name = "seoul-120-sample.csv"
df_detail.to_csv(f'data/{file_name}', index=False)
pd.read_csv(f'data/{file_name}')

 

아래와 같이 잘 불러와졌다!


마지막으로 네이버 증권의 ETF 수집하기를 했다!

 

일단 라이브러리를 불러오고...

수집할 url을 가져오면...

import pandas as pd
import numpy as np
import requests

url = 'https://finance.naver.com/api/sise/etfItemList.nhn?etfType=0&targetColumn=market_sum&sortOrder=desc'
print(url)

이 URL은 검사페이지에서 네트워크 => JS => etfItemList의 헤더에서 얻었다!

 

이 페이지를 확인해 보면...

이렇게 ETF의 데이터가 json형식으로 나와있다!

 

아래와 같이 pd.read_json을 통해 불러오면...

result = pd.read_json(url, encoding='cp949')
result

이렇게 result행에 원하는 데이터가 있다!

 

데이터프레임으로 만들면...

pd.DataFrame(result)

총 668개의 ETF리스트들이 데이터프레임에 있다!

 

이번엔 같은 데이터를 requests를 통해 불러와보자!

요청 후 데이터에 .json()을 적용하면...

response = requests.get(url)
etf_json = response.json()
etf_json

이렇게 json형태로 데이터가 나온다!

 

우리가 원하는 데이터는 딕셔너리에 있는데...

'result'의 밸류값 안에 'etfItemList'의 밸류값이다!

그래서 아래와 같이 데이터를 가져오면...

etfItemList = etf_json['result']['etfItemList']
etfItemList

원하는 데이터만 있게되고...

 

데이터 프레임으로 변환하면...

df = pd.DataFrame(etfItemList)
df

같은 데이터!

 

이제 데이터를 저장하고 다시 불러오면!(오늘 날짜를 파일제목에 넣어준다!)

from datetime import datetime

today_date = datetime.today().strftime('%Y-%m-%d')
today_date
file_name = f'etf-{today_date}.csv'
df.to_csv(f'data/{file_name}', index=False)
pd.read_csv(f'data/{file_name}', dtype={'itemcode' : object})

데이터가 잘 불러와졌다!

데이터를 불러올 때 아이템코드를 오브젝트 타입으로 불러와야지 앞에 0이 안사라진다!


이번주에 많은 것을 배웠다고 생각했는데

 

코드로 보니 생각보다 많지 않은것 같네여...

 

점점 어려워지기 시작해서 따라가기가 벅차지만...

 

꺾이지 않는 마음으로 열심히 해야겠습니다!

댓글