본문 바로가기

Python으로 웹 스크래퍼 만들기

Python으로 웹 스크래퍼 만들기 복습 6일차[find(), find_all(), Attribute, strip(), lstrip(), rstrip(), import] (Feat. 노마드코더)

본 포스팅은 노마드코더님의 온라인 강의에 대한 복습을 기록하기 위한 포스팅입니다.

※ 본 포스팅의 내용은 강의를 들은 후 필자의 개인적인 의견을 기재한 것이니, 정답이 아닐 수 있음을 참고하십시오.

강의 소개 : 파이썬으로 웹 스크래퍼 만들기 (2주 완성반)
  -. 내용 : 파이썬 기초 (타입, 변수, 함수, 클래스 등 및 웹 스크래퍼 코드 작성법)
  -. 비용 : 100% 무료 강의
  -. 비고 : 한글 자막 제공 / 강의 100% 완료 시 10% 할인 쿠폰 제공
온라인 강의 : https://nomadcoders.co/
    /    풀스택 개발자 로드맵 : https://nomadcoders.co/roadmap
유튜브 채널 : https://www.youtube.com/channel/UCUpJs89fSBXNolQGOYKn0YQ
 

노마드 코더 Nomad Coders

한국인 린과 콜롬비아인 니꼴라스의 프로젝트 "노마드 코더" 입니다. 2015년 떠나, 현재까지 원하는 곳에서 일하며, 살고 있습니다. + + Nomad Academy: https://nomadcoders.co

www.youtube.com


 

#2.6 Extracting Titles & #2.7 Extracting Companies &

#2.8 Extracting Locations and Finishing up

  1) Attribute에 따라 find(), find_all() 하기

""" main.py """

from indeed import extract_indeed_pages, extract_indeed_jobs

# "indeed" Object의 "extract_indeed_pages" Function 호출
last_indeed_pages = extract_indeed_pages()
# "indeed" Object의 "extract_indeed_jobs" Function 호출
indeed_jobs = extract_indeed_jobs(last_indeed_pages)
"""indeed.py"""

~~~~~~~

def extract_indeed_jobs(last_pages):
    jobs = []
    for page in range(last_pages):
        result = requests.get(f"{URL}&start={page*LIMIT}")
        soup = BeautifulSoup(result.text, 'html.parser')

        # Indeed에서 각 일자리별 정보를 리스트로 가져오기
        results = soup.find_all("div", {"class":"jobsearch-SerpJobCard"})

        # 여러개의 결과에서 1개 일자리에 대한 정보 찾기
        for result in results:
            # h2 Tag중 클래스가 "title" 인 것
            title = result.find("h2", {"class":"title"})

            print(title)

    return jobs

    -. 이번에 설명해드리는 코드는 BeautifulSoup로 가져온 html 특정 tag에서 특정 Attribute(class, id, name 등)에

    -. 해당하는 값만 가져오기 위해서 사용합니다.

    -. 만약 이런 기능이 없다면 수많은 "div" tag에서 원하는 값을 찾기는 힘들겠죠?

    -. 사용방법은 간단합니다. find/find_all("Tag 이름", {"Attribute 이름" : "Attribute 값"})

    -. 이렇게 find() 혹은 find_all() 명령어 둘 다에서 사용이 가능합니다.

    -. 여기서 내가 원하는 값의 Tag가 무엇인지, Attribute가 무엇인지는 아래의 사진을 참고하시면 됩니다.

Python으로 웹 스크래퍼 만들기 복습 6일 차 1

 

  2) find() 한 결과에서 특정 Attribute 값 가져오기

"""indeed.py"""

~~~~~~~

def extract_indeed_jobs(last_pages):
    jobs = []
    for page in range(last_pages):
        result = requests.get(f"{URL}&start={page*LIMIT}")
        soup = BeautifulSoup(result.text, 'html.parser')

        # Indeed에서 각 일자리별 정보를 리스트로 가져오기
        results = soup.find_all("div", {"class":"jobsearch-SerpJobCard"})

        # 여러개의 결과에서 1개 일자리에 대한 정보 찾기
        for result in results:
            # h2 Tag중 클래스가 "title" 인 것 중에서 anchor Tag속의 attribute 가 "title"인 것
            title = result.find("h2", {"class":"title"}).find("a")["title"]

            print(title)

    return jobs

    -. 해당 코드는 Attribute 값으로 tag의 값을 가져오는 것이 아니라 tag의 이름Attribute의 이름으로

    -. Attribute 값을 찾는 코드로, "Indeed" 사이트의 Title과 같이 .string으로 가져오기 힘든 경우 사용하면 좋다

    -. 사용방법 : find("태그 이름")["Attribute 이름"]

    -. 필요한 Attribute 를 찾는 방법은 아래의 사진을 참조하십시오.

