기본 콘텐츠로 건너뛰기

(완료)1# Python 으로 지출관리 사이트에서 모든지출 데이터 xlsx 파일 뽑아내고 Zapier로 구글시트에 업데이트하기 (Using Python, crawling and exporting company wide expenses data with Xlsx file. Update a Google sheet from this Xlsx file with Zapier.)

 우리 회사는 스팬딧이라는 지출관리 프로그램을 쓴다. 

회사 전체 지출을 가져오고 실시간 지출 데이터를 통해 예산 확인을 손쉽게 하려는 목적이다.


아이디어 구상은 이렇다.

1. 지출 데이터는 이메일로만 출력할 수 있다. (여러가지가 있지만 나에게 필요한 것은 구글시트에 들어가기 좋은 데이터이므로 xlsx 출력을 하고자함.) 

2. 이 지출 데이터를 파이썬으로 크롤링을 하는데 Selenium 으로 로그인하고, 메일로 받는 것 까지는 여기까지 과정에서 완료. 하되 웹훅을 하나 보낸다. 어디로? Zapier로

3. Email parsor by Zapier 를 통해 이메일 본문 안의 URL의 파일을 다운받아서 구글 드라이브에 업로드하는 것 



파이썬 모듈 중 핵심은 Selenium 과 requests 이다.

- Selenium  모듈 : 웹 브라우저 자동화 도구로, Chrome 브라우저를 제어하여 Spendit 앱의 사용자 인터페이스를 자동으로 조작하는데 쓰임.

- reuquests 모듈 : 웹훅 



일단 코드부터 오픈해보자면.. 아래와 같다.


from
selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
import datetime
import requests


'''Selenium predefine'''
driver_path = '크롬드라이버의 path'
id_xpath = '//*[@id="email"]'
password_xpath = '//*[@id="password"]'
login_button = '//*[@id="root"]/div/div/div[2]/form/button'
service = Service(driver_path)


options = webdriver.ChromeOptions()
options.add_experimental_option("detach", True)
'''
headless 모드 설정
'''
options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36")
options.add_argument('--headless')
options.add_argument('--disable-gpu')
options.add_argument('--no-sandbox')
options.add_argument('--disable-dev-shm-usage')
options.add_argument("--start-maximized") # added for prevent error when it runs in headless mode
options.add_argument("--window-size=1920,1080") # added for prevent error when it runs in headless mode



driver = webdriver.Chrome(service=service, options=options)

driver.get("https://app.spendit.kr/sign_in")
id_input = driver.find_element(By.XPATH, id_xpath)
id_input.send_keys('아이디')

password_input = driver.find_element(By.XPATH, password_xpath)
password_input.send_keys('비밀번호')
password_input.send_keys(Keys.RETURN)

# wait until the page is loaded
# wait = WebDriverWait(driver, 30)
# wait.until(EC.visibility_of_element_located((By.CLASS_NAME, 'intercom-1epm6qj.e4nbtsn3')))
time.sleep(10)

# click the expense button
button = driver.find_element(By.XPATH,'//*[@id="expense"]')
button.click()

# wait until the list is loaded - can not find
# wait.until(EC.visibility_of_element_located((By.XPATH, '//*[@id="filtered-custom"]/div[1]/div/div[3]/div/div[2]/button/span[1]')))
time.sleep(10)
print()

# start date input
button = driver.find_element(By.XPATH,'/html/body/div[1]/div/div[2]/div/aside/div[2]/div[2]/div/div[1]/div/input') #first click
button.click()
button = driver.find_element(By.XPATH,'/html/body/div[2]/div[2]/div/div/div[1]/button[2]') #year back
button.click()
button.click()
button = driver.find_element(By.XPATH,'/html/body/div[2]/div[2]/div/div/div[2]/div/div[1]/div[4]') #2023
button.click()
button = driver.find_element(By.XPATH,'/html/body/div[2]/div[2]/div/div/div[2]/div/div[1]/div[1]') # January
button.click()
button = driver.find_element(By.XPATH,'/html/body/div[2]/div[2]/div/div/div[2]/div/div/div[2]/div[1]/div[1]') # 1st - active를 제외하는게 핵심
button.click()

