일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- seaborn
- GIT
- Beautifulsoup
- 데이터수집
- SettingWithCopyWarning
- 영어한글폰트차이
- 숫자형
- 깃허브블로그
- 네이버금융
- Python
- 구글폰트
- Github
- 비밀지도
- hackerrank
- 깊은복사
- 해커랭크
- Requests
- PANDAS
- 카카오채용코테'
- Repositories
- dataframe
- 수치형변수
- github blog
- 한국주식
- 서울정보소통광장
- googlefont
- numpy
- FinanceDataReader
- 프로그래머스
- 120주요질문
- Today
- Total
데린이 재영
서울특별시 다산콜센터(☎120)의 주요 민원 수집하기 - Pandas/Requests/BeautifulSoup/tqdm 본문
서울특별시 다산콜센터(☎120)의 주요 민원 수집하기 - Pandas/Requests/BeautifulSoup/tqdm
재용용 2022. 10. 11. 13:21목표 설정
- 멋쟁이사자 AI 스쿨 11, 12일차(221004, 221005) 학습 내용 정리하기
- 서울정보소통광장 ▶ 시민소통 ▶ 120 주요 민원 수집하기
데이터 수집 과정
1. 라이브러리 로드
# 파이썬에서 사용할 수 있는 엑셀과 유사한 데이터분석 도구
import pandas as pd
import numpy as np
# 매우 작은 브라우저로 웹사이트의 내용과 정보를 불러옴
import requests
# request로 가져온 웹사이트의 html 태그를 찾기위해 사용
from bs4 import BeautifulSoup as bs
# 간격을 두고 가져오기 위해 사용
import time
# 진행 상황 확인하기
from tqdm.notebook import tqdm
2. url 가져오기
# 페이지별 url
page_no = 1
url = f"https://opengov.seoul.go.kr/civilappeal/list?page={page_no}"
3. url의 table 정보 가져오기
# url에 대한 데이터 수집
pd.read_html(url)
위와 같이, 한글이 깨지는 현상 발견 ▶ url 의 인코딩 방식 확인하기 ( Network → Headers → Response Headers → Context-Type)
# 한글 깨짐 현상을 방지하고, 수집된 데이터를 df로 지정
df = pd.read_html(url, encoding="utf-8")[0]
위 코드에서 [0]를 사용한 이유 ?
pd.read_html로 데이터를 불러오면 데이터프레임으로 불러올 수 있는데, 수집된 데이터들은 데이터 프레임으로, 리스트로 묶여서 반환된다.
즉 여러 데이터프레임들이 리스트 안에 들어가는데, 이때 필요한 부분을 가져오기 위해 인덱스 번호를 이용했다.
[0] : 필요한 데이터가 0번째 인덱스에 있다는 뜻
# 수집한 민원 정보를 table 이라는 변수로 가져오기
df = pd.read_html(url, encoding="utf-8")[0]
df
4. 민원 내용 번호 수집하기
아래 링크는 '하반기 농부의 시장 일정은 어떻게 되나요?' 를 클릭하면 볼 수 있는 url 이다.
각 질문에 대한 내용번호를 가져오는 이유는, 각 민원에 대한 문서 정보 제공부서와 분류 데이터를 추가하기 위해서이다.
(i) 내용번호 태그 찾기
# html을 안전하게 가져오기
response = requests.get(url, headers={'User_agent':'Mozilla/5.0'})
response
# 가져온 html을 text로 변환
response.text # Fig 6의 결과물
# beautifulsoup으로 복잡한 태그들을 보기 좋게 구조화하기
html = bs(response.text)
html # Fig 7의 결과물
** BeautifulSoup 를 사용하는 이유?
복잡한 html 구조를 보기 좋게 바꾸어 원하는 태그를 쉽게 찾기 위해서 사용한다. (아래 사진을 비교해 보면 이유를 쉽게 알 수 있다.)
** Copy Selector 를 사용하는 이유?
사이트에서 원하는 태그 위치를 빠르고 쉽게 가져올 수 있음
# content > div > div.view-content > div > table > tbody > tr:nth-child(1) > td.data-title.aLeft > a
# 태그 전체를 select 에 가져올 필요는 없음
a_list = html.select("td.data-title.aLeft > a")
(ii) 내용번호 DataFrame 에 추가하기
# a 태그에서 내용번호만 가져와 리스트에 넣기
# list comprehension 으로 수집
a_links = [tag["href"].split("/")[-1] for tag in a_list]
# 가져온 내용번호를 df에 추가하기
df["내용번호"] = a_links
5. 전체 페이지 목록 수집하기
# 지정한 페이지 가져오기
def get_one_page(page_no):
# 1) url 가져오기
url = f"https://opengov.seoul.go.kr/civilappeal/list?&page={page_no}"
# 2) requests 로 HTTP 요창
response = requests.get(url)
# 3) table tag로 둘러쌓인 게시물 가져오기
df = pd.read_html(response.text, encoding="utf-8")[0]
# 4) df 행이 0개면 페이지를 찾을 수 없다는 메시지를 반환하기
if df.shape[0] == 0:
return f"{page_no} 페이지를 찾을 수 없습니다."
# 5) 내용 번호 컬럼 추가하기
# 오류 예외 처리 기법 : try 수행 중 오류 발생하면 except 블록 수행
try:
html = bs(response.text)
a_list = html.select("td.data-title.aLeft > a")
df["내용번호"] = [a_tag["href"].split("/")[-1] for a_tag in a_list]
except:
return f"{page_no} 페이지를 찾을 수 없습니다."
return df
# 모든 페이지 목록 가져오기
page_no = 1
all_page_list = []
while True:
one_page_df = get_one_page(page_no)
# 반복문 종료 시험 -> 페이지에서 수집된 df이 df 형태가 아닐 때 멈추기
if type(one_page_df) != pd.core.frame.DataFrame:
break
# 한 페이지에 대한 목차를 리스트에 추가하기
all_page_list.append(one_page_df)
# 페이지 수 변경
page_no += 1
# 서버 과부하 방지를 위해 시간 텀 두기
time.sleep(0.01)
# 데이터 합치기
df = pd.concat(all_page_list)
# 데이터 저장하기
file_name = "seoul-120-questions.csv"
df.to_csv(file_name, index=False)
6. 특정 내용 수집하기
# 각 내용번호에 대한 url 가져오기
a_number = 26695536 # 하반기 농부의 시장 일정은 어떻게 되나요? 질문에 대한 내용 번호
a_url = f"https://opengov.seoul.go.kr/civilappeal/{a_number}"
(i) 제공부서, 분류 가져오기
# Response 200 확인하기
a_response = requests.get(a_url, headers={'User_agent':'Mozilla/5.0'})
a_response
# 데이터 프레임으로 가져오기
a_df = pd.read_html(a_response.text)
a_df # Fig 11 결과물
# 특정 부분을 df_1 지정
df_1 = a_df[-1][[2, 3]].set_index(2).T
df_1 # Fig 12 결과물
** BeautifulSoup 없이 바로 pd.read_html 를 사용하는 이유?
필요한 부분이 table 태그로 묶여있었기 때문에 태그 찾는 과정을 생략 하였다.
(ii) 문서 내용 가져오기
# 내용 수집 할 url 가져오기
url = "https://opengov.seoul.go.kr/civilappeal/view/?nid=23194045"
response = requests.get(url)
html = bs(response.text)
# Copy Selector 로 태그 위치 가져오기
# content > div > div.view-content.view-content-article > div:nth-child(2) > div
content = html.select("div.line-all")[0].text # Fig 13 결과물
# \x\n 등이 포함되어 있어 제거를 위해 split 후 join 해 줌
content_list = content.split()
detail = " ".join(content_list) # Fig 14 결과물
** split, join 사용한 이유?
copy selector 로 text를 가져오면, \를 포함한 여러 기호들이 포함됨
이를 제거하기 위해 split , join 사용 ( 제거를 위해 정규표현식을 이용해보려고 했으나 실패... 그냥 split 해봤는데 됨 )
7. 내용 추가하기
# 전체 페이지별 목록이 담긴 데이터 불러오기
df = pd.read_csv("seoul-120-questions.csv")
# 제공부서, 생성일, 분류 추가 함수
def get_details(response):
table = pd.read_html(response.text)[-1]
# 특정 내용만 가져오고, T로 행 열 전환
tb = table[[2, 3]].set_index(2).T
return tb
# 문서 내용 추가하기
def get_one_view(view_no):
url = f"https://opengov.seoul.go.kr/civilappeal/view/?nid={view_no}"
response = requests.get(url)
tb = get_details(response)
html = bs(response.text)
# 문서 내용 부분을 content 로 가져옴
content = html.select("div.line-all")[0].text
# 문서 내용에 \n\x 등 특수문자가 포함되어서 제거를 위해 split 메서드 사용
content_list = content.split()
# 특수문자 제거 후, join으로 string 만들기
detail = " ".join(content_list)
tb["내용"] = detail
tb["내용번호"] = view_no
# 서버 과부하 방지를 위해 시간 텀 두기
time.sleep(0.01)
return tb
# 반복 작업에서 진행 상황을 확인하기 위해 tqdm, progress_map 사용
tqdm.pandas()
details = df["내용번호"].progress_map(get_one_view)
# 추가한 내용들을 df_detail 에 담아 하나의 DataFrame 으로 합치기
df_detail = pd.concat(details.tolist())
# 데이터 프레임 2개를 "내용번호"를 기준으로 합치기
df = df.merge(df_detail, on=["내용번호"])
# 컬럼 순서 바꾸기
df = df[["번호", "분류", "제목", "내용", "내용번호"]]
'멋사 AI school 7기 > TIL' 카테고리의 다른 글
네이버 금융 개별종목 수집하기 - Pandas/Requests/BeautifulSoup (0) | 2022.10.09 |
---|---|
FinanceDataReader 란 ? - 한국 주식 정보 가져오기 (0) | 2022.10.09 |
씨본(Seaborn) 시각화 도구 이해하기 (2) (0) | 2022.10.09 |
씨본(Seaborn) 시각화 도구 이해하기 (1) (0) | 2022.10.08 |
판다스(Pandas) 이해하기 - Series, DataFrame (0) | 2022.10.08 |