?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄 첨부
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄 첨부
Extra Form
라이선스 MIT

안녕하세요?


일부 지방에는 비가 많이 왔다고 하던데 비 피해는 없으셨는지요?



네이버 모바일 이미지 검색(DataLab, https://m.search.naver.com/search.naver)에서 검색된 기사들의 URL을 얻고


그 URL을 통해서 기사 본문에 접근하여 이미지를 멀티스레드로 다운받은 후에 


Pillow 라이브러리를 이용하여 세로로 하나의 파일로 병합(merge)하는 스크립트를 작성하였습니다.



네이버의 기사에서는 최대한 본문의 모든 파일을 다운받는 방향으로 하였고,


다른 신문사의 기사에서는 meta태그에서 이미지를 다운받는 방법을 택했습니다.


requests 모듈로 파일을 다운받는 부분은 멀티스레드를 이용하여 I/O 바운드 처리를 하였습니다.


pillow를 이용한 이미지 처리를 연습하는 것에 중점을 두고 스크립트를 작성했네요.


부족한 점이 많지만 일단 스크립트를 올립니다 ㅠㅠ



import sys, requests, urllib.request, os, time
from bs4 import BeautifulSoup
from PIL import Image, ImageFile
from concurrent.futures import ThreadPoolExecutor


## 이미지 병합 파트 ##

def img_processing(img_list): # 이미지 병합 처리 함수
    images = map(Image.open, img_list)
    widths, heights = zip(*(i.size for i in images))
    max_width = max(widths)
    total_height = sum(heights) + (len(img_list)-1)*50 # 이미지 길이의 총합에 50px씩 여유를 줍니다.
   
    new_im = Image.new('RGB', (max_width, total_height), (255, 255, 255)) # 바탕은 흰색입니다.
    images = map(Image.open, img_list)
    y = 0
    for im in images:
        x = int((max_width - im.size[0])/2) # 이미지의 x축을 센터에 맞춰줍니다.
        new_im.paste(im, (x, y))
        y += im.size[1]+50
    return new_im


def merge_image_main(img_list, directory): # 이미지 관련 메인함수
    complete_image = img_processing(img_list)
    file_name = directory + '\\merge ' + directory.split('\\')[-1] + ' x' + str(len(img_list)) + '.jpg'
    complete_image.save(file_name, quality=95) # 해상도를 95로 지정했습니다.
    print('Success : ' + file_name)


## 이하 파싱 파트 ##

def bs(url): # requests 모듈과 beatifulsoup 모듈로 사이트의 html을 불러오는 함수
    headers = {'Accept-Language': 'ko-KR,ko;q=0.9,en-US', 'User-Agent': 'Mozilla/5.0 (Linux; Android 8.1.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3384.0 Mobile Safari/537.36'}
    req = requests.get(url=url, headers=headers, timeout=5)
    html = req.text
    soup = BeautifulSoup(html, 'html.parser')
    return soup


def parse_naversearch(keyword): # 네이버 검색결과에서 a태그를 추출하는 함수
    search_url = 'https://m.search.naver.com/search.naver?where=m_image&sm=mtb_jum&query=' + keyword
    soup = bs(search_url)
    target_div = soup.find('div', {'class':'photo_grid _grid'})
    news_urls = target_div.select('a')
    return news_urls


def get_article_url(keyword): # 기사 본문의 URL을 얻어오는 함수
    search_url = 'https://m.search.naver.com/search.naver?where=m_image&sm=mtb_jum&query=' + keyword
    soup = bs(search_url)

    div_time_tag = soup.find('div', {'class':'timeline_header'})
    article_date = div_time_tag.select_one('time').text.replace('.', '')[2:]
   
    target_div = soup.find('div', {'class':'photo_grid _grid'})
    news_urls = target_div.select('a')

    naver_urls = []
    other_urls = []
    for news_url in news_urls:
        if 'naver' in news_url['href']:
            changed_url = 'https://entertain.naver.com/read?' + news_url['href'].split('?')[-1]
            naver_urls.append(changed_url)
        else:
            other_urls.append(news_url['href'])
    return naver_urls, other_urls, article_date


def download_naver_img(naver_urls, dir): # 네이버 기사의 이미지를 다운받는 함수
    naver_img_urls = []
    executer1 = ThreadPoolExecutor(max_workers=4)
    for naver_url in naver_urls:
        soup = bs(naver_url)
        count = 1
        while True:
            id_name = 'img' + str(count)
            try: # 네이버 기사에서 img1, img2 ... 이런 식으로 id가 붙는 이미지를 모두 찾습니다.
                target_img = soup.find('img', {'id':id_name})
                if target_img:
                    print(target_img['src'])
                    naver_img_urls.append(target_img['src'])
                    count += 1
                else:
                    break
            except:
                print('Failed to get image URL.')
                break
    for url in naver_img_urls:
        executer1.submit(file_download, url, dir) # 멀티스레드로 다운받습니다.
    print('네이버 : ' + str(len(naver_img_urls)) + '개')
    return naver_img_urls


def get_image_size(url): # 이미지 사이즈를 구하는 함수
    file = urllib.request.urlopen(url)
    parse = ImageFile.Parser()
    while True:
        data = file.read(1024)
        if not data:
            break
        else:
            parse.feed(data)
            if parse.image:
                width = parse.image.size[0]
                return width
    file.close()
    return 'Error!'


def download_other_img(other_urls, dir): # 다른 신문사의 이미지를 다운받는 함수
    executer2 = ThreadPoolExecutor(max_workers=4)
    other_img_urls = []
    for other_url in other_urls:
        soup = bs(other_url)
        try:
            tags = soup.find_all('meta', {'property':'og:image'}) # meta태그에서 이미지를 검색합니다.
            downloaded = ''
            for tag in tags:
                if tag['content'] and tag['content'] != downloaded: # 중복된 다운로드를 피합니다.
                    try:
                        width = get_image_size(tag['content'])
                        if width > 300: # 이미지 가로사이즈가 300 이상인 경우만 다운받습니다.
                            other_img_urls.append(tag['content'])
                            downloaded = tag['content']
                            print(downloaded)
                        for url in other_img_urls:
                            executer2.submit(file_download, url, dir)
                    except:
                        print('네이버 이외의 사이트에서 파싱에 실패했습니다.')
        except:
            print('네이버 이외의 사이트에서 파싱에 실패했습니다.')
    print('네이버 이외의 사이트 : ' + str(len(other_img_urls)) + '개')
    return other_img_urls


def file_download(url, directory): # requests 모듈을 이용하여 파일을 다운받는 함수
    headers = {'Accept-Language': 'ko-KR,ko;q=0.9,en-US', 'User-Agent': 'Mozilla/5.0 (Linux; Android 8.1.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3384.0 Mobile Safari/537.36'}
    response = requests.get(url=url, headers=headers, timeout=5)
    filename = url.split('/')[-1].split('?')[0]
    path_file_name = directory + '\\' + filename
    if response.status_code == 200:
        with open(path_file_name, 'wb') as f:
            f.write(response.content)
    return


def main(keyword): # 메인함수
    article_urls = get_article_url(keyword)
    directory = os.getcwd() + '\\' + keyword + ' - ' + article_urls[2]
    if not os.path.exists(directory):
        os.makedirs(directory) # 날짜와 키워드를 조합하여 폴더를 생성합니다.
    naver_img_list = download_naver_img(article_urls[0], directory)
    other_img_list = download_other_img(article_urls[1], directory)
    total_list = naver_img_list + other_img_list
    print('총' + str(len(total_list)) + '개의 파일을 다운로드하였습니다.')
    file_list = []
    for name in total_list:
        file_list.append(directory + '\\' + name.split('/')[-1].split('?')[0])
    time.sleep(1) # 잠깐 쉬지 않으면 다운로드가 완료되기 전에 이미지 처리로 넘어가서 에러가 발생할 수 있습니다.
    merge_image_main(file_list, directory)


if __name__ == "__main__":
    main('itzy')



실행하면 다음과 같이 폴더가 생성된 후 파일이 다운로드되고, 모든 파일이 병합된 이미지가 생성됩니다.


원래는 '방탄소년단'으로 검색했으나 90개가 넘는 이미지가 다운로드되어 


Pillow가 처리할 수 있는 픽셀의 범위를 초과하게 되어 이미지 병합에 실패했습니다 ㄷㄷ


그래서 'itzy'를 검색어로 사용했습니다.





보시다시피 11개의 이미지가 모두 다운로드 되었음을 확인할 수 있습니다.





각 사진 사이에 50px씩 여유를 두어 한 개의 이미지 파일로 병합된 것을 확인할 수 있습니다 ^^


사진의 저작권은 네이버와 각 신문사 측에 있으니 개인적인 용도로만 사용해주시면 감사하겠습니다.



이번 스크립트를 작성하면서 느낀 점을 간략히 말씀드리려고 합니다.


1. 멀티프로세싱/멀티스레드 처리는 역시 어렵네요 ㅠㅠ


멀티스레드로 파싱한 결과를 리스트에 append하면 값이 안 넘어오네요.


그렇다면 각 스레드마다 파일 다운까지 완료해야 하는데 전반적인 함수의 구조를 다시 짜지 않으면 힘들겠더군요 ㅠㅠ


이 부분은 더 공부를 해야될 것 같습니다.



2. 네이버 모바일 이미지 검색 화면에서 과거의 모든 이미지를 검색하려면 스크롤을 해야하기 때문에


결국 selenium을 사용하는 방법밖에 없을 것 같네요.


그런데 selenium을 사용하면 처리속도가 지금보다 훨씬 떨어지겠죠 ㅠㅠ



3. 전반적으로 제 파싱방법이 사이트 리뉴얼에 취약한데 


인강에서 뵐 수 있는 직업적으로 파싱을 하시는 분들은 어떤 방법을 택하는지 궁금하네요 ^^



네이버와 디시인사이드를 파싱해보니 파싱이란 어떤 것인지 조금이나마 느껴보았네요.


앞으로는 파싱한 결과를 DB로 직접 넣는 것에 포커스를 두고 연습해야 될 것 같아요.


그럼 스포어 회원님들께서도 편안한 밤 되시고 내일 즐거운 불금 되세요 ^^


부족한 글을 읽어주셔서 감사합니다!


  • profile
    라엘 2019.07.12 13:42

    멀티스레드까지 하시다니! 역시 고급개발자 이니스프리님!

    방탄소년단도 결과가 나오게 해주세요!

  • profile
    라엘 2019.07.12 13:45

    고오급 개발자가 되실 이니스프리님을 위해 몇가지 더 살펴보자면,

    뉴스 이미지는 다분히 상업성 이미지라서 계속 가져다가 사용하면, 고의적으로 퍼가서 사용한게 되어서(고의기수범) 법적 문제가 될 수 있다는 것과,

    멀티스레드를 sleep 1 과 사용했는데, 이미지(들)이 1초 이내에 모두 다운로드 되지 않으면 타이밍 이슈가 생길 수 있는 것과,

    이미지는 용량이 크기 때문에 개수나 화질에 따라, 메모리 오류(crash)가 발생할 수 있다는 점이 있겠네요.

  • profile
    이니스프리 2019.07.12 16:38

    ^^ 진정한 개발자 라엘 님께서 그렇게 말씀해주시니 저같이 취미로 코딩을 배운 사람은 몸둘 바를 모르겠네요~


    저같은 사람은 코더가 아니라 야메 짜집퍼라고 하더군요 ㅎㅎ


    솔직히 이 정도 수준까지가 제가 개발자분들을 흉내낼 수 있는 한계인 것 같네요.
     

    사실 좀 더 깊게 공부해보고 싶었는데 제 능력 밖이라는 것을 깨달았습니다 ㅠㅠ
     
     
    1.  
     
    말씀하신 타이밍 이슈에 대해서는 selenium의 Explicit Waits 같은 역할을 하도록 코딩을 해야할텐데  
     
    일단 제가 파이썬을 공부한 수준에서는 무리인 것 같네요.
     
    기회가 닿는다면 C++을 공부해보고 싶지만, 아마 제 평생 그럴 기회는 없을 것 같구요 ㅠㅠ
     
    또한 이미지 용량에 따른 메모리 오류에 대해서는 제 컴퓨터를 업그레이드 하는 방법이


    제가 할 수 있는 최선의 해결책일까요?? ^^
     
     
    2.  
     

    웹 스크래이핑과 관련된 법적인 문제와 대해서는 조금 더 주의를 하도록 하겠습니다.


    저는 개발에 대해서는 잘 알지 못하니 제 본업으로 돌아와서 말씀을 드리면요 ^^
     
    형사소송에서의 거증책임의 문제에 대해 말씀드리면 검사에게 고의를 입증할 책임이 있습니다.
     
    저는 스크립트를 작성하는 과정에서 제 개인 PC에 이미지를 저장했다가 삭제하는 작업을 수십 번 거쳤을 뿐입니다. 


    이 스크립트를 이용하여 뉴스 이미지를 퍼가서 사용한 적은 없고, 사용할만한 사이트를 운영하지도 못하고 있네요 ㅠㅠ
    (물론 이 글을 작성하는 과정에서 결과적으로 이미지를 퍼오기는 했네요) 


    검찰 측에서 제 컴퓨터와 KT를 압수수색한다고 가정하더라도 


    제가 이미지를 사용한 내역이 전혀 없기 때문에 검사 측에서 고의의 입증이 어려울 것으로 생각됩니다.
     
    다만 타인이 이 스크립트를 이용하는 것에 대한 방조범이 문제될 여지는 있겠죠 ㄷㄷ
     
    만약 정범 또는 종범이 성립하면 '계속 가져다가 사용'하는 행위에 대해서는 영업범 등 상습범의 성립 여부가 문제됩니다.
     
    물론 말씀하신대로 '고의'를 입증하는 데 간접증거가 될 수도 있겠죠.
     
     
    그럼 라엘 님께서도 뜻깊은 초복 되시고 즐거운 주말 되시기를 기원합니다!
     
    라엘 님 홈페이지에서나, 스포어에서나 항상 감사드립니다 ^^
     
    - 이니스프리 올림

  • profile
    라엘 2019.07.12 17:18

    아니에요 ㅜㅜ 이니스프리님이 높게 봐주셔서 그렇지, 저는 초보개발자입니다 ㅜㅜ

    이미지 사용 관련은, 아시겠지만, 개인 PC 로는 사진 100만장 받아도 위법이 아니에요. 저작권은 배포 기준으로 판단하는 것이고,

    웹스크래퍼 프로그램이 주로 "주기적으로 수집해서 (어느 곳에) 게시하는" 용도로 쓰고, 이렇게 "사용"할 경우에만 문제가 될 수 있는 것이죠.

    특히나 웹에 게시할 경우 "공중송신/공중배포"가 쉽게 성립이 되서 문제 되기가 더 쉽습니다. (이 경우 이의신청을 받고, 이의 신청을 받았을 경우 저작물을 빠르게 삭제하면 고의성이 조각됩니다. 큰 커뮤니티에서 아무 글이나, 신고기능을 이용하면 그 글은 거의 삭제된다고 생각하면 됩니다.)


    오늘 쌀쌀한 복날인데 따뜻하게 몸보신하시고 좋은 저녁되세요!

    - 라엘 드림

  • profile
    이니스프리 2019.07.12 17:31

    오오~ 저작권법의 실무에 대한 좋은 말씀 감사드립니다!

    학교에서 저작권법과 관련된 강의를 6학점 수강한 것이 전부일 뿐

    제가 저작권 관련 업무를 다루지 않아서 솔직히 잘 모르거든요.

    한 때 지적재산권법 박사과정에 진학하고 싶어했으니 저작권법을 더욱 더 잘 준수해야겠네요 ^^


    라엘 님 같은 개발자 분들이 계시기 때문에 

    저같은 사람도 컴퓨터와 스마트폰을 마음껏 사용할 수 있다는 사실에 다시 한 번 감사드립니다!

    그럼 즐거운 불금 저녁 되세요 :)

  • profile
    title: 황금 서버 (30일)humit 2019.07.14 00:47
    1.
    concurrent.futures 모듈의 wait 함수를 사용하면 해당 인자로 전달된 내용이 전부 완료가 될 떄까지 기다리게 할 수 있습니다.
    즉 ThreadPoolExecutor 객체에 submit 메서드의 결과 값을 따로 리스트에 저장을 한 다음에 wait 메서드의 인자 값으로 넣어주시면 됩니다.

    참고로 옵션에 따라서 처음 완료가 되었을 경우 혹은 처음 예외가 발생했을 경우, 아니면 모두 실행이 완료가 되었을 경우에 값을 반환하게 되어 있습니다.
    https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.wait

    2.
    저렇게 무한 스크롤로 된 경우가 훨씬 구현하기가 편리합니다. 보통 웹 사이트에서 무한 스크롤로 구현이 되어 있는 경우 offset과 limit 값을 전달해줘서 AJAX로 요청을 보내서 가져온 결과를 가공해 이어 붙이는 식으로 되어 있습니다.

    참고로 네이버의 경우에는 XHR로 보내는게 아니고 JS로 요청을 보내서 직접 자바스크립트를 호출하는 방식으로 되어 있네요.

    https://s.search.naver.com/imagesearch/instant/search.naver?where=m_image&json_type=7&query=%EC%9C%A4%ED%95%98&nlu_query=%7B%22timelineQuery%22%3A%22%EC%9C%A4%ED%95%98%22%7D&rev=40&_callback=imageSearchTimeline&display=10&start=21&_=1563030502855

    URL 형태를 보시면 display로 총 몇 개의 원소를 가져올지를 명시하고 start로 몇 번째 원소부터 가지고 올지를 명시하도록 되어 있습니다.

    3.
    사이트가 리뉴얼이 되면 파싱 방식이 달라지기 때문에 새롭게 사이트 구조를 분석하는 방법밖에 없습니다. 다만 해당 사이트가 동적으로 데이터를 로딩해오고, 해당 API의 구현이 바뀌지 않는 경우에는 그대로 사용이 가능합니다.

    PS. 참고로 이미지 주소 뒤쪽에 있는 type=b360 을 없애면 좀 더 높은 화질의 이미지를 받을 수 있으며 해당 픽셀 사이즈의 경우 data-with와 data-height 의 값과 일치합니다. 즉 굳이 파일의 일부를 받아서 이미지 파일의 픽셀 크기를 확인할 필요가 없습니다 :)
  • profile
    이니스프리 2019.07.14 18:09
    허걱 바쁘신데 상세한 답변을 달아주셔서 정말 감사합니다!!

    1.
    제가 파이썬 크롤링책을 보면서 작성해서 그런지 wait 함수에 대한 내용까지는 그 책에 언급이 없더군요 ㅠㅠ
    말씀해주신 공식문서를 참고해서 수정해보겠습니다!

    2.
    제가 동적 웹페이지를 접하면 selenium에 의존하는 경향이 있는 것 같네요.
    예전에 어떤 분께서 동적 웹페이지의 구조를 파악하면 오히려 더 파싱이 쉽다는 취지로 말씀하셨는데,
    당시에는 제가 그 뜻을 전혀 이해하지 못했거든요.
    humit 님의 블로그와 이번 댓글을 보니 동적 웹페이지를 접하더라도
    좀 더 구조를 분석해보고 최대한 requests로 처리하는 연습을 해야겠네요.

    3.
    이미지 서치 결과에 없는 이미지도 네이버 뉴스기사 본문에 있는 경우가 간혹 있어서
    저는 URL만 불러오고 네이버 뉴스기사 본문을 다시 불러와서
    img1, img2... 이런 형태의 id가 붙는 img 태그의 src를 모두 다운받는 방식을 택했거든요.
    다만 네이버가 아닌 경우에는 이런 방식으로 접근할 수가 없어서
    네이버 뉴스가 아닌 경우에는 이미지 파일의 픽셀 크기를 확인하도록 했는데요.
    humit 님께서 말씀해주신 방법을 참고해서 조금 더 간결하게 작성해보도록 하겠습니다!

    그럼 바쁘시겠지만 남은 주말 즐겁게 보내세요 ^^
  • profile
    이니스프리 2019.07.15 23:21
    월요일이라 바쁘셨을텐데 오늘 하루는 잘 마무리하셨는지요? ^^

    https://s.search.naver.com/imagesearch/instant/search.naver?where=m_image&json_type=7&query=%EC%9C%A4%ED%95%98&nlu_query=%7B%22timelineQuery%22%3A%22%EC%9C%A4%ED%95%98%22%7D&rev=40&_callback=imageSearchTimeline&display=10&start=21&_=1563030502855

    크롬 개발자도구의 네트워크탭에서 말씀하신 URL을 찾는 방법에 대해 여쭤보는데요~


    예전에 humit 님께서 블로그에 파싱과 관련해서 글을 올려주셨고

    동적 웹페이지 파싱과 관련해서는 주로 XHR에 관한 것이었는데요.

    엠넷 차트 파싱과 관련하여 'JS로 필터링하면 hourly라는 의심스러운 응답이 있다'고 말씀하셨는데요.

    아직은 제가 완전 초보라서 그런지 파일명만 보고서는 의심스러운 응답이 무엇인지 감이 잡히지 않네요 ㅠㅠ


    네이버 모바일 이미지 검색의 경우에는 개발자도구의 네트워크탭을 켜놓고 아래로 스크롤하면

    스크롤할 때마다 Waterfall 항목에서 새로 받아오는 항목이 있는 것을 볼 수 있는데요.

    무한 스크롤인 경우에는 대체로 이런 방식으로 확인하면 되나요?

    XHR이긴 하지만 인스타그램도 이렇게 하니깐 확인이 가능하긴 하더군요.


    그럼 이번 한주도 화이팅하시고 내일도 비온다는데 비 조심하세요~

    항상 감사드립니다!!
  • profile
    title: 황금 서버 (30일)humit 2019.07.15 23:50
    네 그런 식으로 데이터가 새로 추가가 되는 형태로 이루어져 있다면 Network 탭에서 새로운 요청이 어떤 것이 가는지 여부를 확인하시면 됩니다.

    그리고 거기에서 응답이 될 만한 요청을 찾은 다음 해당 요청을 보낼때 어떤식으로 보내면 되는지 알아내는 방식과 같이 역순으로 진행하시면 되겠습니다.
  • profile
    이니스프리 2019.07.16 00:07
    덕분에 많이 배웠습니다! 감사합니다!!
    말씀해주신 방법으로 무한 스크롤 사이트 몇 군데에서 시도해봤더니 조금이나마 알 것 같네요 ^^
    상대적으로 쉽게 찾아지는 사이트도 있고, 그렇지 않은 사이트도 있네요~
    요청을 어떻게 보내야하는지에 대해서도 좀 더 공부해볼게요 :)
    그럼 humit 님께서도 굿밤 되세요~
    다시 한 번 진심으로 감사드립니다!
  • profile
    이니스프리 2019.11.17 00:44

    라엘 님과 humit 님께서 말씀해주신 내용을 바탕으로 하여 부족한 부분을 보완했습니다.


    concurrent.futures의 wait()를 이용하니 time.sleep()을 이용하지 않아도 잘 작동하는군요~ ^^


    import sys, requests, urllib.request, os, time
    from bs4 import BeautifulSoup
    from PIL import Image, ImageFile
    from concurrent.futures import ThreadPoolExecutor, wait
    
    
    # 이미지 병합 파트
    
    def img_processing(img_list):
        images = map(Image.open, img_list)
        widths, heights = zip(*(i.size for i in images))
        max_width = max(widths)
        total_height = sum(heights) + (len(img_list)-1)*50
       
        new_im = Image.new('RGB', (max_width, total_height), (255, 255, 255))
        images = map(Image.open, img_list)
        y = 0
        for im in images:
            x = int((max_width - im.size[0])/2)
            new_im.paste(im, (x, y))
            y += im.size[1]+50
        return new_im
    
    
    def merge_image_main(img_list, directory, down_count):
        complete_image = img_processing(img_list)
        file_name = directory + '\\merge ' + directory.split('\\')[-1] + ' x' + str(down_count) + '.jpg'
        complete_image.save(file_name, quality=95)
        print('Success : ' + file_name)
    
    
    # 이하 파싱 파트
    
    def bs(url):
        headers = {'Accept-Language': 'ko-KR,ko;q=0.9,en-US', 'User-Agent': 'Mozilla/5.0 (Linux; Android 8.1.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3384.0 Mobile Safari/537.36'}
        req = requests.get(url=url, headers=headers, timeout=5)
        html = req.text
        soup = BeautifulSoup(html, 'html.parser')
        return soup
    
    
    def parse_naversearch(keyword):
        search_url = 'https://m.search.naver.com/search.naver?where=m_image&sm=mtb_jum&query=' + keyword
        soup = bs(search_url)
        target_div = soup.find('div', {'class':'photo_grid _grid'})
        news_urls = target_div.select('a')
        return news_urls
    
    
    def get_article_url(keyword):
        search_url = 'https://m.search.naver.com/search.naver?where=m_image&sm=mtb_jum&query=' + keyword
        soup = bs(search_url)
    
        div_time_tag = soup.find('div', {'class':'timeline_header'})
        article_date = div_time_tag.select_one('time').text.replace('.', '')[2:]
       
        target_div = soup.find('div', {'class':'photo_grid _grid'})
        news_urls = target_div.select('a')
    
        naver_urls = []
        other_urls = []
        for news_url in news_urls:
            if 'naver' in news_url['href']:
                changed_url = 'https://entertain.naver.com/read?' + news_url['href'].split('?')[-1]
                naver_urls.append(changed_url)
            else:
                other_urls.append(news_url['href'])
        return naver_urls, other_urls, article_date
    
    
    def download_naver_img(naver_urls, dir):
        naver_img_urls = []
        executer1 = ThreadPoolExecutor(max_workers=4)
        future1 = []
        for naver_url in naver_urls:
            soup = bs(naver_url)
            count = 1
            while True:
                id_name = 'img' + str(count)
                try:
                    target_img = soup.find('img', {'id':id_name})
                    if target_img:
                        print(target_img['src'])
                        naver_img_urls.append(target_img['src'])
                        count += 1
                    else:
                        break
                except:
                    print('Failed to get image URL.')
                    break
        for url in naver_img_urls:
            future1.append(executer1.submit(file_download, url, dir))
        print(wait(future1))
        print('네이버 : ' + str(len(naver_img_urls)) + '개')
        return naver_img_urls
    
    
    def get_image_size(url):
        file = urllib.request.urlopen(url)
        parse = ImageFile.Parser()
        while True:
            data = file.read(1024)
            if not data:
                break
            else:
                parse.feed(data)
                if parse.image:
                    width = parse.image.size[0]
                    return width
        file.close()
        return 'Error!'
    
    
    def download_other_img(other_urls, dir):
        executer2 = ThreadPoolExecutor(max_workers=4)
        other_img_urls = []
        for other_url in other_urls:
            soup = bs(other_url)
            try:
                tags = soup.find_all('meta', {'property':'og:image'})
                #regex = re.compile('^(http|https).+(jpg|gif|png|jpeg|gif)')
                downloaded = ''
                future2 = []
                for tag in tags:
                    if tag['content'] and tag['content'] != downloaded:
                        try:
                            width = get_image_size(tag['content'])
                            if width > 250:
                                other_img_urls.append(tag['content'])
                                downloaded = tag['content']
                                print(downloaded)
                            for url in other_img_urls:
                                future2.append(executer2.submit(file_download, url, dir))
                            print(wait(future2))
                        except:
                            print('네이버 이외의 사이트에서 파싱에 실패했습니다.')
            except:
                print('네이버 이외의 사이트에서 파싱에 실패했습니다.')
        print('네이버 이외의 사이트 : ' + str(len(other_img_urls)) + '개')
        return other_img_urls
    
    
    def file_download(url, directory):
        headers = {'Accept-Language': 'ko-KR,ko;q=0.9,en-US', 'User-Agent': 'Mozilla/5.0 (Linux; Android 8.1.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3384.0 Mobile Safari/537.36'}
        response = requests.get(url=url, headers=headers, timeout=5)
        filename = url.split('/')[-1].split('?')[0]
        path_file_name = directory + '\\' + filename
        if response.status_code == 200:
            with open(path_file_name, 'wb') as f:
                f.write(response.content)
        return
    
    
    def main(keyword):
        article_urls = get_article_url(keyword)
        directory = os.getcwd() + '\\' + keyword + ' - ' + article_urls[2]
        if not os.path.exists(directory):
            os.makedirs(directory)
        naver_img_list = download_naver_img(article_urls[0], directory)
        other_img_list = download_other_img(article_urls[1], directory)
        total_list = naver_img_list + other_img_list
        down_count = len(os.listdir(directory))
        print('총' + str(len(total_list)) + '개의 파일 중 ' + str(down_count) + '개의 파일을 다운로드하였습니다.')
        if len(down_count) > 50:
            sys.exit()
        file_list = []
        for name in total_list:
            file_list.append(directory + '\\' + name.split('/')[-1].split('?')[0])
        merge_image_main(file_list, directory, down_count)
    
    if __name__ == "__main__":
        main('트와이스')



