Excel & IT Info

아이엑셀러 닷컴, 엑셀러TV

Python

데이터 분석가를 위한 웹 스크래핑: 데이터 수집 프로세스 마스터하기

권현욱(엑셀러) 2024. 8. 17. 18:11
반응형

들어가기 전에

데이터 수집은 모든 데이터 분석의 출발점입니다. 하지만 데이터를 얻는 것이 항상 간단한 것은 아닙니다. 다양한 시스템과 소스가 분산되어 있기 때문이죠. 웹 스크래핑을 이용하여 필요한 데이터를 수집하는 방법을 소개합니다.

권현욱(엑셀러) | 아이엑셀러 닷컴 대표 · Microsoft Excel MVP · Excel 솔루션 프로바이더 · 작가

이미지: 아이엑셀러 닷컴

 

※ 이 글은 아래 기사 내용을 토대로 작성되었습니다만, 필자의 개인 의견이나 추가 자료들이 다수 포함되어 있습니다.


  • 원문: Web Scraping Indeed for Data Analysts: Mastering the Data Collection Process
  • URL: https://medium.com/@adesua/web-scraping-indeed-for-data-analysts-mastering-the-data-collection-process-fb5c8678d013

환경 설정하기

웹 스크래핑을 시작하려면 시스템에 필요한 도구가 설정되어 있는지 확인해야 합니다.

 

  • Python: 시스템에 Python이 설치되어 있는지 확인합니다. 설치되어 있지 않은 경우 공식 Python 웹사이트에서 다운로드하여 설치할 수 있습니다.
  • 라이브러리: 다음 Python 라이브러리인 Requests, BeautifulSoup, Selenium을 설치합니다. Python의 패키지 관리자인 pip를 사용하여 다음 명령어로 설치할 수 있습니다.
pip install requests
pip install beautifulsoup4
pip install selenium

 

도구를 설치했으면 주피터(Jupyter)를 열고 필요한 라이브러리를 가져올 차례입니다.

# Import necessary libraries
from selenium import webdriver
from bs4 import BeautifulSoup
import pandas as pd
from time import sleep

 

위의 코드 스니펫에서는 웹 스크래핑 프로젝트 전체에서 사용할 필수 라이브러리를 가져오고 있습니다.

 

  • Selenium: 이 강력한 도구를 사용하면 웹 브라우저 상호 작용을 자동화할 수 있으므로 Indeed.com과 같은 동적 웹 페이지를 탐색하는 데 적합합니다.
  • BeautifulSoup: HTML 및 XML 문서 구문 분석을 위한 Python 라이브러리입니다. 스크랩한 웹 페이지에서 데이터를 추출하는 데 사용합니다.
  • Pandas: Python의 다목적 데이터 조작 라이브러리. 스크랩한 데이터를 구조화된 형식으로 정리하고 조작하는 데 사용합니다.
  • time: 시간 관련 다양한 함수를 제공하는 Python 모듈입니다. 웹사이트 서버에 과부하가 걸리지 않도록 스크래핑 프로세스에 지연을 추가하는 데 이 모듈을 사용할 것입니다.

필요한 라이브러리를 가져왔으니 이제 웹 스크래핑을 위한 환경을 설정할 차례입니다. 웹 브라우저와의 상호 작용을 자동화하기 위해 Selenium을 사용하고 웹 페이지의 HTML 콘텐츠를 파싱하기 위해 BeautifulSoup을 사용할 것입니다. 아래 코드를 참조하세요.

# Set up Chrome options to customize the browser behavior
options = webdriver.ChromeOptions() 

# Set user-agent to mimic a browser behavior
options.add_argument('user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36')

# Initialize a Chrome WebDriver instance with customized options
driver = webdriver.Chrome(options=options) 

# URL of the webpage to scrape
url = "https://ng.indeed.com/jobs?q=data+analyst&l=Nigeria&from=searchOnHP&vjk=6df200c744f62577"

# Open the URL in the Chrome WebDriver instance
driver.get(url)

# Sleep for 5 seconds to ensure the page loads completely before scraping
sleep(5)

# Get the HTML source code of the page after it has fully loaded
html = driver.page_source

