관리 메뉴

surish

[Python]구글 클래스룸 크롤링으로 출결 체크 업무 자동화 본문

업무자동화

[Python]구글 클래스룸 크롤링으로 출결 체크 업무 자동화

surish 2021. 2. 7. 18:01

구글 클래스룸에서 크롤링으로 업무 자동화!

1. Motivation

2020 한 해, 온라인 수업을 시작하면서 학년 초의 팔 할은 출석체크를 하느라 시간을 낭비했다.

미제출자 현황을 보고 연락을 해야 하므로 매일 미제출자를 파악해야 하는 일이 반복되었는데, 같은 일이 세 번 이상 반복되면 업무 자동화를 해야 한다는 말이 있더라.. 극 공감

우선 기존의 확인하는 방법은

  1. 구글 클래스룸 해당 수업에 들어가서 '성적' 탭을 들어가기
  2. 각 수업을 눌러서 할당 완료(과제 제출 전)인 학생 찾기
  3. G메일로 날아오는 늦게 제출한 학생의 제출 메일

1. 구글 클래스룸 '성적' 탭의 제출 현황
2. 수업에서 할당된 학생 찾기

하지만 단점은

  • 들어가는 반의 개수만큼 클릭해야 하며 성적 탭이 유난히 로딩이 길다. 즉 오래 걸림
  • 누락됨이 뜬 학생을 눈으로 찾아야 한다. 어제의 과제만을 체크해야 하는 것이 아닌, 일주일의 과제를 체크해야 하므로...

2. 크롤링 코드 및 실행 결과

그래서 내가 수업을 들어가는 모든 반(5개 정도)의 과제 제출 현황판 같은 것을 만들고 싶었는데,

실시간으로 변동이 되는 현황판을 만드는 것은 못하고, 결국 연락을 취해야 하는 과제 미제출자의 명단을 출력하는 코드를 만들었다.

흐름은

성적 탭의 url 크롤링

> 수업별 학생 리스트와 과제 현황(제출함/ 할당됨/ 누락됨 등) 추출

> 내가 찾는 할당됨, 누락됨 문자열은 0으로 치환, 나머지는 1로 치환

> 한 리스트에 수업 이름(날짜포함됨)과 그 수업에서 0이 떠있는 학생 명단 반환

> 반별 리스트 합쳐서 반환

from selenium import webdriver 
from selenium.webdriver.common.keys import Keys
import numpy as np #array다루기
import pandas as pd #Dataframe
import time #기다리는 용도(sleep)
import requests#크롤링
from bs4 import BeautifulSoup#크롤링
import re#치환할때 쓰임

# 시작 시간 저장
start = time.time()  

#크롤링 할 수업 개수는 5개로 설정
def crawling(url1, url2, url3, url4, url5, account, pw):
    #1. crawling
    driver = webdriver.Chrome("C:\google\selenium\chromedriver.exe")
    driver.get("https://accounts.google.com/signin/v2/identifier?service=classroom&passive=1209600&continue=https%3A%2F%2Fclassroom.google.com%2Fu%2F0%2Fh&followup=https%3A%2F%2Fclassroom.google.com%2Fu%2F0%2Fh&flowName=GlifWebSignIn&flowEntry=ServiceLogin")
    driver.find_element_by_id("identifierId").send_keys(account)
    driver.find_element_by_id("identifierNext").click()
    time.sleep(2)#기다렸다가 로그인
    driver.find_element_by_name("password").send_keys(pw)
    driver.find_element_by_id("passwordNext").click()
    time.sleep(2)#기다렸다가 성적탭으로 이동    

    driver.get(url1)#구글 클래스룸 성적 탭 url
    time.sleep(10)
    html1 = driver.page_source
    soup1 = BeautifulSoup(html1, 'html.parser')
    title = soup1.select('tbody')
    time.sleep(2)
    driver.get(url2)#구글 클래스룸 성적 탭 url
    time.sleep(10)
    html2 = driver.page_source
    soup2 = BeautifulSoup(html2, 'html.parser')
    title = soup2.select('tbody')
    time.sleep(2)    
    driver.get(url3)#구글 클래스룸 성적 탭 url
    time.sleep(10)
    html3 = driver.page_source
    soup3 = BeautifulSoup(html3, 'html.parser')
    title = soup3.select('tbody')
    time.sleep(2)
    driver.get(url4)#구글 클래스룸 성적 탭 url
    time.sleep(10)
    html4 = driver.page_source
    soup4 = BeautifulSoup(html4, 'html.parser')
    title = soup4.select('tbody')
    time.sleep(2)    
    driver.get(url5)#구글 클래스룸 성적 탭 url
    time.sleep(10)
    html5 = driver.page_source
    soup5 = BeautifulSoup(html5, 'html.parser')
    title = soup5.select('tbody')
    time.sleep(2)
    return soup1, soup2, soup3, soup4, soup5

#영문 제거 함수
def clean_text(text):
    cleaned_text = re.sub('[a-zA-Z]' , '', text)
    return cleaned_text