time.sleep(10)

# end date input
button = driver.find_element(By.XPATH,'/html/body/div[1]/div/div[2]/div/aside/div[2]/div[3]/div/div[1]/div/input') #first click
button.click()
button = driver.find_element(By.XPATH,'/html/body/div[2]/div[2]/div/div/div[1]/button[2]') #year back
button.click()
button.click()
button = driver.find_element(By.XPATH,'/html/body/div[2]/div[2]/div/div/div[2]/div/div[1]/div[4]') #2023
button.click()
button = driver.find_element(By.XPATH,'/html/body/div[2]/div[2]/div/div/div[2]/div/div[3]/div[4]') # December
button.click()
button = driver.find_element(By.XPATH,'/html/body/div[2]/div[2]/div/div/div[2]/div/div/div[2]/div[6]/div[1]') # 31st - 다섯번째 일요일이라 배열에서 5번째 = 4번째
button.click()

time.sleep(10)

'''
select filter self-> all members
'''
#
#
button = driver.find_element(By.XPATH,'/html/body/div[1]/div/div[2]/div/aside/div[2]/div[10]/div/button') # self
button.click()
button = driver.find_element(By.XPATH,'/html/body/div[2]/ul[2]/li[2]/ul/li[1]/div') # all members' expense 모든사용자
button.click()
button = driver.find_element(By.XPATH,'/html/body/div[2]/ul[2]/li[3]/button') # confirm
button.click()

time.sleep(10)


'''
select all and download Spendit template
'''
button = driver.find_element(By.XPATH,'/html/body/div[1]/div/div[2]/div/section/div[3]/div/div/table/thead/tr/th[1]/span/input') # checkbox
button.click()
button = driver.find_element(By.XPATH,'/html/body/div[2]/div[2]/ul/li[2]/button') # all expenses
button.click()
button = driver.find_element(By.XPATH,'/html/body/div[1]/div/div[2]/div/section/div[1]/div/div[1]/div/button[1]/span') # download button
button.click()
button = driver.find_element(By.XPATH,'/html/body/div[2]/div[2]/ul/li[2]/ul/li/span') # Spendit Template
button.click()

time.sleep(1)
driver.quit()


# Zapier 연동을 위한 웹훅 보내기

now = datetime.datetime.now()
year = str(now.year)
month = str(now.month).zfill(2)
day = str(now.day).zfill(2)
hour = str(now.hour).zfill(2)
minute = str(now.minute).zfill(2)
second = str(now.second).zfill(2)

# making a webhook key
time_sent = year + month + day + hour + minute + second
data = "{sent_time : " + time_sent + "}"

# for debugging
print(time_sent)

# URL setting
webhookURL = "https://hooks.zapier.com/hooks/catch/8712349/3oxfgkw/"

# POST request
response = requests.post(webhookURL, data=data)

# response code confirmation
if response.status_code == 200:
print("Data transferred successfully")
else:
print("Data transfer failed")


코드는 대충 모두 button 이라는 함수에 내가 웹상에서 클릭을 원하는 것을 선택하게한다. 그리고 선택한 것을 button.click() 명령으로 클릭하도록 구성되어있다. 

 

크롬에서 F12를 누르면 개발자 메뉴가 우측에 나타나는데 이 때, 위사진에서 네모모양에 커서가 있는 버튼을 누르면 원하는 것을 선택했을 때 어떤 코드로 구성된 것을 누르는지 확인이 가능하다.





그리고 그렇게 찾은 class에서는 Copy- Copy full Xpath를 통해 xpath를 얻어서 selenium 이 그걸 클릭하도록 코딩해주면 된다.



자 그럼 위 코드를 돌렸을 때,  메일로 지출이 잘 날아오는 것을 확인할 수 있다.

