본문 바로가기
카테고리 없음

Python 네이버 requests 로 로그인해보기

by 퍼포먼스마케팅코더 2024. 2. 21.
반응형

네이버 requests 로그인 방법 정리

네이버에 requests를 사용하여 로그인하는 방법은 다음과 같은 단계를 포함합니다. 주요 변수로는 dynamicKey, encpw, bvsd, encnm이 있으며, 이들 중 dynamicKey, encnm, encpw는 고정된 절차를 통해 얻을 수 있고, bvsd는 추가적인 확인이 필요합니다.

로그인 필수 값

로그인 과정에 필요한 값은 다음과 같습니다:

{
    "localechange": "",
    "dynamicKey": "...",
    "encpw": "...",
    "enctp": "1",
    "svctype": "1",
    "smart_LEVEL": "1",
    "bvsd": "{\"uuid\":\"..\",\"encData\":...\"}",
    "encnm": "...",
    "locale": "ko_KR",
    "url": "https://www.naver.com/",
    "id": "",
    "pw": ""
}

1. dynamicKey

  • dynamicKey는 로그인 폼에 동적으로 부여되는 값입니다. ?mode=form에서 id="dynamicKey"를 통해 확인할 수 있습니다.

2. encpw & encnm 생성 방법

  • common_202201.js 파일confirmSubmit() 함수에서 encryptIdPw()를 호출하여 session_keys 값을 처리하고 RSA 암호화하여 encpw 값을 생성합니다.
  • session_keys는 Ajax 통신을 통해 응답 받은 결과에서 확인할 수 있으며, ?mode=form&svctype=262144를 통해 모바일 페이지에서 접근 시 session_keys 값을 확인할 수 있습니다.
  • session_keys에서 나온 값들을 sessionKey, encnm, evalue, nvalue로 구분하며, 여기서 두 번째 값이 encnm입니다.

encpw 값 생성 예제


if (keySplit(session\_keys)) {  
rsa.setPublic(evalue, nvalue);  
try {  
encpw.value = rsa.encrypt(  
getLenChar(sessionkey) + sessionkey +  
getLenChar(id.value) + id.value +  
getLenChar(pw.value) + pw.value);  
} catch(e) {  
return false;  
}  
$('enctp').value = 1;  
id.value = "";  
pw.value = "";  
return true;  
}

3. bvsd 값 생성

  • bvsd는 브라우저의 정상 여부를 확인하기 위한 값으로, 없으면 로그인 과정에서 캡차가 발생합니다.
  • uuid, encData를 포함하며, encData는 마우스 움직임 등의 디바이스 정보를 담습니다.
  • 크롬 DevTools의 Console 탭에서 bvsd.f(function(a) { console.log(a); });를 입력하여 uuidencdata를 얻고, LZString.decompressFromEncodedURIComponent(encData)encData를 복호화하여 JSON 형식으로 변환한 후, 필요한 값을 조정하여 POST 요청을 보내는 방법으로 bvsd 값을 생성할 수 있습니다.
from json import dump  
from lzstring import LZString  
import uuid

json\_string = ''' 여기에 encdata 넣기 '''

json\_data = json.loads(json\_string)  
encData = json\_data\['encData'\]

encData\_depressed = LZString.decompressFromEncodedURIComponent(encData) #복호화

encData\_depressed\_data = json.loads(encData\_depressed)

print(json.dumps(encData\_depressed\_data, indent=4, ensure\_ascii=False))

 

이 과정을 통해 네이버 로그인에 필요한 모든 값을 얻을 수 있으며, 이 값을 활용하여 requests를 통해 로그인을 시도할 수 있습니다. 혹시 몰라 아래에 관련 내용을 넣어둡니다.

 

from rsa import encrypt, PublicKey
from requests import Session
from lzstring import LZString
from uuid import uuid4
from json import dumps
from bs4 import BeautifulSoup
import requests

def get_cookies_session(_cookies):
    cookie_dict = {}
    for key, value in _cookies.items():
        cookie_dict[key] = value
    else:
        _session = requests.Session()
        headers = {'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Mobile Safari/537.36'}
        _session.headers.update(headers)
        _session.cookies.update(cookie_dict)
        mycookie = ''
        for cookie in _cookies.items():
            cookie_dict[cookie[0]] = cookie[1]
            mycookie += f"{cookie[0]}={cookie[1]};"
        else:
            _session.close()
            return  mycookie

