Jeonhui {ios}

Baekjoon Projects - 1. github auto push baekjoon (2) [웹 크롤링] 본문

projects

Baekjoon Projects - 1. github auto push baekjoon (2) [웹 크롤링]

jeonhui 2022. 2. 23. 16:33

안녕하세요 Jeonhui입니다.

이번 시간에는 지난 글에 이어서 

baekjoon의 문제 풀이 결과를 가져와 맞았을 경우 깃허브에 자동으로 push 해주는 프로그램의 코드 구성을 설명하겠습니다.

프로그램의 기본 설정을 보고 싶으신 분들은 이전 글을 확인 해주세요.

2022.02.23 - [projects] - Baekjoon Projects - 1. github auto push baekjoon (1)


앞서 말씀드렸다시피 이 프로그램은 파이썬 패키지인 pygithub, requests, bs4를 이용하여 개발하였습니다.

 

먼저, get_result함수를 살펴보겠습니다. 이 함수는 백준의 문제 결과를 가져오는 함수 입니다.

def get_result():

import requests
from bs4 import BeautifulSoup as Soup

위 함수에서는 Http를 이용하기 위해 requests 패키지와 크롤링을 이용하기 위해 bs4를 import 해줍니다.

그 다음, 백준의 몇 번째 문제인지 사용자에게 입력을 받고 problem_number 변수에 저장을 해줍니다.

 

req = requests.get('https://www.acmicpc.net/status?user_id=' + baekjoon_id + '&problem_id=' + problem_number)
soup = Soup(req.text, 'html.parser')

baekjoon의 문제 결과는 'https://www.acmicpc.net/status?user_id=' + baekjoon_id + '&problem_id=' + problem_number 형식으로 접속을 할 수 있고, 이를 html파일에서 Soup객체로 변환시켜주면 크롤링을 위한 준비를 마쳤습니다.

html 파일을 가져오지 못하거나 Soup객체로 변환시킬 때 예외가 발생할 경우 예외처리를 해주었습니다.

 

if soup.find('input', {'name': 'problem_id'}).get('value') != "":

백준 사이트에서는 '999'와 같은 문제 번호를 입력할 경우 전체 문제 결과를 보여주는 위치로 이동하게 됩니다.

그러므로, problem_id가 존재하지 않을 경우에는 '문제가 존재하지 않습니다.'를 보여준 뒤 함수를 종료합니다.

 

while result[-1] not in break_result:
    try:
        result.append(soup.find("td", "result").text)
        # result의 값을 가져오기

    except Exception:
        print("채점 결과가 없습니다.")
        return
        # 값을 가져오지 못하면 결과가 없는 것

    if len(result) >= 2 and result[-1] != result[-2]:
        print(result[-1])
        # 결과 출력

while문을 이용해 결과를 저장하는 배열인 result배열에 break_result배열의 들어 있는 값인 ["맞았습니다!!", "틀렸습니다", "컴파일 에러"]가 나타나게 되면 채점 결과를 출력하는 것을 멈추고, while문을 빠져나가게 됩니다.

 

마지막으로

try:
    soup = Soup(requests.get('https://www.acmicpc.net/problem/'+problem_number).text, 'html.parser')
    title = problem_number +": " + soup.find('span', {'id': 'problem_title'}).text
except Exception:
    title = ""

if result[-1] == "맞았습니다!!":
    git_push(problem_number, title)

다음과 같이 git_push함수에 '문제번호: 문제 이름'의 형식으로 가져와서 git_push함수에 전달 해주게 되면 

get_result함수는 종료합니다.

 

먼저, git_push함수를 살펴보겠습니다. 이 함수는 깃허브에 push를 해주는 함수 입니다.

def git_push(file_name, *message):

git_push함수는 push할 파일명과 commit_message를 인자로 입력받습니다.

import os
from github import Github
from github import InputGitTreeElement

현재 저장소의 위치를 알기 위해 os와 github에 push하기 위해 github, InputGitTreeElement 패키지를 import 해줍니다.

try:
    g = Github(token)
    # token github에 접속
    repo = g.get_repo(repository)
    # repository 설정
    repo_ref = repo.get_git_ref("heads/main")
    repo_sha = repo_ref.object.sha
    base_tree = repo.get_git_tree(repo_sha)
    # push를 위한 설정

