본문 바로가기

파이썬 구현

[파이썬] 웹에서 자료실 파일 다운로드하는 크롤러 만들기

[파이썬] 웹에서 자료실 파일 다운로드하는 크롤러 만들기

농수산물유통정보(KAMIS) 에서 주간동향 파일들을 모두 다운로드 해야하는 일이 생겼다. 하지만 양이 많고, 일일이 들어가서 다운로드 해줘야 하기 때문에 시간이 오래걸려서 자동화 방법을 찾아 만들어 보았다.

들어가기 앞서 전체 코드는 다음과 같다.

# -*- coding: utf-8 -*-
import os
import re
from urllib import request
from urllib.error import HTTPError
from bs4 import BeautifulSoup
import requests

overlap=[]
url = 'https://www.kamis.or.kr/customer/trend/trade/weekly.do?'  #반복되는 곳 사이트 몸통
site = 'https://www.kamis.or.kr/'   #가져올 사이트 앞부분
rec = "/customer/trend/trade/weekly.do?" #반복되는 부분 뒷부분
dl = "/customer/board/board_file.do?"   #클릭해서 다운 받는 부분

def get_download(url, fname, directory):
   try:
       os.chdir(directory)
       request.urlretrieve(url, fname)
       print('다운로드 완료\n')
   except HTTPError as e:
       print('error')
       return

def downSearch(getDLATag):
   for getDLLink in getDLATag:
       try:
           if dl in getDLLink.get('href'):
               print("다운로드 링크 : {}".format(site) + getDLLink.get('href'))
               accessDLUrl = site + getDLLink.get('href')
               fileOriginalNM = re.sub('<.+?>', '', str(getDLLink), 0).strip().replace('_', ' ')
               fileNM = "[KAMIS주간동향] " + fileOriginalNM
               path = "D:\\KAMIS\\"
               if os.path.isfile(path + fileNM):
                   print("다운로드 실패 : 동일 파일 존재\n")
               else:
                   get_download(accessDLUrl, fileNM, path)
       except:pass

def Search(getA):
   for getLink in getA:
       data = getLink.get('href')
       try:
           if rec in getLink.get("href"):
               if len(data) >= 100 and data not in overlap:
                   overlap.append(data)
                   accessUrl = site + getLink.get("href")
                   r = requests.get(accessUrl)
                   soup = BeautifulSoup(r.text, "html.parser")
                   getDLATag = soup.find_all("a")
                   downSearch(getDLATag)

               elif len(data) >= 85 and data not in overlap:
                   overlap.append(data)
                   accessUrl = site + getLink.get("href")
                   r = requests.get(accessUrl)
                   soup = BeautifulSoup(r.text, "html.parser")
                   getDLATag = soup.find_all("a")
                   Search(getDLATag)
                   # Search(getDLATag,depth+1)
       except:pass

# request 모듈을 사용하여 웹 페이지의 내용을 가져온다
r = requests.get(url)
print("KAMIS 자료실 요청 : ", r)

# beautiful soup 초기화
soup = BeautifulSoup(r.text, "html.parser")
# 태그로 찾기 (모든 항목)
getA = soup.find_all("a")
Search(getA)

언어는 파이썬을 사용했고, 주로 사용된 모듈은 beautifulsoup, requests 이다.

코드 작성에 있어 이 글을 많이 참고 하였다.

하나하나 살펴보면

# request 모듈을 사용하여 웹 페이지의 내용을 가져온다
r = requests.get(url)
print("KAMIS 자료실 요청 : ", r)

# beautiful soup 초기화
soup = BeautifulSoup(r.text, "html.parser")
# 태그로 찾기 (모든 항목)
getA = soup.find_all("a")
Search(getA)

url을 이용해서 html 내용을 받아온다. url은 코드의 9번째줄에서 선언해줬는데 뽑을 정보의 몸통이 되는 주소를 적어주면 된다.

그 정보로 Search() 라는 함수를 실행시켜주는데

def Search(getA):
   for getLink in getA:
       data = getLink.get('href')
       try:
           if rec in getLink.get("href"):
               if len(data) >= 100 and data not in overlap:
                   overlap.append(data)
                   accessUrl = site + getLink.get("href")
                   r = requests.get(accessUrl)
                   soup = BeautifulSoup(r.text, "html.parser")
                   getDLATag = soup.find_all("a")
                   downSearch(getDLATag)

               elif len(data) >= 85 and data not in overlap:
                   overlap.append(data)
                   accessUrl = site + getLink.get("href")
                   r = requests.get(accessUrl)
                   soup = BeautifulSoup(r.text, "html.parser")
                   getDLATag = soup.find_all("a")
                   Search(getDLATag)
       except:pass

for문을 이용해서 받아온 html 내용을 한덩어리씩 보는데, getLink 내용을 출력 해보면

