파이썬에서 다운로드 링크 (URL)가 기록된 CSV 파일을 읽어 들여 자동으로 다운로드 하는 코드

조회수 6513회

현재 다운로드 링크가 URL로 기록된 CSV 파일이 있습니다 그 파일에 있는 URL들을 읽어들여 자동으로 폴더안에 다운로드를 받는 코드를 짜고 싶습니다. 여기서 방법을 몰라서 몇가지 시도를 해보았으나 오류도 나고 아예 막힌 상태라 질문을 드립니다.

import urllib.request

url='http://www.data.go.kr/dataset/fileDownload.do?atchFileId=FILE_000000001212856&fileDetailSn=1&publicDataDetailPk=uddi:07b44140-4ded-40e6-946e-c03b317b833e'

urllib.request.urlretrieve(url,1)

이 시도를 통해 우선 urllib.request 라이브러리가 작동하는지 시도를 해보았으나,

OSError: [WinError 6] 핸들이 잘못되었습니다

라는 에러가 발생합니다.

import requests
url='http://www.data.go.kr/dataset/fileDownload.do?atchFileId=FILE_000000001375425&fileDetailSn=1&publicDataDetailPk=uddi:fff6f608-f3b8-464f-be97-d58c4944e477'
r=requests.get(url,allow_redirects=True)
open('urldata.csv','wb').write(r.content)
r = requests.get(url, allow_redirects=True)
print (r.headers.get('content-type'))

request library를 사용 하는 방법도 있었으나, 이 방법은 제가 파일명과 url를 일일히 입력해야 하는거 같았습니다.

저는 URL이 있는 CSV 파일을 파이썬에서 open, r 등으로 읽어 들인후 , url 레코드를 하나씩 내려가면서 자동으로 다운로드를 하는 코드를 짜고싶습니다. 많은 도움 부탁드립니다.


r = requests.get(url.rstrip(), stream=True)
if r.status_code == 200:
    content_dispotistion = r.headers.get('content-disposition')
    if content_disposition is not None:
        targetFileName = requests.utils.unquote(cgi.parse_header(content_dispotistion)[1]['filename'])
        with open("{}/{}".format(SAVE_DIR, targetFileName), 'wb') as f:
            for chunk in r.iter_content(chunk_size=1024):
                f.write(chunk)
    else:
        print('url {} had no content-disposition header'.format(url))
    return targetFileName
elif r.status_code == 404:
    print('{} returned a 404, no file was downloaded'.format(url))
else:
    print('something else went wrong with {}'.format(url))
  • (•́ ✖ •̀)
    알 수 없는 사용자

