이번 포스팅에서는 Python을 사용해서 웹사이트에서 압축파일을 다운로드해서 압축을 해제하고 데이터셋을 합치는 방법을 소개하겠습니다. 

 

세부 절차 및 이용한 Python 모듈과 메소드는 아래와 같습니다. 

 

(1) os 모듈로 다운로드한 파일을 저장할 디렉토리가 없을 경우 새로운 디렉토리 생성하기

(2) urllib.request.urlopen() 메소드로 웹사이트를 열기 

(3) tarfile.open().extractall() 메소드로 압축 파일을 열고, 모든 멤버들을 압축해제하기

(4) pandas.read_csv() 메소드로 파일을 읽어서 DataFrame으로 만들기

(5) pandas.concat() 메소드로 모든 DataFrame을 하나의 DataFrame으로 합치기

(6) pandas.to_csv() 메소드로 합쳐진 csv 파일을 내보내기

 

 

먼저, 위의 6개 절차를 download_and_merge_csv() 라는 이름의 사용자 정의함수로 정의해보겠습니다.  

 

import os
import glob
import pandas as pd
import tarfile
import urllib.request

## downloads a zipped tar file (.tar.gz) that contains several CSV files, 
## from a public website. 
def download_and_merge_csv(url: str, down_dir: str, output_csv: str):
    """
    - url: url address from which you want to download a compressed file
    - down_dir: directory to which you want to download a compressed file
    - output_csv: a file name of a exported DataFrame using pd.to_csv() method
    """
    
    # if down_dir does not exists, then create a new directory
    down_dir = 'downloaded_data'
    if os.path.isdir(down_dir):
        pass
    else:
        os.mkdir(down_dir)
        
    # Open for reading with gzip compression.
    # Extract all members from the archive to the current working directory or directory path. 
    with urllib.request.urlopen(url) as res:
        tarfile.open(fileobj=res, mode="r|gz").extractall(down_dir)
    
    # concatenate all extracted csv files
    df = pd.concat(
        [pd.read_csv(csv_file, header=None) 
         for csv_file in glob.glob(os.path.join(down_dir, '*.csv'))])
    
    # export a DataFrame to a csv file
    df.to_csv(output_csv, index=False, header=False)

 

참고로, tarfile.open(fileobj, mode="r") 에서 4개의 mode 를 지원합니다. 

tarfile(mode) 옵션
-. mode="r": 존재하는 데이터 보관소로부터 읽기 (read)
-. mode="a": 존재하는 파일에 데이터를 덧붙이기 (append)
-. mode="w": 존재하는 파일을 덮어쓰기해서 새로운 파일 만들기 (write, create a new file overwriting an existing one)
-. mode="x": 기존 파일이 존재하지 않을 경우에만 새로운 파일을 만들기 (create a new file only if it does not already exist)

* for more information on tarfile module: https://docs.python.org/3/library/tarfile.html

 

 

현재 Jupyter Notebook 커널의 디렉토리에는 아래처럼  아직 다운로드한 파일이 없습니다. 

 

jovyan@kubecon-tutorial-0:~$ pwd
/home/jovyan
jovyan@kubecon-tutorial-0:~$ 
jovyan@kubecon-tutorial-0:~$ ls
data  down_merge_csv.ipynb  kale.log  lost+found
jovyan@kubecon-tutorial-0:~$ 
jovyan@kubecon-tutorial-0:~$

 

 

 

 

이제 위에서 정의한 download_and_merge_csv() 를 사용해서 

  (a) url='https://storage.googleapis.com/ml-pipeline-playground/iris-csv-files.tar.gz' 로 웹사이트로 부터 압축파일을 열고 모든 파일들을 해제해서 

  (b) down_dir='downloaded_data' 의 디렉토리에 다운로드하고, 

  (c) output_csv='iris_merged_data.csv'  라는 이름의 csv 파일로 모든 파일을 합쳐서 내보내기

를 해보겠습니다. 

 

download_and_merge_csv(
    url='https://storage.googleapis.com/ml-pipeline-playground/iris-csv-files.tar.gz', 
    down_dir='downloaded_data', 
    output_csv='iris_merged_data.csv')

 

 

아래의 화면캡쳐처럼 'iris_merged_data.csv' 라는 이름의 csv 파일이 새로 생겼습니다. 그리고 'downloaded_data' 라는 폴더도 새로 생겼습니다. 

 

 

 

 

터미널에서 새로 생긴 'downloaded_data' 로 디렉토리를 이동한 다음에, 파일 리스트를 확인해보니 'iris-1.csv', 'iris-2.csv', 'iris-3.csv' 의 3개 파일이 들어있네요. head 로 상위의 10 개 행을 읽어보니 iris 데이터셋이군요. 

 