코드가 잘 돌아간다면, 코드 실행하는 크롬창이 뜨지 않도록 하는 headless 코드를 추가해주자.
위 전체 코드에서 ''' headless 모드 설정''' 부분을 참고하면 된다.

스팬딧 사이트는 크롬창이 적절하게 펼쳐져 있지 않으면 좌측 메뉴가 뜨지 않는데, 이 문제를 해결하기 위해 아래 코드를 추가해 해상도까지 지정했다. (코드에 포함되어있음)

options.add_argument("--window-size=1920,1080") # added for prevent error when it runs in headless mode


일단 나는 웹훅을 사용해서 Zapier에 신호를 주는 걸로 코드를 마무리했다.
이 웹훅을 신호탄으로 특정 구글시트에 파일내용을 업데이트 할 계획이다.


자 그럼 다음번에는 xlsx 파일을 특정 구글시트 파일에 업데이트하는 내용으로 이어가보겠다.















댓글

이 블로그의 인기 게시물

#1 (진행 중)아두이노 뇌파센서 헤드셋 만들기(Arduino EEG brain wave headset for psychological test) 만들어 뇌파 읽기

 15년 겨울쯤엔가 TED에서 흥미로운 동영상을 봤다. 뇌파를 통해 컴퓨터 안의 객체를 조종하는 모습을 시연하는 것이었다. 뇌파로 이런 것들이 가능하다는 것이 놀라웠다. 나는 심리학도가 아닌가. 뇌파가 더 정확한 심리검사를 만들 수 있는 도구가 될 수 있다는 생각이 들었다.  예를들어 검사문항(디지털 검사)이 100개짜리 라면 핵심 문항들(각 10번 단위)을 체크할 때마다 심경의 변화, 뇌파변화를 센서(객관적)도 기록하고 디지털검사(주관적)로도 기록해서 함께 데이터화 한다면 더 정확한 심경을 읽어 낼 수 있지 않을까? 라는 생각이었다. 2011년 대학원 다닐 때 컴공과 학부생들 겨울방학 특강으로 Objective-C를 무려1개월간 청강했고, C언어를 무려 2개월동안 학원에 다니면서 공부한 사람이기에 ! -_-;;;; 할 수 있을 것이다............  우선 뇌파센서를 구매해야겠지.  알리 익스프레스에서 구매한 EEG 뇌파센서 kit.  2개를 구매했다.비싸군 ㅠㅠ 배송이 한달정도 걸렸다. 학창시절 라디오 만들기인가..실과시간에 도전해본 납땜 이후로는 처음 해보는 납땜이어서 고생좀 했다. 뇌파를 측정해서 hex 코드로 컴퓨터로 읽어들일 수 있는 상태다. 읽어들인 hex값들을 10진수로 변환하고 유의미한 그래프로 그리거나 데이터화 하는 것이 필요 해 보이지만 아직 받은 값을 10진수로 변환하는 방법을 모르겠다. ㅠㅠ 소스코드는 그냥 단순히 hex값으로 읽어오는것이다보니.. 별거 없다;; 나중에 10진수로 변환하여 읽어들이고 자료화 하는 단계가 필요한 것 같은데 차근차근 진행 해 봐야겠다. 준비물 :  1. HM-08 블루투스 모듈 ($5.30) 2. 아두이노 나노 호환품 ($1.89) 3. direct nerosky e eg  brain...