class Naver(Session):
    def __init__(self) -> None:
        super().__init__()
        self.headers = {
            "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
        }
        self.base = "https://nid.naver.com/nidlogin.login"
        self.bvsd = {
            "uuid": None,
            "encData": None
        }
        self.default = {
            "localechange": "",
            "dynamicKey": None,
            "encpw": None,
            "svctype": "262144",
            "smart_LEVEL": "-1",
            "bvsd": None,
            "encnm": None,
            "locale": "en_US",
            "url": "https:///m.naver.com/aside/",
            "nvlong": "on",
            "appSchemeView": "true",
            "id": "",
            "pw": ""
        }
 
        self.state_footprint = {
            "a": None, # uuidWithCaptchaSequence
            "b": "1.3.4", # bvsdVersion
            "c": False, # deviceTouchable
            "d": [{ # keyboardLogs
                "a": ["0,d,TAB,9"], # keyStrokeLog
                "b": { # inputIntervalLog
                    "a": None, # valueTimelineList
                    "b": 0 # timelineListIndex
                },
                "c": "", # initialValue
                "d": None, # CompleteValue
                "e": False, # secureMode
                "f": False, # hideValueMode
                "i": "id" # inputFieldId
            }, {
                "a": ["0,d,TAB,"], # keyStrokeLog
                "b": { # inputIntervalLog
                    "a": ["0,"], # valueTimelineList
                    "b": 0 # timelineListIndex
                },
                "c": "", # initialValue
                "d": "", # CompleteValue
                "e": True, # secureMode
                "f": False, # hideValueMode
                "i": "pw" # inputFieldId
            }],
            "e": { # deviceOrientation
                "a": {  # firstOrientation
                    "a": 53.9, # Alpha
                    "b": 65.7, # Beta
                    "c": 4.8 # Gamma
                },
                "b": { # currentOrientation
                    "a": 53.9, # Alpha
                    "b": 65.7, # Beta
                    "c": 4.8 # Gamma
                }
            },
            "f": { # deviceMotion
                "a": { # first
                    "a": { # firstAcceleration
                        "a": 999, # X
                        "b": 999, # Y
                        "c": 999 # Z
                    },
                    "b": { # firstAccelerationIncludingGravity
                        "a": 999, # X
                        "b": 999, # Y
                        "c": 999 # Z
                    }
                },
                "b": { # current
                    "a": { # currentAcceleration
                        "a": 999, # X
                        "b": 999, # Y
                        "c": 999 # Z
                    },
                    "b": { # currentAccelerationIncludingGravity
                        "a": 999, # X
                        "b": 999, # Y
                        "c": 999 # Z
                    }
                }
            },
            "g": { # mouseMove
                "a": [], # mouseActiveLogs
                "b": 0, # timelineListIndex
                "c": 0, # pageXDifference
                "d": 0, # pageYDifference
                "e": -1, # totalInterval
                "f": 0 # errorCount
            },
            "h": "7e518ea5eb58651f6d4af5f24ef83781", # fingerprintHash
            "i": { # browserFingerprintComponents
                "a": self.headers["user-agent"],
                "b": "en",
                "c": 24,
                "d": 8,
                "e": 1,
                "f": 4,
                "g": [1680, 1050],
                "h": [1680, 1010],
                "i": -540,
                "j": 1,
                "k": 1,
                "l": 1,
                "z": 1,
                "m": "unknown",
                "n": "Win32",
                "o": "unknown",
                "aa": ["Chrome PDF Plugin::Portable Document Format::application/x-google-chrome-pdf~pdf", "Chrome PDF Viewer::::application/pdf~pdf", "Native Client::::application/x-nacl~,application/x-pnacl~"],
                "p": "bb84491f8f0ca552e32aa5b90b350297",
                "q": "ebed6372b259af3e658060d47d3aaadb",
                "r": "Google Inc. (Intel)~ANGLE (Intel, Intel(R) UHD Graphics 620 Direct3D11 vs_5_0 ps_5_0, D3D11-26.20.100.7324)",
                "s": False,
                "t": False,
                "u": False,
                "v": False,
                "w": False,
                "x": [0, False, False],
                "y": ["Arial", "Arial Black", "Arial Narrow", "Calibri", "Cambria", "Cambria Math", "Comic Sans MS", "Consolas", "Courier", "Courier New", "Georgia", "Helvetica", "Impact", "Lucida Console", "Lucida Sans Unicode", "Microsoft Sans Serif", "MS Gothic", "MS PGothic", "MS Sans Serif", "MS Serif", "Palatino Linotype", "Segoe Print", "Segoe Script", "Segoe UI", "Segoe UI Light", "Segoe UI Semibold", "Segoe UI Symbol", "Tahoma", "Times", "Times New Roman", "Trebuchet MS", "Verdana", "Wingdings"]
            },
            "j": 134 # fingerprintProcessingDuration
        }
     
    def form(self):
        return self.default
     
    def new_bvsd_data(self):
        return self.bvsd
     
    def new_bvsd_footprint(self):
        return self.state_footprint
     
    def encode_bvsd_data(bvsd_data):
        return LZString.compressToEncodedURIComponent(dumps(bvsd_data))
 
    def fill_bvsd(self, bvsd, id):
        bvsd_uuid = str(uuid4())+"-0" # 로그인 할때 사용되는 bvsd uuid 생성
        bvsd["uuid"] = bvsd_uuid
 
        bvsd_data = self.new_bvsd_footprint()
        bvsd_data["a"] = bvsd_uuid
        bvsd_data["d"][0]["b"]["a"] = ["0,{}".format(id)]
        bvsd_data["d"][0]["d"] = id
 
        bvsd["encData"] = Naver.encode_bvsd_data(bvsd_data)
 
        return bvsd
     
    def get_finalize(response_text: str):
        if response_text.find("location") > -1:
            return {
                "url": response_text.split('("')[1].split('")')[0],
                "result": True
            }
        else:
            return {
                "result": False
            }
 
    def login(self, NAVER_ID: str, NAVER_PW: str) -> bool:
        def download_keys():
            DOM = BeautifulSoup(self.get(self.base, params={
                "svctype": self.default["svctype"] # 모바일 페이지
            }, headers=self.headers).text, 'html.parser')
 
            Keys = DOM.find('input', {'id': "session_keys"}).attrs['value']
 
            session_key, key_name, e, N = Keys.split(",")
 
            return {
                "dynamic_key": DOM.find('input', {'id': 'dynamicKey'}).attrs['value'],
                "session_key": session_key,
                "public_key": PublicKey(int(e, 16), int(N, 16)),
                "key_name": key_name
            }
        
        def encrypt_with_public_key(ID: str, PW: str, Keys: dict) -> str:
            encode_login_info = ''.join([chr(len(s)) + s for s in [Keys['session_key'], ID, PW]]).encode()
 
            return encrypt(encode_login_info, Keys["public_key"]).hex() # 암호화하고 hex 값으로, e와 N값 순서는 네이버가 구라친겁니다.
         
        Keys = download_keys() # 서버의 공개키와 키 세션들을 가져온다.
 
        encrypted_info = encrypt_with_public_key(NAVER_ID, NAVER_PW, Keys) # 공개키를 이용하여 세션키와 함께 로그인 정보를 암호화한다.
        form = self.form() # 새로운 로그인 폼 생성
         
        bvsd = self.fill_bvsd(self.new_bvsd_data(), NAVER_ID) # 새로운 bvsd 폼 생성 후 내용 채우기s
 
        form["dynamicKey"] = Keys["dynamic_key"]
        form["encpw"] = encrypted_info
        form["encnm"] = Keys["key_name"]
        form["bvsd"] = dumps(bvsd)
 
        #result = Naver.get_finalize(self.post(self.base, data=form, headers=self.headers).text)
        cook = self.post((self.base), data=form, headers=(self.headers))

        result = Naver.get_finalize(cook.text)
        
        if result["result"] :
            self.get(result["url"], headers=self.headers)
            return [True, result['url'], cook.cookies]
        else :
            return False