# Parse the HTML using BeautifulSoup
soup = BeautifulSoup(html, 'html.parser')

 

  • Chrome 옵션: 웹 브라우저의 동작을 맞춤 설정하기 위해 Chrome 옵션을 설정합니다. 이 경우 실제 브라우저의 동작을 모방하기 위해 사용자 에이전트를 추가하고 있습니다.
  • 웹 드라이버 인스턴스: 사용자 지정 옵션을 사용하여 크롬 웹드라이버 인스턴스를 초기화합니다. 이 웹드라이버 인스턴스를 통해 웹 페이지와 상호 작용할 수 있습니다.
  • URL: 스크랩하려는 웹페이지의 URL을 정의합니다. 이 예에서는 나이지리아의 데이터 분석가에 대한 채용 공고를 Indeed.com에서 스크랩합니다.
  • Open URL: 크롬 웹드라이버 인스턴스에서 지정된 URL을 엽니다.
  • Sleep: 스크래핑을 시작하기 전에 페이지가 완전히 로드되도록 5초 동안 실행을 일시 중지하는 절전 타이머를 추가합니다. 이렇게 하면 불완전하거나 누락된 데이터를 스크랩하는 것을 방지할 수 있습니다.
  • HTML 가져오기: WebDriver의 page_source 속성을 사용하여 페이지의 HTML 소스 코드를 가져옵니다.
  • HTML 구문 분석: 웹 페이지에서 특정 정보를 추출할 수 있는 BeautifulSoup을 사용하여 HTML 소스 코드를 구문 분석합니다.

 

계속 진행하기 전에 대상 페이지에서 어떤 데이터를 추출할지 결정하는 것이 중요합니다. 이 프로젝트에서는 다음 정보를 수집하는 데 중점을 둘 것입니다.

 

  • Job Title: 채용 공고의 제목
  • Company: 채용 회사 이름
  • Location: 근무지: 채용 공고의 위치(도시 또는 지역)
  • Salary: 급여 또는 급여 범위(가능한 경우)
  • Job Type: 정규직, 파트타임, 계약직 등 직무의 종류
  • Date Posted: 채용 공고가 게시된 날짜
  • Job Summary: 직무 책임과 요구사항에 대한 간략한 요약 또는 설명

 

이 모든 것이 준비되었으면 페이지가 어떻게 구성되어 있는지 이해하는 것이 중요합니다. Indeed.com으로 이동하여 검색창에 "데이터 분석가"(또는 원하는 직책)를 입력합니다. 마찬가지로 위치 표시줄에 "나이지리아"(또는 원하는 위치)를 입력합니다. 아래 이미지와 유사한 페이지 레이아웃이 표시됩니다.

 

이미지: medium

 

HTML 요소 검사하기

구인 공고에서 데이터를 추출하려면 페이지의 HTML 요소를 검사해야 합니다. 다음 단계를 따릅니다.

  • Indeed 웹 페이지에서 채용 공고 위로 마우스를 가져갑니다.
  • 직책을 클릭합니다.
  • 컨텍스트 메뉴에서 '검사'를 선택합니다.

이 작업을 수행하면 브라우저의 개발자 도구가 열리고 페이지의 기본 HTML 구조를 볼 수 있습니다. 아래 이미지와 같이 클래스 이름이 'job_seen_beacon'인 div 요소를 찾습니다.

 

이미지: medium

 

'job_seen_beacon' 클래스는 인디드의 채용공고에 사용되는 공통 식별자입니다. 이 클래스는 각 개별 채용 공고를 캡슐화하여 원하는 데이터 요소를 쉽게 찾고 추출할 수 있게 해줍니다.

채용 공고에서 데이터 추출하기

다음으로 각 채용 공고에서 특정 정보를 추출해야 합니다. 아래는 바로 이 작업을 수행하도록 설계된 Python 함수 get_data(job_listing)입니다.

