[VB.NET] 차근차근 크롬 브라우저 만들기 - 8. 티스토리 로그인 (2/2)

2023. 2. 14. 09:00VB.NET/Chrome Browser

지난 강좌에 이어서 이번 강좌에서는 티스토리 로그인을 완성해보자. 패킷분석으로 할 수도 있지만 우리는 ChromiumWebBrowser를 이용해서 구현중이다. 요즘은 페이지내 스크립트 소스들이 많이 복잡해져서 분석하는데 시간이 제법 걸리기도 하지만 특별한 제약이 있지 않으면 이런 복잡도는 브라우저를 사용하는 순간 모두 해결된다.

왜냐하면, 소스를 아무리 복잡하게 만들어도 결국 사람이 사용하는 UI는 쉽게 만들어야 하기 때문이다.

티스토리 로그인의 직접 수행하기 위해 이전강좌에 첨부해놓은 소스중 ModuleLogin을 차근차근 따라가보자.

2023.02.13 - [VB.NET/Chrome Browser] - [VB.NET] 차근차근 크롬 브라우저 만들기 - 7. 티스토리 로그인 (1/2)

Imports CefSharp

ModuleLogin에서 ChromiumWebBrowser가 제공하는 함수를 사용하기 위해 추가

전역변수

Public LoginFg As Boolean

로그인 작업 수행중인지 여부를 확인/설정하는 플래그

Public LoginGbn As Integer

로그인 작업 단계를 확인/설정하는 전역변수

Public LastTm As Date

위 LoginGbn 변수값에 의해 실행되는 프로시저간 소요시간을 체크하는 변수. 브라우저로 작업을 하다보면 다양한 이유로 원치않게 페이지가 열리지 않는등의 오동작을 경험하게 된다. 하지만 이 변수로 인해 우리는 특정시간까지 원하는 페이지가 열리지 않으면 재시도를 하거나 사용자에게 오류를 보여주는 등의 작업을 수행할 수 있다.

Public LoadComplete As Boolean

브라우저의 로딩이 완료됐는지 확인하는 전역변수. 페이지를 이동할때 False로 설정후 FormBrowser의 BrowserMain_FrameLoadEnd 이벤트에서 로딩완료시 True로 변경해주면 이 값이 True로 변경될때까지 타이머는 대기하면 된다.

Private user_id, user_pw As String

티스토리 사용자 아이디, 비밀번호. 이건 추후 UI에서 사용자에게 직접 입력받는등의 방법으로 수정이 가능하니 일반 사용자용 프로그램을 작성하거나, 여러 사용자 아이디가 필요한 경우 DB에 넣어놓고 사용이 가능하다. 이 강좌에서는 하드코딩으로 내 아이디와 비밀번호를 넣고 작업했다.

TimerLogin_Tick 이벤트에서 직접 호출하는 LoginProc이라는 프로시저는 이전 강좌에 있고, 첨부파일에도 있으니, 여기서는 소스를 첨부하지 않는다. 그냥 위 전역변수중 LoginGbn의 값에 따라 해당하는 프로시저를 호출해주는 역할을 하는 프로시저이다.

이제 드디어 본격적인 작업에 들어간다. 아래 순서도에 기반해서 프로시저 하나하나 자세히 설명하도록 하겠다.

티스토리 로그인 순서도

Private Sub LoginProc0()     ' 작업준비

    user_id = "본인아이디"
    user_pw = "본인비번"
    LoginGbn = 100

End Sub

작업준비라고 했지만 사실 여기서 진짜 작업이 시작되며, 위 순서도의 맨 처음 시작부분의 소스라고 볼 수 있다. 티스토리 아이디와 비번을 설정후 구분값을 100으로 변경해서 다음번 프로시저인 LoginProc100을 실행하도록 설정한다.

Private Sub LoginProc100()   ' 메인페이지 접속

    FormBrowser.BrowserMain.LoadUrl("https://www.tistory.com")
    LoginGbn = 101
    LoadComplete = False
    LastTm = Now

End Sub
Line 1 : FormBrowser.BrowserMain.LoadUrl("https://www.tistory.com")

브라우저에 티스토리 메인페이지를 로딩한다. 이 LoadUrl도 결국 다른 브라우저 함수와 마찬가지로 Async모드로 동작하기 때문에 현재 프로시저인 LoginProc100이 끝나야 동작해서 페이지를 변경하게 된다. 그러니, 이 프로시저에서 필요한 값을 설정후 다음 프로시저를 실행하도록 설정해야 한다.

