ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 웹 스크래핑 기초: 소스코드 읽기와 태그 찾기
    Python/NLP용 python 2021. 10. 23. 17:52

    *본 게시물은 21-2학기 연세대학교 일반대학원 온라인데이터수집과분석(이상엽 교수님) 수업 내용을 정리한 것입니다.

     

      웹스크래핑은 흔히 '크롤링'으로 알고 있는 온라인 데이터 수집 방법입니다. 

    다만 스크래핑은 하나의 웹 페이지에 존재하는 정보를 가져오는 행위인 반면 크롤링은 전체 사이트를 긁어온다는 점에서 차이가 있습니다. 

     

       웹에는 굉장히 많은 데이터가 존재합니다. 웹 페이지들로 구성된 홈페이지는 서버에서 raw data인 소스코드를 받아서 사용자들에게 예쁘게 보여주는 역할을 합니다.(raw data인 소스코드는 chrome > '페이지 소스 보기'로 확인 가능합니다.) 소스코드는 다양한 language들로 작성됩니다. 특히 html이 많이 쓰입니다. html 소스코드는 특히 여러개의 태그로 구성됩니다. 가령 <title>제목</title> 이런 식으로 이루어집니다. 앞의 태그를 시작 태그(start tag), 뒤의 태그를 끝 태그(end tag)라 합니다.

      소스코드는 파이썬 기본 모듈인 request 혹은 urllib을 사용해서 서버에서 다운로드 할 수 있습니다. 이렇게 다운받은 소스코드를 모듈 bs4의 BeautifulSoup 클래스를 활용해서 정보를 담고 있는 태그를 찾고, 정보까지 추출할 수 있습니다. 

     

    request 사용하기

    request는 서버 주소(주로 웹 페이지 URL)에 접근하여 소스 코드를 가져옵니다. 가져올 때 반드시 http://를 붙여서 가져와야 합니다. request의 get()함수를 사용하며, 인자로는 URL이 옵니다. URL은 '서버 컴퓨터 주소(~.com)+ 웹 페이지 폴더 경로'로 이루어져 있습니다. 

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    import requests
    url = 'http://www.yes24.com/Product/Goods/102789938' #일단 get의 인자가 되는 url을 'url' 변수에 저장한 후,
    = requests.get(url)
    print(r.text) # 아래 보이는 소스코드는 브라우저에서 보이는 '페이지 소스'보기와 거의 동일합니다.
    # 소스코드에는 최종적으로 추출하고자 하는 정보들이 태그에 저장되어 있습니다.
    '''
    <!DOCTYPE html >
        <html lang="ko">
     
    <head><link rel ="canonical" href="http://www.yes24.com/Product/Goods/102789938"> <link rel ="alternate" media="only screen and(max-width: 640px)"  href="http://m.yes24.com/Goods/Detail/102789938">
        <meta http-equiv="X-UA-Compatible" content="IE=Edge" />
        <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
        <meta http-equiv="Accept-CH" content="dpr, width, viewport-width, rtt, downlink, ect, UA, UA-Platform, UA-Arch, UA-Model, UA-Mobile, UA-Full-Version" />
        <meta http-equiv="Accept-CH-Lifetime" content="86400" />
        <meta name="viewport" content="width=1170" />
     
        <title> 달러구트 꿈 백화점 2 - YES24 </title> 
     
        
    <meta name="title" content="달러구트 꿈 백화점 2 - YES24"/>
    <meta name="author" content="이미예" />
        
    <meta name="description" content="100만 독자를 사로잡은 『달러구트 꿈 백화점』, 그 두 번째 이야기어느덧 페니가 달러구트 꿈 백화점에서 일한 지도 1년이 넘었다. 재고가 부족한 꿈을 관리하고, 꿈값 창고에서 감정으로 가득 찬 병을 옮기고, 프런트의 수많은 눈꺼풀 저울을 관리하는 일에 능숙해..."/>
        
    <meta name="keywords" content="달러구트 꿈 백화점 2,EBS북카페,문학뉴스레터에소개된책,어른이를위한힐링책,힐링판타지,믿고보는시리즈,단골손님을 찾습니다, 이미예, 팩토리나인, 9791165343729, 116534372X9791165343729,116534372X"/>
    '''
    cs

     

    bs4의 BeautifulSoup 사용하기

    그렇다면 찾고자 하는 태그와, 그 태그가 저장하고 있는 정보에는 어떻게 접근해야 할까요? bs4모듈의 BeautifulSoup를 사용합니다. 

    1) 태그 이름으로 접근하기

    1
    2
    3
    4
    5
    from bs4 import BeautifulSoup
    soup = BeautifulSoup(r.text, 'lxml'#인자는 소스코드, 파서의 이름 
    soup.title #원하는 태그에 '태그의 이름'을 통해 접근 가능: <title> 역사의 역사 - YES24 </title>
    #태그에 접근했으면 정보 추출
    soup.title.text #' 역사의 역사 - YES24 ', str로 리턴
    cs
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    # ' 역사의 역사 - YES24 ' 후처리
    title = soup.title.text
     
    # 공백제거엔 strip
    title = title.strip() #'역사의 역사 - YES24'
    # -YES24 지우기: #split: 특정 문자열 값을 가지고 주어진 문자열 데이터를 쪼개고자 할 때
    title.split('-'# 리스트['역사의 역사 ', ' YES24']
    title.split('-')[0#'역사의 역사 ' 
    title.split('-')[0].strip() #마지막 공백까지 삭제, '역사의 역사'
    title = title.split('-')[0].strip() #'역사의 역사'
     
    #한글에 대해서 utf-8 인코딩이 제일 많이 사용 
    with open('results.txt''w', encoding='utf-8'as f:
        f.write(title)
    cs

     

     

    2) 이름이 똑같은 태그가 여러개 존재할 때, 그리고 속성 정보로도 태그를 찾고 싶을 때

    태그 이름으로 접근하는 것의 한계는 동일한 이름을 가진 태그가 여러개 존재할 수 있다는 것입니다. 

    가령 다음과 같은 html_test.html이란 파일을 보면...

    위 사진은 21-2학기 연세대학교 일반대학원 온라인데이터수집과분석(이상엽 교수님) 수업 유인물의 내용입니다.

     

    이름이 p인 태그는 <p class='title'> 부터 시작해서 <p class= 'description>, <p>...에 이르기까지 매우 빈번하게 나오고 있습니다. 이 html 파일을 일단 파이썬으로 불러오도록 하겠습니다. 

    1
    2
    3
    4
    # 예시 파일 html_test.html 불러오기
    # URL에 있는 html 소스코드는 request의 get()을 사용해서 가져와야 합니다. 
    with open('C:?Users/사용자이름/폴더1/html_test.html''r', encoding = 'utf8'as f:
        html = f.read()
    cs

     

    이렇게 읽어온 html 파일을 html이라는 변수에 저장했습니다. BeautifulSoup를 통해 태그 탐색을 실행해보도록 하겠습니다. 보통의 태그 이름으로 접근하는 방법입니다.

    1
    2
    3
    4
    5
    6
    from bs4 import BeautifulSoup
    soup = BeautifulSoup(html, 'lxml')
    # 태그 이름으로 접근하기의 경우_ 태그가 하나 있을 때
    soup.title #<title> Online Data Scraping Test</title>
    title = soup.title.text # ' Online Data Scraping Test'
    title = title.strip() #'Online Data Scraping Test'
    cs

     

    그렇다면 태그가 여러번 나올 때 태그 이름을 쓰면 어떻게 될까요?

    1
    soup.p #<p class="title">HTML code for the lecture</p>
    cs

    제일 첫 번째 태그밖에 표출이 되지 않습니다. 때문에 다른 방법인 find()find_all()의 방법을 사용해야 합니다. 이 둘은 태그의 이름 혹은 태그의 속성 정보(=attribute, 추가적 정보)를 받습니다. find()의 경우 인자에 해당되는 첫 번째 태그만, find_all()은 인자에 해당되는 모든 태그리스트로 반환합니다. 

     

    인자로 우선 이름을 주겠습니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    soup.find('p'# 태그 이름이 p인 것들 중 첫 번쨰 태그만 반환 
    # <p class="title">HTML code for the lecture</p>
     
    soup.find_all('p'# 태그 이름이 p인 것들을 모두 찾은 후 리스트로 반환
     '''
    [<p class="title">HTML code for the lecture</p>,
     <p class="description">This code will be used as HTML source code for the BS lecture. For more detailed information about BS, you can click
                 <a class="BS_English" href="http://www.pythonforbeginners.com/python-on-the-web/beautifulsoup-4-python/"> BS4 for beginners</a></p>,
     <p class="description">한국어로 된 정보를 원한다면
                 <a class="BS_Korean" href="http://coreapython.hosting.paran.com/etc/beautifulsoup4.html"> BS4 for beginners (한글버전)</a>을 이용하세요</p>,
     <p><span class="red_price">35.6</span></p>,
     <p><span class="blue_price">43.2</span></p>,
     <p><span method="manual">24.3</span></p>]
    '''
    len(soup.find_all('p')) #원소는 6개
    soup.find_all('p')[4#<p><span class="blue_price">43.2</span></p>
    soup.find_all('p')[-1#<<p><span method="manual">24.3</span></p> 음의 인덱스를 사용하여 가장 마지막에 접근 
     
    # 가장 마지막 <p>태그의 정보는 가격입니다. 문자열로 나타납니다.
    # 얘를 숫자로 바꾸어 연산해보습니다.
    price = soup.find_all('p')[-1].text #'24.3', str 타입
    price = float(price) #24.3으로 환원, int로 환원 시 오류남
    price+10 #34.3
    cs

     

    인자 중에는 속성(attribute) 정보도 있다고 했습니다. 만일 태그<p class='title'>가 있다면 class는 속성 정보, 구체적 값은 'title'이 됩니다. find()와 find_all()에 속성 정보를 넣어 검색하는 코드는 다음과 같습니다. 

    soup.find('태그이름', attrs = {'속성정보':'값'})
    soup.find_all('태그이름', attrs = {'속성정보':'값'})

     

    위와 동일하게 find()는 첫 번째 값만, find_all()은 모든 값을 리스트로 반환합니다. 

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    #태그가 가진 정보에는 이름 외에도 많음: 속성(attribute)정보
    # 예: p 태그- 엄청 많음! <p class='title'>중 class가 속성 정보, 구체적 값은 'title'
    # <p class="description">: class는 속성 정보, 값은 ;description'
     
    #find()의 attr 파라미터 사용 
    # soup.find('tag name', attrs = {'attr_name':'value'})
    soup.find('p', attrs= {'class':'title'} )
    soup.find('p', attrs= {'class':'description'} ) # find()는 태그의 첫 번째 값만 반환 
    soup.find_all('p', attrs= {'class':'description'} ) # find_all()는 모든 태그를 리스트로 반환 
    '''
    [<p class="description">This code will be used as HTML source code for the BS lecture. For more detailed information about BS, you can click
                 <a class="BS_English" href="http://www.pythonforbeginners.com/python-on-the-web/beautifulsoup-4-python/"> BS4 for beginners</a></p>,
     <p class="description">한국어로 된 정보를 원한다면
                 <a class="BS_Korean" href="http://coreapython.hosting.paran.com/etc/beautifulsoup4.html"> BS4 for beginners (한글버전)</a>을 이용하세요</p>]
    '''
     
    soup.find('p', attrs= {'class':'title'} ).text #'HTML code for the lecture'
    soup.find('p', attrs= {'class':'description'} ).text 
    #'This code will be used as HTML source code for the BS lecture. For more detailed information about BS, you can click\n\t\t\t BS4 for beginners'
     
    soup.find_all('p', attrs= {'class':'description'} )[-1].text  # find_all()는 모든 태그를 리스트로 반환. 인덱싱 가능 
    #'한국어로 된 정보를 원한다면\n\t\t\t BS4 for beginners (한글버전)을 이용하세요'
    cs
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    # 이번에는 a태그를 찾아봅시당
    soup.find('a')
    soup.find_all('a')
    '''
    [<a class="BS_English" href="http://www.pythonforbeginners.com/python-on-the-web/beautifulsoup-4-python/"> BS4 for beginners</a>,
     <a class="BS_Korean" href="http://coreapython.hosting.paran.com/etc/beautifulsoup4.html"> BS4 for beginners (한글버전)</a>]
    ''' 
    soup.find_all('a')[-1
    #<a class="BS_Korean" href="http://coreapython.hosting.paran.com/etc/beautifulsoup4.html"> BS4 for beginners (한글버전)</a>
     
    # 속성 정보로 찾기
    soup.find('a', attrs= {'class':'BS_Korean'} ) 
    soup.find_all('a', attrs= {'class':'BS_Korean'}) #항상 리스트로 반환, 이 뒤에 .text 안됨 
    soup.find_all('a', attrs= {'class':'BS_Korean'})[-1].text #대신 리스트에서 원소를 인덱싱으로 꺼내는 경우 .text 가능 
    #' BS4 for beginners (한글버전)'
    cs

     

     

    3) BeautifulSoup 의 get(): 속성 정보의 값을 뽑고 싶을 때

    위에서 계속 썼던 .text의 경우 <시작태그>...</끝태그> 사이의 text 정보만을 찾아줬습니다. 이는 웹 페이지에서 추출하려는 정보가 text인 경우에 쓸 수 있으나, 추출하려는 정보가 text가 아닌 속성 정보 값이 경우도 존재합니다. 

    가령 다음과 같은 내용이 있을 때 'href' 정보만 뽑고 싶다면 어떻게 할까요?

     

    <a class="BS_English" href="http://www.pythonforbeginners.com/python-on-the-web/beautifulsoup-4-python/"> BS4 for beginners</a></p>
    <p class="description">한국어로 된 정보를 원한다면
    <a class="BS_Korean" href="http://coreapython.hosting.paran.com/etc/beautifulsoup4.html"> BS4 for beginners (한글버전)</a>

     

    이 때, 즉 특정 태그가 가진 특정 속성 정보를 가져올 때 BeautifulSoup의 get()을 사용하게 됩니다. get()은 인자로 속성 정보 이름을 받습니다.

     

    순서는 특정 태그찾기 -> get()을 사용해서 인자에 속성 정보 이름을 넣은 후 -> 속성 정보 값 추출하기가 됩니다. 

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 특정 태그를 일단 찾은 후 
    # 그 태그에 get()을 적용하여 인자에 속성 정보 이름을 넣은 후, 그 속성 정보의 값을 추출 
     
    # 예) href: 하이퍼링크의 주소 찾기 
    soup.a # 첫번쨰 a태그 에서만 속성 정보의 값을 찾는 경우 이렇게 하기가 가능
    soup.a.get('href')#'http://www.pythonforbeginners.com/python-on-the-web/beautifulsoup-4-python/'
     
    # 태그가 첫 번쨰가 아닌 중간에 껴 있을 때
    soup.find('a', attrs={'class':'BS_Korean'}).get('href'#'http://coreapython.hosting.paran.com/etc/beautifulsoup4.html'
    soup.find('a', attrs={'class':'BS_English'}).get('href'#''http://www.pythonforbeginners.com/python-on-the-web/beautifulsoup-4-python/'
    cs

     

     

    4) 메타 정보가 2개 이상인 경우 

      수업에서는 YES24 홈페이지에서 웹 페이지에 나온 책 제목 정보를 추출하는 연습을 했었습니다. 여기에는 몇 가지 변수들이 있었습니다.

      첫 번째로 태그의 구성이 속성정보 1: 값 1이 아니란 것입니다. 즉 책 제목이 들어간 태그들을 찾을 때 <meta name="title" content="갯마을 차차차 2 - YES24"/>와 같이 속성 정보가 2개인 것을 고려해야 했습니다.

     

      주의할 점은 이 중에 찾아야 하는 정보는 꼭 meta 태그 중 content 속성 정보 이름(클래스)의 값이 "갯마을 차차차 2 - YES24"같지만!!! content의 경우 책이 바뀔 때마다 attribute 값을 바꿔주어야 하므로 모든 메타 태그 이름에 동일하게 들어간 title을 찾아야 합니다. 즉 태그 별로 name="title"은 계속 겹치므로, 책제목이 들어간 태그를 찾을 수 있습니다. 

    1
    2
    3
    4
    5
    6
    7
    url2 = requests.get('http://www.yes24.com/Product/Goods/104105224'#웹페이지 소스코드 다운 
    soup = BeautifulSoup(url2.text, 'lxml')
     
    # 원하는 태그를 찾기 위해 BeautifulSoup 사용 
    soup.find('meta', attrs={'name':'title'}) #<meta content="갯마을 차차차 2 - YES24" name="title"/>
    #의외로 content가 아님! content로 할 경우 책이 바뀔 때마다 attribute 값을 바꿔주어야 함 
    # <meta content="갯마을 차차차 2 - YES24" name="title"/>
    cs

     

    태그들을 찾았으면 이제 책 제목을 찾아야 합니다. 태그의 속성 정보 이름에 딸린 속성 정보 값을 찾는 것이므로 BeautifulSoup의 get()을 사용해야 합니다. 

    1
    2
    3
    4
    # <meta content="갯마을 차차차 2 - YES24" name="title"/> 중 content의 값을 찾고자 함 
    #특정 속성을 찾기 위해 get() 사용
    title = soup.find('meta', attrs={'name':'title'}).get('content'#'갯마을 차차차 2 - YES24'
    title = title.split('-')[0]
    cs

     

     

    Exercise

    수업에서는 YES24 웹 페이지에서 책제목, 저자, 판매가를 추출하고, 이를 텍스트 파일로 저장하는 실습을 했었습니다. 이에 해당하는 코드는 다음과 같습니다. 

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    # http://www.yes24.com/Product/Goods/96971128
    # 책제목, 저자, 판매가 추출한 후 텍스트 파일에 저장 
     
    # 1. url 가져오기 by request.get()
    url3 = 'http://www.yes24.com/Product/Goods/96971128'
    url3 = requests.get(url3)
     
    # 2. BeautifulSoup를 통해 태그에 접근 준비
    soup = BeautifulSoup(url3.text, 'lxml')
     
    # 3. 웹 페이지에서 태그를 찾아 정보 추출하기 
    title3= soup.find('meta', attrs={'name':'title'}).get('content').split('-')[0]
    author3 = soup.find('meta', attrs={'name':'author'}).get('content').split('-')[0]
    prices3 = soup.find('em', attrs={'class':'yes_m'}).text
    print(title3, author3, prices3) #어떤 죽음이 삶에게 말했다  김범석 15,000원
    = [title3, author3, prices3]
    len(a)
     
    # 4. 반복문을 통해 모든 정보를 txt 파일에 저장하기 
    for i in a:
        infos = i+'\n'
        with open('bookinfos.txt''a', encoding = 'utf8'as f:
            f.write(infos)
            f.close()
     
     
    # 교수님꼐서 주신 정답
    ttle = soup.title.text
    ttle = ttle.split('-')[0].strip()# 제일 접근하기 쉬운 태그를 결정할 것 
    aathor = soup.find('meta', attrs={'name':'author'}).get('content')
    pprice = soup.find_all('span', attrs={'class':'nor_price'})[0].text # '13,500원'
    with open('yes24_result.txt''w', encoding='utf8'as f:
        f.write('title'+'\t'+'author'+'\t'+'price'+'\n'# 헤더 정보 넣기
        f.write(ttle+'\t'+aathor+'\t'+pprice+'\n')
        f.close()
     
    cs

     

     

    태그에 간접적으로 접근하기 - BeautifulSoup Navigation

    BeautifulSoup Navigation 방법은 최종적으로 추출해야 하는 정보를 담은 태그에, 위의 방법들 처럼 직접적으로 접근하기 어려울 때 사용합니다. 따라서 그 주변 태그 이름에 직접 접근한 후, 주변 태그를 스타팅 포인트로 하여 최종 정보에 접근합니다. 부모-자식, 형제자매 관계를 사용하기 때문에 hierarchical한 구조를 사용한다고 이야기할 수 있습니다. 

    위 사진은 21-2학기 연세대학교 일반대학원 온라인데이터수집과분석(이상엽 교수님) 수업 유인물의 내용입니다.

     

    아까와 같이 동일한 html에 대해 작업을 수행합니다. 때문에 우선 html을 파이썬으로 불러와야 합니다. 

    1
    2
    3
    4
    5
    # 예시 파일 html_test.html 불러오기
    # URL에 있는 html 소스코드는 request의 get()을 사용해서 가져와야 합니다. 
    with open('C:?Users/사용자이름/폴더1/html_test.html''r', encoding = 'utf8'as f:
        html = f.read()
    soup = BeautifulSoup(html, 'lxml')
    cs

     

    불러왔으면 스타팅 포인트로 삼는 태그를 만들어야 합니다. 

    첫 번째로는 title 이름을 가진 태그를 스타팅 포인트로 삼았습니다. 이후에 부모, 자식, 형제자매 관계로 접근을 하게 됩니다. 

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    # 스타팅 포인트 태그 삼기
    soup.title # <title> Online Data Scraping Test</title>
     
    # 부모 찾기
    soup.title.parent # 부모 찾기 
    '''
    <head>
        <title> Online Data Scraping Test</title>
    </head>
    '''
    #  자식 태그 + 다른 자식 element도 리턴; '\n' 
    soup.head.contents 
    # ['\n', <title> Online Data Scraping Test</title>, '\n'] 서로간은 형제, 자매 관계
    '''
    즉, <title> Online Data Scraping Test</title> 이라는 자식 태그 말고도,
    그 자식 태그와 동일한 위계를 가지는 공백기호를 자식으로 취급합니다. 
    =============
    <head>\n
        <title> Online Data Scraping Test</title>\n
    </head>
    =============
    이러한 생김새를 가지고 있었기 때문입니다. 
    cs

     

    다른 예로는 p태그를 가진 태그를 대상으로 형제 자매 관계를 보려고 합니다. 형제자매 태그는 hierarchy 내 동일한 위계에 있는 태그들 간의 관계입니다. 

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    '''
    <body>\n
        <p class="title">HTML code for the lecture</p>\n
        <p class="description">This code will be used as HTML source code for the BS lecture. For more detailed information about BS, you can click
            <a class="BS_English" href="http://www.pythonforbeginners.com/python-on-the-web/beautifulsoup-4-python/"> BS4 for beginners</a></p>
    '''
     
     
    # 첫 번째 p 태그 찾기: 스타팅 포인트
    soup.p #<p class="title">HTML code for the lecture</p>
     
    # 태그 열리고 닫힌 후 가장 첫 번째의 형제자매 element를 리턴 
    soup.p.next_sibling #'\n'
    '''
    <body>\n
        <p class="title">HTML code for the lecture</p>\n
        <p class="description">This code will be used....
    '''
    # element 다음 형제자매 element인 p 태그 나옴 
    soup.p.next_sibling.next_sibling 
    '''
    결과:
    <p class="description">This code will be used as HTML source code for the BS lecture. For more detailed information about BS, you can click
                <a class="BS_English" href="http://www.pythonforbeginners.com/python-on-the-web/beautifulsoup-4-python/"> BS4 for beginners</a></p>
    '''
     
    # 스타팅 포인트 기준 다시 이전 형제자매 태그로 이동
    soup.p.previous_sibling #'\n'
    cs

    'Python > NLP용 python' 카테고리의 다른 글

    file read and write 코드  (0) 2021.10.23

    댓글

Designed by Tistory.