ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • BeautifulSoup 03 - Basics of data science tasks (2) - 위키피디아 영화 관련 스크래핑
    빅데이터/BeautifulSoup 2021. 12. 8. 09:06

    유투버 'Keith Galli' 강의 참조

     

     

     

     

     

     

    • 리스트의 링크들을 타고 들어가서 그 링크들이 가지고 있는 인포 박스 따오기
      참고로 위키피디아에서 웹스크래핑을 허용 하지만 너무많이 하거나 부적절하게 사용하면 막히게 해놓았다. 그래서 천천히 조금씩 해야 한다!

     

    • 기본 세팅
      from bs4 import BeautifulSoup as bs
      import reques
      
      # Load the webpage
      r = requests.get("https://en.wikipedia.org/wiki/List_of_Walt_Disney_Pictures_films")
      
      # Convert tp a beautifulsoup object
      soup = bs(r.content, "lxml")
      
      # Print
      contents = soup.prettify()
      print(contents)​

     

    • 검사를 해보면 테이블들이 비슷한 패턴으로 반복되는것을 볼 수 있다.

    movies = soup.select(".wikitable.sortable")
    movies
    
    [<table class="wikitable sortable" style="width:100%;">
     <tbody><tr>
     <th style="width:1em;">
     </th>
     <th style="width:35%;">Title
     ....
     
     
     1. soup.find_all 로는 찾아오지 못한다.
     2. select를 써서 class 전체를 긁으니 뒷부분을 찾지 못한다.
    	(wikitable sortable "jquery-tablesorter")
     3. select의 .class1.class2 를 써서 뒷부분을 제외하고 찾아 본다.

    타이틀 및 링크들을 긁어오는것을 보니 1차로 잘 찾아 온다.

     

     

    • 이제 각 타이틀들의 링크들을 긁어 와야 한다
      테이블들이 똑같은 형식으로 반복이 되고 그안에 똑같은 태그명으로도 반복이 되는것을 볼 수 있다.

    - 1차로 <i> 태그만 들고 와 보자 (큰 <table> 태그 안에 똑같은 패턴으로 반복이 되고 <i> 태그가 <a>를 포함 정보를 들고 있다.)

    movies = soup.select(".wikitable.sortable i")
    movies[0:5]
    
    [<i><a href="/wiki/Academy_Award_Review_of_Walt_Disney_Cartoons" title="Academy Award Review of Walt Disney Cartoons">Academy Award Review of Walt Disney Cartoons</a></i>,
     <i><a href="/wiki/Snow_White_and_the_Seven_Dwarfs_(1937_film)" title="Snow White and the Seven Dwarfs (1937 film)">Snow White and the Seven Dwarfs</a></i>,
     <i><a href="/wiki/Pinocchio_(1940_film)" title="Pinocchio (1940 film)">Pinocchio</a></i>,
     <i><a href="/wiki/Fantasia_(1940_film)" title="Fantasia (1940 film)">Fantasia</a></i>,
     <i><a href="/wiki/The_Reluctant_Dragon_(1941_film)" title="The Reluctant Dragon (1941 film)">The Reluctant Dragon</a></i>]

     

     

     

    - 하나의 태그 링크 들고 오기

    movies = soup.select(".wikitable.sortable i")
    movies[0:5]
    movies[0].a['href']
    
    '/wiki/Academy_Award_Review_of_Walt_Disney_Cartoons'
    
    *태그안의 속성을 넣으면 원하는 정보를 들고 올 수 있다.
    movies[0].a['title']
    'Academy Award Review of Walt Disney Cartoons'

    → 인덱스 형식으로 나누어지는고 그 인덱스 정보를 들 고 올 수 있다.

     

     

     

    - 인덱스를 이용하여 링크 + 타이틀 추출

    # Load the webpage
    r = requests.get("https://en.wikipedia.org/wiki/List_of_Walt_Disney_Pictures_films")
    
    # Convert tp a beautifulsoup object
    soup = bs(r.content, "lxml")
    movies = soup.select(".wikitable.sortable i")
    
    for index, movie in enumerate(movies):
        relative_path = movie.a['href']
        title = movie.a['title']
        
        print(relative_path)
        print(title)
        break
    
    /wiki/Academy_Award_Review_of_Walt_Disney_Cartoons
    Academy Award Review of Walt Disney Cartoons

    → 페이지의 모든 타이틀 및 링크를 들고 오게되면 'NoneType' 오류가 뜬다.  해결 해 보자

    for index, movie in enumerate(movies):
        
        try:
            relative_path = movie.a['href']
            title = movie.a['title']
    
        except Exception as e:
            print(movie.get_text())
            print(e)
            
    Escape from the Dark
    'NoneType' object is not subscriptable
    The Omega Connection
    'NoneType' object is not subscriptable
    Trail of the Panda
    'NoneType' object is not subscriptable
    Growing Up Wild
    ....

    * try/except 를 써서 어디에서 오류가 걸리는지 볼 수 있다. 꼭 기억 하자!

     

    → 'NoneType' 형태들을 찾아보면 타이틀이 없거나, 링크가 없거나 등 공통적인 오류가 나온다. 

    movies = soup.select(".wikitable.sortable i a")
    
    * 간단하게'wikitable.sortable'에 <i> 태그를 찾고 그안에 <a> 태그가 있는 것만 들고 오면 된다.

     

     

     

    - 2차 시도

    def get_content_value(row_data):
        if row_data.find("li"):
            return [li.get_text(" ", strip=True).replace("\xa0", " ") for li in row_data.find_all("li")]
        else:
            return row_data.get_text(" ", strip=True).replace("\xa0", " ")
    
    def get_info_box(url):
        
        r = requests.get(url)
        soup = bs(r.content, "lxml")
        info_box = soup.find(class_="infobox vevent")
        info_rows = info_box.find_all("tr")
        
        movie_info = {}
        for index, row in enumerate(info_rows):
            if index == 0:
                movie_info['title'] = row.find("th").get_text()
    
            #사진은 생략
            elif index == 1:
                continue
    
            #계속해서 필요한 정보 긁기
            else:
                content_key = row.find("th").get_text()
                content_value = get_content_value(row.find("td"))
                movie_info[content_key] = content_value
        
        return movie_info
    # Load the webpage
    r = requests.get("https://en.wikipedia.org/wiki/List_of_Walt_Disney_Pictures_films")
    
    # Convert tp a beautifulsoup object
    soup = bs(r.content, "lxml")
    movies = soup.select(".wikitable.sortable i a")
    
    bas_path = "https://en.wikipedia.org/"
    movie_info_list = []
    
    for index, movie in enumerate(movies):
        if index == 10:
            break
            
        try:
            relative_path = movie['href']
            full_path = bas_path+relative_path
            title = movie['title']
            
            movie_info_list.append(get_info_box(full_path))
            
        except Exception as e:
            print(movie.get_text())
            print(e)
    1. 인포박스의 구성은 동일하므로 첫 수업때 썻던 함수를 그대로 활용
    2. 각 링크를 타고들어가서 인포박스를 들고와야하므로 인포박스를 들고 오는 함수 추가 ('def get_info_box(url)')
    3. 인포박스를 긁고 거기서 넘어오는 데이터는 'def get_content_value(row_data)' 함수를 탐
    4. 진행 순서를 보면 2개의 함수를 타기전에 full_path를 만들기 위해서 첫 웹페이지에서 포문을 돌면서 링크들을 따게 됨
    5. 링크를 따고 'full_path'를 만든 후 이 'full_path' 주소가 인포박스(url)로 들어가고 이를 통해 인포박스를 긁어 옴
    6. 긁어 와진 인포박스 내용을 추가로 가공해서 들고 오게 되면 아래와 같은 결과가 나옴 
    movie_info_list[0]
    
    {'title': 'Academy Award Review of Walt Disney Cartoons',
     'Productioncompany ': 'Walt Disney Productions',
     'Distributed by': 'RKO Radio Pictures',
     'Release date': ['May 19, 1937 ( 1937-05-19 )'],
     'Running time': '41 minutes (74 minutes 1966 release)',
     'Country': 'United States',
     'Language': 'English',
     'Box office': '$45.472'}

     

    • 현재까지 진행상태를 확인해보면 여전히 몇몇의 무비가 'NoneType'오류를 일으킨다
    더보기

    from bs4 import BeautifulSoup as bs
    import requests

     

    def get_content_value(row_data):
    #     print(row_data)
        
        if row_data.find('li'):
            return [li.get_text(" ", strip=True).replace("\xa0", " ") for li in row_data.find_all('li')]
        else:
            return row_data.get_text(" ", strip=True).replace("\xa0", " ")

    def get_info_box(url):
        
        r = requests.get(url)

        #Convert to a beautifulsoup object
        soup = bs(r.content, 'html.parser')
        info_box = soup.find(class_="infobox vevent")
        info_rows = info_box.find_all('tr')
        
        movie_info = {}
        
        for idx, row in enumerate(info_rows):
            if idx == 0: # 타이틀의 인덱스는 = 0
                movie = row.find('th').get_text(" ", strip=True)
            elif idx == 1:
                continue
            else:
                content_key = row.find('th').get_text(" ", strip=True)
    #             print(content_key)
                content_value = get_content_value(row.find('td')) #이 부분이 get_content_value를 탐
                movie_info[content_key] = content_value
            
        return movie_info

     

     

    r = requests.get("https://en.wikipedia.org/wiki/List_of_Walt_Disney_Pictures_films")

    #Convert to a beautifulsoup object
    soup = bs(r.content, 'html.parser')


    # movies = soup.select('.wikitable.sortable i')으로는 아래의 Nonetype이 있으므로 조금 더 구체적으로 가져오자
    movies = soup.select('.wikitable.sortable i a') #즉 <i>태그 중 <a>를 가진 것만 들고 옴
    # print(len(movies)) #NoneType 제외하고 개수를 확인 510개로 나옴


    base_path = "https://en.wikipedia.org/" #기본 url을 세팅 후


    movie_info_list = [] #들고 오는 내용을 담을 리스트
    for index, movie in enumerate(movies):
        #디버깅용
    #     if index == 10:
    #         break     
        try:
            relative_path = movie['href']
            full_path = base_path + relative_path #긁어오는 'href'를 더해서 full_path 만든 후 함수에 삽입
            title = movie['title']
    #         relative_path = movie.a['href'] # <i>태그 중 <a>만 들고 오니 .a는 생략
    #         title = movie.a['title']
            #위의 결과들을 print 해보면 relative_path가 가져오는 'href'가 없는 부분이 있다. 여기서 try/catch가 필요
    #         print(relative_path)
    #         print(title)
    #         print()
            
            movie_info_list.append(get_info_box(full_path)) #여기에 들어갈 url 세팅 (base_path + full_path)
            
        except Exception as e: #즉 오류부분이면 여기를타고 아래를 실행 
            print(movie.get_text()) #이 부분에서 어떤 movie에서 오류가 나오는지 보인다
            #몇몇의 movie가 테이블의 Title에서 'href'를 들고오는게아닌 'Note'부분에서 들고 온다. 
            #몇몇의 movie가 테이블의 Title에서 'href'를 들고 오지만, 링크가 아는 형태이다.
            
            #최종코드에서 돌려보면 여전히 몇몇의 movie가 find/find_all/get_text가 안된다
            print(e)

    • 완벽하진않지만 500개가 넘어가는것 중 10개 내외의 오류는 나쁘지 않다고 볼 수 있다.
    • 이제 이 딕셔너리형태의 데이터들을 꺼내서 JSON형태로 저장 한 후 계속해서 진행해볼 예정
    import json
    
    #JSON 형태로 저장
    def save_data(title, data):
        with open(title, 'w', encoding='utf-8') as f:
            #json.dumps() = Python 객체를 JSON으로 변환
            #ensure_ascii - true면 모든 비 ASCII 문자가 출력 안됨, False면 문자 그대로 출력
            #indent - 인덱스폭 (문자 수) 지정
            json.dump(data, f, ensure_ascii=False, indent=2)
            
    
    #JSON 형태로 로드
    def load_data(title):
        with open(title, encoding='utf-8') as f:
            return json.load(f)
            
    save_data("Disney_data.json", movie_info_list)

Designed by Tistory.