Line 2 : LoginGbn = 101

이 LoginGbn 값을 변경하는 이유를 매번 프로시저마다 설명할 필요는 없으니 LoginGbn 값이 변경되면 이 값에 해당하는 프로시저가 실행된다고 이해하도록 하자. 물론, 그렇게 되도록 맨위에 LoginProc 프로시저를 만들었으니 가능한 얘기겠지만...

Line 3 : LoadComplete = False

브라우저가 페이지를 변경하도록 설정한 프로시저에서는 변경할때 무조건 LoadComplete를 False로 설정후, 다음번 프로시저에서 이 값이 True로 변경될때까지 대기하면 된다.

Line 4 : LastTm = Now

현재 프로시저의 실행시간을 기록해둠으로써 다음번 프로시저에서 대기시간을 원하는대로 설정해줄 수 있다. 한가지 팁이라면 LastTm이라는 변수와 동일한 개념으로 NextTm 이라는 변수를 선언해서 다음번 프로시저를 실행할 시간을 임의로 설정할 수도 있다.

이 프로시저가 종료되는 순간 화면에서 브라우저는 티스토리 메인페이지를 로딩하게 된다.

Private Sub LoginProc101()   ' 메인페이지 로딩완료 대기

    If DateDiff("s", LastTm, Now) > 5 Then
        Debug.Print("5초이상 응답이 없으니 재시도를 하든 오류로 종료하든 예외처리 필요")
        Debugger.Break()
    End If

    If Not LoadComplete Then
        LoginGbn = 101        ' 로딩 완료시까지 대기
        Exit Sub
    End If

    If FormBrowser.TextURL.Text <> "https://www.tistory.com/" Then
        Debug.Print("다른 페이지가 로딩됐는지 다시한번 확인")
        Debugger.Break()
    End If

    LoginGbn = 200          ' 로그인 여부 확인용 자바스크립트 실행
    LastTm = Now

End Sub

이제 브라우저로 페이지를 변경했으니 제대로 변경됐나 확인을 할 차례이다. LoadUrl로 페이지를 호출했으니 변경됐다고 생각하고 바로 다음 작업을 시작하면 정말 원치않는 에러들이 무수히 쏟아질 수 있다. 항상 이런류의 Async 방식으로 동작하는 컨트롤들은 반드시 변경이 제대로 됐는지 기다리면서 확인해야 한다.

If DateDiff("s", LastTm, Now) > 5 Then
    Debug.Print("5초이상 응답이 없으니 재시도를 하든 오류로 종료하든 예외처리 필요")
    Debugger.Break()
End If

여기서는 그냥 Debug.Print로 화면에 찍고 Debugger.Break로 막았지만 실전에서는 여기에 예외처리를 해서 다시 LoginProc100으로 가도록 설정해주고, 재시도 횟수도 3~5회정도로 설정하고, 그래도 응답이 없으면 오류메시지 출력후 종료하는등의 작업들을 해줘야 한다.

If Not LoadComplete Then
    LoginGbn = 101        ' 로딩 완료시까지 대기
    Exit Sub
End If

LoginProc100에서 False로 설정한 LoadComplete 변수의 값이 True가 될때까지 대기하면서 LoginProc101이 반복적으로 실행되도록 만들어주는 구문이다. 기다리면서 그때그때 확인해서 브라우저 로딩이 완료되면 다음 단계로 넘어가게 된다.

그 밑에 두줄은 모든 프로시저에서 동일하니 참고하자.

Private Sub LoginProc200()   ' 로그인 여부 확인용 자바스크립트 실행

    Dim script = "var btntag = document.getElementsByTagName('button');"
    script &= "var result = 'false';"
    script &= "for (var i = 0; i < btntag.length; i++)"
    script &= "{"
    script &= "  if (btntag.item(i).className == 'btn_logout')"
    script &= "  {"
    script &= "     result = 'true';"
    script &= "     break;"
    script &= "  }"
    script &= "}; bound.setValue(result);"

    ScriptValue = ""
    FormBrowser.BrowserMain.ExecuteScriptAsync(script)

    LoginGbn = 201          ' 로그인 여부 확인후 분기
    LastTm = Now

End Sub

