이전 글
[데이터 분석] 정말 비행기가 가장 안전한 교통 수단일까? 0. 분석 계기
다음 글
[데이터 분석] 정말 비행기가 가장 안전한 교통 수단일까? 2. 데이터 전처리
1. 크롤러
Aviation Safty Network 에서 데이터를 크롤링하는 프로그램을 만들었다. Github 링크
우선 주기능만 되도록 구현했다. README, 주석, 로깅, 예외처리는 제대로 안됐지만 기능은 잘 수행한다.
구성 파일은 crawler_asn_data.py와 crawler_asn_data_detail.py 가 있다.
1) crawler_asn_data.py
(1) 설명
crawler_asn_data.py : 간략한 사고 리스트 데이터베이스를 크롤링하는 코드이다.
아래 사진은 크롤링할 페이지 캡쳐 화면이다.
데이터는 사고 날짜(acc. date), 기종(type), 항공편명(reg.), 항공사(operator), 사망자 수(fat), 사고 위치(location), 데미지(dmg) 컬럼이 있다.
(2) 코드
crawling 함수이다.
url을 입력받아 html 파싱한 결과를 return 한다.
def crawling(url):
response = requests.get(url, headers={'User-agent':'Mozila/5.0'})
try:
if response.status_code == 200:
html = response.text
soup = BeautifulSoup(html, 'html.parser')
return soup
# response code 반환
else :
print('error', response.status_code)
sys.exit()
except Exception as e:
sys.exit()
메인 함수이다.
연도별로 나누어져있는 페이지를 방문해, 해당 페이지의 테이블을 가져와 csv로 저장한다.
def main():
total_df = pd.DataFrame()
for y in range(1919, 2025):
base_url = f'https://asn.flightsafety.org/database/year/{y}'
title_selector = '#contentcolumnfull > div > span'
soup = crawling(base_url)
'''
title example :
2 occurrences in the ASN safety database
216 occurrences in the ASN safety database; showing occurrence 1 - 100
'''
title = (soup.select_one(title_selector))
# row_cnt : the number of accident in {y}(year)
row_cnt = int(str(title)[22:].split()[0])
# calculate the number of page in {y}(year)
if row_cnt%100 == 0:
page_cnt = row_cnt//100
else: page_cnt = row_cnt//100 + 1
df = pd.DataFrame()
# Get data from all page
for p in range(1, page_cnt+1):
url = base_url + f'/{p}'
result = crawling(url)
temp = result.find_all('table')
table = temp[0]
p = parser.make2d(temp[0])
tmp_df=pd.DataFrame(p[1:],columns=p[0][:-1])
df = pd.concat([df, tmp_df])
# Check all data collected
if row_cnt != len(df):
print(f'Year {y} Error !!!!')
else:
print(f'Year {y} OK ')
# Combine Data
total_df = pd.concat([total_df, df])
time.sleep(0.5)
total_df.to_csv(f'{PATH}/ASN_Safety_Database.csv', sep=',', encoding='utf-8', index=False, header=True)
코드는 금방 짤 수 있었는데
페이지 주소가 간단하게되어있어서 어렵지 않았다.
연도별로 데이터 종회가 가능한데, 페이지 주소가 asn.flightsafety.org/database/year/{연도} 이렇게 되어있어서 y를 for로 돌게했다.
그리고 한 페이지에 100개씩 표시되는데, 연도 위에 페이지 숫자로 구분된다. asn.flightsafety.org/database/year/{연도}/{페이지 숫자}
title = (soup.select_one(title_selector))
# row_cnt : the number of accident in {y}(year)
row_cnt = int(str(title)[22:].split()[0])
# calculate the number of page in {y}(year)
if row_cnt%100 == 0:
page_cnt = row_cnt//100
else: page_cnt = row_cnt//100 + 1
모든 페이지를 긁어오기 위헤 연도별로 총 몇페이지까지 있는지 알아야한다.
그래서 그리고 첫 줄에 해당 년도에 총 사고 수가 나오는데, title에서 row_cnt(총 사고 수)를 추출해서 100으로 나눈 몫+1 이 총 페이지 수가된다.(사고 수가 100의 배수라면 몫)
그리고 모든 연도의 모든 페이지를 방문해서 테이블을 긁어와서 dataframe으로 바꾼다.
# Check all data collected
if row_cnt != len(df):
print(f'Year {y} Error !!!!')
else:
print(f'Year {y} OK ')
100년 넘는 데이터가 있으므로 모든 년도 데이터를 잘 가져왔는지 확인하기 위해 맨 처음 title에서 추출한 row_cnt와 df 길이를 비교해 확인한다.
(3) 결과물
아래는 샘플 데이터이다. 웹 페이지에서 보이는도 잘 크롤링되었다.
2) crawler_asn_data_detail.py
: 자세한 사고 데이터를 크롤링하는 코드이다.
(1) 설명
총 18개의 컬럼이 있다. (날짜, 시간, 항공기 종류, 소유자/운영자, 편명, MSN, 제조년도, 엔진 모델, 사망자, 항공기 손상, 위치, 단계, 출발공항, 도착 공항 ..
(2) 코드
def main():
for y in range(1919, 2025):
total_df = pd.DataFrame()
base_url = f'https://asn.flightsafety.org/database/year/{y}'
title_selector = '#contentcolumnfull > div > span'
soup = crawling(base_url)
title = (soup.select_one(title_selector))
'''
title example :
2 occurrences in the ASN safety database
216 occurrences in the ASN safety database; showing occurrence 1 - 100
'''
row_cnt = int(str(title)[22:].split()[0])
if row_cnt%100 == 0:
page_cnt = row_cnt//100
else: page_cnt = row_cnt//100 + 1
print(f'##### Total accident in {y} : {row_cnt}, total page : {page_cnt}')
# Get list of all detail page link
links = []
for p in range(1, page_cnt+1):
url = f'{base_url}/{p}'
result = crawling(url)
links = links + result.find_all('span', attrs = {'class':'nobr'})
# Get
page_df = pd.DataFrame()
for link in links:
link = str(link)
# extract detail page link only
try:
pattern = 'wikibase|database\/[^\"]+'
if re.findall(pattern, link):
detail_urls = re.findall('wikibase\/\d+|database\/[^\"]+', link)[0]
# get table from detail page
detail_page = crawling(f'https://asn.flightsafety.org/{detail_urls}')
temp = detail_page.find_all('table')[0]
p = parser.make2d(temp)
ddf = pd.DataFrame(p)
ddf=ddf.transpose()
ddf = ddf.rename(columns=ddf.iloc[0]).loc[1:]
time.sleep(0.1)
page_df = pd.concat([page_df, ddf])
except TypeError as e:
print('\n #### pass', link, '\n' )
pass
total_df = pd.concat([total_df, page_df])
print('##### total_df : ', len(total_df))
# Check all data collected
if row_cnt != len(total_df):
print(f'Year {y} Error !!!!')
else:
print(f'Year {y} OK ')
time.sleep(0.1)
total_df.to_csv(f'{PATH}/ASN_Safety_Database_detail_{y}.csv', sep=',', encoding='utf-8', index=False, header=True)
detail 데이터를 가져오기 위헤서는 사고 리스트 데이터의 날짜에 걸려있는 하이퍼 링크 값이 필요하다.
이번 역시 크게 어렵지 않았는데, detail 페이지도 표로 되어있었다.
# Get list of all detail page link
links = []
for p in range(1, page_cnt+1):
url = f'{base_url}/{p}'
result = crawling(url)
links = links + result.find_all('span', attrs = {'class':'nobr'})
crawler_asn_data.py에서 표의 링크 값만을 모은 후, 해당 링크로 들어가 표를 가져오면된다.
클래스 이름이 nobr인 <span> 태그를 모은다.
# extract detail page link only
try:
pattern = 'wikibase|database\/[^\"]+'
if re.findall(pattern, link):
detail_urls = re.findall('wikibase\/\d+|database\/[^\"]+', link)[0]
# get table from detail page
detail_page = crawling(f'https://asn.flightsafety.org/{detail_urls}')
temp = detail_page.find_all('table')[0]
링크 리스트를 만든 후, 링크가 'wikibase/숫자' 이거나 'database/record.php?id=19910104-1'인 링크에만 접속해서 데이터 테이블을 긁어온다.
이부분이 살짝 까다로웠는데 테이블이 아래와 같은 형식이었다.
Date: | Tuesday 2 January 2024 |
Time: | 17:47 |
Type: | Airbus A350-941 |
Owner/operator: | Japan Airlines |
Registration: | JA13XJ |
MSN: | 538 |
Year of manufacture: | 2021 |
Engine model: | Rolls-Royce Trent XWB-84 |
Fatalities: | Fatalities: 0 / Occupants: 379 |
Other fatalities: | 5 |
Aircraft damage: | Destroyed, written off |
Category: | Accident |
Location: | Tokyo International Airport/Haneda (HND/RJTT) - Japan |
Phase: | Landing |
Nature: | Passenger - Scheduled |
Departure airport: | Sapporo-New Chitose Airport (CTS/RJCC) |
Destination airport: | Tokyo-Haneda Airport (HND/RJTT) |
그래서 크롤링 결과가
[['Date:', 'Tuesday 2 January 2024'], ['Time:', '17:47'], ...] 이런 형식으로 나왔다.
데이터 프레임으로 만들어보니
컬럼명 리스트가 0열에 들어가게되었다.
p = parser.make2d(temp)
ddf = pd.DataFrame(p)
ddf=ddf.transpose()
ddf = ddf.rename(columns=ddf.iloc[0]).loc[1:]
그래서 transpose 로 행열을 뒤집고, row 0을 컬럼명으로 지정했다.
crawler_asn_data_detail.py 실행 화면. sleep을 주었더니 시간이 꽤 걸린다.
(3) 결과물
웹페이지에서 보던 데이터가 잘 크롤링되었다.
#무안 #무안공항 #제주항공 #제주항공무안공항참사
이전 글
[데이터 분석] 정말 비행기가 가장 안전한 교통 수단일까? 0. 분석 계기
다음 글
'Data > Data Analystics' 카테고리의 다른 글
[데이터 분석] 정말 비행기가 가장 안전한 교통 수단일까? 2. 데이터 전처리 (0) | 2024.12.30 |
---|---|
[데이터 분석] 정말 비행기가 가장 안전한 교통 수단일까? 0. 분석 계기 (1) | 2024.12.30 |
[2023 빅콘테스트] 클래식 공연 활성화를 위한 효과적 가격 모델 수립 (0) | 2024.05.01 |
[ADP 실기 준비] 코로나19 - 인구대비 상위 5개국 구하기 + 시각화 (1) | 2020.08.24 |