except Exception:
    print("잘못된 접근입니다. (token or repository)")
    return

사용자에게 이전에 입력받았던 token값과 repository를 이용해 github에 접속하고, push를 위한 설정을 해줍니다.

token값 또는 repository위치가 잘못된 경우 예외가 발생하므로 예외 처리도 해줍니다.

github_file_path = ("solved/" if user_github_file_path == "_" else user_github_file_path) + file_name + ".py"
# 파일 경로 (github)
# InputGitTreeElement에 넣기 위해 repository 안 경로로 설정
if user_file_path == "_":
    file_path = os.getcwd() + "/solved/" + file_name + ".py"  # default
else:
    file_path = user_file_path + ("/" if user_file_path[-1] != "/" else "") + file_name + ".py"
# 내 컴퓨터의 파일 경로

위 코드는 github 사용자 지정 경로를 설정했는지 안 했는지 확인하며, 지정했다면 지정경로로 바꿔주는 코드입니다.

기본값은 github_file_path 는 solved/ 이고, file_path는 현재 위치/solved/ 입니다.

github_file_path의 경우 ""로 작성할 경우 repository 최상위에 위치하게 됩니다.

 

try:
    with open(file_path, 'r') as file:
        data = file.read()

        print()
        print("source code")
        print(file_path)
        print("____________________________")
        print(data)
        print("____________________________\n")

        element = InputGitTreeElement(github_file_path, '100644', 'blob', data)
        # 파일 내용 element 저장

        try:
            if len(message) != 0:
                commit_message = message[0]
            else:
                print("commit message: ", end="")
                commit_message = sys.stdin.readline().rstrip()
            tree = repo.create_git_tree([element], base_tree)
            parent = repo.get_git_commit(repo_sha)
            commit = repo.create_git_commit(commit_message, tree, [parent])
            repo_ref.edit(commit.sha)
            print("Push에 성공했습니다.")
            # push
        except Exception:
            print("Push에 실패했습니다.")
            return

except Exception:
    print("파일이 존재하지 않습니다.")
    return

다음과 같이 사용자에게 지정한 파일의 소스코드를 보여준 뒤 지정한 파일 내용을 element에 저장하고,

처음 git_push함수에서 호출될 때에 받았던 message를 확인하여 없을 경우 사용자에게 commit메시지를 입력받아

github에 push하게 되면서 이 함수를 종료합니다. 

file open을 할 때에 예외가 발생하면 파일이 존재하지 않는 것이고, push를 하는 도중에 예외가 발생하면 push를 하지 못 한 것이기 때문에 이 역시 예외처리를 해주었습니다.

 

이 프로그램에서는 백준 문제뿐만 아니라 problem_id에 'p'를 입력하여 기존의 파일들도 github의 push를 할 수 있도록 작성하였습니다.

 

긴 글 읽어주셔서 감사합니다. 잘못된 부분이 있을 경우 댓글을 남겨주세요. :)

다음 시간에는 간단한 두 번째 프로젝트 Baekjoon Projects - 2. get Problem에 대해 작성하도록 하겠습니다.


전체 소스 코드는  https://github.com/Jeonhui/baekjoon/blob/main/baekjoon_autoPush.py에 저장되어있습니다.

 

웹 스크래핑 (Web Scraping)

백준 온라인 저지는 웹 스크래핑을 금지하고 있습니다.

지나치게 많은 트래픽을 발생시키는 경우에는 사이트 이용이 정지됩니다.

스타트링크는 웹 스크래핑의 수요와 이유를 충분히 이해하고 있기 때문에, API를 제공할 계획이 있습니다.
https://help.acmicpc.net/rule

 

이용 규칙

블로그블로그에는 지속적으로 의미를 가질 수 있는 다음과 같은 글을 올릴 수 있습니다. 아래는 예시이며 아래 적히지 않은 내용도 가능합니다.알고리즘/자료구조 튜토리얼문제에 종속적이지

help.acmicpc.net

확인해본 결과 백준에서는 웹 스크래핑을 금지하고 있기 때문에 이 소스코드는 참고만 해주시기 바랍니다.