1. <a href="/customer/trend/trade/weekly.do?action=detail&amp;brdctsno=429805&amp;pagenum=1&amp;search_option=&amp;search_keyword=&amp;" title="농수축산물 주간 거래동향 (8.13~8.19)">농수축산물 주간 거래동향 (8.13~8.19)</a>
2. <a href="/customer/trend/trade/weekly.do?action=detail&amp;brdctsno=429793&amp;pagenum=1&amp;search_option=&amp;search_keyword=&amp;" title="농수축산물 주간 거래동향 (8.6~8.12)">농수축산물 주간 거래동향 (8.6~8.12)</a>
3. <a class="page_btn" href="/customer/trend/trade/weekly.do?action=list&amp;pagenum=1&amp;search_option=&amp;search_keyword=&amp;" title="처음페이지">&lt;&lt;</a>
4. <a href="/customer/trend/trade/weekly.do?action=list&amp;pagenum=2&amp;search_option=&amp;search_keyword=&amp;" title="2페이지">2</a>
5. <a href="/customer/trend/trade/weekly.do?action=list&amp;pagenum=3&amp;search_option=&amp;search_keyword=&amp;" title="3페이지">3</a>

이런 내용이 나온다. 자료를 받을 수 있는 곳의 주소를 살펴보면

https://www.kamis.or.kr/customer/trend/trade/weekly.do?action=detail&brdctsno=429928&pagenum=1&search_option=&search_keyword=& 이런식이다. 받는 곳 주소들의 https://www.kamis.or.kr/customer/trend/trade/weekly.do? 이 부분이 반복되므로 rec 라는 변수에 반복되는 부분을 저장해놓고, 뽑은 html 덩어리에서 rec라는 부분이 있으면 다시 뽑아낸다.(다운로드 하는데에 필요한 부분이니까)

위에 임의로 출력한 html 덩어리 들을 살펴보자. 1,2번째 내용은 다운로드 받을 수 있는 곳이고, 4,5번째 내용은 페이지를 넘어가는 곳이다. 여기서 나는 덩어리의 길이를 사용했는데, 100글자가 넘으면 다운받는 곳이라고 생각하고 다운을 받게 해주었고, 그렇지 않고 85자를 넘으면 페이지를 표시하는 곳이라 생각하여 다시 Search()해주도록 하였다.

이 때 overlap 배열을 이용하여 한번 방문한 곳은 재방문 하지 않게 해주었다.

def downSearch(getDLATag):
   for getDLLink in getDLATag:
       try:
           if dl in getDLLink.get('href'):
               print("다운로드 링크 : {}".format(site) + getDLLink.get('href'))
               accessDLUrl = site + getDLLink.get('href')
               fileOriginalNM = re.sub('<.+?>', '', str(getDLLink), 0).strip().replace('_', ' ')
               fileNM = "[KAMIS주간동향] " + fileOriginalNM
               path = "D:\\KAMIS\\"
               if os.path.isfile(path + fileNM):
                   print("다운로드 실패 : 동일 파일 존재\n")
               else:
                   get_download(accessDLUrl, fileNM, path)
       except:pass

downSaerch() 함수는 받아야 할 다운로드 링크 이면 dl 변수에 저장해주고 dl 변수가 들어가 있는 html 내용이면 이미 존재하는 파일인지 확인한 후 없으면 get_download()를 이용해서 다운해준다.

다운 받아야 할 링크를 알아내는 법은 크롬 기준으로 다운받을 곳의 게시판을 들어가서 F12를 누른다.


img


그럼 이런 창이 뜨는데, 왼쪽 위에 네모에 화살표 모양 그림을 클릭한 뒤,

img


첨부파일이 있는곳을 클릭하면

img


이런식으로 자동으로 찾아준다.

다운받을 html이 "/customer/board/board_file.do?" 이런식으로 반복되므로 dl이란 변수에 저장해서 한 덩어리씩 보면서 dl이란 내용이 있으면 다운로드를 해주는 식이다.

def get_download(url, fname, directory):
   try:
       os.chdir(directory)
       request.urlretrieve(url, fname)
       print('다운로드 완료\n')
   except HTTPError as e:
       print('error')
       return

get_download() 함수가 url, 파일이름, 저장폴더를 받아와서 다운을 해준다.




고찰

변수rec, dl 등의 정보가 있나 찾을 때 파싱해온 html에서 NULL값이 나오면 에러가 났다. 그래서 try, except 예외 처리를 해주었다.

Search() 함수 두가지 케이스로 나뉘는데, 처음에 반복되는 부분 함수로 작성했다가 제대로 추출이 안돼서 일단 하드코딩 해서 만들어놨다. 그 부분 문제점 찾아서 다시 함수로 바꿔도 좋을듯 하다.

길이로 나눈 부분이라던지, 중복처리 하는 부분이라던지 정규식 이용해서 더 간단한 정보를 가지고 저장, 처리하면 더 좋은 성능을 낼 수 있을듯 하다.