jovyan@kubecon-tutorial-0:~$ ls
data  downloaded_data  down_merge_csv.ipynb  iris_merged_data.csv  kale.log  lost+found
jovyan@kubecon-tutorial-0:~$ 
jovyan@kubecon-tutorial-0:~$ 
jovyan@kubecon-tutorial-0:~$ cd downloaded_data/
jovyan@kubecon-tutorial-0:~/downloaded_data$ ls
iris-1.csv  iris-2.csv  iris-3.csv
jovyan@kubecon-tutorial-0:~/downloaded_data$ 
jovyan@kubecon-tutorial-0:~/downloaded_data$ 
jovyan@kubecon-tutorial-0:~/downloaded_data$ head iris-1.csv
5.1,3.5,1.4,0.2,setosa
4.9,3.0,1.4,0.2,setosa
4.7,3.2,1.3,0.2,setosa
4.6,3.1,1.5,0.2,setosa
5.0,3.6,1.4,0.2,setosa
5.4,3.9,1.7,0.4,setosa
4.6,3.4,1.4,0.3,setosa
5.0,3.4,1.5,0.2,setosa
4.4,2.9,1.4,0.2,setosa
4.9,3.1,1.5,0.1,setosa
jovyan@kubecon-tutorial-0:~/downloaded_data$

 

 

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요.  :-)

 

728x90
반응형
Posted by Rfriend
,

이번 포스팅에서는 Python의 urllib 과 BeautifulSoup 모듈을 사용해서 웹 페이지의 내용을 파싱하여 필요한 데이터만 크롤링, 스크래핑하는 방법을 소개하겠습니다. 

 

urllilb 모듈은 웹페이지 URL 을 다룰 때 사용하는 Python 라이브러리입니다. 가령, urllib.request 는 URL을 열고 읽을 때 사용하며, urllib.parse 는 URL을 파싱할 때 사용합니다. 

 

BeautifulSoup 모듈은 HTML 과 XML 파일로부터 데이터를 가져올 때 사용하는 Python 라이브러리입니다. 이 모듈은 사용자가 선호하는 파서(parser)와 잘 작동하여, parse tree 를 조회하고 검색하고 수정하는 자연스러운 방법을 제공합니다. 

 

python urllib, BeautifulSoup module for web scraping

 

 

이번 예제에서는

(1) urllib.request 의 urlopen 메소드로 https://oilprice.com/ 웹페이지에서 'lng' 라는 키워드로 검색했을 때 나오는 총 20개의 페이지를 열어서 읽은 후

(2) BeautifulSoup 모듈을 사용해 기사들의 각 페이지내에 있는 20개의 개별 기사들의 '제목(title)', '기사 게재일(timestamp)', '기사에 대한 설명 (description)' 의 데이터를 파싱하고 수집하고,

(3) 이들 데이터를 모아서 pandas DataFrame 으로 만들어보겠습니다. (총 20개 페이지 * 각 페이지별 20개 기사 = 총 400 개 기사 스크랩핑)

 

webpage crawling, scraping using python urllib, BeautifulSoup, pandas

 

아래의 예시 코드는 파송송님께서 짜신 것이구요, 각 검색 페이지에 20개씩의 기사가 있는데 제일 위에 1개만 크롤링이 되는 문제를 해결하는 방법을 문의해주셔서, 그 문제를 해결한 후의 코드입니다.

 

##-- How to Scrape Data on the Web with BeautifulSoup and urllib

from bs4 import BeautifulSoup
from urllib.request import urlopen
import pandas as pd
from datetime import datetime

col_name = ['title', 'timestamp', 'descrip']
df_lng = pd.DataFrame(columns = col_name)

for j in range(20):
    ## open and read web page
    url = 'https://oilprice.com/search/tab/articles/lng/Page-' + str(j+1) + '.html'
    with urlopen(url) as response:
        soup = BeautifulSoup(response, 'html.parser')
        headlines = soup.find_all(
        	'div', 
        	{'id':'search-results-articles'}
        	)[0]
        
        ## getting all 20 titles, timestamps, descriptions on each page
        title = headlines.find_all('a')
        timestamp = headlines.find_all(
        	'div', 
        	{'class':'dateadded'}
            )
        descrip = headlines.find_all('p')
        
        
        ## getting data from each article in a page
        for i in range(len(title)):
            title_i = title[i].text
            timestamp_i = timestamp[i].text
            descrip_i = descrip[i].text

            # appending to DataFrame
            df_lng = df_lng.append({
            	'title': title_i, 
                'timestamp': timestamp_i, 
                'descrip': descrip_i}, 
                ignore_index=True)

        if j%10 == 0:
            print(str(datetime.now()) + " now processing : j = " + str(j))

# remove temp variables
del [col_name, url, response, title, title_i, timestamp, timestamp_i, descrip, descrip_i, i, j]

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요!  :-)

 

 

728x90
반응형
Posted by Rfriend
,