Flask

Python으로 웹사이트를 만들 수 있게 해주는 micro-framework
공식문서 / 한글화된 공식문서

url에 따른 구조

폴더 구조

venv 폴더와 requirements.txt 파일은 가상환경 사용하면서 생성함


완성 코드

main.py

from flask import Flask, render_template, request, redirect, send_file
from scrapper import get_jobs, save_to_file

app = Flask("SuperScrapper")

db = {}

# 데코레이터를 통해, 해당 루트로 접근하면 함수가 자동으로 실행됨
# 데코레이터 아래에는 함수만! 변수는 X
@app.route('/')
def home():
    return render_template('home.html')

@app.route('/contact')
def contact():
    return 'Contact me!'

# Dynamic URL
@app.route('/<username>')
def greeting(username):
    return f'Hello {username}, how are you?'

# Query Arguments
@app.route('/report')
def report():
    word = request.args.get('word')
    if word:
        word = word.lower()
        from_db = db.get(word)
        if from_db:
            jobs = from_db
        else:
            jobs = get_jobs(word)
            db[word] = jobs
    else:
        return redirect('/')
    return render_template('report.html', resultsCount=len(jobs), word=word, jobs=jobs)

@app.route('/export')
def export():
    try:
        word = request.args.get('word')
        if not word:
            # exception(error)이 발생하면 try 블록 중단하고 except 블록 실행
            raise Exception()
        word = word.lower()
        jobs = db.get(word)
        if not jobs:
            raise Exception()
        save_to_file(jobs)
        return send_file('jobs.csv')
    except:
        return redirect('/')

app.run()

scrapper.py

import requests    # url로 get 요청 보내서 html 받아오기
from bs4 import BeautifulSoup    # 응답으로 받은 html에서 필요한 데이터 추출하기

import csv

# -----------------------------------------------------
# Part 1. Scrapping

def extract_jobkorea_pages(url):
    response = requests.get(url)

    # 마지막 페이지 번호가 안 보이기 때문에 전체 공고 수를 이용
    soup = BeautifulSoup(response.text, 'html.parser')
    dev_tot = soup.find('strong', {'class': 'dev_tot'}).string
    counts = ''

    for item in dev_tot:
        if '0' <= item <= '9':
            counts += item

    counts = int(counts)
    # 한 페이지에 보여지는 공고 수 20을 이용하여 마지막 페이지 번호 구하기
    max_page = counts // 20 + 1 if counts % 20 != 0 else counts // 20
    return max_page


def extract_jobkorea_jobs(max_page, url):
    jobs = []

    for page in range(1, max_page + 1):
        print(f'Scrapping page {page}')
        response = requests.get(f'{url}&Page_No={page}')
        soup = BeautifulSoup(response.text, 'html.parser')
        posts = soup.find_all('div', {'class': 'post'})
        for post in posts:
            # 한 페이지의 posts 중 공고 post를 다 가져온 뒤 공고가 아닌 post를 만나면 종료
            try:
                title = post.find('a', {'class': 'title'})['title'].strip()
                company = post.find('a', {'class': 'name'})['title'].strip()
                location = post.find('span', {'class': 'loc long'}).string.strip()
                link = post.find('a', {'class': 'title'})['href'].strip()
                # 잡코리아 링크의 일부분이 아닌 전체 링크를 가지고 있을 경우 대비
                if link[:4] != 'http':
                    link = "https://www.jobkorea.co.kr" + link
                job = {'title': title, 'company': company, 'location': location, 'link': link}
                jobs.append(job)
            except:
                break

    return jobs

# --------------------------------------------------------
# Part 2. Saving to CSV file
# Comma Seperated Value, column은 ,로 row는 new line으로 구분
# excel 프로그램이 있어야 하는 xls와 달리 다양한 환경에서 사용할 수 있음

def save_to_file(jobs):
    file = open('jobs.csv', mode="w")    # 쓰기 전용으로 파일 열기
    writer = csv.writer(file)
    writer.writerow(['title', 'company', 'location', 'link'])
    for job in jobs:
        writer.writerow(list(job.values()))
    return


# --------------------------------------------------------
# Part 3. Run

def get_jobs(word):
    JOBKOREA_URL = f"https://www.jobkorea.co.kr/Search/?stext={word}&tabType=recruit"
    last_jobkorea_page = extract_jobkorea_pages(JOBKOREA_URL)
    jobkorea_jobs = extract_jobkorea_jobs(last_jobkorea_page, JOBKOREA_URL)
    return jobkorea_jobs

templates/home.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Job Search</title>
</head>
<body>
  <h1>Job Search</h1>
  <form action="/report" method="get">
    <input type="text" placeholder="Which job do you want to search?" required name="word">
    <button>Search</button>
  </form>
</body>
</html>

templates/report.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Job Search</title>
  <style>
    section {
      display: grid;
      gap: 20px;
      grid-template-columns: repeat(4, 1fr);
    }
  </style>
