ABOUT ME

IT와 컴퓨터 관련 팁, 파이썬 등과 아파트 정보, 일상적인 경험 등의 생활 정보를 정리해서 올리는 개인 블로그

  • [Python] 파이썬 웹 크롤링 - 스크래핑 관련 유튜브 강의[나도코딩] 연습 코드 정리
    코딩 연습/코딩배우기 2021. 6. 27. 14:42
    반응형

     

    [출처] 파이썬 코딩 무료 강의 (활용편3) - 웹 크롤링? 웹 스크래핑! 제가 가진 모든 비법을 알려드리겠습니다. [나도코딩]  https://youtu.be/yQ20jZwDjTE

    "파이썬 기본편을 학습한 분들을 위한  파이썬 웹 크롤링 - 스크래핑 무료 강의"
    본 포스팅은 상기 유튜브 영상을 보면서 연습한 코드임
    다른 코드 연습 중 관련 함수 등이 필요할 경우 참고하기 위해 올려놓음

    ■ 웹 스크래핑 requests 응답

    import requests
    
    # 웹 스크래핑 requests 응답
    def requests_():
        response = requests.get('http://naver.com')
        print('응답코드 :', response.status_code)
        
        response = requests.get('http://nadocoding.tistory.com')
        response.raise_for_status()
        print('응답코드 :', response.status_code)
        # response 에러 발생한다고 가정 시 response.raise_for_status() 에러 코드 보이고 프로그램 종료시킴
        
        if response.status_code == requests.codes.ok:  # 200
            print('정상')
            
    requests_()
    
    
    [결과]
    응답코드 : 200
    응답코드 : 200
    정상

     

    ■ 스크래핑 데이터 저장해보기

    import requests
    
    # 스크래핑 데이터 저장해보기
    def mygoogle_html():
        response = requests.get('http://gogle.com')
        response.raise_for_status()
        
        # 현재 디렉토리에 mygoogle.html로 저장
        with open('./mygoogle.html', 'w', encoding='utf8') as f:
            f.write(response.text)
            
    mygoogle_html()
    

    good4me.co.kr

     

    ■ 정규표현식 연습

    import re
    
    # 정규표현식 연습
    def re_(pattern, args):
        # 어떤 (정규)식을 컴파일 할 지 정해주기 re.compile('원하는 형태')
        # .은 하나의 문자, ^는 문자열의 시작, $는 문자열의 끝
        p = re.compile(pattern)  
        
        # match()는 주어진 문자열의 처음부터 일치하는지 확인
        m = p.match(args)
        if m:  # 매치 되었을 경우
            print(f'args: {args}')
            print(f'm.group(): {m.group()}')  # 일치하는 문자열 반환
            print(f'm.string: {m.string}')  # 입력받은 문자열 그대로 출력
            print(f'm.start: {m.start()}')  # 일치하는 문자열의 시작 index
            print(f'm.end: {m.end()}')  # 일치하는 문자열의 끝 index
            print(f'm.span: {m.span()}')  # 일치하는 문자열의 시작/끝 index
        else:
            print(f'match: {args} 매치 안됨')
            
        # search()는 주어진 문자열 중에 일치하는게 있는지 확인
        s = p.search(args)
        if s:
            print(f'search: {s.group()}')
        else:
            print(f'search: {args} 매치X')
        
        # findall()는 일치하는 모든 것을 리스트 형태로 반롼
        lst = p.findall(args)
        print(f'findall(): {lst}')
        
        print()
            
    lst = ['case', 'caffe', 'care', 'careless', 'icare', 'good care cafe']
    
    for ls in lst:
        re_('ca.e', ls)
        
        
       [결과]
       args: case
    m.group(): case
    m.string: case
    m.start: 0
    m.end: 4
    m.span: (0, 4)
    search: case
    findall(): ['case']
    
    match: caffe 매치 안됨
    search: caffe 매치X
    findall(): []
    
    args: care
    m.group(): care
    m.string: care
    m.start: 0
    m.end: 4
    m.span: (0, 4)
    search: care
    findall(): ['care']
    
    args: careless
    m.group(): care
    m.string: careless
    m.start: 0
    m.end: 4
    m.span: (0, 4)
    search: care
    findall(): ['care']
    
    match: icare 매치 안됨
    search: care
    findall(): ['care']
    
    match: good care cafe 매치 안됨
    search: care
    findall(): ['care', 'cafe']

     

    ■ requests 응답이 없을 때, 추가로 headers 정보 전송하여 응답 오류 해결하기 

    import requests
    
    # requests 응답이 없을 때, 추가로 headers 정보 전송하여 응답 오류 해결하기 
    # User- Agent 확인 : https://www.whatismybrowser.com/detect/what-is-my-user-agent
    def nadocoding():
        url = 'http://nadocoding.tistory.com'
        
        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36'
        }
        response = requests.get(url, headers=headers)
        response.raise_for_status()
        
        with open('./nadocoding.html', 'w', encoding='utf8') as f:
            f.write(response.text)
            print('파일 저장 완료')
    
    nadocoding()
    

     

    ■ BeautifulSoup 활용, 스크래핑 함수들 

    import requests
    from bs4 import BeautifulSoup
    
    # BeautifulSoup 활용, 스크래핑 함수들 
    # find, next_sibling, previous_sibling, find_next_sibling, find_next_siblings
    def bs4_use():
        url = 'https://comic.naver.com/webtoon/weekday.nhn'
        response = requests.get(url)
        response.raise_for_status()
        
        soup = BeautifulSoup(response.text, 'lxml')  # html.parser
        print(soup.title)
        print(soup.title.get_text())
        print(soup.a)  # soup 객체에서 첫번째 발견되는 a element 반환
        print(soup.a.attrs)  # dict, a element의 속성 정보 출력
        print(soup.a['href'])
        print(soup.find('a', attrs={'class': 'Nbtn_upload'}))
        print(soup.find('a', {'class':'Nbtn_upload'}))
        print(soup.find('a', class_ = 'Nbtn_upload'))
        print(soup.find(attrs={'class':'Nbtn_upload'})) 
        # Nbtn_upload 속성이 하나이기 때문에 태그 지정 없이 가능
        
        print(soup.find('li', class_='rank01'))  # 인기급상승 1위
        rank01 = soup.find('li', {'class':'rank01'})
        print(rank01.a.get_text())
        
        ## li 태그가 여러 개, 형제 엘리먼트를 호출하기
        print(rank01.next_sibling)  # 아무것도 안나올 경우, 태그 간에 개행 정보가 있기 때문...
        rank02 = rank01.next_sibling.next_sibling  # next_sibling 한 번 더
        print(rank02.get_text())
        rank03 = rank02.next_sibling.next_sibling
        print(rank03.get_text())
        print(rank03.previous_sibling.previous_sibling)
        
        ## next_sibling 2번 안쓸 수 있게
        rank2 = rank01.find_next_sibling('li')  # find_next_sibling()
        print(rank2.a.text)
        
        ## 부모 태그로 가기
        print(rank01.parent)
        
        ## 형제들 가져오기
        ranks = rank01.find_next_siblings('li')  # find_next_siblings()
        print(rank01.a.text)
        for rank in ranks:
            print(rank.a.text)
            
            '''
            싸움독학-85화 : 싸움보다 어렵냐
            이번 생도 잘 부탁해-50화
            투신전생기-1화
            입학용병-34화
            약한영웅-145화
            맘마미안-100화
            곱게 키웠더니, 짐승-32화
            나만 보여!-26화
            열렙전사-2부 101화 - 마지막 거짓말
            소녀재판-64화
            '''
            
        ## 'text=' 로 찾아 가져오기
        webtoon = soup.find('a', text='노답소녀-33화')
        
    bs4_use()

     

    ■ 네이버 웹툰 전체 제목 가져오기

    import requests
    from bs4 import BeautifulSoup
    
    # 네이버 웹툰 전체 제목 가져옴 - fina_all()
    def naver_webtoon():
        url = 'https://comic.naver.com/webtoon/weekday.nhn'
        response = requests.get(url)
        response.raise_for_status()
        
        soup = BeautifulSoup(response.text, 'lxml')  # html.parser
        
        cartoons = soup.find_all('a', attrs={'class':'title'})
        print(type(cartoons))  # <class 'bs4.element.ResultSet'>
        
        for cartoon in cartoons:
            print(cartoon.get_text())  
        
    naver_webtoon()
    
    
    [결과]
    <class 'bs4.element.ResultSet'>
    신의 탑
    참교육
    뷰티풀 군바리
    파이게임
    윈드브레이커
    신입일기
    소녀의 세계
    장씨세가 호위무사
    삼매경
    앵무살수
    백수세끼
    만렙돌파
    요리GO
    칼가는 소녀
    ...
    - 이후 생략 -

     

    ■ 가우스전자 만화 제목, 링크 가져오기

    import requests
    from bs4 import BeautifulSoup
    
    # 가우스전자 만화 제목, 링크 가져오기
    def naver_webtoon():
        url = 'https://comic.naver.com/webtoon/list.nhn?titleId=675554'
        response = requests.get(url)
        response.raise_for_status()
        
        soup = BeautifulSoup(response.text, 'lxml')  # html.parser
        cartoons = soup.find_all('td', class_='title')
        # title = cartoons[0].a.get_text()
        # print(title)
        
        for cartoon in cartoons:
            title = cartoon.a.get_text()
            link = 'https://comic.naver.com' + cartoon.a['href']
            print(title, link)
        
        total_rate = 0
        cartoon_ratings = soup.find_all('div', attrs={'class':'rating_type'})
        for cartoon in cartoon_ratings:
            rate = cartoon.find('strong').get_text()
            print(rate)
            total_rate += float(rate)
        print(f'총점: {total_rate:.2f}\n평균: {(total_rate / len(cartoon_ratings)):.2f}')  # f'{:.2f}'
        
    naver_webtoon()
    
    
    [결과]
    후기 + 10년 후 가우스 https://comic.naver.com/webtoon/detail.nhn?titleId=675554&no=911&weekday=mon
    시즌4 430화 내일 봐요 https://comic.naver.com/webtoon/detail.nhn?titleId=675554&no=910&weekday=mon
    시즌4 429화 잠행 https://comic.naver.com/webtoon/detail.nhn?titleId=675554&no=909&weekday=mon
    시즌4 428화 추억 https://comic.naver.com/webtoon/detail.nhn?titleId=675554&no=908&weekday=mon
    시즌4 427화 섬세한사람 https://comic.naver.com/webtoon/detail.nhn?titleId=675554&no=907&weekday=mon
    시즌4 426화 적응 https://comic.naver.com/webtoon/detail.nhn?titleId=675554&no=906&weekday=mon
    시즌4 425화 대견 https://comic.naver.com/webtoon/detail.nhn?titleId=675554&no=905&weekday=mon
    시즌4 424화 초빙강사 https://comic.naver.com/webtoon/detail.nhn?titleId=675554&no=904&weekday=mon
    시즌4 423화 추억의 물건 https://comic.naver.com/webtoon/detail.nhn?titleId=675554&no=903&weekday=mon
    시즌4 422화 아니요 https://comic.naver.com/webtoon/detail.nhn?titleId=675554&no=902&weekday=mon
    9.98
    9.98
    9.97
    9.97
    9.97
    9.98
    9.97
    9.97
    9.97
    9.97
    총점: 99.73
    평균: 9.97

     

    ■ 쿠팡(Coupang)에서 노트북 검색결과

    import requests
    from bs4 import BeautifulSoup
    import re
    
    # 쿠팡에서 노트북 검색결과
    # https://www.coupang.com/np/search?component=&q=노트북&channel=user
    
    def coupang_search():
        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36'
        }
    
        for i in range(1, 3):
            coupang = 'https://www.coupang.com'
            url = 'https://www.coupang.com/np/search?q=%EB%85%B8%ED%8A%B8%EB%B6%81&channel=user&component=&eventCategory=SRP&trcid=&traid=&sorter=scoreDesc&minPrice=&maxPrice=&priceRange=&filterType=&listSize=36&filter=&isPriceRange=false&brand=&offerCondition=&rating=0&page={}&rocketAll=false&searchIndexingToken=&backgroundColor='.format(i)
            
            response = requests.get(url, headers=headers)
            response.raise_for_status()
            
            soup = BeautifulSoup(response.text, 'html.parser')  # 'lxml' or 'html.parser'
            # <li class='search-product search-product__ad-badge" 이지만, '광고' 글자 없으면 search-product 만 있음
            # 이럴 경우 스크래핑이 안되는 경우가 있는데, 이를 해결하는 방법으로
            # 정규식 re 모듈 사용하여 class 속성 전체를 지정하게 함. 시작을 search-product로...
            items = soup.find_all('li', attrs={'class':re.compile('^search-product')})
            print(items[0].find('div', {'class':'name'}))
            
            for item in items:
                # 광고(아이콘) 상품 제외하기
                ad_badge = item.find('span', attrs={'class':'ad-badge-text'})
                if ad_badge:
                    print('###<광고 상품> - 제외\n')
                    continue
                
                name = item.find('div', {'class':'name'}).get_text()
                # Apple 상품 제외하기 
                if [n for n in ('Apple', '애플', 'apple') if n in name]:
                    print('###<애플 상품> - 제외\n')
                    continue
                link = item.find('a', class_='search-product-link')['href']
                price = item.find('strong', {'class':'price-value'}).get_text()
                rate = item.find('em', class_='rating')
                if rate:
                    rate = rate.get_text()
                else:
                    rete = '평점없음'
                rate_cnt = item.find('span', attrs={'class':'rating-total-count'})
                if rate_cnt:
                    rate_cnt = rate_cnt.get_text()[1:-1]  # 숫자만 슬라이싱
                else:
                    rate_cnt = '평점수없음'
                print(f'{i}page > 상품명: {name}\n바로가기: {coupang + link}\n가격: {price}\n평점: {rate}\n평점수: {rate_cnt}\n')
    
    coupang_search()
    
    
    [결과]
    <div class="name">에이수스 노트북 실버 S513EA-CP129 (i5-1135G7 Iris), SSD 256GB, 윈도우 미포함, 8GB</div>
    ###<광고 상품> - 제외
    
    1page > 상품명: 델 게이밍 노트북 Phantom Grey with speckles G15 5515-DG5515-WH01DKR (라이젠7-5800H 39.6cm Win10 Home RTX3060), 윈도우 포함, NVMe 512GB, 16GB
    바로가기: https://www.coupang.com/vp/products/5619336514?itemId=9104260632&vendorItemId=76390529350
    가격: 1,599,000
    평점: None
    평점수: 평점수없음
    
    1page > 상품명: 삼성전자 플러스2 퓨어화이트 노트북 NT550XDA-K14AW (샐러론 6305 39.6cm WIN10 Pro Edu), 윈도우 포함, NVMe 128GB, 8GB
    바로가기: https://www.coupang.com/vp/products/5244543934?itemId=7416308410&vendorItemId=74707281741
    가격: 549,000
    평점: 5.0
    평점수: 166
    
    - 이후 생략 -
    
    

     

    ■ 다음(Daum) 연도별 영화 순위 웹페이지에서 이미지 다운로드하기

    import requests
    from bs4 import BeautifulSoup
    import re
    import os
    
    # 다음(Daum) 연도별 영화 순위 웹페이지에서 이미지 다운로드하기
    def daum_movie():  # 2011~2020년영화순위
    
        for year in range(2011, 2021):
            # {year}년영화순위
            url = f'https://search.daum.net/search?w=tot&q={year}%EB%85%84%EC%98%81%ED%99%94%EC%88%9C%EC%9C%84&DA=MOR&rtmaxcoll=MOR'
            response = requests.get(url)
            response.raise_for_status()
            
            soup = BeautifulSoup(response.text, 'lxml')  # html.parser
            movies = soup.find('ol', class_=re.compile('^type_plural'))  # re.compile()
            images = movies.find_all('img', attrs={'class':'thumb_img'})
            
            for idx, image in enumerate(images, 1):
                image_url = image['src']
                
                # 코드 상의 url이 '//'으로 시작하는 경우, startswith()
                if image_url.startswith('//'):
                    image_url = 'https:' + image_url
                # print(image_url)
                image_res = requests.get(image_url)
                image_res.raise_for_status()
                
                path = './' + str(year)
                if not os.path.exists(path):  # 저장할 디렉토리(폴더) 생성
                    os.mkdir(path)
                    
                with open(f'{path}/movie_{year}_{idx}.jpg', 'wb') as f:
                    f.write(image_res.content)
                print(idx)
            
    daum_movie()
    
    
    [결과]
    '프로그램 실행 디렉토리에 연도별 디렉토리 생성후 이미지가 다운로드 되어 있음'
    

     

    ■ 네이버 금융 - 국내 증시 시가총액 가져와서 .csv 파일로 저장하기

    csv 모듈로 csv 파일 쓰기
    - .csv 파일을 만들고 그 파일을 쓰기 모드로 오픈한 후 csv 파일 객체(csv.writer())에 넣는다.
    - 파일 객체는 writerow() 메서드로 list 데이터를 한 라인 추가 (for 문 사용 시 list 내 모든 요소 추가)
    - Windows의 경우 csv모듈에서 데이터를 쓸 때 각 라인 뒤에 빈 라인이 추가됨 (open()에 newline= 옵션 지정하면 됨)
    f = open('csv 파일명', 'w', encoding='utf-8', newline='')
    writer = csv.writer(f)
    writer.writerow(리스트)
    f.close()
    
    csv 모듈로 csv 파일 읽기
    - .csv 파일을 읽기 모드로 오픈한 후 csv 파일 객체(csv.reader())에 넣는다.
    - 파일 객체는 Iterator 타입의 reader 객체 리턴(리스트 타입) (for 문으로 한 라인씩 가져올 수 있음)
    f = open('csv 파일명', 'r', encoding='utf-8')
    reader = csv.reader(f)
    for line in reader:
        print(line)
    f.close()
    import requests
    from bs4 import BeautifulSoup
    import csv
    
    # 네이버 금융 - 국내 증시 시가총액 가져와서 .csv 파일로 저장하기
    def naver_stock():
        # url = 'https://finance.naver.com/sise/sise_market_sum.nhn'
        
        filename = 'naver_stock_sise.csv'
        
        # csv write 시 줄바꿈 1줄이 생김 --> newline= 주면 자동으로 줄바꿈이 생기지 않음
        # 엑셀 내 한글이 깨질 경우 'utf-8' 대신 'utf-8-sig' 사용
        f = open(filename, 'w', encoding='utf-8-sig', newline='')
        
        writer = csv.writer(f)  # csv 파일 객체
        
        # 엑셀의 각 column 제목 만들기
        title = 'N	종목명	현재가	전일비	등락률	액면가	시가총액	상장주식수	외국인비율	거래량	PER	ROE'.split('\t')
        writer.writerow(title)
        
        for page in range(1, 2):
            url = 'https://finance.naver.com/sise/sise_market_sum.nhn?&page=' + str(page)
            response = requests.get(url)
            response.raise_for_status()
            
            soup = BeautifulSoup(response.text, 'lxml')  # html.parser
            
            # table의 tr에 정보 있는 구조, table thead에도 tr 있음. tbody 얻어와서 tr을 대상으로 작업
            data_rows = soup.find('table', attrs={'class':'type_2'}).find('tbody').find_all('tr')
            
            for row in data_rows:
                columns = row.find_all('td')
                # 빈 list (개행 tr)와 list 내 \n\t 등이 있음
                if len(columns) <= 1:  # 의미없는 빈 list (개행 tr) skip
                    continue
                data = [column.get_text().strip() for column in columns]
                print(data)
                # csv에 쓸 때 리스트 형태로 넘겨줌
                # csv 파일객체 메서드 wreterow()로 데이터 쓰기
                writer.writerow(data)  
    
    naver_stock()
    
    
    [결과]
    ['1', '삼성전자', '81,600', '400', '+0.49%', '100', '4,871,343', '5,969,783', '53.61', '12,966,342', '19.59', '9.99', '']
    ['2', 'SK하이닉스', '128,500', '2,500', '+1.98%', '5,000', '935,483', '728,002', '49.53', '2,516,045', '18.35', '9.53', '']
    
    -이후 생략-
    '디렉토리 내에 엑셀 파일도 저장되어 있음'

     

    ■ 셀레니움(Selenium) 다루어보기

    # 셀레니움(Selenium) 웹드라이버 다운로드
    https://sites.google.com/a/chromium.org/chromedriver/downloads

    # 크롬 버전 확인
    Chrome://version

    from selenium import webdriver
    from selenium.webdriver.common.keys import Keys
    import time
    
    # 셀레니움(Selenium) 다루어보기
    def selenium_use():
        # 현재 경로인 경우 './chromedriver.exe' 필요 없음
        browser = webdriver.Chrome('./chromedriver.exe')  
        browser.get('http://naver.com')
        time.sleep(1)
        
        
        elem = browser.find_element_by_id('query')
        elem.send_keys('나도코딩')
        elem.send_keys(Keys.ENTER)
        time.sleep(2)
        print(elem, type(elem))
        
        # send_keys() 사용 시 생각해야 할 것!! ----------------------------
        # elem = browser.find_element_by_id('query').send_keys('나도코딩').send_keys(Keys.ENTER)
        # 또는
        # elem = browser.find_element_by_id('query').send_keys('나도코딩')
        # elem.send_keys(Keys.ENTER)
        # 이 경우, send_keys() 반환값을 받은 elem 은 NoneType이 되고, 오류 발생함
        # AttributeError: 'NoneType' object has no attribute 'send_keys' 발생
        # print(elem)  # None 
        # print(type(elem))  # <class 'NoneType'>
        
        # webdriver WebElement 객체 타입인 elem에서 send_keys() 수행토록 함
        # --------------------------------------------------------------
        
        browser.back()  # 뒤로
        browser.find_element_by_id('query').send_keys('파이썬')
        browser.find_element_by_class_name('ico_search_submit').click()
        
        time.sleep(1.5)
        browser.back()
        browser.find_element_by_class_name('link_login').click()
        time.sleep(1.5)
        browser.back()
        time.sleep(0.5)
        browser.forward()
        time.sleep(0.5)
        browser.back()
        browser.refresh()  # 새로고침
        
        browser.get('http://daum.net')
        elem = browser.find_element_by_name('q')
        elem.send_keys('나도코딩')
        elem.send_keys(Keys.ENTER)  # daum에서는 이상없이 수행됨
        time.sleep(1)
        browser.back()
        elem = browser.find_element_by_name('q')
        elem.send_keys('파이썬')
        elem_btn = browser.find_element_by_xpath('//*[@id="daumSearch"]/fieldset/div/div/button[2]').click()
        print(elem_btn, type(elem_btn))
        browser.quit()
    
    selenium_use()
    

     

    ■ 셀레니움(Selenium) 이용 네이버 자동 로그인

    from selenium import webdriver
    import time
    
    # 셀레니움(Selenium) 이용 네이버 자동 로그인
    def naver_login():
        # 현재 경로인 경우 './chromedriver.exe' 필요 없음
        browser = webdriver.Chrome('./chromedriver.exe')  
        browser.get('http://naver.com')
        time.sleep(1)
        
        browser.find_element_by_class_name('link_login').click()
        time.sleep(0.5)    
        
        script = 'document.getElementById("id").value="{id}"; document.getElementById("pw").value="{pw}"'\
            .format(id='ID', pw='PW')
        time.sleep(0.5)
        browser.execute_script(script)
        time.sleep(0.5)
        browser.find_element_by_id('log.login').click()
        print(browser.page_source)
        time.sleep(1)
        browser.quit()
    
    naver_login()
    

     

    ■ 셀레니움(Selenium) 네이버 항공권 예매 및 로딩 지연에 대한 처리

    from selenium import webdriver
    from selenium.webdriver.common.by import By
    from selenium.webdriver.support.ui import WebDriverWait
    from selenium.webdriver.support import expected_conditions as EC
    import time
    
    # 셀레니움(Selenium) 네이버 항공권 예매 및 로딩 지연에 대한 처리
    # WebDriverWait( , ).until(EC.presence_of_element_located((By.XPATH, '')))
    
    def naver_flight():
        # 현재 경로인 경우 './chromedriver.exe' 필요 없음
        browser = webdriver.Chrome('./chromedriver.exe')
        browser.maximize_window()
        browser.get('https://flight.naver.com/flights/')
        time.sleep(1.5)
        
        # 날짜 선택
        browser.find_element_by_link_text('가는날 선택').click()  # 텍스트로 선택
        
        # # 캘린더 월이 2개 - 날짜도 2개
        # browser.find_elements_by_link_text('29')[0].click()  # 가는 날
        # browser.find_elements_by_link_text('30')[0].click()  # 오는 날
        
        # # 다음 달이면,
        # browser.find_elements_by_link_text('29')[1].click()  # 가는 날
        # browser.find_elements_by_link_text('30')[1].click()  # 오는 날
        
        # 달을 달리하면,
        browser.find_elements_by_link_text('29')[0].click()  # 가는 날
        browser.find_elements_by_link_text('3')[1].click()  # 오는 날
        
        # 여행지 선택
        browser.find_element_by_xpath('//*[@id="content"]/div/div[1]/ul/li[2]').click()  # 국내 클릭
        browser.find_element_by_xpath('//*[@id="recommendationList"]/ul/li[1]/div/dl').click()  # 제주 클릭
        
        # 항공권 검색 클릭
        browser.find_element_by_link_text('항공권 검색').click()
        
        # 어떤 엘리먼트(예로, XPATH 지정)가 나올 때까지 지정 시간만큼 기다리기 처리(인수 괄호에 주의!)
        # 지정 시간(10초) 전이면 이후 진행, 시간 초과 시 에러 발생
        # 이 부분은 예외처리하고, 오류 시 브라우저 종료하는 것이 좋음
        try:
            elem = WebDriverWait(browser, 10).until(EC.presence_of_element_located((By.XPATH, \
                                                '//*[@id="content"]/div[2]/div/div[4]/ul/li[1]')))
            print(elem.text.split('\n'))
        finally:
            time.sleep(1.5)
            browser.quit()
        
    naver_flight()
    
    
    [결과]
    ['제주항공', '출발지', 'GMP', '06:00', '도착지', 'CJU', '07:10', '총 소요시간', '01시간 10분', '할인석', '편도 37,100원', '편도 36,740원 (KB국민카드 결제시 1% 청구할인)', '성인이벤트혜택']
    

     

    ■ 동적 페이지 웹 스크래핑(예로, 사용자가 스크롤링 할 때 등) - 구글 무비 

        ※ 아래 코드(requests 사용 결과)는 원하는 데이터를 가져올 수 없음

    import requests
    from bs4 import BeautifulSoup
    
    # 구글 무비 인기 차트 영화에서 할인중인 영화 목록 추출해보기
    # 구글 무비는 User-Agent(국가별)에 따라 서비스 페이지가 다르고, 스크롤 시 무비 데이터가 업데이트됨(동적페이지)
    # headers 정보에 User-Agent와 Accept-Language 내용도 필요
    
    def google_movie():
        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36',
            'Accept-Language': 'ko-KR,ko'
        }    
        url = 'https://play.google.com/store/movies/top'
        response = requests.get(url, headers=headers)
        response.raise_for_status()
        soup = BeautifulSoup(response.text, 'lxml')
        
        movies = soup.find_all('div', attrs={'class':'ImZGtf mpg5gc'})
        print(len(movies))  # 0, headers 추가 후 10, 전체 영화 목록 안보임
        
        # # 소스 검증 위해 파일 저장
        # with open('movie.html', 'w', encoding='utf8') as f:
        #     # f.write(response.text)  # 판독 불가
        #     f.write(soup.prettify())  # html 문서를 예쁘게 출력
        
        for movie in movies:
            title = movie.find('div', class_='WsMG1c nnK0zc').get_text()
            print(title)
            
    google_movie()
    
    
    [결과]
    10
    노바디
    콰이어트 플레이스 (자막판)
    고질라 VS. 콩
    라야와 마지막 드래곤
    테넷
    소울
    킬러의 보디가드
    킬러의 보디가드 무삭제 특별판 (자막판)
    어벤져스 : 엔드게임 (자막판)
    너의 이름은. (자막판)

     

    ■ google movie 에 대해 selenium 사용 (동적 페이지 웹 스크래핑)

    from bs4 import BeautifulSoup
    from selenium import webdriver
    import time
    
    # google movie(동적 페이지)에 대해 셀레니움으로 처리하기
    def google_movie_selenium():
        # 현재 경로인 경우 './chromedriver.exe' 필요 없음
        browser = webdriver.Chrome('./chromedriver.exe')  
        browser.maximize_window()
        url = 'https://play.google.com/store/movies/top'
        browser.get(url)
        time.sleep(1.5)
        
        # 스크롤링 하기
        # 모니터(해상도) 높이인 1080 위치로 스크롤 내리기 - 위치 숫자(해상도) 증가 시 그 위치만큼 이동
        # browser.execute_script('widow.scrollTo(0, 1080)')
        # browser.execute_script('widow.scrollTo(0, 2080)')
        
        # 현재 보이는 화면 스크롤 끝(가장 아래)까지 스크롤 내리기
        browser.execute_script('window.scrollTo(0, document.body.scrollHeight)')
        time.sleep(2)
        # 구글 무비 사이트는 스크롤 시마다 영화 목록이 추가(업데이트)되며, 영화 목록 끝까지 스크롤을 내릴려면
        # 현재 화면 높이를 가져와서 높이가 변하지 않을 때까지 스크롤을 내린다
        
        # 현재 화면(문서) 높이를 가져와서 저장
        prev_height = browser.execute_script('return document.body.scrollHeight')
        print(prev_height)
        
        while True:
            # 페이지에서 스크롤을 가장 아래로 내림
            browser.execute_script('window.scrollTo(0, document.body.scrollHeight)')
            time.sleep(2)
            
            # 현재 페이지 높이 저장
            curr_height = browser.execute_script('return document.body.scrollHeight')
            if curr_height == prev_height:
                break
            prev_height = curr_height
        
        print('스크롤 완료')
        
        # selenium과 같이 사용할 경우, BeautifulSoup 객체에 전달할 소스코드는 webdrive의 page_source
        soup = BeautifulSoup(browser.page_source, 'lxml')
        
        # Tip! 스크래핑할 컨텐츠가 있는 태그와 속성이 하나가 아닌 경우, 리스트로 속성을 감싸준다. (여기서는 아님)
        movies = soup.find_all('div', attrs={'class':'Vpfmgd'})    
        print(len(movies))
        
        for movie in movies:
            title = movie.find('div', class_='WsMG1c nnK0zc').get_text()
            print(title)
            
        # 할인된 영화만 추출해보기        
        print('#' * 50)
        
        for movie in movies:
            title = movie.find('div', class_='WsMG1c nnK0zc').get_text()
            original_price = movie.find('span', class_='SUZt4c djCuy')
            if original_price:
                original_price = original_price.get_text()
            else:
                print(title, ' -- 할인되지 않은 영화 제외')
                continue
        
            # 할인된 가격
            price = movie.find('span', attrs={'class':'VfPpfd ZdBevf i5DZme'}).get_text()
            
            # 영화 링크
            link = movie.find('a', attrs={'class':'JC71ub'})['href']
            
            print(f'제목 : {title}')
            print(f'할인 전 금액 : {original_price}')
            print(f'할인 후 금액 : {price}')
            print('링크 :', 'https://play.google.com' + link)
            print('-' *50)
        
        print('구글 인기차트 영화 리스트 추출 완료!')
        browser.quit()
                
    google_movie_selenium()
    
    
    [결과]
    노바디  -- 할인되지 않은 영화 제외
    콰이어트 플레이스 (자막판)  -- 할인되지 않은 영화 제외
    고질라 VS. 콩  -- 할인되지 않은 영화 제외
    라야와 마지막 드래곤  -- 할인되지 않은 영화 제외
    테넷  -- 할인되지 않은 영화 제외
    소울  -- 할인되지 않은 영화 제외
    킬러의 보디가드  -- 할인되지 않은 영화 제외
    제목 : 킬러의 보디가드 무삭제 특별판 (자막판)
    할인 전 금액 : ₩2,500
    할인 후 금액 : ₩1,200
    링크 : https://play.google.com/store/movies/details/%ED%82%AC%EB%9F%AC%EC%9D%98_%EB%B3%B4%EB%94%94%EA%B0%80%EB%93%9C_%EB%AC%B4%EC%82%AD%EC%A0%9C_%ED%8A%B9%EB%B3%84%ED%8C%90_%EC%9E%90%EB%A7%89%ED%8C%90?id=ea3io4U1-qs
    --------------------------------------------------
    어벤져스 : 엔드게임 (자막판)  -- 할인되지 않은 영화 제외
    너의 이름은. (자막판)  -- 할인되지 않은 영화 제외
    몬스터 헌터 Monster Hunter  -- 할인되지 않은 영화 제외
    날씨의 아이 (자막)  -- 할인되지 않은 영화 제외
    잭 스나이더의 저스티스 리그  -- 할인되지 않은 영화 제외
    베놈   Venom  -- 할인되지 않은 영화 제외
    어벤져스 : 인피니티 워 (자막판)  -- 할인되지 않은 영화 제외
    바람에 젖은 여자  -- 할인되지 않은 영화 제외
    건마의 신:어린 아내의 알바  -- 할인되지 않은 영화 제외
    모탈 컴뱃  -- 할인되지 않은 영화 제외
    제목 : 미나리
    할인 전 금액 : ₩7,700
    할인 후 금액 : ₩5,500
    링크 : https://play.google.com/store/movies/details/%EB%AF%B8%EB%82%98%EB%A6%AC?id=kOWoZydbl3o.P
    
    -이하 생략-
    

     

    ■ 크롬 안띄우고 처리해보기(headless chrome : Chrome without Chrome)

    from bs4 import BeautifulSoup
    from selenium import webdriver
    import time
    
    # 동적 페이지에 대해 셀레니움으로 처리하기 
    # - 크롬 안띄우고 처리해보기(headless chrome : Chrome without Chrome)
    
    def google_movie_headless():
        ## 크롬 띄우고 처리할 경우 아래를 주석 처리 -----------------------
        options = webdriver.ChromeOptions()
        options.headless = True  # 크롬 안띄우기
        options.add_argument('window-size=1920x1080')  # 윈도우 창 크기 지정
        browser = webdriver.Chrome('./chromedriver.exe', options=options)
        # 여기까지 주석 처리 -------------------------------------------
        
        # browser = webdriver.Chrome('./chromedriver.exe')  # 크롬 띄울 경우 주석 처리 풀기
        browser.maximize_window()
        url = 'https://play.google.com/store/movies/top'
        browser.get(url)
        time.sleep(1.5)
        
        prev_height = browser.execute_script('return document.body.scrollHeight')
        while True:
            # 페이지에서 스크롤을 가장 아래로 내림
            browser.execute_script('window.scrollTo(0, document.body.scrollHeight)')
            time.sleep(2)
            
            # 현재 페이지 높이 저장
            curr_height = browser.execute_script('return document.body.scrollHeight')
            if curr_height == prev_height:
                break
            prev_height = curr_height
        
        print('스크롤 완료')
        
        # selenium과 같이 사용할 경우, BeautifulSoup 객체에 전달할 소스코드는 webdrive의 page_source
        soup = BeautifulSoup(browser.page_source, 'lxml')
        
        movies = soup.find_all('div', attrs={'class':'Vpfmgd'})    
        print(len(movies))
            
        # 할인된 영화만 추출해보기        
        for movie in movies:
            title = movie.find('div', class_='WsMG1c nnK0zc').get_text()
            original_price = movie.find('span', class_='SUZt4c djCuy')
            if original_price:
                original_price = original_price.get_text()
            else:
                print(title, ' -- 할인되지 않은 영화 제외')
                continue
        
            # 할인된 가격
            price = movie.find('span', attrs={'class':'VfPpfd ZdBevf i5DZme'}).get_text()
            
            # 영화 링크
            link = movie.find('a', attrs={'class':'JC71ub'})['href']
            
            print(f'제목 : {title}')
            print(f'할인 전 금액 : {original_price}')
            print(f'할인 후 금액 : {price}')
            print('링크 :', 'https://play.google.com' + link)
            print('-' *50)
        
        print('구글 인기차트 영화 리스트 추출 완료!')
        browser.quit()    
    
    google_movie_headless()

     

    ■ headlees Chrome 사용 시 주의할 점 - User-Agent 값 변경

    Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) 
    Chrome/91.0.4472.114 Safari/537.36
    
    아래 User-Agent의 HeadlessChrome 부분이 바뀌었음
    
    Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) 
    HeadlessChrome/91.0.4472.114 Safari/537.36
    
    - 'User-Agent'가 'HeadlessChrome'으로 표시 --> 스크래핑 거부될 수 있음
    
    이를 방지하기 위한 조치 - options.add_argument('user-agent=올바른 값 넣어주기')
    
    from selenium import webdriver
    import time
    
    # options.add_argument('user-agent=올바른 값 넣어주기')
    def chrome_headless():
        options = webdriver.ChromeOptions()
        options.headless = True
        options.add_argument('window-size=1920x1080')
        options.add_argument('user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36')
        
        browser = webdriver.Chrome('./chromedriver.exe', options=options)
        browser.maximize_window()
        url = 'https://www.whatismybrowser.com/detect/what-is-my-user-agent'
        browser.get(url)
        time.sleep(1)
        
        detected_value = browser.find_element_by_id('detected_value')
        print(detected_value.text)
        browser.quit()
        
    chrome_headless()
    

     

    * 나도코딩 유튜브 채널 - https://www.youtube.com/channel/UC7iAOLiALt2rtMVAWWl4pnw/videos

     

     

    반응형
Designed by goodthings4me.