from flask import Flask # 대소문자 주의
from flask import render_template #html을 가져와주기 위해서 부름, Flask import 뒤에 ','로도 연결할 수 있음
from flask import request #query문을 가져오기 위해서 import
from flask import redirect #다른 페이지로 넘기기 위해
from scrapper import get_jobs #이전에 만들어둔 곳에서 함수를 가져옴
from exporter import save_to_file csv 파일로 서버에 저장시키기
from flask import send_file #파일로 다운로드 하게 하기
app = Flask("Fantastic Scarpper")
#init 할 때 db를 생성해두는 것
db = {}
# (1) Dynamic URL
# / 로 접속하면 function 을 작동하도록
# @decorator는 바로 아래 있는 함수를 찾게 된다.
# "/"로 접속 요청이 들어오면 아래 function!!(function만!)을 시행하면 되는 구나 라고 생각함.
@app.route("/")
def home():
return render_template("potato.html")
#1 templates 폴더에 potato.html 를 FLASK가 알아서 찾아냄
@app.route("/report")
def report():
# print(request.args) #요청된 자료에서 argument들을 Dictionary로 출력해낸다
keyword = request.args.get("search") #"search"에 해당되는 potato.html에 있는 input query
if keyword: #keyword가 있다면
keyword = keyword.lower()
existingJobs= db.get(keyword)
if existingJobs : #db에 keyword가 있는지 확인
jobs = existingJobs
else:
jobs = get_jobs(keyword) #scrapper.py 에서 가져온 함수
db[keyword] = jobs #검색 결과를 db에 저장해두어 빠르게 함
else: #keyword 입력이 안되면( 검색어 없이 들어오면) home 으로 redirect
return redirect("/")
return render_template("report.html", searchingBy = keyword, resultsNumber=len(jobs), results = jobs) # html 파일에 {{}}을 이용하면, html파일에서 인식 가능
# ※ html을 가져오는 것은 reder_template로 가져오고, 사용할 데이터는 {{}} 로서 미리 정해둔다.
@app.route("/export")
def export():
try:
keyword = request.args.get('search')
if not keyword:
raise Exception() #except로 보냄
keyword = keyword.lower()
jobs = db.get(keyword)
if not jobs:
raise Exception()
save_to_file(jobs)
return send_file("jobs.csv")
except:
return redirect("/")
@app.route("/contact")
def contact():
return "Contact me!!"
@app.route("/<username>")
def potato(username):
return f"Hello {username}. How are you doing?"
app.run(host="0.0.0.0") #웹사이트 구축, local에서 작동한다면, host를 "0.0.0.0"은 작동하지 않는다.
potato.html - Templates 폴더에 넣어야 함
<!DOCTYPE html>
<html>
<head>
<title> Job searcher, Web scrapper</title>
</head>
<body>
<h1> Indeed 직업 검색기</h1>
<form action = "/report" method ="get" > <!-- report?word= blahblach 로 기록됨-->
<input placeholder="직업을 입력하세요" name ="search" required/>
<button> 검색</button>
</form>
</body>
</html>
report.html - Templates 폴더에 넣어야 함
html 파일에 변수를 {{}}로 감싸서 rendering 할 수 있다.
<!DOCTYPE html>
<html>
<head>
<title> Job searcher, Web scrapper</title>
<style>
section{
display:grid;
gap : 20px;
grid-template-columns: repeat(4,1fr);
border : 1px solid green;
}
</style>
</head>
<body>
<h1> 검색 결과</h1>
<div>
당신이 검색한 것은 {{searchingBy}}
</div>
<div> 검색 수 : {{resultsNumber}} </div>
<div> <a href ="/export?search={{searchingBy}}" targe="_blank" >Export </a></div>
<section>
<h4>Title</h4>
<h4>Company</h4>
<h4>Location</h4>
<h4>Link</h4>
{%for job in results %}
<span>{{job.title}}</span>
<span>{{job.company}}</span>
<span>{{job.location}}</span>
<a href="{{job.link}}" target="_blank">Link</a>
{% endfor %}
</body>
</html>
scrapper.py
import requests
# 좌측 메뉴에서 packge에 들어가서 request를 설치한다. : pip install requests
from bs4 import BeautifulSoup #beautiful Soup <- S 대문자여야 하낟
# Beautifulsoup4 로 다운로드 한다. html scrapper package : pip install bs4 , from bs4 import BeautifulSoup
################################
def extract_indeed_pages(url):
url_result = requests.get(url)
# print(indeed_result) # 출력 : <Response[200]>
# print(indeed_result.text) # 출력 모든게 다나옴
soup = BeautifulSoup(url_result.text,"html.parser")
#class
pagination = soup.find("div", {"class":"pagination"})
links = pagination.find_all('a') #links는 list로 만들어진다.
pages = [] #span list를 담을 그릇
for link in links[:-1] : #[:-1] 뒤에 마지막 것 제외하고 출력하기
pages.append(int(link.find("span").string)) #.string을 이용해서 text만 가져옴, 가져온 text를 int로 바꿔줌
# 가장 마지막 숫자를 찾는다.
max_page = pages[-1]
return max_page
# TITLE 가져오고 각각 REQUEST 보냄
def extract_indeed_jobs(last_page,url,limit):
jobs = []
for page in range(1):
print(f"스크랩해온 페이지는 {page+1}/{last_page}입니다")
result = requests.get(f"{url}&start={page*limit}") #변수끼리 계산할 것이 있으면 계산할 것 하고 {}로 닫아준다
print(result.status_code) #반응하는지 확인, 200이 20개 나와줘야함.
soup = BeautifulSoup(result.text,"html.parser")
# 제목 division을 찾아온다
results = soup.find_all("div", {"class":"jobsearch-SerpJobCard"})
# 각 페이지에서 List 로 생성된 results를 for 구문으로 돌린다.
for result in results :
#title을 추출함
title = result.find("div", {"class":"title"}).find("a")["title"] #list에서 title value 에 배정된 값을 가져와
print(title)
#company 이름을 추출함
company = result.find("span",{"class":"company"})
if company is not None:
company_anchor = company.find("a")
# ※참고※ 만약 span이 2개로 나온다면 | company, anchor = html.find("h3").find_all("span",recursive = False) | 와 같이 각각을 한번에 담을 수도 있다
if company_anchor is not None: #anchor가 있으면 anchor 내용을 가져온다.
company = company_anchor.string
else:
company = company.string
print("☆no anchor",company)
# 여백이 너무 많아서 .strip()을 이용하여 지워줌
else:
company = "none"
company = company.strip()
#Location 추가 함
location = result.find("div",{"class":"recJobLoc"})["data-rc-loc"] #data-rc-loc에 배정된 내용을 가져온다.
#job_id 추가함 : 다른 페이지 링크로 연결됨
job_id = result["data-jk"]
# 전체 다 가지는 배열 만듦
job = {
'title':title,
'company':company,
'location':location,
'link': f"https://www.indeed.com/viewjob?jk={job_id}"
} #job list에 하나씩 쌓아줌.
print(job)
jobs.append(job)
return jobs
###################################
def get_jobs(keyword):
limit = 50
url = f"https://www.indeed.com/jobs?q={keyword}&limit={limit}"
max_indeed_page = extract_indeed_pages(url) #URL을 다른 함수로 보내줘야함, 그래서 url로 바꿔줌 이전엔 전역에서 지정해줬으나, 함수이므로, 각각 넣어줘야함.
jobs = extract_indeed_jobs(max_indeed_page,url,limit)
return jobs
exporter.py
import csv
def save_to_file(jobs):
file = open("jobs.csv", mode="w")
writer = csv.writer(file)
writer.writerow(["title","company","location","link"]) #상단 1행
for job in jobs:
writer.writerow(list(job.values()))
return
'세상 바라보는 시선 👁 > Growth Hacking' 카테고리의 다른 글
[Pretotype] 아이디어 불패의 법칙 (0) | 2020.09.03 |
---|---|
[데이터 사이언스] 딥러닝 - one hot encoding 을 하려면... @dsschool (0) | 2020.04.20 |
[Pandas] 시각화 - 한글화 시키기 (0) | 2020.04.09 |
[Python] Web scrapper - indeed.com (노마드코더) (0) | 2020.04.07 |
[Python] cheating sheet - 노마드코더 (0) | 2020.04.07 |
[DS School] Level6 에서 기록으로 남겨 놓을 것 (Decision tree, Random Forest, DecisionTreeClassifier, DecisionTreeRegressor) (0) | 2020.04.01 |
[Pandas] Visualization 관련 정리 (시각화) (0) | 2020.03.25 |
♡를 눌러주시면 블로그를 작성하는데 큰 힘이 됩니다♪
로그인이 필요없어요.
로그인이 필요없어요.