</head>
<body>
  <h1>Search Results</h1>
  <h3>Found {{resultsCount}} results for {{word}}</h3>
  <a href="/export?word={{word}}">Export to CSV</a>
  <section>
    <h4>Title</h4>
    <h4>Company</h4>
    <h4>Location</h4>
    <h4>Link</h4>
    {% for job in jobs %}
      <span>{{job.title}}</span>
      <span>{{job.company}}</span>
      <span>{{job.location}}</span>
      <a href="{{job.link}}" target="_blank">Apply</a>
    {% endfor %}
  </section>
</body>
</html>

결과물

/

/report?word=Vue

/export?word=Vue


jobs.csv
246.1 kB

※ 강의 링크: https://nomadcoders.co/python-for-beginners

'Clone Coding > Python으로 웹 스크래퍼 만들기' 카테고리의 다른 글

#3 Get Ready for Django(More about Python)  (0) 2022.07.04
#2 Building a Web Scrapper  (0) 2022.07.04
#1 Python Theory  (0) 2022.07.04

arguments & Object Oriented Programming

arguments

  • *args
    • 정해지지 않은 수의 positional arguments를 함수에 전달할 수 있음
    • 하나의 tuple로 묶여서 전달됨
  • **kargs
    • 정해지지 않은 수의 keyword arguments를 함수에 전달할 수 있음
    • 하나의 dictionary로 묶여서 전달됨
def plus(*nums):
    result = 0
    for num in nums:
        result += num
    return result

print(plus(1, 2, 3, 5, 7, 28, 5489, 15))

Object Oriented Programming

  • class: 객체를 생성하기 위한 설계도
    • method: class 내부에 있는 function, 첫 번째 인자로 무조건 self를 받아야 함
    • property: class 내부에 있는 variable
    • dir(class): class의 모든 methods와 properties를 리스트로 확인할 수 있음
    • override: class의 method를 재정의하는 것
    • extends: 클래스 상속
  • instance: class로 생성한 객체
class Car():
    # instance를 생성하는 method
    def __init__(self, **kwargs):
        self.wheels = 4
        self.doors = 4
        self.windows = 4
        self.seats = 5
        self.color = kwargs.get('color', 'black')
        self.price = kwargs.get('price', '$20')

    # object를 string으로 바꾸는 method
    def __str__(self):
        return f'Car with {self.wheels} wheels'

    # 사용자 정의 method
    def go(self):
        print('Go')

# extend Car class
class Convertible(Car):
    # extend __init__ method
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.time = kwargs.get('time', 10)

    def take_off(self):
        return "taking off"

# Car class로 생성한 instance
mini = Car(color='white', price='$1000')
porche = Convertible(color='red', price='$99999', time='5')
mini.go()
porche.take_off()
print(porche.time)

※ 강의 링크: https://nomadcoders.co/python-for-beginners

'Clone Coding > Python으로 웹 스크래퍼 만들기' 카테고리의 다른 글

#4 Website with Flask  (0) 2022.07.05
#2 Building a Web Scrapper  (0) 2022.07.04
#1 Python Theory  (0) 2022.07.04

Web Scrapping

웹 상의 데이터를 추출하는 작업

완성 코드

import requests    # url로 get 요청 보내서 html 받아오기
from bs4 import BeautifulSoup    # 응답으로 받은 html에서 필요한 데이터 추출하기

import csv    # csv 파일 입출력, 읽고 쓰기

# -----------------------------------------------------
# Part 1. Scrapping

JOBKOREA_LIMIT = 20    # 한 페이지에 보여지는 공고 수
JOBKOREA_URL = "https://www.jobkorea.co.kr/Search/?stext=python&tabType=recruit"

def extract_jobkorea_pages():
    response = requests.get(JOBKOREA_URL)

    # 마지막 페이지 번호가 안 보이기 때문에 전체 공고 수를 이용
    soup = BeautifulSoup(response.text, 'html.parser')
    dev_tot = soup.find('strong', {'class': 'dev_tot'}).string
    counts = ''

    for item in dev_tot:
        if '0' <= item <= '9':
            counts += item

    counts = int(counts)
    max_page = counts // JOBKOREA_LIMIT + 1 if counts % JOBKOREA_LIMIT != 0 else counts // JOBKOREA_LIMIT
    return max_page


def extract_jobkorea_jobs(last_page):
    jobs = []

    for page in range(1, last_page + 1):
        response = requests.get(f'{JOBKOREA_URL}&Page_No={page}')
        soup = BeautifulSoup(response.text, 'html.parser')
        posts = soup.find_all('div', {'class': 'post'})
        for post in posts:
            # 한 페이지의 posts 중 공고 post를 다 가져온 뒤 공고가 아닌 post를 만나면 종료
            try:
                title = post.find('a', {'class': 'title'})['title'].strip()
                company = post.find('a', {'class': 'name'})['title'].strip()
                location = post.find('span', {'class': 'loc long'}).string.strip()
                link = post.find('a', {'class': 'title'})['href'].strip()
                # 잡코리아 링크의 일부분이 아닌 전체 링크를 가지고 있을 경우 대비
                if link[:4] != 'http':
                    link = "https://www.jobkorea.co.kr" + link
                job = {'title': title, 'company': company, 'location': location, 'link': link}
                jobs.append(job)
            except:
                break

    return jobs

