From e6764a85bdc09a22dd83b6ebdd2cc8abe9e4c5bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=84=B1=EA=B2=BD?= Date: Thu, 9 Jul 2026 15:20:29 +0900 Subject: [PATCH] =?UTF-8?q?=EC=95=88=ED=8B=B0=EB=B4=87=20=EC=9A=B0?= =?UTF-8?q?=ED=9A=8C=EC=9A=A9=20=EB=B8=8C=EB=9D=BC=EC=9A=B0=EC=A0=80=20?= =?UTF-8?q?=ED=94=84=EB=A1=9C=ED=95=84=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/utils/nvMapPwScraper.py | 53 ++++++++++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 12 deletions(-) diff --git a/app/utils/nvMapPwScraper.py b/app/utils/nvMapPwScraper.py index 2b14814..fe843b6 100644 --- a/app/utils/nvMapPwScraper.py +++ b/app/utils/nvMapPwScraper.py @@ -1,4 +1,5 @@ import asyncio +import random import re from html import unescape from difflib import SequenceMatcher @@ -21,27 +22,55 @@ class NvMapPwScraper(): _max_retry = 3 _timeout = 30 # place id timeout threshold seconds _retry_delay_ms = 1500 # 검색 실패 후 재시도 전 대기시간 (네이버 요청 밀도 완화) - + + # UA·뷰포트·sec-ch-ua를 한 세트로 묶은 브라우저 프로필 후보군 (안티봇 핑거프린트 회피용) + _UA_PROFILES = [ + { + "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36", + "sec_ch_ua": '"Not?A_Brand";v="99", "Chromium";v="130", "Google Chrome";v="130"', + "viewport": {"width": 1280, "height": 720}, + }, + { + "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36", + "sec_ch_ua": '"Not?A_Brand";v="99", "Chromium";v="129", "Google Chrome";v="129"', + "viewport": {"width": 1366, "height": 768}, + }, + { + "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36", + "sec_ch_ua": '"Not?A_Brand";v="99", "Chromium";v="130", "Google Chrome";v="130"', + "viewport": {"width": 1440, "height": 900}, + }, + { + "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36", + "sec_ch_ua": '"Not?A_Brand";v="99", "Chromium";v="131", "Google Chrome";v="131"', + "viewport": {"width": 1536, "height": 864}, + }, + ] + _current_profile = None + # instance var page = None + @classmethod + def _pick_profile(cls): + """현재 프로필과 다른 프로필을 무작위로 선택한다 (후보가 1개뿐이면 그대로 반환).""" + candidates = [p for p in cls._UA_PROFILES if p is not cls._current_profile] + return random.choice(candidates or cls._UA_PROFILES) + @classmethod def default_context_builder(cls): + cls._current_profile = cls._pick_profile() + profile = cls._current_profile + context_builder_dict = {} - context_builder_dict['viewport'] = { - 'width' : cls._win_width, - 'height' : cls._win_height - } - context_builder_dict['screen'] = { - 'width' : cls._win_width, - 'height' : cls._win_height - } - context_builder_dict['user_agent'] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36" + context_builder_dict['viewport'] = dict(profile['viewport']) + context_builder_dict['screen'] = dict(profile['viewport']) + context_builder_dict['user_agent'] = profile['user_agent'] context_builder_dict['locale'] = 'ko-KR' context_builder_dict['timezone_id']='Asia/Seoul' return context_builder_dict - + @classmethod async def initiate_scraper(cls): if not cls._playwright: @@ -203,7 +232,7 @@ patchedGetter.apply(navigator); patchedGetter.toString();''') await self.page.set_extra_http_headers({ - 'sec-ch-ua': '\"Not?A_Brand\";v=\"99\", \"Chromium\";v=\"130\"' + 'sec-ch-ua': self._current_profile['sec_ch_ua'] }) await self.page.goto("http://google.com")