Target.com은 처음 보기엔 단순해 보여도, 막상 스크래핑을 해보면 전혀 다릅니다. Requests와 BeautifulSoup로 짧게 Python 스크립트를 짜서 Target 상품 페이지를 돌려봤는데 가격 필드가 None만 나왔던 경험이 있다면, 그건 당신만 겪은 일이 아닙니다.
저도 주요 리테일 사이트들을 여러 방식으로 직접 스크래핑해 봤는데, Target은 늘 손에 꼽을 정도로 까다로운 사이트였습니다. 규모의 이 사이트는 가격, 평점, 재고, 리뷰 같은 상품 데이터가 잔뜩 쌓여 있는 보물창고 같지만, React 기반 클라이언트 렌더링과 Akamai의 봇 탐지 조합 때문에 단순한 방법은 거의 바로 막힙니다. 그래도 실제로 먹히는 Python 방식은 3가지가 있습니다. 각 방법을 하나씩 살펴보고, 왜 처음 시도가 자꾸 실패하는지 짚어본 다음, Python이 너무 번거로울 때 쓸 수 있는 노코드 대안까지 소개하겠습니다.
Target.com 첫 스크래핑이 왜 자꾸 None을 반환할까
해결책보다 먼저, 문제부터 보겠습니다. 초보자들이 가장 흔히 쓰는 코드는 이런 형태입니다:
1import requests
2from bs4 import BeautifulSoup
3url = "https://www.target.com/p/some-product/-/A-12345678"
4response = requests.get(url, headers={"User-Agent": "Mozilla/5.0"})
5soup = BeautifulSoup(response.text, "html.parser")
6price = soup.select_one('[data-test="current-price"]')
7print(price) # None
출력 결과는? None입니다. 거의 언제나 그렇습니다.
이건 코드가 잘못된 게 아닙니다. Target에서 requests.get()이 받아오는 HTML은 사실상 껍데기만 있는 셸에 가깝습니다. “실제 페이지를 렌더링하려면 이 JavaScript를 불러와”라고 말하는 React 프레임 같다고 보면 됩니다. 상품 가격, 평점, 리뷰, 재고 여부는 모두 초기 페이지 로드 후 JavaScript로 주입됩니다. 그런데 Python의 Requests는 JavaScript를 실행하지 않으니, 응답 안에 그런 요소가 아예 없는 겁니다.
포럼을 보면 같은 벽에 막힌 개발자 글이 정말 많습니다. 은 이걸 아주 직설적으로 설명합니다. “요소가 None으로 보이는 이유는 JavaScript로 렌더링되는데 requests는 JavaScript로 렌더링된 HTML을 가져올 수 없기 때문이다.” 도 비슷하게 말합니다. “Target URL에 HTTP 요청을 보내면 HTML 응답에는 의미 있는 데이터가 없다.”
그리고 설령 JavaScript 문제를 해결하더라도, Target에는 한 겹 더 있습니다. Akamai의 봇 탐지가 TLS 핸드셰이크 지문을 확인해서, HTML이 오기도 전에 Python requests를 바로 잡아냅니다. 이 부분은 조금 뒤에서 더 자세히 보겠습니다.
왜 Target.com은 Python으로 스크래핑하기 그렇게 어려운가
Target은 단순히 “JavaScript를 쓰는 웹사이트”가 아닙니다. 여러 겹의 방어 체계를 가진 사이트고, 어떤 층을 상대하는지 이해해야 제대로 된 스크래핑 방법을 고를 수 있습니다.
JavaScript로 렌더링되는 상품 데이터
Target.com은 React 위에 구축되어 있습니다. 실제 브라우저에서 상품 페이지나 검색 페이지를 열면 이런 순서로 돌아갑니다.
- 서버가 최소한의 HTML 셸을 보냄
- JavaScript 번들이 로드되고 실행됨
- 프런트엔드가 Target 내부 Redsky API를 호출함
- 상품 데이터(가격, 평점, 이미지, 재고)가 DOM에 렌더링됨
2~4단계를 건너뛰면 — 바로 requests.get()이 하는 일이죠 — 빈 페이지를 받게 됩니다. 에 따르면, 정적 HTTP 요청으로 Target에서 얻을 수 있는 데이터는 약 수준입니다. 나머지 70%는 JavaScript 실행이나 API 접근이 필요합니다.
검색 결과 페이지는 더 심합니다. 초기 HTML에는 일부 상품만 보이고, 나머지는 스크롤할 때마다 로드됩니다.
Target의 안티봇 방어: 흔한 “프록시만 쓰면 됨” 조언을 넘어서
대부분의 스크래핑 가이드는 안티봇 대응을 “그냥 프록시를 쓰세요” 정도로 끝내지만, Target의 방어는 그보다 훨씬 더 구체적으로 봐야 합니다.
TLS 핑거프린팅(가장 중요). HTTPS 핸드셰이크 동안 클라이언트는 “Client Hello” 패킷을 보내는데, 여기에 TLS 버전, 암호 스위트, 확장, 타원곡선 정보가 들어 있습니다. 이 정보는 JA3 지문으로 해시됩니다. Python requests는 인 8d9f7747675e24454cd9b7ed35c58707를 만들어 내기 때문에, 안티봇 DB에서 바로 걸립니다. Chrome은 GREASE 값을 포함한 16개의 암호 스위트를 정교한 순서로 보내지만, Python은 브라우저와 다른 순서로 60개 이상을 보냅니다. 즉, HTTP 내용이 오기도 전에 막히는 겁니다.
IP 평판 점수. Akamai는 IP를 신뢰 등급별로 분류합니다. 대로, 데이터센터 IP는 “봇에 사용될 가능성이 높기 때문에 상당한 음수 신뢰 점수”를 받습니다. 반면 Residential IP는 긍정 점수를 받습니다. 특히 Target에서는 데이터센터 IP 대역이 거의 즉시 눈에 띕니다.
JavaScript 핑거프린팅. Akamai는 JavaScript를 주입해서 JS 엔진 사양, 하드웨어 성능, OS 정보, 폰트, 플러그인, 행동 데이터(타이핑 속도, 마우스 이동, 클릭 타이밍)를 수집합니다. 이 결과로 _abck 쿠키가 생성되는데, 이 상태 기반 지문 토큰이 없으면 요청이 차단됩니다.
속도 제한. Target은 IP당 분당 대략 30~60회 요청 수준에서 429 오류를 유발합니다. 일부 사용자는 실제로 “Pardon Our Interruption” 차단 페이지가 들어 있는 을 받았다고 보고하기도 하는데, 자동 감지를 더 까다롭게 만듭니다.
으로 평가합니다. 특히 Akamai 우회 난이도는 수준입니다.
Python으로 Target.com을 스크래핑하는 3가지 방법 비교
세 가지 실전 방법을 한 번에 비교해 보는 글은 흔치 않습니다. 아래에서 솔직하게 평가해 보겠습니다.
| 기준 | Requests + BS4 | Selenium / Playwright | Redsky API |
|---|---|---|---|
| JS 렌더링 처리 | ❌ 아니오 | ✅ 예 | ✅ 예(JSON) |
| 상품 1개당 속도 | ⚡ 약 0.5–1초 | 🐢 약 5–10초 | ⚡ 약 0.5–1초 |
| 안티봇 리스크 | ⚠️ 높음(TLS 지문) | ⚠️ 중간 | ⚠️ 중간(인증 키 변경 가능) |
| 초기 설정 난이도 | 낮음 | 중간 | 중간~높음(역공학 필요) |
| 데이터 완성도 | 약 30%(정적 HTML만) | 약 95%(전체 페이지) | 약 90%(구조화 JSON) |
| 적합한 용도 | 정적 메타데이터, __TGT_DATA__ | 전체 상품 페이지, 리뷰 | 대량 상품 데이터 수집 |
이제 하나씩 구현해 보겠습니다.
방법 1: Python Requests와 BeautifulSoup로 Target.com 스크래핑하기
이 방법으로는 검색 페이지의 JavaScript 렌더링 가격을 가져올 수는 없습니다. 하지만 가볍고 빠르며, 어디를 봐야 하는지 알면 생각보다 많은 정보를 얻을 수 있습니다.
핵심은 Target이 일부 상품 데이터를 <script> 태그 안에 __PRELOADED_QUERIES__를 포함한 __TGT_DATA__ 변수로 심어 둔다는 점입니다. 이 JSON 블롭에는 상품명, 설명, 특징, 그리고 경우에 따라 개별 상품 페이지의 가격도 들어 있습니다. 검색 결과 HTML에서 상품 제목과 URL도 뽑아낼 수 있습니다.
1단계: Python 환경 준비하기
프로젝트 폴더를 만들고 의존성을 설치합니다:
1mkdir target-scraper && cd target-scraper
2python -m venv venv
3source venv/bin/activate # Windows: venv\Scripts\activate
4pip install requests beautifulsoup4 curl_cffi
여기서는 표준 requests보다 curl_cffi를 쓰는 편이 좋습니다. 브라우저의 TLS 지문을 흉내 내기 때문에 Target에서 차단을 피하는 데 가장 큰 도움이 됩니다. 에 따르면 curl_cffi는 을 보이는 반면, 표준 requests는 에 불과합니다. 무려 15배 차이입니다.
2단계: Target 검색 결과 스크래핑하기
Target 검색 URL 형식은 단순합니다: https://www.target.com/s?searchTerm={keyword}
1from curl_cffi import requests as cureq
2from bs4 import BeautifulSoup
3import time, random
4headers = {
5 "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
6 "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
7 "Accept-Language": "en-US,en;q=0.9",
8}
9url = "https://www.target.com/s?searchTerm=bluetooth+headphones"
10resp = cureq.get(url, headers=headers, impersonate="chrome124")
11soup = BeautifulSoup(resp.text, "html.parser")
12# 상품 카드에 이 data-test 속성이 사용됩니다
13cards = soup.find_all("div", {"data-test": "@web/site-top-of-funnel/ProductCardWrapper"})
14for card in cards:
15 link_tag = card.find("a")
16 title = link_tag.get_text(strip=True) if link_tag else "N/A"
17 href = "https://www.target.com" + link_tag["href"] if link_tag and link_tag.get("href") else "N/A"
18 print(f"{title} — {href}")
이렇게 하면 상품명과 URL은 얻을 수 있습니다. 가격은요? 이 HTML에서는 아마 못 얻습니다. 그게 정상입니다.
3단계: 상품 페이지에서 내장 JSON 데이터 추출하기
개별 상품 페이지는 __TGT_DATA__ 스크립트 태그 안에 더 풍부한 데이터를 넣어 둡니다:
1import re, json
2product_url = "https://www.target.com/p/some-product/-/A-12345678"
3resp = cureq.get(product_url, headers=headers, impersonate="chrome124")
4soup = BeautifulSoup(resp.text, "html.parser")
5# __TGT_DATA__ 스크립트를 찾기
6scripts = soup.find_all("script")
7for script in scripts:
8 if script.string and "__TGT_DATA__" in script.string:
9 # 스크립트 내용에서 JSON 추출
10 match = re.search(r'__TGT_DATA__\s*=\s*({.*?});?\s*$', script.string, re.DOTALL)
11 if match:
12 tgt_data = json.loads(match.group(1))
13 # 상품 상세 정보가 들어 있는 JSON 구조 탐색
14 queries = tgt_data.get("__PRELOADED_QUERIES__", {})
15 # 상품 데이터는 중첩 구조 안에 있음 — 페이지마다 구조가 다를 수 있음
16 print(json.dumps(queries, indent=2)[:500]) # 구조 미리보기
__TGT_DATA__ 안의 JSON 구조에는 상품명, 설명, 특징, 그리고 종종 가격 정보가 들어 있습니다. 정확한 중첩 구조는 페이지마다 달라서, 출력 결과를 확인하면서 원하는 항목까지 내려가야 합니다.
4단계: 페이지네이션 처리하기
Target 검색 페이지네이션은 Nao 파라미터를 사용합니다. 1페이지는 Nao=0, 2페이지는 Nao=24, 3페이지는 Nao=48 식으로 24씩 증가합니다.
1for page in range(0, 120, 24): # 처음 5페이지
2 paginated_url = f"https://www.target.com/s?searchTerm=bluetooth+headphones&Nao={page}"
3 resp = cureq.get(paginated_url, headers=headers, impersonate="chrome124")
4 # 파싱 및 추출...
5 time.sleep(random.uniform(2, 5)) # 예의 있게 요청
5단계: 스크래핑한 데이터 저장하기
1import csv
2with open("target_products.csv", "w", newline="", encoding="utf-8") as f:
3 writer = csv.DictWriter(f, fieldnames=["title", "url", "price", "description"])
4 writer.writeheader()
5 for product in products:
6 writer.writerow(product)
얻을 수 있는 것: 상품 제목, URL, 설명, 내장 메타데이터. 안정적으로 얻기 어려운 것: 검색 결과 페이지의 동적 가격과 평점. 이런 정보가 필요하면 방법 2 또는 3이 필요합니다.
방법 2: Selenium 또는 Playwright로 Target.com 스크래핑하기
헤드리스 브라우저는 JavaScript를 렌더링하고, 동적 콘텐츠를 불러오고, 실제 사용자 행동도 흉내 냅니다. 가격, 평점, 리뷰를 얻고 싶다면 이 방법이 맞습니다.
Selenium과 Playwright 중 무엇이 좋냐는 질문에는 되어 있다는 점부터 봐야 합니다. 2026년 기준 이며, 벤치마크상 (20페이지 기준 11초 vs 28초). 여기서는 커뮤니티가 더 크고 튜토리얼도 많은 Selenium으로 설명하겠지만, 새로 시작한다면 Playwright가 더 좋은 선택입니다.
1단계: Selenium과 ChromeDriver 설치하기
1pip install selenium webdriver-manager
webdriver-manager가 ChromeDriver 버전을 자동으로 맞춰 주기 때문에, 이제 “ChromeDriver 버전이 안 맞는다”는 문제로 골치 아플 일이 줄어듭니다:
1from selenium import webdriver
2from selenium.webdriver.chrome.service import Service
3from selenium.webdriver.chrome.options import Options
4from webdriver_manager.chrome import ChromeDriverManager
5options = Options()
6options.add_argument("--headless=new")
7options.add_argument("--window-size=1920,1080")
8options.add_argument("--disable-blink-features=AutomationControlled")
9options.add_argument("--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36")
10driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options)
2단계: Target 페이지를 열고 콘텐츠가 로드될 때까지 기다리기
1from selenium.webdriver.common.by import By
2from selenium.webdriver.support.ui import WebDriverWait
3from selenium.webdriver.support import expected_conditions as EC
4driver.get("https://www.target.com/s?searchTerm=bluetooth+headphones")
5# 상품 카드가 렌더링될 때까지 대기 (time.sleep보다 명시적 wait가 더 낫다)
6WebDriverWait(driver, 15).until(
7 EC.presence_of_element_located((By.CSS_SELECTOR, '[data-test="product-title"]'))
8)
명시적 대기는 정말 중요합니다. time.sleep(10)은 빠르게 로딩될 때는 시간을 낭비하고, 느리게 로딩될 때는 부족합니다. 둘 다 별로입니다. WebDriverWait는 요소가 나타나거나 타임아웃이 날 때까지 500ms 간격으로 확인합니다.
3단계: 페이지를 스크롤해 모든 상품 불러오기
Target은 스크롤할 때마다 상품을 지연 로딩합니다. 스크롤하지 않으면 전체가 아니라 4~5개 상품만 보일 수도 있습니다:
1import time
2last_height = driver.execute_script("return document.body.scrollHeight")
3for _ in range(10):
4 driver.execute_script("window.scrollBy(0, 300);")
5 time.sleep(1.5)
6 new_height = driver.execute_script("return document.body.scrollHeight")
7 if new_height == last_height:
8 break
9 last_height = new_height
에 따르면 1.5초 간격으로 10번 스크롤하면, 스크롤 없이 볼 때의 4~5개 대신 8개 이상의 상품을 얻을 수 있습니다. 각 스크롤은 사람처럼 보이게 200~300px 정도가 적당합니다.
4단계: 렌더링된 페이지에서 상품 데이터 추출하기
1products = []
2cards = driver.find_elements(By.CSS_SELECTOR, '[data-test="@web/site-top-of-funnel/ProductCardWrapper"]')
3for card in cards:
4 try:
5 title = card.find_element(By.CSS_SELECTOR, '[data-test="product-title"]').text
6 except:
7 title = "N/A"
8 try:
9 price = card.find_element(By.CSS_SELECTOR, '[data-test="current-price"]').text
10 except:
11 price = "N/A"
12 try:
13 link = card.find_element(By.CSS_SELECTOR, 'a[href*="/p/"]').get_attribute("href")
14 except:
15 link = "N/A"
16 products.append({"title": title, "price": price, "link": link})
17for p in products:
18 print(f'{p["title"]} — {p["price"]}')
Target에서 자주 쓰이는 주요 data-test 선택자는 다음과 같습니다(2026년 기준 확인):
| 데이터 필드 | 선택자 |
|---|---|
| 상품 카드 | data-test="@web/site-top-of-funnel/ProductCardWrapper" |
| 상품 제목 | data-test="product-title" |
| 현재 가격 | data-test="current-price" |
| 평점 값 | data-test="rating-value" |
| 평점 수 | data-test="rating-count" |
5단계: 상품 리뷰 스크래핑하기(보너스)
개별 상품 페이지로 들어가 리뷰 섹션까지 스크롤한 다음 리뷰 데이터를 추출할 수 있습니다:
1from bs4 import BeautifulSoup
2driver.get("https://www.target.com/p/some-product/-/A-12345678")
3# 아래로 스크롤해 리뷰 로드
4for _ in range(5):
5 driver.execute_script("window.scrollBy(0, 500);")
6 time.sleep(2)
7soup = BeautifulSoup(driver.page_source, "html.parser")
8reviews = soup.find_all("div", {"data-test": "review-card--text"})
9for review in reviews:
10 print(review.get_text(strip=True)[:100])
리뷰는 Bazaarvoice 연동으로 불러오며, 페이지네이션(최대 51페이지), 최신순 정렬, 사진만 보기 필터를 지원합니다. 기준으로 Selenium은 상품당 약 5.1초 정도 걸립니다.
작업이 끝나면 브라우저를 닫는 것도 잊지 마세요:
1driver.quit()
방법 3: Redsky API로 Target.com 스크래핑하기
Target 프런트엔드는 redsky.target.com의 내부 API에서 모든 데이터를 가져옵니다. 이 API를 Python으로 직접 호출하면 HTML 파싱도, 브라우저도, JavaScript 렌더링도 필요 없습니다. 응답은 깔끔한 JSON이고, 가격, 평점, 리뷰, 이미지, 재고, 배송/픽업, 사양, 변형 정보까지 40개 이상의 필드를 포함합니다. 대량 상품 데이터를 다룬다면 이 방법이 압도적으로 가장 빠르고 안정적입니다.
1단계: Chrome DevTools로 Redsky API 찾기
대부분의 튜토리얼은 이 과정을 건너뛰지만, 직접 찾는 방법은 어렵지 않습니다.
- Chrome에서 Target 상품 페이지를 엽니다
- DevTools(F12) → Network 탭을 엽니다
- Fetch/XHR로 필터링합니다
- 페이지를 새로고침합니다
redsky.target.com또는redsky.a]target.com요청을 찾습니다- 하나를 클릭해서 Request URL과 Headers를 확인합니다
대략 이런 URL이 보일 겁니다:
1https://redsky.target.com/redsky_aggregations/v1/web/pdp_fulfillment_v1?key=9f36aeafbe60771e321a7cc95a78140772ab3e96&tcin=12345678&store_id=2148&zip=55401
핵심 파라미터는 다음과 같습니다.
key— API 키(고정되어 있지만 엔드포인트마다 다름)tcin— Target.com 상품 번호(8자리 상품 ID)store_id— Target 매장 위치zip— 배송/픽업 정보용 ZIP 코드
요청 헤더에서 API 키를 확인할 수 있고, URL 쿼리 파라미터로도 들어 있습니다.
2단계: Python으로 Redsky API 직접 요청하기
1from curl_cffi import requests as cureq
2import json
3API_KEY = "9f36aeafbe60771e321a7cc95a78140772ab3e96" # DevTools에서 추출
4TCIN = "12345678"
5url = f"https://redsky.target.com/redsky_aggregations/v1/web/pdp_fulfillment_v1?key={API_KEY}&tcin={TCIN}&store_id=2148&zip=55401"
6headers = {
7 "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
8 "Accept": "application/json",
9 "Origin": "https://www.target.com",
10 "Referer": "https://www.target.com/",
11 "Sec-Fetch-Site": "same-site",
12 "Sec-Fetch-Mode": "cors",
13 "Sec-Fetch-Dest": "empty",
14}
15resp = cureq.get(url, headers=headers, impersonate="chrome124")
16data = resp.json()
17# JSON 응답에서 상품 세부 정보 추출
18product = data.get("data", {}).get("product", {})
19title = product.get("item", {}).get("product_description", {}).get("title", "N/A")
20price = product.get("price", {}).get("formatted_current_price", "N/A")
21rating = product.get("ratings_and_reviews", {}).get("statistics", {}).get("rating", {}).get("average", "N/A")
22print(f"{title} — {price} — 평점: {rating}")
HTML 파싱이 전혀 필요 없습니다. 응답은 구조화되어 있고, 깔끔하고, 빠릅니다.
3단계: API로 상품 검색 결과 스크래핑하기
product_summary_with_fulfillment_v1 엔드포인트는 여러 TCIN을 한 번에 받을 수 있습니다:
1tcins = ["12345678", "23456789", "34567890"]
2tcin_str = ",".join(tcins)
3search_url = f"https://redsky.target.com/redsky_aggregations/v1/web/product_summary_with_fulfillment_v1?key={API_KEY}&tcins={tcin_str}&store_id=2148&zip=55401"
4resp = cureq.get(search_url, headers=headers, impersonate="chrome124")
5results = resp.json()
6for item in results.get("data", {}).get("product_summaries", []):
7 title = item.get("title", "N/A")
8 price = item.get("price", {}).get("formatted_current_price", "N/A")
9 print(f"{title} — {price}")
TCIN을 얻는 방법은 두 가지입니다. 검색 결과 HTML에서 상품 URL의 /A-XXXXXXXX 부분을 뽑아내거나, __TGT_DATA__에 포함된 JSON에서 가져오면 됩니다.
4단계: 동시 요청으로 규모 확장하기
1from concurrent.futures import ThreadPoolExecutor
2import time, random
3def fetch_product(tcin):
4 url = f"https://redsky.target.com/redsky_aggregations/v1/web/pdp_fulfillment_v1?key={API_KEY}&tcin={tcin}&store_id=2148&zip=55401"
5 time.sleep(random.uniform(2, 5))
6 resp = cureq.get(url, headers=headers, impersonate="chrome124")
7 return resp.json()
8tcin_list = ["12345678", "23456789", "34567890", "45678901"]
9with ThreadPoolExecutor(max_workers=3) as executor:
10 results = list(executor.map(fetch_product, tcin_list))
동시성은 너무 세게 잡지 마세요. 3~5개 스레드에 2~5초 랜덤 지연 정도면 충분합니다. Target의 속도 제한은 IP당 수준입니다.
Redsky API를 사용할 때 알아둘 중요한 주의사항
실무 파이프라인에 바로 넣기 전에, 몇 가지는 꼭 알고 있어야 합니다.
- API 키는 고정되어 있지만 엔드포인트별로 다릅니다. Redsky의 각 엔드포인트는 서로 다른 키를 사용합니다. 자주 바뀌지는 않지만, Target이 언제든 바꿀 수는 있습니다.
- 문서화되지 않은 내부 API입니다. 다만 Target 엔지니어링 팀이 고 밝혀 법적 리스크는 조금 줄어듭니다. 그래도 공식 SLA가 있는 공개 API는 아닙니다.
- 상품 변형(색상, 사이즈)은 각각 별도 TCIN을 가집니다. 변형마다 따로 조회해야 합니다.
Sec-Fetch-*헤더가 빠지면 바로 차단될 수 있습니다. 정말 흔한 실수입니다. 항상Sec-Fetch-Site,Sec-Fetch-Mode,Sec-Fetch-Dest를 넣어 주세요.
차단당하지 않고 Target.com을 대량 스크래핑하는 팁
아래 방법들은 어떤 방식이든 프로덕션 규모에서 유효합니다.
Residential 프록시를 쓰고, 데이터센터 프록시는 피하기
Target의 Akamai 구현은 데이터센터 IP 대역을 한눈에 알아봅니다. 지속적인 스크래핑에는 Residential 프록시가 사실상 필수입니다. 가격은 천차만별이지만 , 시작하며, 대량 사용 시 $3~4/GB 수준까지 내려갑니다.
가능하면 50~100 요청마다 IP를 바꾸거나, 프록시 풀에서 허용하면 매 요청마다 회전하세요.
curl_cffi로 TLS 지문을 위장하기
이건 가장 효과가 큰 단일 변경입니다. requests의 드롭인 대체재로 쓸 수 있습니다:
1from curl_cffi import requests as cureq
2# 표준 requests — 보호된 사이트에서 성공률 약 12%
3# resp = requests.get(url, headers=headers)
4# curl_cffi — 성공률 약 92%
5resp = cureq.get(url, headers=headers, impersonate="chrome124")
는 GitHub 스타 8,200개 이상을 보유하고 있고, chrome99부터 chrome146까지의 Chrome 버전과 Safari, Edge, 모바일 변형까지 지원합니다. 동기 모드에서는 .
현실적인 요청 속도와 헤더를 유지하기
- 랜덤 지연: 요청 사이 2~7초(고정 간격은 피하고, 무작위성이 중요)
- User-Agent 회전: 실제 브라우저 UA 문자열 5~10개를 유지하며 번갈아 사용
- 세션 워밍업: 상품 페이지 전에
target.com홈으로 먼저 들어가 쿠키를 형성 - 헤더 일관성:
Sec-Ch-Ua는 UA에 적힌 브라우저 버전과 맞아야 하고,Sec-Ch-Ua-Platform은 운영체제와 일치해야 합니다. 불일치는 금방 들킵니다. - 세션 유지: 세션 안에서는 쿠키를 계속 유지하세요. 회전형 Residential 프록시와 함께 48시간 세션 안정성을 권장합니다.
코드 없이 Target.com을 스크래핑하려면: Thunderbit 노코드 대안
Target.com은 정말로 프로그래밍 방식으로 스크래핑하기 까다로운 리테일 사이트 중 하나입니다. JavaScript 렌더링, Akamai의 TLS 핑거프린팅, 데이터센터 프록시 탐지, ChromeDriver 버전 문제까지 — 챙길 게 많습니다. Python 공부용으로는 꽤 좋은 연습이지만, 실제 업무용으로 Target 상품 데이터가 필요하다면 비용 대비 효용이 맞지 않을 수 있습니다.
코드 작성 없이 데이터를 얻고 싶은 분이라면 이 까다로운 부분을 알아서 처리해 줍니다.
Thunderbit가 Target.com의 난제를 처리하는 방식
Thunderbit의 AI 웹 스크래퍼는 브라우저 안에서 돌아가므로 JavaScript를 자연스럽게 렌더링합니다. Selenium 설정도 필요 없고, 헤드리스 브라우저 구성도 필요 없고, ChromeDriver 버전 맞추기도 없습니다. 브라우저 자체가 스크래퍼 역할을 합니다.
작업 흐름은 다음과 같습니다.
- 을 설치하고 Target 상품 페이지나 검색 페이지로 이동합니다
- **“AI 필드 추천”**을 클릭합니다 — Thunderbit가 페이지를 읽고 열 이름(Product Title, Price, Rating, Image URL 등)을 제안합니다
- **“스크래핑”**을 클릭합니다 — 렌더링된 페이지에서 바로 몇 초 만에 데이터가 추출됩니다
프록시 설정도 필요 없고, TLS 지문 위장도 필요 없고, None 결과도 없습니다.
Target 상품 목록과 상세 페이지를 함께 스크래핑하기
멀티페이지 워크플로우에서 차이가 더 뚜렷하게 드러납니다. Target 검색 결과 페이지에서 상품 목록을 먼저 모은 뒤, 서브페이지 스크래핑으로 각 상품 URL을 자동 방문하게 하고 상세 페이지의 설명, 전체 리뷰, 사양까지 표에 채워 넣을 수 있습니다. 페이지네이션 코드를 따로 짤 필요도 없고, 브라우저 세션을 직접 관리할 필요도 없습니다.
결과는 Excel, Google Sheets, Airtable, Notion으로 바로 내보낼 수 있습니다. csv.writer 보일러플레이트도 없고, 파일 인코딩 문제도 없습니다.
반복적인 Target.com 스크래핑 자동화하기
지속적인 가격 모니터링이나 재고 추적이 필요하다면 Thunderbit의 Scheduled Scraper가 자연어로 일정을 받습니다. 예를 들어 “매주 월요일 오전 9시”처럼 지정하면 됩니다. cron 작업도, 서버 세팅도, VPS에서 Python 스크립트를 계속 돌려둘 필요도 없습니다. 특히 이 필요한 이커머스 팀에 유용합니다. 가 이미 자동 가격 스크래핑을 쓰고 있고, 가격 인텔리전스의 평균 ROI는 에 이릅니다.
Target.com 스크래핑에 어떤 방법을 언제 써야 할까
간단한 의사결정 기준은 다음과 같습니다.
| 상황 | 추천 방법 |
|---|---|
| Python을 배우는 중이고, 작은 프로젝트를 한다 | 방법 1: Requests + BS4(정적 데이터 및 __TGT_DATA__용) |
| 가격과 리뷰가 포함된 전체 상품 페이지가 필요하다 | 방법 2: Selenium / Playwright |
| 대량 상품 데이터를 한 번에 추출해야 한다 | 방법 3: Redsky API |
| 코딩 없이 빠르게 데이터가 필요하다 | Thunderbit (노코드) |
| 반복적인 가격 모니터링이 필요하다 | Thunderbit Scheduled Scraper 또는 Redsky API + cron |
| 일회성 리서치, 비기술 팀이다 | Thunderbit — 솔직히 가장 빠른 길 |
프로덕션용 데이터 파이프라인을 만든다면, 방법 3(Redsky API)이 속도와 안정성 면에서 가장 좋습니다. 일회성 리서치이거나 팀에 Python 경험이 없다면 Thunderbit가 시간을 훨씬 아껴 줍니다. 웹 스크래핑을 배우는 단계라면 방법 1 → 방법 2 → 방법 3 순으로 익히는 게 각 단계마다 실제로 배울 점이 있어서 흐름도 좋습니다.
Target.com 스크래핑 시 알아둘 법적·윤리적 고려사항
간단히 짚고 넘어가겠습니다. Target의 robots.txt에는 약 120개 이상의 Disallow 경로가 있지만, /p/(상품)나 /c/(카테고리)는 차단하지 않습니다. 즉 상품 및 카테고리 페이지는 크롤링이 명시적으로 허용됩니다. 다만 장바구니, 계정, 결제 페이지는 제한됩니다.
그렇지만 Target의 이용약관은 자동화된 접근을 금지합니다. 그럼에도 Redsky API가 는 점(Target 엔지니어링 확인)은 API 기반 수집의 법적 리스크를 조금 낮춰 줍니다.
참고할 만한 주요 판례는 다음과 같습니다.
- (제9순회항소법원, 2022): 공개 데이터 스크래핑은 CFAA 위반이 아님
- (2024): Meta 패소 — 공개 데이터 스크래핑에 CFAA 위반이 없다고 판단
대규모 상업 스크래핑을 계획한다면 법률 자문을 받으세요. 공개 데이터를 활용한 시장 조사, 가격 비교, 개인 프로젝트라면 비교적 안정적인 법적 근거 위에 있습니다. 다만 항상 속도 제한을 지키고 Target 서버에 과부하를 주지 마세요.
결론 및 핵심 요약
Target.com이 까다로운 사이트로 평가받는 데는 이유가 있습니다. 단순한 Requests + BeautifulSoup 방식이 실패하는 이유는 Target이 상품 데이터를 JavaScript로 렌더링하고, 응답이 오기도 전에 Akamai가 TLS 핸드셰이크를 지문 채취하기 때문입니다. 하지만 올바른 방법을 쓰면 수집 자체는 어렵지 않습니다.
신뢰도 기준으로 정리한 3가지 방법:
- Redsky API — 대량 데이터에 가장 빠르고 안정적이며, 깔끔한 JSON을 돌려줍니다. 다만 DevTools로 API 엔드포인트를 역공학해야 합니다.
- Selenium / Playwright — JavaScript 렌더링을 처리해 페이지의 모든 요소를 가져올 수 있습니다. 느리지만 포괄적입니다.
- Requests + BeautifulSoup — 정적 HTML과 내장된
__TGT_DATA__JSON까지만 가능합니다. 빠르지만 완전하지는 않습니다.
가장 큰 기술적 이득:
- 표준
requests대신curl_cffi를 쓰면 안티봇 우회 성공률이 됩니다 - Residential 프록시는 필수입니다. 데이터센터 IP는 바로 표시됩니다
- 모든 요청에
Sec-Fetch-*헤더를 포함하세요. 빠지면 바로 막힐 수 있습니다 - 세션 워밍업(먼저 홈페이지 방문)은 성공률을 크게 올립니다
그리고 Python이 이 용도에서 너무 번거롭다면, 이 JavaScript 렌더링, 안티봇 대응, 데이터 내보내기를 알아서 처리합니다. 을 써보고, 몇 시간이 아니라 몇 분 만에 필요한 데이터를 가져올 수 있는지 확인해 보세요.
더 많은 스크래핑 가이드와 데이터 추출 팁은 와 에서 확인할 수 있습니다.
자주 묻는 질문
Python Requests와 BeautifulSoup만으로 Target.com을 스크래핑할 수 있나요?
부분적으로는 가능합니다. 상품 페이지의 __TGT_DATA__ 스크립트 태그에서 상품 제목, URL, 일부 내장 JSON 데이터를 추출할 수 있습니다. 하지만 검색 결과 페이지의 가격, 평점, 리뷰, 재고 정보는 JavaScript로 렌더링되므로 정적 HTTP 요청으로는 보이지 않습니다. 완전한 데이터를 원한다면 Selenium/Playwright 또는 Redsky API를 사용하세요.
왜 Target.com 스크래퍼가 가격을 None으로 반환하나요?
Target은 초기 페이지 로드 후 JavaScript로 가격 데이터를 불러옵니다. requests.get()을 사용하면 JavaScript가 실행되기 전의 사전 렌더링 HTML 셸만 받게 됩니다. 즉 가격 요소 자체가 응답에 존재하지 않는 거죠. JavaScript를 렌더링하는 헤드리스 브라우저(Selenium 또는 Playwright)를 사용하거나, Redsky API를 직접 호출해 JSON 데이터를 받거나, 렌더링된 브라우저 페이지에서 스크래핑하는 같은 도구를 사용하세요.
Target.com을 스크래핑하는 것은 합법인가요?
현재 미국 판례(hiQ v. LinkedIn, Meta v. Bright Data)에 따르면 공개 데이터 스크래핑은 일반적으로 허용됩니다. Target의 robots.txt도 상품 및 카테고리 페이지 크롤링을 허용합니다. 다만 Target의 이용약관은 자동 접근을 금지하므로 회색지대는 있습니다. 시장 조사나 가격 비교처럼 공개 데이터를 활용하는 목적이라면 비교적 무난한 법적 위치에 있습니다. 대규모 상업 운영이라면 변호사와 상의하세요.
Target의 Redsky API는 무엇이고 어떻게 접근하나요?
Redsky는 Target 프런트엔드가 상품 데이터를 불러올 때 쓰는 내부 API입니다. 문서가 공개된 공용 API나, 가입 후 키를 발급받는 형태의 API는 아닙니다. React 앱이 상품 페이지를 렌더링할 때 호출하는 백엔드라고 보면 됩니다. Chrome DevTools를 열고 Network 탭에서 XHR/Fetch로 필터링한 뒤 redsky.target.com 요청을 찾으면 엔드포인트를 확인할 수 있습니다. API 키는 요청 URL의 쿼리 파라미터로 들어 있습니다. Target 엔지니어링 팀도 이 API가 의도적으로 외부 접근 가능하다고 확인했습니다.
Target.com에서 차단을 피하려면 어떻게 해야 하나요?
가장 효과가 큰 한 가지는 표준 Python requests 대신 curl_cffi를 사용해 브라우저 TLS 지문을 위장하는 것입니다. 이것만으로도 성공률이 로 올라갑니다. 그 외에도 Residential 프록시 사용(데이터센터는 피하기), User-Agent 회전, 요청 사이 2~7초 랜덤 지연 추가, 모든 Sec-Fetch-* 헤더 포함, 그리고 먼저 홈페이지에 접속해 세션을 워밍업하는 방법이 있습니다. 또는 처럼 안티봇 대응을 자동으로 처리하는 도구를 쓰는 것도 좋은 선택입니다.
더 알아보기