이번 가이드에서는 작업하는 컴퓨터가 아닌 원격 우분투16.04 서버(vps)에 올리는 부분까지 다룹니다. 테스트는 crontab -e 명령어를 사용할 수 있는 환경에서 가능하며, VISA/Master카드등 해외결제가 가능한 카드가 있다면 서비스 가입 후 실제로 배포도 가능합니다. 이 가이드에서는 Vultr VPS를 이용합니다.
앞서 Django를 이용해 크롤링한 데이터를 DB에 저장해 보았습니다.
하지만 크롤링을 할 때마다 동일한(중복된) 데이터를 DB에 저장하는 것은 바람직 하지 않은 일이죠.
또한, 크롤링을 자동으로 해 사이트에 변경사항이 생길 때 마다 내 텔레그램으로 알림을 받을 수 있다면 더 편리하지 않을까요?
이번 가이드에서는 클리앙 중고장터 등을 크롤링해 새 게시글이 올라올 경우 새글 알림을 텔레그램으로 보내는 것까지를 다룹니다.
다루는 내용:
Telegram Bot API
requests / BeautifulSoup
Crontab
시작하며
widgets:
텔레그램은 REST API를 통해 봇을 제어하도록 안내합니다.
물론 직접 텔레그램 api를 사용할 수도 있지만, 이번 가이드에서는 좀 더 빠른 개발을 위해 python-telegram-bot 패키지를 사용합니다.
python-telegram-bot은 Telegram Bot API를 python에서 쉽게 이용하기 위한 wrapper 패키지입니다.
위 가이드에서 텔레그램 봇의 토큰을 받아오세요. 토큰은 aaaa:bbbbbbbbbbbbbb와 같이 생긴 문자열입니다.
이번 가이드는 텔레그램 봇을 다루는 내용보다는 Cron으로 크롤링을 하고 변화 발견시 텔레그램 메시지를 보내는 것에 초점을 맞췄습니다.
텔레그램 봇 API키를 받아왔다면 아래와 같이 크롤링을 하는 간단한 python파일을 작성해 봅시다. 클리앙에 새로운 글이 올라오면 “새 글이 올라왔어요!”라는 메시지를 보내는 봇을 만들어 보겠습니다.
클리앙 새글 탐지코드 만들기
widgets:
우선 게시판의 글 제목중 첫번째 제목을 가져오고 txt파일로 저장하는 코드를 만들어 봅시다.
회원 장터 주소는 http://clien.net/cs2/bbs/board.php?bo_table=sold이고, 첫 게시글의 CSS Selector는 #content > div.board_main > table > tbody > tr:nth-child(3) > td.post_subject > a임을 알 수 있습니다. 따라서 아래와 같이 latest 변수에 담아 같은 폴더의 latest.txt 파일에 써 줍시다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
# clien_market_parser.py import requests from bs4 import BeautifulSoup import os
# 파일의 위치 BASE_DIR = os.path.dirname(os.path.abspath(__file__))
req = requests.get('http://clien.net/cs2/bbs/board.php?bo_table=sold') req.encoding = 'utf-8'# Clien에서 encoding 정보를 보내주지 않아 encoding옵션을 추가해줘야합니다.
with open(os.path.join(BASE_DIR, 'latest.txt'), 'r+') as f_read: before = f_read.readline() if before != latest: # 같은 경우는 에러 없이 넘기고, 다른 경우에만 # 메시지 보내는 로직을 넣으면 됩니다. f_read.close()
with open(os.path.join(BASE_DIR, 'latest.txt'), 'w+') as f_write: f_write.write(latest) f_write.close()
새글이라면? 텔레그램으로 메시지 보내기!
widgets:
이제 메시지를 보내볼게요. telegram을 import하신 후 bot을 선언해주시면 됩니다. token은 위에서 받은 토큰입니다.
# clien_market_parser.py import requests from bs4 import BeautifulSoup import os
import telegram
# 토큰을 지정해서 bot을 선언해 줍시다! (물론 이 토큰은 dummy!) bot = telegram.Bot(token='123412345:ABCDEFgHiJKLmnopqr-0StUvwaBcDef0HI4jk') # 우선 테스트 봇이니까 가장 마지막으로 bot에게 말을 건 사람의 id를 지정해줄게요. # 만약 IndexError 에러가 난다면 봇에게 메시지를 아무거나 보내고 다시 테스트해보세요. chat_id = bot.getUpdates()[-1].message.chat.id
# 파일의 위치 BASE_DIR = os.path.dirname(os.path.abspath(__file__))
with open(os.path.join(BASE_DIR, 'latest.txt'), 'r+') as f_read: before = f_read.readline() if before != latest: bot.sendMessage(chat_id=chat_id, text='새 글이 올라왔어요!') else: # 원래는 이 메시지를 보낼 필요가 없지만, 테스트 할 때는 봇이 동작하는지 확인차 넣어봤어요. bot.sendMessage(chat_id=chat_id, text='새 글이 없어요 ㅠㅠ') f_read.close()
with open(os.path.join(BASE_DIR, 'latest.txt'), 'w+') as f_write: f_write.write(latest) f_write.close()
이제 clien_market_parser.py파일을 실행할 때 새 글이 올라왔다면 “새 글이 올라왔어요!”라는 알림이, 새 글이 없다면 “새 글이 없어요 ㅠㅠ”라는 알림이 옵니다.
지금은 자동으로 실행되지 않기 때문에 python clien_market_parser.py명령어로 직접 실행해 주셔야 합니다.
자동으로 크롤링하고 메시지 보내기
widgets:
가장 쉬운방법: while + sleep
가장 쉬운 방법은 python의 while문을 쓰는 방법입니다. 물론, 가장 나쁜 방법이에요. 안전하지도 않고 시스템의 메모리를 좀먹을 수도 있어요.
with open(os.path.join(BASE_DIR, 'latest.txt'), 'r+') as f_read: before = f_read.readline() if before != latest: bot.sendMessage(chat_id=chat_id, text='새 글이 올라왔어요!') else: bot.sendMessage(chat_id=chat_id, text='새 글이 없어요 ㅠㅠ') f_read.close()
with open(os.path.join(BASE_DIR, 'latest.txt'), 'w+') as f_write: f_write.write(latest) f_write.close()
time.sleep(60) # 60초(1분)을 쉬어줍니다.
파이썬 동작중에는 CTRL+C로 빠져나올수 있습니다.
추천: 시스템의 cron/스케쥴러를 이용하기
이번 가이드에서 핵심인 부분인데요, 이 부분은 이제 우분투 16.04 기준으로 진행할게요.
우선 Ubuntu 16.04가 설치된 시스템이 필요합니다. 이번 강의에서는 Vultr VPS를 이용합니다.
Vultr는 가상 서버 회사인데, Tokyo리전의 VPS를 제공해줘 빠르게 이용이 가능합니다. 트래픽도 굉장히 많이주고요.
가이드를 만드는 지금은 월 2.5달러 VPS는 아쉽게도 없어서, 월5달러 VPS로 진행하지만 월2.5달러 VPS로도 충분합니다!
아래의 Deploy Now를 누르면 새 Cloud Instance(VPS)가 생성되는데요, 서버가 생성된 후 들어가 보면 다음과 같이 id와 pw가 나와있습니다. 패스워드는 눈 모양을 누르면 잠시 보입니다.
이 정보로 ssh에 접속해 봅시다. (윈도는 putty이나 Xshell등을 이용해주세요.)
우분투 16.04버전에는 이미 Python3.5버전이 설치되어있기 때문에 pip3, setuptools을 설치해 주고 Ubuntu의 Locale을 설정해줘야 합니다. 아래 명령어를 한줄씩 순차적으로 치시면 완료됩니다.
with open(os.path.join(BASE_DIR, 'latest.txt'), 'r') as f_read: before = f_read.readline() f_read.close() if before != latest: bot.sendMessage(chat_id=chat_id, text='새 글이 올라왔어요!') with open(os.path.join(BASE_DIR, 'latest.txt'), 'w+') as f_write: f_write.write(latest) f_write.close()
이제 이 clien_market_parser.py 파일을 python3으로 실행해야 하기 때문에, python3이 어디 설치되어있는지 확인 해 봅시다.(아마 /usr/bin/python3일거에요!)
1 2
root@vultr:~# which python3 /usr/bin/python3
이제 Crontab에 이 파이썬으로 우리 파일을 매 1분마다 실행하도록 만들어 봅시다.
crontab 수정은 crontab -e명령어로 사용 가능합니다. 만약 에디터를 선택하라고 한다면 초보자는 Nano를, vi를 쓰실수 있으시다면 vi를 이용하세요.
터미널에서 gdrive list라고 했을때 에러가 나지 않는 상태에서 아래 가이드를 진행해주세요.
들어가며
이전 가이드에서 스크린샷을 찍고 구글드라이브에 올린 후 이미지의 공유 URL을 가져오는 스크립트를 작성했습니다.
하지만 키보드 Shortcuts를 이용한 편리성에는 따라가기가 어렵죠. .sh스크립트를 키보드로 연동하는 방법 중 여러가지 방법이 있지만, 이번에는 MacOS App으로 만든 후 앱을 실행하는 것을 서비스에 등록하고 Automator를 통해 키보드와 앱실행 서비스를 연동하는 과정을 다룹니다.
이번 가이드에서는 업로드 되는 폴더를 정확히 명시하지는 않았습니다. gdrive패키지에서 -p를 이용하면 폴더를 지정가능하다고 하지만 테스트 결과 제대로 업로드 되지 않는 것을 확인했기 때문에, 현재 주로 사용하지는 않는 다른 구글 아이디에 gdrive를 연결해 두었습니다.
Imgur, Dropbox등 여러 이미지 Serving 업체들이 있지만, 구글이 가진 구글 Fiber망과 서비스의 안정성은 여타 서비스들이 따라가기 어려운 점이라고 생각합니다 :)
깃헙 페이지를 Jekyll등을 이용해 Markdown파일을 이용하다보면 스크린샷을 저장하고 깃헙 레포 폴더에 옮긴 후 수동으로 url을 추가해 주는 작업이 상당히 귀찮고, 심지어 깃헙 레포당 저장공간은 1G로 제한됩니다.
Dropbox의 경우에는 MacOS 내장 스크린샷(CMD+Shift+4)를 이용할 경우 파일을 자동으로 dropbox에 올린 후 공유 url이 나옵니다. 하지만 일반 유저는 용량 제한도 있고, 트래픽 제한도 있습니다.
따라서 무료로 15G의 용량과 명시적 트래픽 제한이 없는 구글드라이브를 이용하는 방안을 고려해보았습니다.
정확히는 Github은 레포당 용량을 명시적으로 제한하지는 않지만 1G가 넘어가는 경우 스토리지를 이용하도록 가이드합니다. Dropbox링크를 통한 트래픽은 무료 유저의 경우 일 20G, 유료플랜 유저의 경우 일 200G를 줍니다. GoogleDrive의 경우 무료 계정도 일 100G(추정치)의 트래픽을 제공하기 때문에 큰 무리는 따르지 않는다 생각합니다.
하지만, social-auth-app-django를 통해 유저를 생성 할 경우 OAuth Provider에 따라 다른 User를 생성합니다. 즉, 같은 이메일 주소를 가지고 있는 유저라 하더라도 페이스북을 통해 로그인 한 유저와 구글을 통해 로그인 한 유저는 다르게 다뤄진다는 뜻입니다.
사실 이메일 주소를 신뢰하지 않고 Provier마다 다른 유저로 생성하는 것이 기본으로 되어있는 이유는 Oauth Provier의 신뢰 문제입니다. 모든 Oauth Provier가 가입한 유저의 Email의 실 소유권을 확인하지는 않기 때문입니다.
이를 해결하고 같은 이메일을 통해 로그인한 유저는 모두 같은 유저로 취급하기 위해서는 장고 프로젝트 폴더의 settings.py파일 안에서 social-auth-app-django의 Pipeline설정을 변경해 줘야 합니다.
물론 실제 배포시에는 빌드 과정을 거쳐 나온 파일을 관리해야 한다. 하지만 React를 처음 배우는 과정에서는 로컬의 한 HTML파일 안에서 모든 과정이 작동하기를 원하게 된다. 따라서 클라이언트 렌더링을 고려할 수 있다.
물론 클라이언트 렌더링은 성능 이슈가 있기 때문에 실 배포시에는 사용하지 않아야 한다.
Browser.js 사용하기
Babel은 6버전부터 Browser.js를 업데이트 하지 않았다. 하지만 정상적으로 동작하는 파일이 CDN에 존재하기 때문에, HTML문서에 다음 세 줄을 추가해 주면 script태그에 type="text/babel"이라는 타입을 가진 코드들을 ES6로 간주하고 ES5로 변환해 준다.
Django는 내장된 runserver라는 개발용 웹 서버가 있습니다. 하지만 개발용 웹 서버를 상용 환경에서 사용하는 것은 여러가지 문제를 가져옵니다. 메모리 문제등의 성능 이슈부터 Static file서빙의 보안 문제까지 다양한데요, 이 때문에 Django는 웹 서버(ex: Apache2NginX등)를 통해 배포하게 됩니다.
하지만 이러한 배포작업은 아마존 EC2등의 VPS나 리얼 서버에서 Apache2를 깔고, python3와 mod_wsgi등을 깔아야만 동작하기 때문에 배포 자체가 어려움을 갖게 됩니다. 또한 SSH에 접속히 직접 명령어를 치는 경우 오타나 실수등으로 인해 정상적으로 작동하지 않는 경우도 부지기수입니다.
따라서 이러한 작업을 자동화해주는 도구가 바로 Fabric이고, 이번 가이드에서는 Django 프로젝트를 Vultr VPS, Ubuntu에 올리는 방법을 다룹니다.
Vultr VPS 생성하기
Vultr는 VPS(가상서버) 제공 회사입니다. 최근 가격 인하로 유사 서비스 대비 절반 가격에 이용할 수 있어 가성비가 좋습니다.
사용자가 많지 않은 (혹은 혼자 사용하는..) 서비스라면 최소 가격인 1cpu 512MB의 월 2.5달러짜리를 이용하시면 됩니다.
Vultr는 일본 Region에 서버가 있어 한국에서 사용하기에도 핑이 25ms정도로 양호합니다.
VPS하나를 만든 후 root로 접속해 장고를 구동할 사용자를 만들어 봅시다.
django 유저 만들기(sudo권한 가진 유저 만들기)
Fabric을 사용할 때 초기에 apt를 이용해 패키지를 설치해야 할 필요가 있습니다.
하지만 처음에 제공되는 root계정은 사용하지 않는 것을 보안상 추천합니다. 따라서 우리는 sudo권한을 가진 django라는 유저를 생성하고 Fabric으로 진행해 보겠습니다.
Fabric은 fabfile있는 곳에서 fab 함수이름의 명령어로 실행 가능합니다. 단, _로 시작하는 함수는 Fabric이 관리하지 않습니다.
이제 서버에서 실행할 SSH를 캡슐화한다고 보면 됩니다. 크게 setup와 deploy로 나눌 수 있다고 봅니다. Setup은 장고 코드와 무관한 OS의 패키지 관리와 VirtualEnv관리, Deploy는 장고 소스가 변화할 경우 업데이트 되어야 하는 코드입니다.
Setup에는 APT 최신 패키지 설치와 apt_requirements설치, 그리고 virtualenv를 만드는 것까지를 다룹니다.
Deploy에서는 Git에서 최신 소스코드를 가져오고, Git에서 관리되지 않는 환경변수 파일을 서버에 업로드하고, 장고 settings.py파일을 상용 환경으로 바꿔주고, virtualenv로 만든 가상환경에 pip 패키지를 설치하고, StaticFile들을 collect하고, DB를 migrate해주고, Apache2의 VirtualHost에 장고 웹 서비스를 등록하고, 폴더 권한을 잡아주고, 마지막으로 Apache2 웹서버를 재부팅하는 과정까지를 다룹니다.
여기서부터는 코드가 너무 길어지는 관계로 apt_requirements 포함한 윗부분을 생략합니다.
Apache2는 웹서버가 OS의 환경변수를 사용하지 않기 때문에 json와 같은 파일을 통해 환경변수를 관리해 줘야 합니다. 저는 envs.json이라는 파일을 manage.py파일이 있는 프로젝트 폴더에 만든 후 환경변수를 장고의 settings.py에서 불러와 사용합니다.
워커와 클라이언트는 연결이 끊어지거나 실패하면 자동으로 다시 연결을 시도합니다. 그리고 몇몇 브로커들은 Primary/Primary나 Primary/Replica 의 복제방식을 통해 고가용성을 제공합니다.
빨라요!
하나의 셀러리 프로세스는 1분에 수십만개의 태스크를 처리할 수 있고, ms초 이하의 RTT(왕복지연시간)로 태스크를 처리 가능하답니다. (RabbitMQ, librabbitmq와 최적화된 설정을 할 경우)
유연해요!
셀러리의 대부분 파트는 그 자체로 이용할 수도 있고 원하는 만큼 확장도 가능합니다. Custom pool implementations, serializers, compression schemes, logging, schedulers, consumers, producers, broker transports을 포함해 더 많이요.
셀러리의 기능들
모니터링
모니터링 이벤트 스트림은 각 워커에서 나오고, 클러스터에서 수행하는 작업을 실시간으로 알려줍니다.
# parser.py import requests from bs4 import BeautifulSoup import json import os
# python파일의 위치 BASE_DIR = os.path.dirname(os.path.abspath(__file__))
req = requests.get('https://beomi.github.io/beomi.github.io_old/') html = req.text soup = BeautifulSoup(html, 'html.parser') my_titles = soup.select( 'h3 > a' )
data = {}
for title in my_titles: data[title.text] = title.get('href')
with open(os.path.join(BASE_DIR, 'result.json'), 'w+') as json_file: json.dump(data, json_file)
이전의 parser.py파일은 위와 같습니다. 이제 이 파일을 parse_blog라는 함수로 만들고, {‘블로그 글 타이틀’: ‘블로그 글 링크’}로 이루어진 딕셔너리를 반환하도록 만들어 봅시다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
# parser.py import requests from bs4 import BeautifulSoup
defparse_blog(): req = requests.get('https://beomi.github.io/beomi.github.io_old/') html = req.text soup = BeautifulSoup(html, 'html.parser') my_titles = soup.select( 'h3 > a' ) data = {} for title in my_titles: data[title.text] = title.get('href') return data
이제 parse_blog라는 함수를 다른 파일에서 import해 사용할 수 있습니다.
또한, 현재 프로젝트 폴더의 구조는 아래와 같습니다.
하지만 현재 parse_blog함수는 Django에 저장하는 기능을 갖고 있지 않습니다. 따라서 약간 더 추가를 해줘야 합니다.
# parser.py import requests from bs4 import BeautifulSoup # 아래 4줄을 추가해 줍니다. import os # Python이 실행될 때 DJANGO_SETTINGS_MODULE이라는 환경 변수에 현재 프로젝트의 settings.py파일 경로를 등록합니다. os.environ.setdefault("DJANGO_SETTINGS_MODULE", "websaver.settings") # 이제 장고를 가져와 장고 프로젝트를 사용할 수 있도록 환경을 만듭니다. import django django.setup()
defparse_blog(): req = requests.get('https://beomi.github.io/beomi.github.io_old/') html = req.text soup = BeautifulSoup(html, 'html.parser') my_titles = soup.select( 'h3 > a' ) data = {} for title in my_titles: data[title.text] = title.get('href') return data
위 코드에서 아래 4줄을 추가해 줄 경우, 이 파일을 단독으로 실행하더라도 마치 manage.py을 통해 django를 구동한 것과 같이 django환경을 사용할 수 있게 됩니다.
Django ORM으로 데이터 저장하기
1 2 3 4
import os os.environ.setdefault("DJANGO_SETTINGS_MODULE", "websaver.settings") import django django.setup()
# parser.py import requests from bs4 import BeautifulSoup import os os.environ.setdefault("DJANGO_SETTINGS_MODULE", "websaver.settings") import django django.setup() # BlogData를 import해옵니다 from parsed_data.models import BlogData
defparse_blog(): req = requests.get('https://beomi.github.io/beomi.github.io_old/') html = req.text soup = BeautifulSoup(html, 'html.parser') my_titles = soup.select( 'h3 > a' ) data = {} for title in my_titles: data[title.text] = title.get('href') return data
# 이 명령어는 이 파일이 import가 아닌 python에서 직접 실행할 경우에만 아래 코드가 동작하도록 합니다. if __name__=='__main__': blog_data_dict = parse_blog() for t, l in blog_data_dict.items(): BlogData(title=t, link=l).save()
위와 같이 parser.py를 수정한 후 터미널에서 parser.py파일을 실행해 봅시다.
1
python parser.py
아무런 에러가 나지 않는다면 성공적으로 저장된 것입니다.
저장된 데이터 Django Admin에서 확인하기
SuperUser | 관리자계정 만들기
Django는 Django Admin이라는 강력한 기능을 제공합니다.
우선 Admin 계정을 만들어야 합니다. createsuperuser 명령어로 만들 수 있습니다.
기본적으로 유저이름, 이메일, 비밀번호를 받습니다.
이메일은 입력하지 않아도 됩니다.
앱에 Admin 등록하기
Django가 어떤 앱을 admin에서 관리하도록 하려면 앱 폴더(parsed_data) 안의 admin.py파일을 수정해줘야 합니다.
1 2 3 4 5 6 7
# parsed_data/admin.py from django.contrib import admin # models에서 BlogData를 import 해옵니다. from .models import BlogData
# 아래의 코드를 입력하면 BlogData를 admin 페이지에서 관리할 수 있습니다. admin.site.register(BlogData)
Django Runserver | 장고 서버 실행하기
이제 manage.py가 있는 위치에서 runserver명령어로 장고 개발 서버를 실행해 봅시다.
Selenium은 주로 웹앱을 테스트하는데 이용하는 프레임워크다. webdriver라는 API를 통해 운영체제에 설치된 Chrome등의 브라우저를 제어하게 된다.
브라우저를 직접 동작시킨다는 것은 JavaScript를 이용해 비동기적으로 혹은 뒤늦게 불러와지는 컨텐츠들을 가져올 수 있다는 것이다. 즉, ‘눈에 보이는’ 컨텐츠라면 모두 가져올 수 있다는 뜻이다. 우리가 requests에서 사용했던 .text의 경우 브라우저에서 ‘소스보기’를 한 것과 같이 동작하여, JS등을 통해 동적으로 DOM이 변화한 이후의 HTML을 보여주지 않는다. 반면 Selenium은 실제 웹 브라우저가 동작하기 때문에 JS로 렌더링이 완료된 후의 DOM결과물에 접근이 가능하다.
어떻게 설치하나?
pip selenium package
Selenium을 설치하는 것은 기본적으로 pip를 이용한다.
1
pip install selenium
참고: Selenium의 버전은 자주 업데이트 되고, 브라우저의 업데이트 마다 새로운 Driver를 잡아주기 때문에 항상 최신버전을 깔아 주는 것이 좋다.
이번 튜토리얼에서는 BeautifulSoup이 설치되어있다고 가정합니다.
BeautifulSoup은 pip install bs4로 설치 가능합니다.
webdriver
Selenium은 webdriver라는 것을 통해 디바이스에 설치된 브라우저들을 제어할 수 있다. 이번 가이드에서는 Chrome을 사용해 볼 예정이다.
Binary 자체로 제공되기 때문에, Linux를 제외한 OS에서는 외부 dependency없이 바로 실행할 수 있다.
압축을 풀어주면 아래와 같은 많은 파일들이 있지만, 우리가 사용하는 것은 bin폴더 안의 phantomjs파일이다.
위 폴더 기준으로 할 경우 /Users/beomi/Downloads/phantomjs-2.1.1-macosx/bin/phantomjs가 PhantomJS드라이버의 위치다.
Selenium으로 사이트 브라우징
Selenium은 webdriver api를 통해 브라우저를 제어한다.
우선 webdriver를 import해주자.
1
from selenium import webdriver
이제 driver라는 이름의 webdriver 객체를 만들어 주자.
이름이 꼭 driver일 필요는 없다.
이번 가이드에서는 크롬을 기본적으로 이용할 예정이다.
1 2 3 4 5 6
from selenium import webdriver
# Chrome의 경우 | 아까 받은 chromedriver의 위치를 지정해준다. driver = webdriver.Chrome('/Users/beomi/Downloads/chromedriver') # PhantomJS의 경우 | 아까 받은 PhantomJS의 위치를 지정해준다. # driver = webdriver.PhantomJS('/Users/beomi/Downloads/phantomjs-2.1.1-macosx/bin/phantomjs')
Selenium은 기본적으로 웹 자원들이 모두 로드될때까지 기다려주지만, 암묵적으로 모든 자원이 로드될때 까지 기다리게 하는 시간을 직접 implicitly_wait을 통해 지정할 수 있다.
1 2 3 4 5
from selenium import webdriver
driver = webdriver.Chrome('/Users/beomi/Downloads/chromedriver') # 암묵적으로 웹 자원 로드를 위해 3초까지 기다려 준다. driver.implicitly_wait(3)