id = 'id입력'
pw = 'pw입력'

if __name__ == "__main__":
    # TEST FIELD
    Browser = Naver()
    resultArr = Browser.login(id, pw)  
    
    if resultArr :
        cookiesText = get_cookies_session(resultArr[2])
        # 요청 URL
        blog_url = "blog_admin_url"
        
        # 헤더 정보 설정
        blog_headers = {'Content-Type': "'application/x-www-form-urlencoded'", 
             'Cookie': cookiesText, 
             'Sec-Ch-Ua-Mobile': "'?1'", 
             'User-Agent': "'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36'"
        }       
        # GET 요청 보내기
        response = requests.get(url=blog_url, headers=blog_headers)
        print("로그인성공")
        admin = BeautifulSoup(response.text, 'lxml')
        #print(admin)
    else :
        #print("로그인실패")

 

 

출처: 
https://minyeamer.github.io/blog/smartstore-login-2/#%EB%84%A4%EC%9D%B4%EB%B2%84-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%9D%B4%ED%95%B4
https://cafe.naver.com/nameyee/42171?art=ZXh0ZXJuYWwtc2VydmljZS1uYXZlci1zZWFyY2gtY2FmZS1wcg.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjYWZlVHlwZSI6IkNBRkVfVVJMIiwiY2FmZVVybCI6Im5hbWV5ZWUiLCJhcnRpY2xlSWQiOjQyMTcxLCJpc3N1ZWRBdCI6MTcwODQ4MzQyMjA5Mn0.ML0OrhdMF9Pdf3Nbz0yOb7yRLfzgzILJF3m1dQ3LTi0
https://blog.naver.com/nkj2001/222722322802
https://studyforus.com/tipnknowhow/830721
https://github.com/sh-cho/k-webtoon-crawler/blob/master/WebtoonScraper.py

반응형

댓글