이제 자바스크립트와의 통신이다. 지난번 강좌에서 배웠고, 주된 내용은 Vb.NET 보다는 JavaScript이기는 하지만 그래도 이조차 생소한 사람들을 위해 차근차근 설명해보자. 기본은 자바스크립트를 만들어서 script라는 변수에 넣고, 이 자바스크립트를 브라우저에서 실행하면 최종적으로 자바스크립트의 결과를 ScriptValue라는 변수에 설정하게 된다.

자바스크립트 소스를 말로 설명하면 현재 페이지에서 <button> 태그를 찾아서 모든 button 태그를 루프돌며 class가 btn_logout이라는 버튼이 있으면 result에 true를 없으면 false를 넣고, 프로젝트내 ClassJavaScript의 setValue라는 메소드에 result 값을 전달해주는 내용이다.

ScriptValue = ""

이제 이렇게 문자열로 만들어준 자바스크립트를 호출해야 하는데, 정상적으로 잘 동작하면 ScriptValue에 자바스크립트의 결과값(true/false)이 들어올 예정이니 호출전에 이 변수를 초기화한다.

FormBrowser.BrowserMain.ExecuteScriptAsync(script)

이것도 지난 강좌에서 설명한 내용인데 script라는 변수안에 들어있는 자바스크립트를 페이지에 삽입해서 실행하는 구문이다. 함수명에도 Async가 들어가듯, Async 방식으로 동작하기 때문에 반드시 이 프로시저가 종료된 후에야 ScriptValue에 값을 받을 수 있고, 그렇기에 이번 예제에서 타이머를 사용해서 만들고 있는 것이다. 타이머도 여러 선택지중 하나일 뿐이라는것도 알고 넘어가자. 꼭 이방법만 있는건 아니니까.

Private Sub LoginProc201()  ' 로그인 여부 확인후 분기

    If DateDiff("s", LastTm, Now) > 5 Then
        Debug.Print("5초이상 응답이 없으니 재시도를 하든 오류로 종료하든 예외처리 필요")
        Debugger.Break()
    End If

    If ScriptValue = "" Then
        LoginGbn = 201      ' 자바스크립트 처리 완료시까지 대기
        Exit Sub
    End If

    If ScriptValue = "true" Then
        MsgBox("이미 로그인이 되어 있어서 타이머를 종료합니다.")
        LoginGbn = 9        ' 이미 로그인이 되어 있으니 작업 종료
    Else
        LoginGbn = 300      ' 로그인 페이지 접속 - 시작하기 클릭
    End If

    LastTm = Now

End Sub

위 LoginProc200에서 실행한 자바스크립트 함수의 실행완료까지 대기후 로그인이 되어있으면 종료하고, 아직 안되어 있으면 로그인 하러 가는 분기문이 포함된 프로시저이다.

ScriptValue 값은 위 LoginProc200 프로시저의 자바스크립트에서 btn_logout 버튼이 있으면 true, 없으면 false이니, 이 값에 따라서 false일 경우에는 로그인하러 가면 된다. 여기서도, 자바스크립트가 어떤 이유로든 실행이 안되거나 오동작할 가능성이 단 1%라도 있으니 무조건 오류처리를 해줘야 한다.

여기서 왜 자바스크립트의 결과가 ScriptValue 값으로 들어오는지 이해가 안되시는 분들께서는 이전 강좌

2023.02.10 - [VB.NET/Chrome Browser] - [VB.NET] 차근차근 크롬 브라우저 만들기 - 5. 자바스크립트와 통신

을 참고하도록 하자.

그다음 티스토리에서 시작하기 버튼을 누르고, 그후에 카카오로 로그인하기 버튼을 누르는 LoginProc300, LoginProc301, LoginProc310, LoginProc311은 모두 위 200, 201과 동일하게 자바스크립트를 호출하고 페이지변경등 완료까지 대기하는 부분이니 설명은 건너뛰자.

Private Sub LoginProc400()   ' 아이디 입력 1 - 클립보드에 복사

    FormBrowser.ActiveControl = FormBrowser.BrowserMain

    Dim script = " document.getElementById('loginKey--1').focus();"
    FormBrowser.BrowserMain.ExecuteScriptAsync(script)

    Clipboard.SetText(user_id)

    LoginGbn = 401          ' 아이디 입력 2 - 클립보드 붙여넣기
    LastTm = Now

End Sub

