Jay's Project/wanted 채용 공고 분석

[wanted 채용 공고 분석] 3. 데이터 수집(2)

jay3108 2022. 1. 14. 15:17

 

지난 포스팅에서 채용 공고들 각각의 URL을 수집하였다. 

이번에는 채용 공고들을 클릭하고 들어가면 볼 수 있는 Job description을 구체적으로 수집해본다.

마찬가지로 selenium을 이용하여 크롤링하며 기본적인 진행과정은 전 단계와 동일하다.

 

수집한 채용 공고의 개별 URL 접속


import sys 
import os  

import pandas as pd  
import numpy as np


from bs4 import BeautifulSoup   
from selenium import webdriver   
from selenium.webdriver.common.keys import Keys
import chromedriver_autoinstaller 

import time    
from tqdm import tqdm_notebook

# 워닝 무시
import warnings
warnings.filterwarnings('ignore')

 

데이터 수집 단계, 특히나 웹 크롤링 부분에서는 우선 하나의 목표 데이터에 접근하여 코드를 구성하는 것이 좋다.

웹 페이지의 데이터들은 한 사이트, 혹인 한 페이지 안에서 일정한 형식으로 게시되어 있기 마련이다.

이 떄, 목표 데이터를 하나씩 수집하는 코드를 구성하는 것을 목표로 크롤링 코드를 작성한다.

하나의 목표 데이터 셋을 완벽히 크롤링하는 코드를 구성하는 것에 성공한다면 전체를 구조화하여 전체 데이터셋을 자동으로 크롤링하는 것은 비교적 쉬운 일이다. (아닐 떄도 많다....)

따라서 이번에도 첫번째 URL로 접속하여 한 회사의 채용공고 게시글에서 필요한 데이터를 수집하는 코드를 구성한다.

이후 크롤링이 성공하면 각각의 URL마다 접속하여 자동으로 전체 데이터셋을 가져오는 코드만 구성하면 된다.

 

# url csv 파일 로드
url_load = pd.read_csv('wanted_url.csv',encoding='utf-8-sig')
url_load = url_load.drop('Unnamed: 0',axis=1)
num_list = len(url_load)
print(num_list)

# 웹드라이버 실행
chrome_path = chromedriver_autoinstaller.install()
driver = webdriver.Chrome(chrome_path)

i = 0
url = url_load['url'][i]
driver.get(url)
time.sleep(1)

목표 데이터 수집 test


포지션, 회사명 수집

- 포지션과 회사명은 모두 JobHeader_className__HttDA 이라는 클래스에 담겨있다.

- 포지션은 class_name 코드을 이용해 가져왔으며 해당 클래스의 하위 항목인 <h2> 태그에 담겨있다.

- css_selector 코드에서는 셀렉터 앞에 . 을 붙여서 가져오지만 class_name 코드에서는 바로 이름만 써주면 된다.

- 회사명은 해당 클래스에서 <div> 태그 아래 <h6> 태그에 담겨 있다.

- 만약 헤드라인 html 태그가 두 항목이 일치하였다면 이처럼 단순하게 크롤링할 수 없으며 다른 방식으로 접근한다.

# 제목 크롤링 시작
pos = driver.find_element_by_class_name('JobHeader_className__HttDA>h2')   # title
position = pos.text  # 셀레늄 덩어리 안의 텍스트 가져오기
position
# 회사명 크롤링 시작
company_name = driver.find_element_by_css_selector('.JobHeader_className__HttDA > div > h6')
name = company_name.text
name

 

관심 수 크롤링

관심 수 크롤링에서 이슈가 발생하였다. selenium을 통해 웹 크롤링을 진행할 때는 보통 F12의 개발자 도구창에서 접근 방식을 고민하게 된다. 그러나 원티드 웹사이트는 일종의 반응형 웹으로 구성되어 있어 개발자 도구 창을 늘리면 두 번째 사진과 같이 모바일 전용 웹 화면을 자동 전환된다. 이 떄는 HTML과 CSS 구성이 모바일 화면에 맞게 자동으로 변경되기 떄문에 반드시 실행하는 webdriver 창의 크기에 맞추어 놓고 크롤링 작업을 진행해야 한다. 

 

- 관심수는 likes 클래스에 span 항목에 나타나있으므로 해당 경로를 가져온다.