last_jobkorea_page = extract_jobkorea_pages()
jobkorea_jobs = extract_jobkorea_jobs(last_jobkorea_page)


# --------------------------------------------------------
# Part 2. Saving to CSV file
# Comma Seperated Value, column은 ,로 row는 new line으로 구분
# excel 프로그램이 있어야 하는 xls와 달리 다양한 환경에서 사용할 수 있음

def save_to_file(jobs):
    file = open('jobs.csv', mode="w")    # 쓰기 전용으로 파일 열기
    writer = csv.writer(file)    # open한 파일에 작성
    writer.writerow(['title', 'company', 'location', 'link'])
    for job in jobs:
        writer.writerow(list(job.values()))
    return

save_to_file(jobkorea_jobs)

indeed는 복잡하고 Stack Overflow Job 페이지는 지원 종료라서 잡코리아로 바꿔서 해봤음

결과물

2022.07.04. 기준
jobs.csv
0.63MB

※ 강의 링크: https://nomadcoders.co/python-for-beginners

'Clone Coding > Python으로 웹 스크래퍼 만들기' 카테고리의 다른 글

#4 Website with Flask  (0) 2022.07.05
#3 Get Ready for Django(More about Python)  (0) 2022.07.04
#1 Python Theory  (0) 2022.07.04

Data Types

variable(변수): 데이터를 저장하는 곳, 변수명 = 데이터
변수명을 여러 단어로 지을 경우 _로 연결하는 것이 일반적(snake case)

None Sequence Types

  • integer a_int = 3
  • float b_float = 2.5
  • boolean d_bool = True
  • none = e_none = None

Sequence Types

  • string c_str = "Hello World" --- ", ' 둘 다 가능
  • list f_list = ["Mon", "Tue", "Wed", "Thur", "Fri"] --- mutable(수정 가능)
  • tuple g_tuple = ("Mom", "Dad", "Me") --- immutable(수정 불가능)
  • dictionary h_dict = {"name": "Nico", "age": 29}

    서로 다른 타입의 값들을 하나의 변수에 넣을 수 있음


Function

특정 기능을 반복할 수 있도록 만든 것

Built-in Functions

  • Python에서 기본적으로 제공하는 함수들
  • print(): 콘솔에 출력
  • len(): 길이를 구함
  • type(): 타입을 알려줌
  • int(), float(), str(), bool(), list(), tuple(), dict(): 다른 타입의 값을 해당 타입으로 변환

Define Function

# name의 기본값을 Anonymous로 지정
def hello(name="Anonymous", age):
    # String에 변수를 삽입하려면 f를 따옴표 앞에 붙이고 변수를 중괄호로 감싸줌
    print(f"Hello {name} you are {age} years old")
    result = f"Bye {name}"
    return result
    # return이 실행되면 함수는 그대로 종료되어 이후의 코드를 실행하지 않음
    print("No")

# Positional arguments --- 입력한 순서대로 인자를 넘김
greeting1 = hello("Nico", 29)

# Keyword arguments --- 인자 이름 = 값으로 넘겨서 순서에 상관 없음
greeting2 = hello(age=29, name="Nico")

print(greeting1)
print(greeting2)

# 결과는 똑같음
# Hello Nico --- hello 함수 내부의 print
# Bye Nico --- hello 함수의 return값이 저장된 greeting을 print

들여쓰기로 함수 코드를 판단하므로 들여쓰기에 주의! 들여쓰기는 tab과 spacebar를 섞어서 쓰지 말 것!


Conditionals

if-elif-else

def age_check(age):
    if age < 18:
        print("you can\'t drink")
    elif age == 18:
        print("you are new to this")
    else:
        print("enjoy your drink")

age_check(17)
age_check(18)
age_check(19)

and, or, not 등을 사용해서 여러 조건을 연결할 수 있고, elif는 여러 번 사용할 수 있음


Loops

for in

days = ["Mon", "Tue", "Wed", "Thur", "Fri"]

for day in days:
    if day is "Wed":
        break
    else:
        print(day)

Sequence 타입의 요소를 순차적으로 하나씩 사용해서 반복 작업 수행


Modules

from math import ceil, fabs
from math import fsum as fs

print(ceil(1.2))
print(fabs(-1.2))
print(fs([1, 2, 3, 4, 5, 6]))
  • 함수들의 집합, import해서 사용할 수 있음
  • 자주 사용하는 모듈: math, datetime, json, csv 등
  • 사용하지 않는 기능들을 모두 import하는 것은 비효율적이기 때문에 사용할 것만 import하기
  • as를 사용해 이름을 바꿔서 사용할 수 있음

※ 더 많은 내용은 공식문서 참고
※ 강의 링크: https://nomadcoders.co/python-for-beginners

+ Recent posts