Python으로 웹 스크래퍼 만들기 복습 6일 차 2

 

  3) html 소스의 양식이 다른 경우에 회사 이름 가져오기

"""indeed.py"""

~~~~~~~

def extract_indeed_jobs(last_pages):
    jobs = []
    for page in range(last_pages):
        result = requests.get(f"{URL}&start={page*LIMIT}")
        soup = BeautifulSoup(result.text, 'html.parser')

        # Indeed에서 각 직업별 정보를 리스트로 가져오기
        results = soup.find_all("div", {"class":"jobsearch-SerpJobCard"})
        # 여러개의 결과에서 1개 일자리에 대한 정보 찾기
        for result in results:
            # h2 Tag중 클래스가 title 인것 중에서 anchor Tag속의 attribute 가 "title"인 것
            title = result.find("h2", {"class":"title"}).find("a")["title"]
            company = result.find("span", {"class":"company"})

			# company 중에 "a" Tag가 있는 경우
            if company.find("a") is not None:
              company = str(company.find("a").string)
            else:
              company = str(company.string)

    return jobs

    -. 위 코드를 살펴보면 title과는 다르게 if - else 구문사용 된 것을 보실 수 있습니다.

    -. 왜냐하면 company라는 Attribute가 "span" 태그 바로 아래에 있는 경우와 "span > a" 태그 아래에 있는 경우로

    -. 나뉘어 있기 때문인데요

    -. 이 경우와 같이 어떤 정보를 가져올 때는 반드시 여러 차례 테스트를 통해 확인해보는 것이 중요합니다.

    -. html 소스는 아래 사진을 참조하시기 바랍니다. (파란색 : "span" / 빨간색 : "span > a")

Python으로 웹 스크래퍼 만들기 복습 6일 차 3

 

  4) strip(), lstrip(), rstrip()으로 가져온 값의 앞/뒤에 있는 공백(Space) 삭제하기

Python으로 웹 스크래퍼 만들기 복습 6일 차 4

    -. 위 사진을 보시면 위/아래의 결과가 다르게 나오는 것을 보실 수 있습니다.

    -. 바로 회사명 다음에 한 줄이 띄워져 있냐 / 안 띄워져 있냐 인데요.

    -. 사실은 저것이 Enter(\n 혹은 /n)이 아니라 엄청난 수의 공백(Space)으로 한 줄씩 띄워져 있는 것처럼 보이는데요

    -. 이렇게 되면 Data를 처리하는데 있어서 큰 문제가 발생합니다. 대표적으로 회사명의 길이를 구할 수가 없겠죠

"""indeed.py"""

~~~~~~~

def extract_indeed_jobs(last_pages):
    jobs = []
    for page in range(last_pages):
        result = requests.get(f"{URL}&start={page*LIMIT}")
        soup = BeautifulSoup(result.text, 'html.parser')

        # Indeed에서 각 직업별 정보를 리스트로 가져오기
        results = soup.find_all("div", {"class":"jobsearch-SerpJobCard"})
        # 여러개의 결과에서 1개 일자리에 대한 정보 찾기
        for result in results:
            # h2 Tag중 클래스가 title 인것 중에서 anchor Tag속의 attribute 가 "title"인 것
            title = result.find("h2", {"class":"title"}).find("a")["title"]
            company = html.find("span", {"class": "company"})

            # company 중에 "a" Tag가 있는 경우
            if company.find("a") is not None:
                company = str(company.find("a").string)
            else:
                company = str(company.string)
            
            # company 값의 앞/뒤에 있는 공백 삭제하기
            company = company.strip()

            print(company)

    return jobs

    -. 위의 코드와 같이 수정을 하시면 이 문제는 해결할 수 있습니다.

    -. strip() : String의 좌/우 모두에서 해당 값을 제거한다.

    -. lstip() : String의 좌측에 있는 해당 값을 제거한다.

    -. rstip() : String의 우측에 있는 해당 값을 제거한다.