List of Articles
번호 분류 제목 글쓴이 날짜 조회 수
78 코드 세린서버에서 시도중인 백업 스크립트 입니다. 4 NoYeah 2017.06.27 856
77 자료 소셜XE / 기존 통합 로그인 스킨 V2.2 2 file NoYeah 2017.06.28 1096
76 자료 경험치 현황 위젯 6 file NoYeah 2017.06.28 918
75 자료 Gentelella 3 file NoYeah 2017.06.29 1070
74 자료 Gentelella 레이아웃에 사용가능한 가격 테이블 위젯입니다. 3 file NoYeah 2017.07.03 715
73 자료 AdminLTE용 에디터 스타일 4 file title: 은메달도다 2017.07.07 896
72 코드 엑셀파일 불러서 히스토그램 그려주는 함수 국내산라이츄 2017.08.03 888
71 코드 엑셀 읽어서 그래프 그려주는 함수 1 국내산라이츄 2017.08.03 1576
70 자료 RBGE - 이쁘고 깔끔한 에러페이지 4 file title: 열려라 맛스타의 자물쇠TVJ 2017.08.08 789
69 자료 even_move - 감성적인 에러 페이지 7 file title: 열려라 맛스타의 자물쇠TVJ 2017.08.08 924
68 자료 [XE / Rhymix] Bootstrap 패널 위젯 스타일 file title: 은메달도다 2017.08.09 722
67 자료 [1.8a] Bootstrap 'Panel' 위젯 스타일 1 file title: 은메달도다 2017.08.09 706
66 코드 Git 저장소에서 자동으로 받아 업데이트하는 쉘 스크립트 5 NoYeah 2017.09.16 859
65 자료 [Bootstrap] xeACE 레이아웃 3 title: 은메달도다 2017.09.17 761
64 자료 링크 파싱 애드온용 스킨 (트위터 스타일) 3 file SNAX 2017.10.03 656
63 자료 이게 팔릴까 - Xe/라이믹스 에러페이지 [2017-10-04] 3 file title: 열려라 맛스타의 자물쇠TVJ 2017.10.04 776
62 자료 AdBlock 접근 방지 애드온 v0.1 3 file 네모 2017.10.05 765
61 코드 브라우저 언어에 따라 다른 폴더를 사용하는 PHP 코드 4 file 네모 2017.10.10 937
60 코드 파이썬을 이용한 텔레그램 새 글 알림 (허접합니다) 4 이니스프리 2017.11.19 2562
59 코드 새 글 자동 댓글 스크립트 (AutoHotkey) 9 이니스프리 2017.11.26 3590
Board Pagination Prev 1 2 3 4 Next
/ 4