def get_data(job_listing):
    # Extract job title
    title = job_listing.find("a").find("span").text.strip()
    
    # Extract company name if available, otherwise assign an empty string
    try:
        company = job_listing.find('span', class_='css-92r8pb eu4oa1w0').text.strip()
    except AttributeError:
        company = ''
    
    # Extract job location if available, otherwise assign an empty string
    try:
        location  = job_listing.find('div', class_='css-1p0sjhy eu4oa1w0').text.strip()
    except AttributeError:
        location = ''
        
    # Extract salary information if available, otherwise assign an empty string
    try:
        salary  = job_listing.find('div', class_='metadata salary-snippet-container css-5zy3wz eu4oa1w0').text.strip()
    except AttributeError:
        salary = ''
    
    # Extract job type if available, otherwise assign an empty string
    try:
        job_type = job_listing.find('div', class_='metadata css-5zy3wz eu4oa1w0').text.strip()
    except AttributeError:
        job_type = ''
    
    # Extract date posted
    date_posted = job_listing.find('span', class_='css-qvloho eu4oa1w0').text.strip()
    
    # Extract job summary
    summary = job_listing.find('div', class_='css-9446fg eu4oa1w0').text.strip()
    
    # Return a tuple containing all the extracted information
    return (title, company, location, salary, job_type, date_posted, summary)

 

  • Job Title: 채용 정보 내에서 앵커(a) 태그를 찾아 중첩된 스팬 요소의 텍스트 콘텐츠를 검색하여 직책을 추출합니다.
  • Company Name: 클래스명이 'css-92r8pb eu4oa1w0'인 특정 스팬 요소를 검색하여 회사명을 추출하려고 시도합니다. 발견되면 텍스트 콘텐츠를 검색하고, 그렇지 않으면 빈 문자열을 할당합니다.
  • Job Location: 마찬가지로 클래스 이름이 'css-1p0sjhy eu4oa1w0'인 div 요소를 검색하여 작업 위치를 추출하려고 합니다. 가능한 경우 텍스트 콘텐츠를 검색하고, 그렇지 않으면 빈 문자열을 할당합니다.
  • Salary Information: 급여 정보를 추출하기 위해 클래스명이 '메타데이터 급여-스니펫-컨테이너 css-5zy3wz eu4oa1w0'인 div 요소를 찾습니다. 발견되면 텍스트 콘텐츠를 검색하고, 그렇지 않으면 빈 문자열을 할당합니다.
  • Job Type: 클래스명이 'metadata css-5zy3wz eu4oa1w0'인 div 요소를 검색하여 직종을 추출합니다. 존재하는 경우 텍스트 콘텐츠를 검색하고, 그렇지 않으면 빈 문자열을 할당합니다.
  • Date Posted: 클래스 이름이 'css-qvloho eu4oa1w0'인 스팬 요소를 찾아 텍스트 콘텐츠를 검색하여 게시된 날짜를 추출합니다.
  • Job Summary: 마지막으로 클래스 이름이 'css-9446fg eu4oa1w0'인 div 요소를 찾아 텍스트 콘텐츠를 검색하여 작업 요약을 추출합니다.

 

이 함수는 각 채용 공고에 대해 추출된 모든 정보가 포함된 튜플을 반환합니다.

 

이제 get_data 함수를 정의하여 Indeed.com의 채용 공고에서 관련 데이터를 추출할 수 있게 되었습니다. 이로써 스크랩된 데이터에서 가치 있는 인사이트를 수집하는 데 한 걸음 더 가까워졌습니다.

여러 페이지 스크래핑

Indeed.com의 여러 채용 공고 페이지에서 데이터를 스크랩하려면 더 이상 사용할 수 있는 페이지가 없을 때까지 반복하는 루프를 활용합니다. 이를 위한 코드 스니펫은 다음과 같습니다.

# Loop to scrape data from multiple pages until there are no more pages available
while True:
    try:
        # Extract the URL of the next page if available
        url = 'https://ng.indeed.com/' + soup.find('a', {'aria-label':'Next Page'}).get('href')
    except AttributeError:
        # If there are no more pages available, break the loop
        break
    
    # Open the next page in the browser
    driver.get(url)
    
    # Get the HTML source code of the next page
    html = driver.page_source
    
    # Parse the HTML of the next page using BeautifulSoup
    soup = BeautifulSoup(html, 'html.parser')
    
    # Find all job listings on the next page
    job_listings = soup.find_all('div', class_='job_seen_beacon') 

    # Iterate through each job listing on the page
    for job_listing in job_listings:
        # Extract data from the current job listing
        record = get_data(job_listing)
        
        # Append the extracted data to the records list
        records.append(record)