text = "      abcde      "
s_text = text.strip()
l_text = text.lstrip()
r_text = text.rstrip()

print(f"s_text = {s_text}")
print(f"l_text = {l_text}")
print(f"r_text = {r_text}")


""" 출력 값 """
s_text = abcde
l_text = abcde      
r_text =       abcde




text = "aaaabbbbaaaa"
s_text = text.strip("a")
l_text = text.lstrip("a")
r_text = text.rstrip("a")

print(f"s_text = {s_text}")
print(f"l_text = {l_text}")
print(f"r_text = {r_text}")


""" 출력 값 """
s_text = bbbb
l_text = bbbbaaaa
r_text = aaaabbbb

    -. 위 코드를 보시면... 뭐 더이상 설명이 필요 없겠죠?

 

  5) Location 값 가져오기

"""indeed.py"""

~~~~~~~

def extract_job(html):
      # h2 Tag중 클래스가 title 인것 중에서 anchor Tag속의 attribute 가 "title"인 것
    title = html.find("h2", {"class":"title"}).find("a")["title"]
    company = html.find("span", {"class": "company"})

    # company 중에 "a" Tag가 있는 경우
    if company.find("a") is not None:
        company = str(company.find("a").string)
    else:
        company = str(company.string)
    
    # company 값의 앞/뒤에 있는 공백 삭제하기
    company = company.strip()

    location = html.find("span", {"class":"location"})
    print(location)

    return {"title": title, "company": company, "location": location}

def extract_indeed_jobs(last_pages):
    jobs = []
    for page in range(last_pages):
        result = requests.get(f"{URL}&start={page*LIMIT}")
        soup = BeautifulSoup(result.text, 'html.parser')

        # Indeed에서 각 직업별 정보를 리스트로 가져오기
        results = soup.find_all("div", {"class":"jobsearch-SerpJobCard"})
        # 여러개의 결과에서 1개 일자리에 대한 정보 찾기
        for result in results:
          job = extract_job(result)

          jobs.append(job)

    return jobs

    -. 우선 위 코드는 기존 코드에서 result 내부 값을 꺼내는 코드별도의 function으로 분리 준 코드입니다.

    -. 그리고 "location" 항목이 추가된 것을 보실 수 있는데요. 하지만, 이 코드로는 원하는 값을 구할 수가 없습니다.

    -. 아래 사진을 보시죠, 분명히 "span" 태그 중 class가 "location"인 항목들이 있습니다.

    -. 하지만 코드 실행 결과를 보시면 None이 있는 것을 보실 수 있는데요. 

    -. 이 경우 .string 문자 값을 구하면 "None Type"은 문자 값을 구할 수 없으므로 에러가 발생합니다.

Python으로 웹 스크래퍼 만들기 복습 6일 차 5

    -. 그 이유는 일부 일자리에는 location이 없는 경우도 있기 때문입니다.

    -. 이 것을 해결하려면 html 소스를 잘 살펴보셔야 합니다.

    -. 아래 사진을 다시 한 번 살펴보세요. location 값은 "span" 태그에만 존재하나요?

Python으로 웹 스크래퍼 만들기 복습 6일 차 6

    -. 아니죠 "span"태그를 감싸고 있는 "div" 태그에 Attribute 값으로 location 값이 있는 것을 확인 하실 수 있습니다.

Python으로 웹 스크래퍼 만들기 복습 6일 차 7

    -. 그러므로 아래의 코드를 사용하여 location 값을 구하시면 됩니다.

"""indeed.py"""

~~~~~~~

def extract_job(html):
      # h2 Tag중 클래스가 title 인것 중에서 anchor Tag속의 attribute 가 "title"인 것
    title = html.find("h2", {"class":"title"}).find("a")["title"]
    company = html.find("span", {"class": "company"})

    # company 중에 "a" Tag가 있는 경우
    if company.find("a") is not None:
        company = str(company.find("a").string)
    else:
        company = str(company.string)
    
    # company 값의 앞/뒤에 있는 공백 삭제하기
    company = company.strip()

    # # locaiton 값이 없는 일자리가 있어서 None이 출력 되는 경우 발생
    # location = html.find("span", {"class":"location"})
    location = html.find("div", {"class":"recJobLoc"})["data-rc-loc"]
    print(location)

    return {"title": title, "company": company, "location": location}