5 답변

  • 점심시간이라...잠깐 코딩을 해봤습니다만 학습에 참고만 하세요.

    import cgi
    import requests
    
    
    SAVE_DIR = 'C:/'
    
    def downloadURLResource(url):
        r = requests.get(url, stream=True)
        if r.status_code == 200:
            targetFileName = requests.utils.unquote(cgi.parse_header(r.headers['content-disposition'])[1]['filename'])
            with open("{}/{}".format(SAVE_DIR, targetFileName), 'wb') as f:
                for chunk in r.iter_content(chunk_size=1024):
                    f.write(chunk)
            return targetFileName
    
    
    with open('h:/url.csv') as f:    # url.csv 가 url 목록이 있는 파일입니다.
        print(list(map(downloadURLResource, f.readlines())))
    
    • 코딩 해주신 부분을 보니 전체적인 흐름은 알겠는데 r.iter_content 나 utils.unquote 같은 부분은 이해가 잘 가지않습니다. 그리고 해당 코드를 제가 python 3.52에 입력하여 맨밑에서 두번째 줄의 파일명과 위치를 수정하여 실행해보았습니다. 레코드 수가 6만개가 넘어서 그런지 아직 결과가 나오거나 저장되지 않고 계속 shell에서 실행중인것으로 나옵니다. 혹시 이 부분은 단순히 레코드 수가 많아 6만건이 다 완료되면 그때 결과를 출력하는건가요? 알 수 없는 사용자 2017.7.19 14:57
    • url에는 ascii 문자만 허용합니다. 즉 한글은 안되니 한글등을 전달하기 위해 encode를 하게 됩니다. 반대로 다시 한글로 만들기 위해 decode를 하게 되는데 unquote 가 decode 하는 함수입니다. iter_content 같은 경우는 requests에서 제공해주는 펑션으로 이 역시 IO를 공부하게 되면 이해가 될겁니다. 쉽게 1024씩 받아와서 파일에 차곡차곡 쌓는다는 생각으로 이해하면 됩니다. 정영훈 2017.7.19 16:32
    • 6만건을 한번에 처리할려면 멀티프로세스등의 병렬화 기법을 사용해야합니다. 그렇더라도 6만번을 같은 url로 접속시도를 할 경우 될지도 의문입니다. 보통 관공서, 기업등에서 ids, ips 등 보안장비가 있어 dos 공격으로 감지할 수도 있습니다. 일단 서너건만 넣고 테스트 해보세요 정영훈 2017.7.19 16:34
    • 답변 항상 감사드립니다. 6만번을 같은 url로 접속시도를 할때 여기서 '같은 url'은 http://www.data.go.kr/dataset/fileDownload.do?atchFileId= 뒤의 값은 달라도 같은 사이트(공공데이터포탈 )이기에 불가능 할 수도 있다는 말씀이신가요? 그리고 6만건중 4개만 붙여 넣은 csv파일을 만들었습니다. 이 파일을 입력하여 코드 실행시 쉘에서의 결과값이 [None, None, None, None] 으로 나옵니다. 알 수 없는 사용자 2017.7.19 16:51
    • 음..제가 url.csv에 url을 넣고 해보니 잘 되어 올려드린 코드에요.만드신 csv파일을 allinux36@gmail.com으로 보내보세요. 정영훈 2017.7.19 17:06
    • 이메일을 보내드렸습니다. 감사합니다 알 수 없는 사용자 2017.7.19 17:20
    • r = requests.get(url.rstrip(), stream=True) 이렇게 수정하세요. 문장끝에 \n 혹은 \r\n 일 수 있습니다. rstrip을 수행하여 명시적으로 제거해주면 됩니다. 정영훈 2017.7.19 22:54
    • 감사합니다. 수정해주신 부분을 적용하여 다시 코드를 실행하니 어느정도 정상적으로 진행이 잘되는것 같습니다. 그러던 중 오류가 났는데 이 부분이 조금 복잡합니다. 200개의 파일을 다운받은 후, KeyError: 'content-disposition 가 나옵니다. 위에 말씀하신걸로 보아선 이 부분이 파일의 이름을 읽고 지정하는 역할을 하는 코드인것 같습니다. 우선 200개의 파일을 다운받았으면 위에서부터 1~200개가 되었을거라고 예상을 했는데 csv파일의 201,202 번째도 다운받아진것을 확인할 수 있었습니다. 아마 링크중 파일이 없는 잘못된 링크가 있나 싶었습니다. 그런데 203번째의 링크는 익스플로러에서 확인시 다운로드가 안되고 페이지(탭)의 제목이 insert title here로 나오는것을 볼 수 있었습니다. 이 링크도 파일이 없는? 링크 일경우 왜 앞선 1~202사이의 url에서는 에러코드 없이 단순 스킵이 되나, 203번째의 url에서는 에러코드가 쉘에 입력되는지 궁금합니다. 지속적으로 도움을 주셔서 정말 감사합니다. 알 수 없는 사용자 2017.7.20 10:26
    • 203번째 파일의 헤더를 받아보니 다른라인들은 content disposition이 있는데 이 라인(url)은 content dispositon이 없는것으로 확인되었습니다. content disposition 이 없는 url은 일반 익스플로러에 그대로 붙여넣어도 다운로드가 밑에 팝업되지 않으므로 잘못된 자료인가요 ? 또한 202개중 누락된 2개는 반대로 content disposiition은 있으나 파일이 없기에 에러로는 출력되지 않았으나 다운로드가 안되고 스킵된 경우인가요? 알 수 없는 사용자 2017.7.20 11:33
    • 혹시 이 코드에서 다운로드가 안되거나 에러가 나는 파일들의 이름 또는 url 들을 리스트에 저장하기 위해서는 어느 부분에 어떤 코드를 수정하면 될까요? 에러가 나는 url도 r.status_code 는 200으로 뜨는것으로 보아 if 문 사이는 아니고 정의된 함수 downloadURLResource 자체가 none 일때, print content_disposition,'오류' 이런식으로 수정하면될까요? 알 수 없는 사용자 2017.7.20 15:04
    • response 헤더에 content-disposition 가 없을수도 있습니다. 작성한 코드에는 없는 경우는 고려치 않았기에 오류가 발생한겁니다. content-disposition 값이 없을때는 별도의 파일명으로 저장되도록 해야 합니다. 정영훈 2017.7.20 20:42
    • content dispostion이 없는 경우에는 쉘에 오류가 반환이 되면서 멈추는데, 그러지 않고 파일은 다운로드가 안되었는데 쉘에 오류가 나오지 않고 진행되는 경우는 어떻게 해야 다운로드 되지 않은 파일들의 이름이나 URL을 별도의 리스트에 저장하거나 할 수 있을까요? 지금 오류가 두 종류면 각각의 경우로 코드를 수정 해야하는건가요..... 알 수 없는 사용자 2017.7.21 08:39
    • 우선 content-disposition 가 없는 주소를 적어주세요. 문제가 없는 URL인지 확인해봐야겠네요. 정영훈 2017.7.21 09:30
    • http://www.data.go.kr/dataset/fileDownload.do?atchFileId=FILE_000000001210727&fileDetailSn=1&publicDataDetailPk=uddi:4cf4dc4c-e0e9-4aee-929e-b2a0431bf03e 이 URL입니다. 알려주신 응답 헤더 얻는 코드를 이 URL에 적용해보니 이 URL은 다른거와 다르게 content dispoistion이 없었습니다 알 수 없는 사용자 2017.7.21 10:04
  • 질문은 없고 희망사항만 있네요.

    1. URL이 라인단위로 존재하는 CSV파일을 읽는다.
    2. 1의 결과에서 라인별로 파싱하여 URL을 얻는다. 1, 2를 모르겠다면 PYTHON 으로 IO 다루는 공부를 해야 합니다.

      1. 2에서 얻은 URL을 이용하여 파일을 다운로드 합니다. url을 이용하여 리소스를 받는 부분을 모르겠다면 http 프로토콜 공부를 해야 합니다.

    http://www.data.go.kr/dataset/fileDownload.do?atchFileId=FILE_000000001375425&fileDetailSn=1&publicDataDetailPk=uddi:fff6f608-f3b8-464f-be97-d58c4944e477 의 응답헤더는 아래와 같습니다.

    HTTP/1.1 200 OK
    Date: Wed, 19 Jul 2017 02:57:47 GMT
    Server: Apache
    Content-Disposition: attachment;filename=%EC%B6%A9%EC%B2%AD%EB%B6%81%EB%8F%84+%EC%A6%9D%ED%8F%89%EA%B5%B0_%EB%AF%BC%EB%B0%95%ED%8E%9C%EC%85%98%EC%97%85%EC%86%8C_20170511.csv
    Content-Language: ko
    Content-Length: 378
    Keep-Alive: timeout=5, max=10000
    Connection: Keep-Alive
    Content-Type: application/octet;charset=utf-8
    

    Content-Disposition 을 보면 파일이름을 얻을 수 있습니다.

    공부를 해보시고 이해가 안되시는 부분들을 질문해보시기 바랍니다.

    • 답변 정말 감사합니다. 제가 코딩쪽으로 많이 부족한데 Python과 R을 제외한 다른 언어나 툴은 해본적도 없는 상태라 구글에 검색해도 이해를 하기 힘들더라구요.. 적어주신 1번 2번을 잘 모르겠기에 말씀해주신 IO와 http 프로토콜 검색해서 읽어보고 공부하겠습니다. 감사합니다! 알 수 없는 사용자 2017.7.19 13:29
  • http://www.data.go.kr/dataset/fileDownload.do?atchFileId=FILE_000000001210727&fileDetailSn=1&publicDataDetailPk=uddi:4cf4dc4c-e0e9-4aee-929e-b2a0431bf03e

    위 주소의 결과는 아래와 같습니다.

    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
    
    <body>
    
        <input type="hidden" name="exception" value="요청한 파일을 찾을 수 없습니다." style="display:none"/>
    
    </body>
    </html>
    

    보다시피 잘못된 url 일 가능성이 큽니다.

    • 이런 URL을 거르는 코드는 content disposition 이 없을 경우 with open('h:/url.csv') as f 하지 않고 print(URL,'실패') 이런식으로 짜면 될까요?? 알 수 없는 사용자 2017.7.21 10:59
    • 방법이야 다양합니다. 잘하려고 하면 끝도 없죠. 정영훈 2017.7.21 21:51
    • 코드를 수정해보고 질문에 추가했습니다. 저렇게 코드를 작성하면 content disposition 이 없는 URL들을 정상적으로 쉘에 출력해줍니다. 그러나 모든 URL에서 다운받고 에러가난 url들을 출력을 해주면 좋은데 content disposition이 없는 url에서 실행이 멈추고 url을 알려줍니다. 코드가 계속 실행되게 하고 마지막에 에러가는 url들을 한번에 출력하게 하라면 어떤식으로 수정을 해야할까요? 알 수 없는 사용자 2017.7.24 04:27
  • import cgi
    import requests
    
    
    TARGET_DIR = 'C:/'
    
    def downloadURLResource(url):
        r = requests.get(url, stream=True)
        if r.status_code == 200 and 'content-disposition' in r.headers:        # 상태가 ok이면서 content-disposition 값이 존재할 때
            targetFileName = requests.utils.unquote(cgi.parse_header(r.headers['content-disposition'])[1]['filename'])
            with open("{}/{}".format(TARGET_DIR, targetFileName), 'wb') as f:
                for chunk in r.iter_content(chunk_size=1024):
                    f.write(chunk)
            return True                # content-disposition 존재하면 True
        else:
            return False               # content-disposition 존재하지 않거나 404이었거나...등등 오류시
    
    
    with open('h:/urlcsv.csv') as f:
        failItems = filter(lambda i:i[1] == False, {url.rstrip():downloadURLResource(url.rstrip()) for url in f.readlines()}.items())        # url:함수 리턴값 형태의 딕셔너리를 만들고 결과가 False 만 필터한다.
        list(map(print, failItems))        # 출력
    

    테스트해보세요.

    • 감사합니다. 작성해주신 코드를 214개의 라인에 대해서 실행해보니 203번째에 에러가 나지않고 214개를 다 거친후에 쉘에서 오류가 난 URL을 출력해줍니다. 그러나 여전히 211개의 파일만이 다운로드 되었으며, 에러 URL은 1개만 출력되는것으로 보아 2개는 404와 Content-disposition가 아닌 무슨 문제 때문에 다운로드 되지 않는지 모르겠네요. 이 경우에는 214개 라인의 URL을 수동으로 쳐보며 다운로드 되지 않은 URL을 확인 해보는게 나을까요? 알 수 없는 사용자 2017.7.24 14:32
    • 로그를 남겨서 확인하는 방법이 효율적입니다. https://docs.python.org/3.6/howto/logging.html 정영훈 2017.7.24 15:06
    • 댓글 주시는 동안 한번 일일히 해봤는데 좀 이상한 점을 발견했습니다. 100개를 한번에 다운받으려고 했을때 99개가 받아지고 1개가 빈 상태로 작동이 완료되었는데, 이 100개를 50개씩 두개의 csv 파일로 나누어서 받으면 정상적으로 50개씩 2개, 100개가 받아집니다. 알 수 없는 사용자 2017.7.24 15:19
    • 전체 url을 올려봐주세요 정영훈 2017.7.24 15:22
    • 204 개의 라인으로 이루어진 CSV 파일이라 여기에 올리면 길고 복잡해질 것 같아서 실례가 안된다면 저번에 알려주신 이메일로 보내드리겠습니다. 알 수 없는 사용자 2017.7.24 15:27
    • 그리고 6만개를 돌리면서 중간에 한번씩 멈추는경우가 있습니다. 다운로드가 1시간이상 진전이 없으며 그렇다고 해서 완료가 된 상태도 아닌 경우 실행 중인 상태를 kill 해버리면 에러 url을 받지 못하게 됩니다. 이 경우 제가 작성한 코드로 진행하면서 Content disposition 에러로 멈출때마다 해당 라인을 기준으로 위의 라인을 다 삭제하고 다시 진행하는게 맞을까요, 아니면 달리 방법이 있을까요? 멈추는 이유도 불분명해서 마땅한 해결책을 찾이 못하겠습니다. content disposition 과 기타(404등) 문제로 다운로드 안되는 url들을 기록하며 마지막에 한번에 딕셔너리 안에 기록하여 출력하는게 제일 이상적인데, 어떤 문제인지 중간에 다운로드가 진행이 안되는 상황이 종종있습니다. 알 수 없는 사용자 2017.7.24 16:30
    • 주관적인 판단에는 그런 모든 것들을 처리하시기에는 힘드실 듯 합니다. 디버깅을 위해서는 로깅처리는 필수이며 6만건을 효율적으로 처리하기 위해서는 병렬처리가 필수 입니다. 정영훈 2017.7.24 17:25
  • 아래와 같이 로그를 남겨보세요.

    어디서 문제가 발생했는지 명시적으로 알 수 있어요.

    import cgi
    import requests
    import logging
    logging.basicConfig(level=logging.DEBUG)
    
    
    TARGET_DIR = 'C:/'
    
    def downloadURLResource(index, url):
        logging.debug('index {}'.format(index))
        r = requests.get(url, stream=True)
        if r.status_code == 200 and 'content-disposition' in r.headers:
            targetFileName = requests.utils.unquote(cgi.parse_header(r.headers['content-disposition'])[1]['filename'])
            logging.debug('index {} File downloding {}. '.format(index, targetFileName))
            with open("{}/{}".format(TARGET_DIR, targetFileName), 'wb') as f:
                for chunk in r.iter_content(chunk_size=1024):
                    f.write(chunk)
            return True
        else:
            logging.debug('index {} URL: {} failed.'.format(index, url))
            return False
    
    
    with open('h:/data.csv') as f:
        failItems = filter(lambda i:i[1] == False, {url.rstrip():downloadURLResource(index, url.rstrip()) for index, url in enumerate(f)}.items())
        list(map(print, failItems))
    
    
    DEBUG:root:index 0
    DEBUG:requests.packages.urllib3.connectionpool:Starting new HTTP connection (1): www.data.go.kr
    DEBUG:requests.packages.urllib3.connectionpool:http://www.data.go.kr:80 "GET /dataset/fileDownload.do?atchFileId=FILE_000000001212856&fileDetailSn=1&publicDataDetailPk=uddi:07b44140-4ded-40e6-946e-c03b317b833e HTTP/1.1" 200 10139648
    DEBUG:root:index 0 File downloding 12.5월_산업동향.hwp. 
    DEBUG:root:index 1
    DEBUG:requests.packages.urllib3.connectionpool:Starting new HTTP connection (1): www.data.go.kr
    DEBUG:requests.packages.urllib3.connectionpool:http://www.data.go.kr:80 "GET /dataset/fileDownload.do?atchFileId=FILE_000000001215215&fileDetailSn=1&publicDataDetailPk=uddi:07c50b21-6f66-4943-b405-8fdf4adec661 HTTP/1.1" 200 31232
    DEBUG:root:index 1 File downloding 2013-09.xls. 
    DEBUG:root:index 2
    DEBUG:requests.packages.urllib3.connectionpool:Starting new HTTP connection (1): www.data.go.kr
    DEBUG:requests.packages.urllib3.connectionpool:http://www.data.go.kr:80 "GET /dataset/fileDownload.do?atchFileId=FILE_000000001215878&fileDetailSn=1&publicDataDetailPk=uddi:07c9308d-3382-48ca-bafe-ad3990342e77 HTTP/1.1" 200 17920
    DEBUG:root:index 2 File downloding 공무소(대행업체)지정현황(2014년도).hwp. 
    
    • 항상 친절하게 답변 해주셔서 정말 감사합니다. 학교에서는 파이썬을 그래프나 간단한 전처리 위주로만 배우다가 현장실습을 나와 실무를 하며 막히는 부분이 많았는데 덕분에 이해도 가며 많이 배워갑니다. Python에서의 병렬처리에 대해 검색해보니 CPU의 코어 수 만큼 병렬 처리 하는거군요. Python Parallel은 코드를 완성하고 다운받아서 시도를 해보도록 하겠습니다. 우선 올려주신 링크을 읽으며 로깅에 대해 알아보고, 올려주신것과 같이 로깅을 하여 다운로드 되지 않은 파일들은 왜 안되었는지 알아보도록 하겠습니다. 알 수 없는 사용자 2017.7.24 17:59

답변을 하려면 로그인이 필요합니다.

프로그래머스 커뮤니티는 개발자들을 위한 Q&A 서비스입니다. 로그인해야 답변을 작성하실 수 있습니다.

(ಠ_ಠ)
(ಠ‿ಠ)