#1 (완료) Auto touch와 Activator를 활용한 차량용 아이패드 미니1(탈옥) 세팅

 작년 7월 경 차를 하나 샀다. 벌써 1.6만km를 함께 달려왔구나~ 계속 중고차만 타다가 처음으로 장만한 이쁜이 우리 푸푸(골프의 애칭)에겐 아쉽게도 네비게이션이 없다. 2.0 고급형 모델과 일반형 모델이 네비게이션+가죽시트+스마트키 정도의 차이인데도 500만원 정도로 가격차이가 너무커서 사제 네비게이션을 달고 가죽시트를 포기하겠다는 생각으로(실제로 독일에서는 가죽시트보다 알칸타라 직물시트가 더 인기가 많음 - 아마 주행 중 엉덩이 미끄러짐 현상이 있기 때문인 듯.) 골랐는데 막상 네비게이션 장착점에 가니 100만원 ~ 150만원을 부른다. 아니.............. 그돈이면 아이패드 프로를 사요 아저씨. 네비로도 쓰고, 떼어서 영화도 보고 그림도 그릴 수 있다구요.... 그래서 아이패드 프로 12.9도 아닌, 아이패드 프로 9.7도 아닌, 아이패드 에어2도 아닌, 아이패드 에어1도아닌, 아이패드 미니4도 아닌, 아이패드 4도 아닌, 아이패드 미니2도 아닌, 아이패드 3도 아닌, 아이패드2 급의 아이패드 미니1을..... 그 당시 중고가 20만원가량을 주고 구입했다. 8.4.1 버전으로 탈옥이 가능했고, 탈옥 후 Auto touch(루아 스크립트 언어를 사용하는 Cydia앱) + Activator(탈옥기기엔 거의 필수인 Cydia앱) 으로 아래와 같은 아이디어를 실현해보고자 한다. 아니 이미 실현했으나.. 블로그를 만든지가 얼마 안돼 밀려서 쓰고 있다 ㅠ_ㅠ 아이디어 1.  차량에 시동을 걸면 아이패드가 자동으로 블루투스로 연결되고, 셀룰러 데이터, GPS ON 2.  음악앱(벅스 뮤직 플레이어)과 네비게이션 앱을 실행한다. 아래와 같이 코드를 입력했다. 잘 작동한다. 다만 lua명령 만으로는 와이파이나 셀룰러 on/off 제어가 안되기에 Activator 의 기능을 함께 활용 하는 방법을 택했다. 아이패드가 구형모델이다보니 다소 느린편이라 usleep 명령어를 통해 딜레이를 충분히 주었...

(완료)라즈베리파이와 + Homebridge API로 Siri를 통한 Arduino Web server 제어하기!

 예전에 해외 사이트( https://sourceforge.net/projects/siriproxyrpi/) 에서 siriproxyrpi에 대해서 본 적이 있다. 아이폰 빌트인 Siri로 전자제품을 자유롭게 제어하는 것.. 당연히 해보고 싶었기에 이 때 라즈베리파이를 구매하게 되었으나, 회사홈페이지때문에 바쁘다는 핑계로 묵혀두었던 나의 홈오토메이션 열정에 기름을 붓게 된 분이 계셨으니 바로 클리앙 이라는 IT  커뮤니티의 '채플린'님 이다.  클리앙 팁과 강좌 게시판에서 homebridge 와 NEST API + esp8266 + mutt broker + app 조합으로 홈 오토메이션을 멋드러지게 소화해 내신 '채플린'님(   https://github.com/chaeplin/neptune  ) 의 사례를 보고 리알못(리눅스를 전혀 알지 못함 ㅠ_ㅠ)인 내가 먼지가 내려 앉은 라즈베리파이를 꺼내들고 용기내어 도전하게 되었다.  우선 아두이노 웹서버는 철거 하기 귀찮을 뿐만 아니라.. 라즈베리파이에서 Python 으로 대체하는데에는 다소 시간이 걸릴것으로 생각되기 때문에! 기존 http GET 주소를 그대로 사용한다. (파알못이라 ㅠ_ㅠ)  라즈베리 파이3 모델B에 우선 최신 운영체제인 Jessie를 설치! http://www.raspberrypi.org 위 사이트에 방문해서 상단 Downloads-Raspbian-Raspbian  Jessie-Download  ZIP 파일을 다운받고.. micro SD 카드를 FAT32로 우선 포맷-SDformatter앱(sd카드는 포맷할때 이 앱을 써야한다고 한다-) 으로 포맷을 하고 win32diskimager( https://sourceforge.net/projects/win32diskimager/ ) 를 통해 img파일을 부팅가능하도록 micro SD카드에 굽는다. 나는 맥북에 있는 빌트인 SD카드리더에 항상 T...