def extract_indeed_jobs(last_pages):
    jobs = []
    for page in range(last_pages):
        result = requests.get(f"{URL}&start={page*LIMIT}")
        soup = BeautifulSoup(result.text, 'html.parser')

        # Indeed에서 각 직업별 정보를 리스트로 가져오기
        results = soup.find_all("div", {"class":"jobsearch-SerpJobCard"})
        # 여러개의 결과에서 1개 일자리에 대한 정보 찾기
        for result in results:
          job = extract_job(result)

          jobs.append(job)

    return jobs

 

  6) id 값을 구하여 일자리 상세페이지로 이동하는 Link 구하기

Python으로 웹 스크래퍼 만들기 복습 6일 차 8

    -. 일자리를 Ctrl + 클릭하게 되면 상세페이지가 뜨게 되는데 url을 자세히 살펴보면

    -. "jk" 라는 값에 각 일자리별 고유의 "id"들어 가는 것을 확인할 수 있습니다.

    -. 그렇다면 이 jk 값은 어디 있을까요?

Python으로 웹 스크래퍼 만들기 복습 6일 차 9

    -. 바로 ("div", {"class":"jobsearch-SerpJobCard"}) 안에 있는데요 어딘가 익숙하시죠?

    -. 맞습니다. results 로 값을 구해서 result로 1개씩 꺼내온 바로 그 곳에 위치해 있는데요.

    -. 우리는 extract_jon(html) 이라는 function으로 분리를 했으므로, html 안에 값이 있다는 걸 알 수 있습니다.

    -. 해당 값을 구하는 방법은 추가적인 설명 없이 아래 코드를 보시면 되겠습니다.

"""indeed.py"""

~~~~~~~

def extract_job(html):
      # h2 Tag중 클래스가 title 인것 중에서 anchor Tag속의 attribute 가 "title"인 것
    title = html.find("h2", {"class":"title"}).find("a")["title"]
    company = html.find("span", {"class": "company"})

    # company 중에 "a" Tag가 있는 경우
    if company.find("a") is not None:
        company = str(company.find("a").string)
    else:
        company = str(company.string)
    
    # company 값의 앞/뒤에 있는 공백 삭제하기
    company = company.strip()

    # # locaiton 값이 없는 일자리가 있어서 None이 출력 되는 경우 발생
    # location = html.find("span", {"class":"location"})
    location = html.find("div", {"class":"recJobLoc"})["data-rc-loc"]
    
    job_id = html["data-jk"]
    print(job_id)

    return {"title": title, "company": company, "location": location, "link": f"https://www.indeed.com/viewjob?jk={job_id}"}

def extract_indeed_jobs(last_pages):
    jobs = []
    for page in range(last_pages):
        result = requests.get(f"{URL}&start={page*LIMIT}")
        soup = BeautifulSoup(result.text, 'html.parser')

        # Indeed에서 각 직업별 정보를 리스트로 가져오기
        results = soup.find_all("div", {"class":"jobsearch-SerpJobCard"})
        # 여러개의 결과에서 1개 일자리에 대한 정보 찾기
        for result in results:
          job = extract_job(result)

          jobs.append(job)

    return jobs

 

※ Indeed 사이트에서 일자리 정보 Scrapping 하기 전체 코드 (~#2.8)

  -. Scrapping 순서

    1) 페이지 가져오기

    2) requests 만들기

    3) jobs 가져오기

"""main.py"""
from indeed import extract_indeed_pages, extract_indeed_jobs

# "indeed" Object의 "extract_indeed_pages" Function 호출
last_indeed_pages = extract_indeed_pages()
# "indeed" Object의 "extract_indeed_jobs" Function 호출
indeed_jobs = extract_indeed_jobs(last_indeed_pages)

print(indeed_jobs)
"""indeed.py"""
import requests
from bs4 import BeautifulSoup

LIMIT = 50
# 스크래핑 하려는 INDEED_URL 저장
URL = f"https://www.indeed.com/jobs?q=python&limit={LIMIT}"