#미제출 학생 출력 함수
def no_submit_student_list(soup,keyword):
    index_1 = soup.select('th')#soup에 해당하는 것 바꿔야함(여러 반일 경우 모두)
    data1 = []
    for n in index_1:
        data1.append(n.text.strip())
    #data1이 과제+학생명렬로 이뤄져있어서 구분해야함
    st_start_number = data1.index('\ue7fb학급 평균')#과제와 학생 구분 기준
    student = data1[st_start_number:]#학생 명렬
    #스크롤하면 더 보이긴 하는데, 우선 20개 이상일 경우 20개의 과제만 보임
    n = data1.index('Aez 88Est Estigfendout of est더 많은 과제 로드 중...')
    assignment = data1[1:n]#과제 이름 리스트 보이는 n개만
    #len(assignment) #20
    index_2 = soup.select('td')

    #할당됨, 누락됨 > 미제출
    #제출함 > 제출함
    data2 = []
    for n in index_2:
        data2.append(n.text)
    as_num = st_start_number-1 
    st_num = int(len(data2)/as_num)

    temp = []
    for i in range(st_num * as_num):
        temp.append("-")
    data2 = np.array(data2) #형태 바꾸기
    array = data2.reshape(st_num,as_num)
    ###########
    temp = []
    for i in range(st_num):
        for j in range(as_num):
            temp.append(clean_text(array[i][j]))


    for i in range(as_num, len(temp)):
        temp_str = temp[i]
        if keyword in temp_str:
            temp[i] = '0' 
        else:
            temp[i] = '1'

    for i in range(as_num-2):
        temp[i] = assignment[i]
    temp = np.array(temp).reshape(st_num,as_num)
    data3 = temp.T

    df = pd.DataFrame(data3)
    df.columns = student
    df = df[:5]
    df = df.T
    no_submit = []
    for j in range(5): # 5 = 과제 열람 개수(최근순)
        temp = []
        temp.append(assignment[j])
        for i in range(len(student)):    
            if df[j][i] == '0' :
                temp.append(student[i])
            else : 
                continue
        temp.append("미제출 : {}명".format(len(temp)-1))
        no_submit.append(temp)
    ###    
    no_submit_alza = []
    for i in range(len(no_submit)):
        if '미제출 : 0명' in no_submit[i] : 
            continue
        else:
            no_submit_alza.append(no_submit[i])
    no_submit_alza        
    return no_submit_alza

def give_me_list(url1, url2, url3, url4, url5, account, pw, keyword):
    soup1, soup2, soup3, soup4, soup5 = crawling(url1, url2, url3, url4, url5, account, pw)
    return no_submit_student_list(soup1,keyword), no_submit_student_list(soup2,keyword), no_submit_student_list(soup3,keyword), no_submit_student_list(soup4,keyword), no_submit_student_list(soup5,keyword)

##설정란#################################
#url1~5 안에는 구클 성적탭의 url 붙여넣기 https://classroom.google.com/u/0/c/***********/gb/sort-name 이런 형식
url1 = ""
url2 = ""
url3 = ""
url4 = ""
url5 = ""

account = "***@*****.**"
pw = "" #비밀번호 입력
keyword = "누락" or "할당" #과제현황이 누락 또는 할당으로 떠있는 학생들만 추리기
########################################
list1, list2, list3, list4, list5 = [], [], [], [], []
list1, list2, list3, list4, list5 = give_me_list(url1, url2, url3, url4, url5, account, pw, keyword)
print("\n걸린시간 : {:.1f}초".format(time.time() - start))  # 현재시각 - 시작시간 = 실행 시간
list1+list2+list3+list4+list5#합쳐서 출력하기

코드 실행 결과

3. 보완할 점

어쨌든 빠르진 않지만 원터치(?)로 다섯 반의 제출 현황을 보고해주니까 기다리는 시간에 다른 업무를 해도 되고, 훨씬 간편해서 만족하지만, 보완할 점은

  • 코드가 너무 이상하다
  • 속도 개선
    • time.sleep 시간을 10초로 잡았는데, 성적 탭이 로딩되는 시간을 측정할 수는 없는지? 측정한 시간보다 쪼금 더한 시간을 정한다면 시간을 줄일 수 있을 것 같은데...
    • 120초에서 로딩되는 시간인 약 1분을 제외하고 나머지 1분을 줄이는 방법
  • 문자열에 포함된 \ue5d4 이런 것 해결(아무리 해봐도 힘들다 ㅠㅠ)
  • 출력 결과 파일 형태 : 프린트뿐 아니라 csv파일로 저장해놓으면, 연락을 취한 것을 표시할 때, 제출 여부 다시 체크할 때 편할 것 같아서 시도해봤는데 왜인지 콤마를 기준으로 분리되지 않고 모든 리스트가 한 셀로 출력된다.

문제는 1년 내내 고생하다가, 결국 코드를 1월 방학 때 완성해서 몇 번 쓰지도 못했다는 것... ㅠㅠ

2021학년도는 전국 공통, 온라인 수업을 들을 경우 3일 이내 (목요일의 수업은 월요일 자정까지)에 들어야 한다고 한다. 그러면 체크해야 할 수업이 좀 줄겠지만..(2020학년도는 5일 이내) 코드를 활용해서 효율적으로 시간을 쓰는 것으로!

'업무자동화' 카테고리의 다른 글

[API] Application Programming Interface  (0) 2023.02.12