- 역시 이후 관심 수 순으로 정렬하기 위해 수집 단계에서 int로 자료형태를 변환한다.

# 관심 수 크롤링
likes = driver.find_element_by_css_selector('.likes>span')
like = int(likes.text)
like

 

Job description 수집 (주요업무, 자격요건, 우대사항)

- 각 채용공고는 위와 같이 기업들이 자유 양식으로 기술할 수 있는 부분과 주요업무, 자격요건, 요구사항, 복리후생과 같이 미리 정해놓은 포맷으로 입력하는 부분이 나누어져 있다.

- 이 프로젝트의 목적은 데이터 분석가 공고의 요구 기술 스택과 이에 관한 키워드 분석이므로 포맷으로 지정되어 있는 주요업무, 자격요건, 우대사항의 세부항목을 수집한다.

- 전체 본문은 JobDescription_JobDescription__VWfcb 클래스에 담겨 있으며 이후 <p> 태그와 <span>으로 구성된다.

- 따라서 주요업무를 크롤링하려면 JobDescription_JobDescription__VWfcb>p:nth-child(3) > span 하위 태그를 지정해야한다. 

- 이 때, 개발자 도구창에서 copy → selector를 통해 살펴보면 조금 더 쉽게 해당 항목에 접근할 수 있다.

- 이 후 자격요건, 우대사항 모두 같은 방식으로 경로를 지정해 크롤링이 성공적인지 test해본다.

# 주요업무 크롤링
descriptions = driver.find_element_by_css_selector('.JobDescription_JobDescription__VWfcb>p:nth-child(3) > span')
description = descriptions.text
description

# 자격요건 크롤링
requirements = driver.find_element_by_css_selector('.JobDescription_JobDescription__VWfcb>p:nth-child(5) > span')
requirement = requirements.text
requirement

# 우대사항 크롤링
preferreds = driver.find_element_by_css_selector('.JobDescription_JobDescription__VWfcb>p:nth-child(7) > span')
preferred = preferreds.text
preferred

 

데이터 수집 코드 구조화

여기까지 각 항목이 크롤링 되는 지 하나의 채용공고를 test 해보았다. 각 항목들이 성공적으로 크롤링이 된 것을 확인했으면 이제 전체 데이터 수집을 위해 구조화를 진행한다. 우선 개별적으로 각 공고들의 내용을 담을 target_info딕셔너리를 설정하고 포지션, 회사명 등을 딕셔너리의 key값으로 주고 수집한 항목을 value 로 넣어준다. 그 후 모든 공고를 담을 target_dict 딕셔너리를 만들고 그 안에 다시 각 공고를 value로 넣어준다. 

target_dict = {}  # 전체 크롤링 데이터를 담을 그릇
target_info = {}  # 개별 블로그 내용을 담을 딕셔너리 생성

target_info['position'] = position
target_info['name'] = name
target_info['description'] = description
target_info['requirement'] = requirement
target_info['preferred'] = preferred
target_info['like'] = like
target_dict[0] = target_info

전체 공고를 value 값을 담을 전체 딕셔너리 target_dict 확인

target_dict

개별 공고의 데이터가 담긴 target_info 확인

target_info

코드의 아웃풋을 보면 성공적으로 데이터 크롤링이 완료되어 딕셔너리 형태로 담기게 된 것을 볼 수 있다. 앞에서도 기술하였듯 하나의 채용 공고를 성공적으로 크롤링 하였다고 같은 방식을 for문이나 while문을 통해 자동으로 반복하여 크롤링할 수 있는 프로젝트 수집의 전체 코드를 작성하는 단계로 넘어간다.

하나의 대상 데이터 크롤링을 test 하면서 데이터 수집 방법을 고민하는 것은 참으로 고된 일이었다. 웹 페이지 마다 구조가 다르고 중복된는 클래스명, css가 많기 때문에 사실 각 데이터를 크롤링하는 부분이 핵심이다. 여기까지 코드로 구현했다면 이후의 과정들은 매우 수월하게 진행된다. 또한 전체 데이터를 간결하고 빠른 방식으로 수집한는 코드를 구상하는 작업은 묘한 쾌감을 주기도 했다.

 

전체 데이터 크롤링 코드 작성


# for문으로 전체 범위 구조화

target_dict = {}    # 전체 크롤링 데이터를 담을 그릇

# 수집할 글 갯수 정하기
number = num_list