# "Indeed" 웹사이트를 스크래핑 하는 Function 생성
def extract_indeed_pages():

    # url에 저장된 웹사이트를 requestsdml get Function을 사용하여 가져오기
    result = requests.get(URL)

    # # html을 text로 가져오기
    # print(result.text)
    # bs4를 이용하여 "Indeed" 검색 결과를 html로 가져오기
    soup = BeautifulSoup(result.text, 'html.parser')

    # "div" Tag 중 Class가 "pagination"인 것 가져오기
    pagination = soup.find("div", {"class": "pagination"})
    # "pagination" 변수에 저장된 html 중에서 "anchor" Tag 인 것 모두 가져와서 리스트로 저장
    links = pagination.find_all("a")

    # span을 저장할 빈 리스트 객체 생성
    pages = []
    # "links" 리스트에 저장된 값을 차례대로 "link"라는 객체로 받아온 후 "span" Tag 인 것을 "pages" 리스트에 추가 # [:-1] = "links" 리스트에 저장된 값 중 마지막 값을 제외한다는 뜻
    for link in links[:-1]:
        # pages.append(link.find("span").string)
        # anchor Tag의 String을 가지고 와도 span의 String을 가지고 온다
        pages.append(int(link.string))

        # # "pages" 리스트에 저장된 값 중 마지막에서 1번째 item
        # print(pages[-1])

        # "pages"에 저장된 값 중 가장 큰 값(마지막 값)만 가져오기
        max_page = pages[-1]

    return max_page


def extract_job(html):
    # h2 Tag중 클래스가 title 인것 중에서 anchor Tag속의 attribute 가 "title"인 것
    title = html.find("h2", {"class": "title"}).find("a")["title"]
    company = html.find("span", {"class": "company"})

    # company 중에 "a" Tag가 있는 경우
    if company.find("a") is not None:
        company = str(company.find("a").string)
    else:
        company = str(company.string)

    # company 값의 앞/뒤에 있는 공백 삭제하기
    company = company.strip()

    # # locaiton 값이 없는 일자리가 있어서 None이 출력 되는 경우 발생
    # location = html.find("span", {"class":"location"})
    location = html.find("div", {"class": "recJobLoc"})["data-rc-loc"]

    job_id = html["data-jk"]

    return {
        "title": title,
        "company": company,
        "location": location,
        "link": f"https://www.indeed.com/viewjob?jk={job_id}"
    }


def extract_indeed_jobs(last_pages):
    jobs = []
    for page in range(last_pages):
        print(f"Scrapping page : {page}")
        result = requests.get(f"{URL}&start={page*LIMIT}")
        soup = BeautifulSoup(result.text, 'html.parser')

        # Indeed에서 각 직업별 정보를 리스트로 가져오기
        results = soup.find_all("div", {"class": "jobsearch-SerpJobCard"})
        # 여러개의 결과에서 1개 일자리에 대한 정보 찾기
        for result in results:
            job = extract_job(result)

            jobs.append(job)

    return jobs

 

#2.9 StackOverflow Pages

  1) StackOverflow 파일 생성 및 Pagination 처리

"""main.py"""
from indeed import get_jobs as get_indeed_jobs
from stackoverflow import get_jobs as get_stackoverflow_jobs

# indeed_jobs = get_indeed_jobs()
stackoverflow_jobs = get_stackoverflow_jobs()
print(stackoverflow_jobs)
"""stackoverflow.py"""
import requests
from bs4 import BeautifulSoup

# 스크래핑 하려는 URL 저장
URL = f"https://stackoverflow.com/jobs?q=python"

def extract_pages():
  result = requests.get(URL)
  soup = BeautifulSoup(result.text, "html.parser")

  pages = soup.find("div", {"class":"s-pagination"}).find_all("a")
  print(pages)



def get_jobs():
  last_page = extract_pages()
  return []

  -. "main.py"는 기존 indeed 사이트를 Scrapping 할 때와 같은 파일을 사용합니다. 단, indeed 관련 function은

  -. 주석처리를 해서 stackoverflow를 테스트 함에 있어 indeed 사이트를 Scrapping 하지 않도록 설정하였습니다.

 

※ 본 포스팅의 내용은 강의를 들은 후 필자의 개인적인 의견을 기재한 것으로,

   정답이 아닐 수 있음을 참고하십시오.

 


이상으로 Python으로 웹 스크래퍼 만들기 복습 6일 차를 마치겠습니다.

 

Python으로 웹 스크래퍼 만들기 복습 6일 차 10