스크래핑한 내용을 파일로 export 하기
먼저 csv 파일을 만들어 줄 수 있는 open() 함수를 사용한다.
open('파일명.확장자', 파일권한)
여기서 csv 란 ,가 구분자로 이루어진 파일이다.
file = open(f"{keyword}.csv", 'w')
파일을 이어 작성해주기 위해선 .write() 메서드를 사용한다.
file = open(f"{keyword}.csv", 'w')
file.write('Position,Company,Location,URL\n')
jobs = indeed + wwr # list 끼리 합치기
for job in jobs:
file.write(
f'{job["position"]},{job["company"]},{job["location"]},{job["link"]}\n')
file.close()
close() 로 파일 쓰기를 마칠 땐 명시해준다.
파일 열기(open) -> 파일 쓰기(write) -> 파일 닫기(close)
웹 스크래퍼는 여기서 끝!
https://flask.palletsprojects.com/en/2.3.x/
Welcome to Flask — Flask Documentation (2.3.x)
flask.palletsprojects.com
Flask 란?
파이썬의 마이크로 웹 프레임워크 중 하나로 Django 보다 가볍게 사용 가능
pip install flask
from flask import Flask
기본적으로 아래처럼 Flask 를 가져와서 홈페이지를 run 해주면 Not Found 화면을 볼 수 있을 것이다.
이는 아직 홈페이지에 대한 응답이 작성되지 않아서 임.
app = Flask(실행시킬객체)
app.run(debug = True)
app.run() 시킬 때 debug = True 로 해두면 코드에 변경 사항이 있을 때 알아서 리로딩 된다.
중간에 라우터 처리를 해주면..
app = Flask(__name__)
@app.route('/')
def home():
return 'Hi!'
app.run(debug=True)
@app.route('/') : 호스트:포트/ 요청이 들어오면 해당 코드 바로 아래의 함수를 실행하게 된다. 그 반환값이 웹 페이지에 보이는 것
@app.route('/')
def home():
return 'Hi!'
@app.route('/hello')
def hello():
return 'Hello!'
이렇게 라우팅 처리를 해주면 된다.
그럼 반환값에 HTML 로 만든 웹페이지를 띄워주려면?
직접 문자열로 html 을 작성해주기
가능하지만... 당연히 비추
templates 폴더 안에 html 파일 만들기
flask 는 기본적으로 templates 라는 폴더를 찾아가므로 꼭 해당 이름으로 폴더를 만들어서 넣어줘야 한다.
그리고 main.py 와 같은 레벨에 두어주는 것도 필수!
from flask import render_template
render_template 를 import 시켜줘야 templates 폴더의 html 을 가져올 수 있다.
@app.route('/')
def home():
return render_template('home.html')
app의 함수에서 데이터를 html 으로 보내주기
def home():
return render_template('home.html', name='nico')
함수의 매개변수로 위처럼 키=값을 보내주면, 아래처럼 html 에서 {{ }} 중괄호를 2번 사용하여 변수를 사용해줄 수 있따.
<p>My name is {{name}}</p>
html form 만들기
input 태그의 name 속성은 서버측에 보내지는 정보의 키라고 보면 된다
name="keyword"
이런 속성을 가지고 있을 때 button 을 눌러서 서버측에 정보를 보내게 되면,
http://127.0.0.1:5000/?keyword=python
이런 식으로 keyword=값 형식으로 쿼리스트링이 뜨게 된다.
<form action="/search">
<input type="text" name="keyword" placeholder="Write one keyword plz!" />
<button>Search</button>
</form>
이 때 form 태그의 action 속성으로 해당 버튼을 누르면 이동할 주소처리를 해줄 수 있다.
그럼 위의 예제에서 버튼을 누르면 기본 주소인 http://127.0.0.1:5000 에서 /search 가 붙고, 그 다음 쿼리스트링의 시작인 ?가 붙으면서
keyword=값 이 뜨게 되는 것이다. (form 태그의 method속성의 기본값이 get 이므로)
http://127.0.0.1:5000/search?keyword=python
이제 위에서 받아온 쿼리스트링을 사용하기 위해선
from flask import request
request를 받아온다. 이는 브라우저가 웹사이트에 가서 컨텐츠를 요청하는 것으로 요청하고 있는 url, ip, cookie 정보 등이 담겨 있다.
request.args로 쿼리스트링에 접근할 수 있고, 원하는 키값으로 가져오려면 get() 을 사용해준다.
@app.route('/search')
def search():
keyword = request.args.get('keyword')
indeed = extract_indeed_jobs(keyword)
wwr = extract_wwr_jobs(keyword)
jobs = indeed + wwr
return render_template('search.html', keyword=keyword, jobs=jobs)
flask 의 html 규칙
변수를 사용해주려면 {{ }} 를 사용한다.
문법을 사용해주려면 {% %} 안에 써준다. 문법이 끝나는 부분에도 표기해줘야 한다.
그간 발견되지 않던 ChromeWebdriver 에러가 발생되어 indeed 에 써주었던 selenium 이 작동하지 않았다.
https://ddolcat.tistory.com/674 참고를 했지만 결국 같은 에러가 발생하여(애초에 여기 내용대로 작성되었었음)
일단 강의 완강을 위해 indeed 는 제외했다.
Pico 사용하여 디자인 요소를 간단하게 주기
Pico.css • Minimal CSS Framework for semantic HTML
Elegant styles for all native HTML elements without .classes and dark mode automatically enabled. 7.9 kB minified and gzipped!
picocss.com
간단하게 cdn 으로 넣어보자
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@1/css/pico.min.css">
그냥 CDN 을 넣어준 걸로도 디자인이 되었다 ㅇㅁㅇ..! 신기했음
<main class="container">
<h1>Search Results for '{{keyword}}'</h1>
<table>
<thead>
<tr>
<th>Position</th>
<th>Company</th>
<th>Location</th>
<th>Link</th>
</tr>
</thead>
<tbody>
{% for job in jobs %}
<tr>
<td>{{job.position}}</td>
<td>{{job.company}}</td>
<td>{{job.location}}</td>
<td>
<a href="{{job.link}}" target="_blank">Apply now</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</main>
캐싱해주기
# 임시 DB
db = {}
map 자료구조로 임시 db를 main.py 에 만들어주고 해당 맵에 스크래핑 해올 때 자료가 저장되어 있는지 확인해준다.
캐싱 되었으므로 새로고침해도 전보다 페이지 로딩 속도가 빨라진다.
@app.route('/search')
def search():
keyword = request.args.get('keyword') # request.args 로 쿼리스트링을 읽어올 수 있음
if keyword in db:
jobs = db[keyword]
else:
# indeed = extract_indeed_jobs(keyword) 크롬 웹드라이버 에러로 주석처리
wwr = extract_wwr_jobs(keyword)
jobs = wwr # + indeed
db[keyword] = jobs
return render_template('search.html', keyword=keyword, jobs=jobs)
물론 해당 방법은 임시 DB이므로 서버가 꺼졌다 다시 켜지면 재스크래핑이 필요하다.
form에 아무것도 입력하지 않았을 때 에러 핸들링
from flask import redirect
if keyword == None:
return redirect('/')
form 태그에 아무것도 입력하지 않으면 None 이 들어가게 되므로 해당 조건을 추가하여
flask 의 redirect 로 원래 페이지로 돌아가게 핸들링 해준다.
크롤링한 내용을 파일로 저장하는 기능 추가
<a href="/export?keyword={{keyword}}" target="_blank">Export to File</a>
/export 주소를 추가하여 저장하는 기능을 만들어줄 것인데,
키워드가 없는 경우와 db에 저장되지 않았을 경우를 나누어 예외처리를 먼저 해준다.
@app.route('/export')
def export():
keyword = request.args.get('keyword')
if keyword == None:
return redirect('/')
if keyword not in db:
return redirect(f'/search?keyword={keyword}')
그 다음 전에 만들었던 파일을 만드는 함수를 불러와서 만든다음
from flask import send_file
flask 의 send_file() 함수를 사용하여 만들어준 파일을 return 해준다. 파일명은 확장자를 포함해서 작성해줘야함
as_attachment 는 다운로드 될 수 있게 해주는 설정이므로 True 해주면 된다.
@app.route('/export')
def export():
keyword = request.args.get('keyword')
if keyword == None:
return redirect('/')
if keyword not in db:
return redirect(f'/search?keyword={keyword}')
save_to_file(keyword, db[keyword])
return send_file(f'{keyword}.csv', as_attachment=True)
여기서 파일이 자꾸 상위 폴더에 만들어져서 조금 헤맸는데, 이 경우에는 save_to_file() 함수의 저장되는 위치를 직접 지정해서 일단 기능만 구현해주었다.
def save_to_file(keyword, jobs):
file = open(f"first-python/{keyword}.csv", 'w', encoding='utf-8')
file.write('\ufeffPosition,Company,Location,URL\n') # 문자열이 깨질 때 \ufeff 추가
for job in jobs:
file.write(
f'{job["position"]},{job["company"]},{job["location"]},{job["link"]}\n')
file.close()
\ufeff 는 문자열이 깨질 때 추가해주면 좋다고 보아서 추가해준 것
encoding 옵션도 추가해주었다.
정상 작동한다.
길었던 웹 스크래퍼 완강!ㅎㅎ 크롤링은 어떻게 하나 궁금했었는데 좀 해소되는거 같고
SEO 를 위해 html 구조가 참 중요하구나 여기서 또 느낀다 :>