# 수집한 url 돌면서 데이터 수집
for i in tqdm_notebook(range(0, number)):
    # 글 띄우기
    url = url_load['url'][i]
    chrome_path = chromedriver_autoinstaller.install()
    driver = webdriver.Chrome(chrome_path)

    driver.get(url)   # 글 띄우기
    time.sleep(1)
    # 크롤링
    # 예외 처리
    try : 
        target_info = {}  # 개별 블로그 내용을 담을 딕셔너리 생성

        # 제목 크롤링
        pos = driver.find_element_by_class_name('JobHeader_className__HttDA>h2')   # title
        position = pos.text  # 셀레늄 덩어리 안의 텍스트 가져오기
        position
        # 사명 크롤링
        company_name = driver.find_element_by_css_selector('.JobHeader_className__HttDA > div > h6')
        name = company_name.text
        name

        # 주요 업무 크롤링
        descriptions = driver.find_element_by_css_selector('.JobDescription_JobDescription__VWfcb>p:nth-child(3) > span')
        description = descriptions.text
        description
        
        # 자격요건 크롤링
        requirements = driver.find_element_by_css_selector('.JobDescription_JobDescription__VWfcb>p:nth-child(5) > span')
        requirement = requirements.text
        requirement
        
        # 우대사항 크롤링
        preferreds = driver.find_element_by_css_selector('.JobDescription_JobDescription__VWfcb>p:nth-child(7) > span')
        preferred = preferreds.text
        preferred
        
        # 관심 수 크롤링
        likes = driver.find_element_by_css_selector('.likes>span')
        like = int(likes.text)
        like
        
        # 글 하나는 target_info라는 딕셔너리에 담기게 되고,
        target_info['position'] = position
        target_info['name'] = name
        target_info['description'] = description
        target_info['requirement'] = requirement
        target_info['preferred'] = preferred
        target_info['like'] = like

        # 각각의 글은 target_dict라는 딕셔너리에 담기게 됩니다.
        target_dict[i] = target_info
        time.sleep(1)
        
        # 크롤링이 성공하면 글 제목을 출력하게 되고,
        print(i, position)

        # 글 하나 크롤링 driver.close() 
       # 에러나면 현재 크롬창을 닫고 다음 글(i+1)로 이동합니다.
    except:
        # print("에러",i, title)
        driver.close()
        time.sleep(1)
        continue
        
        # 중간,중간에 파일로 저장하기
    if i == 30 or i==50 or i==80:
        # 판다스로 만들기
        import pandas as pd
        result_df = pd.DataFrame.from_dict(target_dict, 'index')
        # 저장하기
        result_df.to_csv("wanted_content.csv", encoding='utf-8-sig')
        time.sleep(3)

print('수집한 글 갯수: ', len(target_dict))
print('작업이 완료되었습니다.')

 

데이터 프레임 생성


result_df = pd.DataFrame.from_dict(target_dict, orient='index')
result_df

 

result_df.sort_values('like',ascending=False).head() # 관심 수 기준 정렬

 

지금까지 selenium 을 이용한 데이터 크롤링을 해보았다. 프로젝트에서 상당 시간이 소요되는 부분이 데이터 수집 단계가 아닐까 싶다. 크롤링에 능숙한 분석가라도 각각 웹 페이지 구조가 다르기 때문에 다양하게 접근을 시도해봐야 하는 것 같다. 또한 경험적이 부분으로 해결하는 면도 있어서 다양하게 크롤링을 연습하는 과정이 필요하다.

본인은 첫 프로젝트였던 만큼 사실 포스팅에는 작성하지 않은 무수히 많은 시도와 실패가 있었다. 하지만 크롤링이 되서 결과창에 원하는 데이터가 나왔을 때의 희열 또한 잊을 수 없을 것이다. 다음부터는 무작정 들이대기보다 html 구조와 css 의 구성을 잘 살펴보면서 어떻게 데이터에 접근할 것인지 고민하는 시간을 먼저 갖는 것이 효율적인 수집이 될 것 같다.

다음으로는 데이터 전처리와 시각화 부분을 포스팅할 예정이다. 

 

해당 크롤링 작업은 개인적인 데이터 분석 공부를 위한 것이며 상업적으로 활용하거나 개인정보를 침해할 요소가 없음을 알립니다. 원티드에서 꼭 취업할게요! 화이팅!