# Close the Chrome WebDriver instance
driver.quit()

 

  • 페이지 반복하기: While 루프를 사용하여 여러 페이지의 채용공고 목록을 반복합니다. 이 루프는 더 이상 사용할 수 있는 페이지가 없을 때까지 무한정 계속됩니다.
  • 다음 페이지 URL 추출하기: 루프 내에서 BeautifulSoup을 사용하여 다음 페이지의 URL을 추출하려고 시도합니다. '다음 페이지' 링크가 발견되면 URL을 구성하고 다음 페이지로 이동합니다. 그렇지 않으면 루프에서 벗어납니다.
  • 채용 정보 스크래핑: 각 페이지에 대해 HTML 소스 코드를 스크랩하고 BeautifulSoup을 사용하여 파싱합니다. 그런 다음 'job_seen_beacon' 클래스를 사용하여 페이지의 모든 채용 공고를 찾습니다.
  • 데이터 추출: 앞서 정의한 get_data 함수를 사용하여 각 구인 목록을 반복하고 관련 데이터를 추출합니다. 추출된 데이터는 레코드 목록에 추가됩니다.
  • WebDriver 닫기: 모든 페이지가 스크랩되면 시스템 리소스를 확보하기 위해 Chrome WebDriver 인스턴스를 닫습니다.

이 루프를 사용하면 Indeed.com의 여러 페이지에서 채용 공고를 체계적으로 스크랩하여 분석에 필요한 포괄적인 데이터 세트를 캡처할 수 있습니다.

데이터를 DataFrame으로 변환하고 CSV로 저장하기

추출한 구인 공고 데이터를 정리하고 추가 분석을 위해 저장하려면 레코드 목록을 DataFrame으로 변환한 다음 CSV 파일로 저장합니다. 다음은 이를 수행하는 코드입니다.

# Convert list of records into a DataFrame
df = pd.DataFrame(records, columns=['Title', 'Company', 'Location', 'Salary', 'Job Type', 'Date Posted', 'Summary'])

# Save DataFrame to a CSV file
df.to_csv('indeed_job_data.csv', index=False)

print("Data saved to job_data.csv")

 

  • 데이터프레임 변환: pd.DataFrame() 함수를 사용하여 레코드(기록) 목록을 데이터프레임으로 변환합니다. 추출된 데이터와 일치하도록 열 이름을 '직함', '회사', '위치', '급여', '직종', '게시 날짜', '요약'으로 지정합니다.
  • CSV 내보내기: 그런 다음 to_csv() 메서드를 사용하여 데이터 프레임을 'indeed_job_data.csv'라는 이름의 CSV 파일로 내보냅니다. CSV 파일에서 DataFrame 인덱스를 제외하기 위해 index=False를 설정합니다.

마지막으로 데이터가 CSV 파일에 성공적으로 저장되었음을 나타내는 확인 메시지를 인쇄합니다. 이 단계가 완료되면 이제 추출된 구인 목록 데이터가 구조화된 형식으로 저장되어 분석 또는 추가 처리를 위한 준비가 완료됩니다. 이 데이터 세트는 또 다른 데이터 분석 프로세스인 데이터 정리에 관한 다음 프로젝트에 사용될 수 있습니다. 아래 이미지는 판다 데이터 프레임에 저장된 데이터 세트입니다.

 

이미지: medium

 

중요 참고 사항: 윤리적 고려 사항

웹 스크래핑은 데이터를 수집하는 강력한 도구가 될 수 있지만, 윤리를 염두에 두고 접근하는 것이 중요합니다. 웹사이트의 서비스 약관을 존중하고 서버 부하를 고려하는 것이 중요합니다.

웹사이트에서 데이터를 스크랩할 때는 항상 해당 웹사이트의 서비스 약관 및 robots.txt 파일을 검토하고 준수하세요. 이러한 문서에는 데이터 액세스 및 사용과 관련하여 웹사이트 소유자가 설정한 규칙과 제한 사항이 간략하게 설명되어 있습니다. 이러한 가이드라인을 무시하면 법적 문제가 발생하고 평판이 손상될 수 있습니다.

또한 스크래핑 활동이 웹사이트 서버에 미치는 영향도 염두에 두어야 합니다. 과도한 요청은 서버 리소스에 부담을 주고 다른 사용자의 서비스를 방해할 수 있습니다. 속도 제한 및 정중한 스크래핑 관행과 같은 기술을 사용하여 사용 공간을 최소화하고 모든 사용자가 웹사이트에 공정하게 액세스할 수 있도록 하세요.

이 프로젝트의 전체 코드는 [여기]에서 확인할 수 있으니 자유롭게 참고하시기 바랍니다.