이제 아이디를 입력하자. 그런데 아이디 입력과는 거리가 먼 소스들이 몇줄 눈에 띈다. 그냥 자바스크립트로 document.getElementById('loginKey--1').value = '아이디'; 이런방식으로 하면 되지 않나? 싶어서 해봤는데 안되더라. ㅠㅠ 왜 안되는지 확인하려면 크롬 개발자도구로 디버깅해서 실제 호출하는 스크립트 함수를 찾아내서 호출해주면 되겠지만 귀찮기도 하고 더 복잡해질듯 싶어서 그냥 클립보드를 사용하기로 했다.

클립보드에 아이디를 복사해놓고 붙여넣기하면 되는데 그러려고 하니 포커싱 문제가 있어서 브라우저를 포그라운드로 만들고, 그다음 아이디 입력 input을 포그라운드로 만들어주기 위해 아래 소스들이 사용됐다.

FormBrowser.ActiveControl = FormBrowser.BrowserMain

브라우저를 ActiveControl로 만들어준다. 이게 예전 VB6에서는 FormBrowser.BrowserMain.SetFocus 로 사용했는데 닷넷에서 사용법이 변경된 부분이다.

Dim script = " document.getElementById('loginKey--1').focus();"
FormBrowser.BrowserMain.ExecuteScriptAsync(script)

아이디 입력 input 태그에 포커스를 줘서 커서가 깜빡이게 만들어준다.

Clipboard.SetText(user_id)

클립보드에 아이디를 복사해준다. 닷넷에서는 이런식으로 윈도우 기본기능에 해당하는 함수들이 제법 많이 제공되고 있어서 사용하기가 편리하다.

Private Sub LoginProc401()  ' 아이디 입력 2 - 클립보드 붙여넣기

    SendKeys.Send("^v")

    LoginGbn = 402          ' 아이디 입력 확인 요청
    LastTm = Now

End Sub

아까 클립보드에 복사해놓은 티스토리 아이디를 Ctrl+V 키를 입력해서 붙여넣어주는 기능이다.

Private Sub LoginProc402()  ' 아이디 입력 확인 요청

    Dim script As String = ""

    script &= "var result = document.getElementById('loginKey--1').value;"
    script &= " bound.setValue(result);"

    ScriptValue = ""
    FormBrowser.BrowserMain.ExecuteScriptAsync(script)

    LoginGbn = 403          ' 아이디 정상입력 확인
    LastTm = Now

End Sub

여기서도 같은얘기를 또 하게 되는데 뭔가 페이지에 입력을 했다면, 제대로 입력이 됐는지 꼭 다시 확인해야 한다. 이건 무조건 이렇게 해야 한다. 실제 아이디 input에 내가 붙여넣은 사용자 아이디가 입력됐는지 확인하자.

Pivate Sub LoginProc403()

    If DateDiff("s", LastTm, Now) > 5 Then
        Debug.Print("5초이상 응답이 없으니 재시도를 하든 오류로 종료하든 예외처리 필요")
        Debugger.Break()
    End If

    If ScriptValue = "" Then
        LoginGbn = 403      ' 자바스크립트 처리 완료시까지 대기
        Exit Sub
    End If

    If ScriptValue = user_id Then
        LoginGbn = 410      ' 비밀번호 입력 1 - 클립보드에 복사
    Else
        Debug.Print("아이디 입력오류 - 디버깅")
        Debugger.Break()
    End If

    LastTm = Now

End Sub

자바스크립트 결과로 받은 ScriptValue의 값과, user_id 값이 동일해야 정상입력이 확인된 것이다.

비밀번호 입력인 LoginProc410, 411, 412, 413도 아이디 입력과 동일하니 설명은 건너뛰자. 그다음 로그인 버튼을 클릭하는 LoginProc420, 421도 동일하게 이해할 수 있을 것이다.

결국 소스는 길지만, 거의 비슷하고, 이를 모듈화하면 훨씬 짧게 구현도 가능하지만 이번 강좌에서는 이해를 위해서 최대한 풀어써봤다. 최종적으로 LoginProc421에서 다시 LgoinGbn을 200으로 만들어서 위로 올라가서 로그인여부를 다시 확인하는걸로 순서도대로 작업을 마무리했다.

ChromiumWebBrowser로 강좌를 몇개 만들다보니 자꾸 욕심이 생기는데 다음번 강좌는 뭘로 할지 좀 고민해봐야겠다.

반응형