2026년에 Python으로 Target.com 크롤링하기: 실제로 통하는 3가지 방법

최종 업데이트: June 3, 2026
2026년에 Python으로 Target.com 크롤링하기: 실제로 통하는 3가지 방법

Target.com은 겉보기엔 단순해서 긁기도 쉬워 보여요. 막상 해 보기 전까지만요. Requests와 BeautifulSoup로 짧은 Python 스크립트를 짜서 Target 상품 페이지에 던졌더니 가격 필드가 None으로 돌아온 경험이 있다면, 흔하디흔한 일이에요.

주요 리테일 사이트 대부분에 크롤링 방식을 시험해 본 결과, Target은 늘 손에 꼽히게 까다로운 축이었어요. 월간 방문자 2억 800만~2억 8천만 명이 드나드는 만큼 상품 데이터, 가격, 평점, 재고, 리뷰가 가득한 보물창고이긴 해요. 그런데 React 기반 클라이언트 렌더링에 Akamai 봇 탐지까지 겹쳐서, 어설픈 방식은 거의 즉시 막혀요. 그래도 실제로 통하는 Python 길은 셋 있어요. 하나씩 짚어 보고, 왜 첫 시도가 늘 헛도는지 풀어 본 다음, Python이 굳이 값을 못 할 때 쓸 노코드 우회법도 보여 드릴게요.

왜 Target.com을 Python으로 처음 크롤링하면 None이 나올까요

해법으로 가기 전에 문제부터 짚어요. 초보자가 가장 자주 짜는 코드는 이런 모양이에요:

import requests
from bs4 import BeautifulSoup

url = "https://www.target.com/p/some-product/-/A-12345678"
response = requests.get(url, headers={"User-Agent": "Mozilla/5.0"})
soup = BeautifulSoup(response.text, "html.parser")

price = soup.select_one('[data-test="current-price"]')
print(price)  # None

출력은요? None이에요. 매번 그래요.

코드 버그가 아니에요. Target에서 requests.get()으로 받는 HTML은 사실상 껍데기거든요. "이 JavaScript를 로드해서 진짜 페이지를 그려라"라고 말하는 React 셸이죠. 가격, 평점, 리뷰, 재고는 초기 로드가 끝난 뒤 JavaScript가 나중에 채워 넣어요. Python의 Requests는 JavaScript를 실행하지 않으니, 그런 요소는 응답 안에 아예 없는 거예요.

개발자 포럼엔 이 벽에 부딪힌 사연이 넘쳐나요. 한 ScrapeOps 분석은 아주 단도직입적이에요. "요소가 None으로 보이는 이유는 Javascript로 렌더링되기 때문이며, requests는 Javascript로 렌더링된 HTML을 가져올 수 없기 때문이다." Crawlbase 튜토리얼도 같은 얘기예요. "Target URL로 HTTP 요청을 보내면 HTML 응답에 의미 있는 데이터가 없다."

게다가 JavaScript 문제를 풀어도 관문이 하나 더 있어요. Target의 Akamai 봇 탐지는 TLS 핸드셰이크를 지문처럼 읽어서, HTML 한 바이트가 오가기도 전에 Python requests를 끊어 내요. 이 부분은 조금 뒤에 더 짚어요.

Python으로 Target.com 크롤링이 특히 어려운 이유

Target은 그냥 "JavaScript를 쓰는 웹사이트"가 아니에요. 방어가 여러 겹이고, 층마다 이해해야 어떤 방식이 맞는지 가려낼 수 있어요.

JavaScript로 렌더링되는 상품 데이터

Target.com은 React로 만들어졌어요. 진짜 브라우저로 상품 페이지나 검색 페이지를 열면 이 순서로 흘러가요:

  1. 서버가 최소한의 HTML 셸을 보냄
  2. JavaScript 번들이 로드되고 실행됨
  3. 프런트엔드가 Target의 내부 Redsky API를 호출함
  4. 상품 데이터(가격, 평점, 이미지, 재고)가 DOM에 렌더링됨

2~4단계를 건너뛰면 — requests.get()이 딱 그 짓을 하죠 — 빈 페이지를 받아요. GroupBWT 벤치마크를 보면 정적 HTTP 요청만으로는 Target에서 쓸 수 있는 데이터의 30%쯤만 잡혀요. 나머지 70%는 JavaScript 실행이나 API 접근이 있어야 해요.

검색 결과 페이지는 더 고약해요. 초기 HTML엔 상품 몇 개만 있고, 나머지는 스크롤할 때 들어와요.

Target의 안티봇 방어: 흔한 "프록시 쓰세요" 조언보다 훨씬 복잡해요

크롤링 가이드 대부분은 안티봇을 그냥 "프록시 쓰면 돼요" 정도로 넘겨요. 그런데 Target 방어는 좀 더 뜯어봐야 해요.

TLS 핑거프린팅(가장 중요한 부분). HTTPS 핸드셰이크 동안 클라이언트는 TLS 버전, 암호 스위트, 확장, 타원곡선 정보를 담은 "Client Hello" 패킷을 보내요. 이게 JA3 지문으로 해시돼요. Python requests고정된, 이미 알려진 해시8d9f7747675e24454cd9b7ed35c58707 — 를 찍어 내서, 안티봇 데이터베이스가 곧장 잡아내요. Chrome은 GREASE 값이 섞인 16개 암호 스위트를 정교하게 정렬해 보내지만, Python은 브라우저와 다른 순서로 60개 넘게 보내요. 이 차이 하나로 HTTP 내용이 오가기 전에 끊겨요.

IP 평판 점수. Akamai는 IP를 신뢰도 등급으로 나눠요. Scrapfly 설명처럼 데이터센터 IP는 "봇에 쓰일 가능성이 높다"며 큰 음수 신뢰 점수를 받아요. 반대로 주거용 IP는 양의 점수를 받고요. 특히 Target에선 데이터센터 IP 대역이 바로 플래그돼요.

JavaScript 핑거프린팅. Akamai는 JavaScript를 심어 JS 엔진 사양, 하드웨어 성능, OS 정보, 폰트, 플러그인, 행동 데이터(입력 속도, 마우스 움직임, 클릭 타이밍)를 긁어모아요. 이걸로 _abck 쿠키라는 상태형 지문 토큰을 만들어요. 유효한 _abck가 없으면 요청이 막혀요.

속도 제한. Target은 IP당 분당 30~60건쯤에서 429 오류를 내요. 일부 사용자는 사실 "Pardon Our Interruption" 차단 페이지를 담은 200 OK 응답을 받았다고도 해요. 자동 탐지를 더 헷갈리게 만드는 수법이죠.

ScrapeOps는 Target 난이도를 전체적으로 7/10으로 봐요. 특히 Akamai 우회 난이도는 9/10이에요.

Python으로 Target.com을 크롤링하는 3가지 방법 한눈에 보기

세 방법을 한자리에 모아 비교한 글은 의외로 드물어요. 아래에 솔직하게 늘어놔 볼게요.

기준Requests + BS4Selenium / PlaywrightRedsky API
JS 렌더링 처리❌ 아니요✅ 예✅ 예(JSON)
항목당 속도⚡ 약 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 환경 설정하기

프로젝트 폴더를 만들고 의존성을 깔아요:

mkdir target-scraper && cd target-scraper
python -m venv venv
source venv/bin/activate  # Windows에서는: venv\Scripts\activate
pip install requests beautifulsoup4 curl_cffi

여기선 표준 requests 대신 curl_cffi를 쓰는 게 좋아요. 브라우저 TLS 지문을 가장 그럴듯하게 흉내 내서 Target에서 막힐 확률을 낮춰 줘요. 벤치마크를 보면 curl_cffi92% 안티봇 우회율을 내는 반면, 표준 requests12%쯤이에요. 15배 차이인 셈이죠.

2단계: Target 검색 결과 크롤링하기

Target 검색 URL 형식은 단순해요: https://www.target.com/s?searchTerm={keyword}

from curl_cffi import requests as cureq
from bs4 import BeautifulSoup
import time, random

headers = {
    "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",
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
    "Accept-Language": "en-US,en;q=0.9",
}

url = "https://www.target.com/s?searchTerm=bluetooth+headphones"
resp = cureq.get(url, headers=headers, impersonate="chrome124")
soup = BeautifulSoup(resp.text, "html.parser")

# 상품 카드는 이 data-test 속성을 사용해요
cards = soup.find_all("div", {"data-test": "@web/site-top-of-funnel/ProductCardWrapper"})
for card in cards:
    link_tag = card.find("a")
    title = link_tag.get_text(strip=True) if link_tag else "N/A"
    href = "https://www.target.com" + link_tag["href"] if link_tag and link_tag.get("href") else "N/A"
    print(f"{title} — {href}")

상품명과 URL은 나와요. 가격은요? 이 HTML에선 아마 안 잡혀요. 예상한 결과예요.

3단계: 상품 페이지에서 포함된 JSON 데이터 추출하기

개별 상품 페이지는 __TGT_DATA__ 스크립트 태그 안에 더 알찬 데이터를 담아요:

import re, json

product_url = "https://www.target.com/p/some-product/-/A-12345678"
resp = cureq.get(product_url, headers=headers, impersonate="chrome124")
soup = BeautifulSoup(resp.text, "html.parser")

# __TGT_DATA__ 스크립트를 찾기
scripts = soup.find_all("script")
for script in scripts:
    if script.string and "__TGT_DATA__" in script.string:
        # 스크립트 내용에서 JSON 추출
        match = re.search(r'__TGT_DATA__\s*=\s*({.*?});?\s*$', script.string, re.DOTALL)
        if match:
            tgt_data = json.loads(match.group(1))
            # 상품 세부 정보를 위해 JSON 구조 탐색
            queries = tgt_data.get("__PRELOADED_QUERIES__", {})
            # 상품 데이터는 안쪽에 중첩돼 있어요 — 페이지마다 구조가 달라요
            print(json.dumps(queries, indent=2)[:500])  # 구조 미리 보기

__TGT_DATA__ 속 JSON에는 상품명, 설명, 특징, 종종 가격까지 들어 있어요. 정확한 중첩 구조는 페이지마다 다르니, 출력을 보면서 필요한 위치로 파고들면 돼요.

4단계: 페이지네이션 처리하기

Target 검색 페이지네이션은 Nao 파라미터를 써요. 1페이지는 Nao=0, 2페이지는 Nao=24, 3페이지는 Nao=48 식으로 24씩 늘어요:

for page in range(0, 120, 24):  # 처음 5페이지
    paginated_url = f"https://www.target.com/s?searchTerm=bluetooth+headphones&Nao={page}"
    resp = cureq.get(paginated_url, headers=headers, impersonate="chrome124")
    # 파싱 및 추출...
    time.sleep(random.uniform(2, 5))  # 예의 있게 요청하기

5단계: 크롤링한 데이터 저장하기

import csv

with open("target_products.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.DictWriter(f, fieldnames=["title", "url", "price", "description"])
    writer.writeheader()
    for product in products:
        writer.writerow(product)

얻을 수 있는 것: 상품 제목, URL, 설명, 포함된 메타데이터. 안정적으로 얻기 어려운 것: 검색 결과 페이지의 동적 가격과 평점. 그런 데이터가 필요하면 방법 2나 3으로 가야 해요.

방법 2: Selenium 또는 Playwright로 Target.com 크롤링하기

헤드리스 브라우저는 JavaScript를 그리고, 동적 콘텐츠를 불러오고, 실제 사용자 행동도 흉내 내요. 가격, 평점, 리뷰를 가져오려면 이 길이 필요해요.

Selenium이냐 Playwright냐 고민된다면, 2026년 기준 Playwright가 Selenium의 채택률을 앞섰고45.1% 대 22.1%예요 — 벤치마크상 속도도 2.5배 더 빨라요(20페이지 기준 11초 vs. 28초). 여기선 커뮤니티가 크고 튜토리얼이 많은 Selenium을 보여 드릴게요. 다만 새로 시작한다면 Playwright가 더 나은 선택이에요.

1단계: Selenium과 ChromeDriver 설치하기

pip install selenium webdriver-manager

webdriver-manager는 ChromeDriver 버전을 알아서 맞춰 줘서 "ChromeDriver 버전 안 맞음" 같은 골칫거리를 없애요:

from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from webdriver_manager.chrome import ChromeDriverManager

options = Options()
options.add_argument("--headless=new")
options.add_argument("--window-size=1920,1080")
options.add_argument("--disable-blink-features=AutomationControlled")
options.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")

driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options)

2단계: Target 페이지를 열고 콘텐츠가 로드될 때까지 기다리기

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

driver.get("https://www.target.com/s?searchTerm=bluetooth+headphones")

# 상품 카드가 렌더링될 때까지 기다리기 (명시적 대기 > time.sleep)
WebDriverWait(driver, 15).until(
    EC.presence_of_element_located((By.CSS_SELECTOR, '[data-test="product-title"]'))
)

명시적 대기가 정말 중요해요. time.sleep(10)은 빠른 로딩에선 시간을 버리고, 느린 로딩에선 모자라요. 양쪽 다 손해죠. WebDriverWait은 요소가 뜨거나 타임아웃이 날 때까지 500ms마다 계속 확인해요.

3단계: 페이지를 스크롤해서 모든 상품 불러오기

Target은 스크롤할 때 상품을 조금씩 불러와요. 스크롤하지 않으면 전체 대신 4~5개만 보이기도 해요:

import time

last_height = driver.execute_script("return document.body.scrollHeight")
for _ in range(10):
    driver.execute_script("window.scrollBy(0, 300);")
    time.sleep(1.5)
    new_height = driver.execute_script("return document.body.scrollHeight")
    if new_height == last_height:
        break
    last_height = new_height

ScrapeOps 테스트에서는 1.5초 간격으로 10번 스크롤하면 안 했을 때 45개 대신 8개 넘는 상품을 잡았어요. 스크롤은 사람처럼 보이게 200300px 정도가 좋아요.

4단계: 렌더링된 페이지에서 상품 데이터 추출하기

products = []
cards = driver.find_elements(By.CSS_SELECTOR, '[data-test="@web/site-top-of-funnel/ProductCardWrapper"]')

for card in cards:
    try:
        title = card.find_element(By.CSS_SELECTOR, '[data-test="product-title"]').text
    except:
        title = "N/A"
    try:
        price = card.find_element(By.CSS_SELECTOR, '[data-test="current-price"]').text
    except:
        price = "N/A"
    try:
        link = card.find_element(By.CSS_SELECTOR, 'a[href*="/p/"]').get_attribute("href")
    except:
        link = "N/A"

    products.append({"title": title, "price": price, "link": link})

for p in products:
    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단계: 상품 리뷰 크롤링하기(보너스)

개별 상품 페이지로 넘어가 리뷰 섹션까지 스크롤한 뒤 리뷰 데이터를 뽑아요:

from bs4 import BeautifulSoup

driver.get("https://www.target.com/p/some-product/-/A-12345678")
# 아래로 스크롤해서 리뷰 로드하기
for _ in range(5):
    driver.execute_script("window.scrollBy(0, 500);")
    time.sleep(2)

soup = BeautifulSoup(driver.page_source, "html.parser")
reviews = soup.find_all("div", {"data-test": "review-card--text"})
for review in reviews:
    print(review.get_text(strip=True)[:100])

리뷰는 Bazaarvoice 연동으로 들어오고, 페이지네이션(최대 51페이지), 최신순 정렬, 사진만 보기 필터를 지원해요. ScrapeOps 벤치마크 기준 Selenium은 항목당 5.1초쯤이에요.

작업이 끝나면 브라우저를 닫는 것도 잊지 마세요:

driver.quit()

방법 3: Redsky API로 Target.com 크롤링하기

Target 프런트엔드는 모든 데이터를 내부 API인 redsky.target.com에서 가져와요. Python으로 직접 부를 수 있고, HTML 파싱도 브라우저도 JavaScript 렌더링도 필요 없어요. 응답은 가격, 평점, 리뷰, 이미지, 재고, 배송, 사양, 옵션까지 담은 40개 넘는 필드를 가진 깔끔한 JSON이에요. 대량 상품 데이터라면 속도와 안정성 면에서 이 길이 압도적이에요.

1단계: Chrome DevTools로 Redsky API 찾기

대부분 튜토리얼은 이 대목을 그냥 건너뛰어요. 직접 API를 찾는 법은 이래요:

  1. Chrome에서 Target 상품 페이지 하나를 열어요
  2. DevTools(F12)를 열고 Network 탭으로 가요
  3. Fetch/XHR로 필터링해요
  4. 페이지를 새로고침해요
  5. redsky.target.com 또는 redsky.a]target.com으로 가는 요청을 찾아요
  6. 하나를 클릭해 Request URLHeaders를 확인해요

이런 모양의 URL이 보일 거예요:

https://redsky.target.com/redsky_aggregations/v1/web/pdp_fulfillment_v1?key=9f36aeafbe60771e321a7cc95a78140772ab3e96&tcin=12345678&store_id=2148&zip=55401

핵심 파라미터는 이래요:

  • key — API 키(정적이며 자주 바뀌지 않음. 엔드포인트마다 다른 키를 사용)
  • tcin — Target.com Item Number(8자리 상품 ID)
  • store_id — Target 매장 위치
  • zip — 배송/수령 정보를 위한 우편번호

요청 헤더에서 API 키를 뽑아요. URL 안에 쿼리 파라미터로 박혀 있어요.

2단계: Python으로 Redsky API에 직접 요청하기

from curl_cffi import requests as cureq
import json

API_KEY = "9f36aeafbe60771e321a7cc95a78140772ab3e96"  # DevTools에서 추출
TCIN = "12345678"

url = f"https://redsky.target.com/redsky_aggregations/v1/web/pdp_fulfillment_v1?key={API_KEY}&tcin={TCIN}&store_id=2148&zip=55401"

headers = {
    "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",
    "Accept": "application/json",
    "Origin": "https://www.target.com",
    "Referer": "https://www.target.com/",
    "Sec-Fetch-Site": "same-site",
    "Sec-Fetch-Mode": "cors",
    "Sec-Fetch-Dest": "empty",
}

resp = cureq.get(url, headers=headers, impersonate="chrome124")
data = resp.json()

# JSON 응답에서 상품 정보 추출
product = data.get("data", {}).get("product", {})
title = product.get("item", {}).get("product_description", {}).get("title", "N/A")
price = product.get("price", {}).get("formatted_current_price", "N/A")
rating = product.get("ratings_and_reviews", {}).get("statistics", {}).get("rating", {}).get("average", "N/A")

print(f"{title} — {price} — 평점: {rating}")

HTML 파싱이 아예 없어요. 응답은 구조화돼 있고, 깔끔하고, 빨라요.

3단계: API로 상품 검색 결과 크롤링하기

product_summary_with_fulfillment_v1 엔드포인트는 여러 TCIN을 한 번에 받아요:

tcins = ["12345678", "23456789", "34567890"]
tcin_str = ",".join(tcins)

search_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"

resp = cureq.get(search_url, headers=headers, impersonate="chrome124")
results = resp.json()

for item in results.get("data", {}).get("product_summaries", []):
    title = item.get("title", "N/A")
    price = item.get("price", {}).get("formatted_current_price", "N/A")
    print(f"{title} — {price}")

TCIN을 얻는 길은 둘이에요. 검색 페이지 HTML에서 상품 URL의 /A-XXXXXXXX 부분을 파싱하거나, __TGT_DATA__에 담긴 JSON에서 뽑으면 돼요.

4단계: 병렬 요청으로 규모 확장하기

from concurrent.futures import ThreadPoolExecutor
import time, random

def fetch_product(tcin):
    url = f"https://redsky.target.com/redsky_aggregations/v1/web/pdp_fulfillment_v1?key={API_KEY}&tcin={tcin}&store_id=2148&zip=55401"
    time.sleep(random.uniform(2, 5))
    resp = cureq.get(url, headers=headers, impersonate="chrome124")
    return resp.json()

tcin_list = ["12345678", "23456789", "34567890", "45678901"]

with ThreadPoolExecutor(max_workers=3) as executor:
    results = list(executor.map(fetch_product, tcin_list))

동시성은 보수적으로 유지해요. 35개 스레드에 요청마다 25초 랜덤 지연이 적당해요. Target 속도 제한은 IP당 분당 30~60건쯤이에요.

Redsky API에 대한 중요한 주의사항

프로덕션 파이프라인으로 굳히기 전에 챙겨 둘 게 몇 가지 있어요:

  • API 키는 정적이지만 엔드포인트별로 달라요. Redsky의 엔드포인트마다 다른 키를 써요. 자주 바뀌진 않지만 Target이 언제든 갈 수 있어요.
  • 문서화되지 않은 내부 API예요. Target 엔지니어링 팀이 의도적으로 외부에 공개된 것이라고 확인했으니 법적 위험은 줄지만, SLA가 있는 공식 퍼블릭 API는 아니에요.
  • 상품 옵션(색상, 크기)은 각자 고유한 TCIN을 가져요. 옵션마다 따로 조회해야 해요.
  • Sec-Fetch-* 헤더가 빠지면 즉시 막힐 수 있어요. 자주 놓치는 부분이니 Sec-Fetch-Site, Sec-Fetch-Mode, Sec-Fetch-Dest는 항상 넣어요.

차단 없이 대규모로 Target.com을 크롤링하는 팁

아래 방법들은 어떤 방식이든 프로덕션 규모에 그대로 얹을 수 있어요.

데이터센터가 아니라 주거용 프록시를 사용하세요

Target의 Akamai 구현은 데이터센터 IP 대역을 보는 순간 표시해요. 지속적인 크롤링엔 주거용 프록시가 사실상 필수예요. 가격은 천차만별인데, Smartproxy/Decodo는 GB당 $4.50부터, Bright Data는 GB당 $5.04부터 시작하고, 대량 사용 땐 GB당 $3~4까지 내려가요.

프록시 풀을 쓸 수 있으면 50~100건마다, 가능하면 매 요청마다 IP를 바꿔요.

curl_cffi로 TLS 지문을 흉내 내세요

효과가 가장 큰 단일 변화예요. requests의 드롭인 대체제로 쓰면 돼요:

from curl_cffi import requests as cureq

# 표준 requests — 보호된 사이트에서 성공률 12%
# resp = requests.get(url, headers=headers)

# curl_cffi — 성공률 92%
resp = cureq.get(url, headers=headers, impersonate="chrome124")

GitHub 스타 8,200개 넘는 curl_cffichrome99부터 chrome146까지의 Chrome 버전은 물론 Safari, Edge, 모바일 변형도 지원해요. 동기 모드에선 tls_client보다 20~30% 더 빨라요.

현실적인 요청 속도와 헤더를 설정하세요

  • 랜덤 지연: 요청 사이에 2~7초 간격을 둬요(고정이 아니라 랜덤이어야 해요)
  • User-Agent 순환: 실제 브라우저 User-Agent 문자열 5~10개를 풀로 돌려요
  • 세션 워밍업: 상품 페이지를 바로 치지 말고 먼저 target.com 홈을 들러 쿠키를 만들어요
  • 헤더 일관성: Sec-Ch-Ua는 실제 User-Agent가 주장하는 브라우저 버전과 맞아야 해요. Sec-Ch-Ua-Platform도 OS와 맞고요. 어긋나면 바로 티가 나요.
  • 세션 유지: 세션 안에서는 요청 사이에 쿠키를 유지해요. Scraperly는 주거용 프록시를 돌릴 때 48시간 세션 안정성을 권해요.

코드 없이 Target.com을 크롤링하기: Thunderbit로 해결하는 방법

Target.com은 정말로 코드로 긁기 힘든 리테일 사이트 중 하나예요. JavaScript 렌더링, Akamai TLS 지문, 데이터센터 프록시 감지, ChromeDriver 버전 문제까지 — 챙길 게 많죠. Python을 배우는 입장이면 좋은 훈련이지만, 당장 업무에 필요한 Target 상품 데이터라면 시간 대비 효율이 안 나올 수 있어요.

엔지니어링 프로젝트 없이 데이터부터 손에 쥐고 싶다면 Thunderbit가 어려운 구간을 알아서 처리해 줘요.

Thunderbit가 Target.com의 난제를 처리하는 방식

Thunderbit의 AI 웹 스크래퍼는 브라우저 안에서 돌아서 JavaScript를 자연스럽게 그려요. Selenium 설정도, 헤드리스 브라우저 구성도, ChromeDriver 버전 관리도 필요 없어요. 브라우저 자체가 스크래퍼예요.

흐름은 단순해요:

  1. Thunderbit Chrome 확장 프로그램를 깔고 Target 상품 페이지나 검색 페이지로 가요
  2. "AI Suggest Fields"를 클릭하면 Thunderbit가 페이지를 읽고 열 이름(Product Title, Price, Rating, Image URL 등)을 제안해요
  3. "Scrape"를 클릭하면 렌더링된 페이지에서 몇 초 만에 데이터가 뽑혀요

프록시 설정도, TLS 지문 흉내도, None 결과와 씨름할 일도 없어요.

Target 상품 목록과 상세 페이지 크롤링하기

여러 페이지를 다루는 작업이 특히 편해요. Target 검색 결과 페이지에서 상품 목록을 먼저 가져온 뒤, Subpage Scraping으로 각 상품 URL을 자동으로 들러 상세 페이지 데이터까지 테이블에 채워요. 페이지네이션 코드를 짜거나 브라우저 세션을 관리할 필요가 없어요.

결과는 Excel, Google Sheets, Airtable, Notion으로 바로 빠져나가요. csv.writer 보일러플레이트도, 파일 인코딩 문제도 없어요.

반복되는 Target.com 크롤링 자동화하기

가격 모니터링이나 재고 추적이 계속 필요하면 Thunderbit의 Scheduled Scraper로 일정을 평범한 문장으로 적기만 하면 돼요(예: "매주 월요일 오전 9시"). 크론 작업도, 서버 설정도, VPS에 Python 스크립트를 띄워 두는 일도 없어요. 특히 경쟁사 가격을 추적하는 이커머스 팀에 잘 맞아요. 미국 소매업체의 81%가 이미 자동 가격 크롤링을 쓰고, 가격 인텔리전스 평균 ROI는 27:1이에요.

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엔 Disallow 경로가 120개 넘게 있지만, 중요한 건 /p/(상품)나 /c/(카테고리)는 막지 않는다는 점이에요. 즉 상품·카테고리 페이지 크롤링은 명시적으로 허용돼요. 반대로 장바구니, 계정, 결제 페이지는 제한돼 있어요.

Target 이용약관은 자동 접근을 금지해요. 다만 Redsky API가 의도적으로 외부에 공개된 형태라는 점(Target 엔지니어링 확인)은 API 기반 데이터 수집의 법적 위험을 낮춰 줘요.

알아둘 만한 주요 판례는 이래요:

  • hiQ 대 LinkedIn (제9순회항소법원, 2022): 공개 데이터 크롤링은 CFAA 위반이 아님
  • Meta 대 Bright Data (2024): Meta 패소 — 공개 데이터 크롤링에 대해 CFAA 위반이 아니라고 판단

대규모 상업용 크롤링이면 변호사와 상담하세요. 공개 데이터를 쓴 시장 조사, 가격 비교, 개인 프로젝트라면 법적으로 비교적 안전한 편이에요. 다만 늘 속도 제한을 존중하고 Target 서버에 과부하를 주지 마세요.

결론과 핵심 요약

Target.com이 까다롭다는 평가는 괜히 나온 게 아니에요. 어설픈 Requests + BeautifulSoup 방식은, Target이 상품 데이터를 JavaScript로 그리고, 응답을 받기도 전에 Akamai가 TLS 핸드셰이크를 지문으로 읽기 때문에 실패해요. 하지만 맞는 방법을 쓰면 추출 자체는 어렵지 않아요.

신뢰도 순으로 정리한 3가지 방법:

  1. Redsky API — 대량 데이터에 가장 빠르고 안정적이며, 깔끔한 JSON을 줘요. 다만 DevTools로 API 엔드포인트를 역공학해야 해요.
  2. Selenium / Playwright — JavaScript 렌더링을 처리해서 페이지에 보이는 모든 걸 가져와요. 느리지만 두루 챙겨요.
  3. Requests + BeautifulSoup — 정적 HTML과 포함된 __TGT_DATA__ JSON에 한정돼요. 빠르지만 완전하진 않아요.

가장 큰 기술적 개선 포인트:

  • 표준 requests 대신 curl_cffi로 안티봇 우회율을 15배 올려요
  • 주거용 프록시는 필수예요 — 데이터센터 IP는 즉시 플래그돼요
  • 모든 요청에 Sec-Fetch-* 헤더를 넣어요 — 빠지면 바로 막혀요
  • 세션 워밍업(홈페이지 먼저 들르기)은 성공률을 크게 끌어올려요

그리고 여러분 사용 사례에 Python이 굳이 값을 못 한다면, Thunderbit Chrome 확장 프로그램이 JavaScript 렌더링, 안티봇 대응, 데이터 내보내기를 알아서 처리해요. 무료 플랜을 써 보고, 몇 시간 걸릴 일이 몇 분 만에 끝나는지 확인해 보세요.

더 많은 크롤링 가이드와 데이터 추출 팁은 Thunderbit 블로그YouTube 채널을 확인해 보세요.

자주 묻는 질문

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을 받거나, 렌더링된 브라우저 페이지에서 긁는 Thunderbit 같은 도구를 쓰세요.

Target.com을 크롤링하는 건 합법인가요?

현재 미국 판례법상 공개 데이터 크롤링은 일반적으로 허용돼요(hiQ 대 LinkedIn, Meta 대 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 지문을 흉내 내는 거예요. 이것만으로 성공률이 12%에서 92%로 올라가요. 그 밖에 주거용 프록시 사용(데이터센터 금지), User-Agent 순환, 요청 사이 2~7초 랜덤 지연, 모든 Sec-Fetch-* 헤더 포함, 홈페이지 먼저 들러 세션 워밍업하는 방식이 좋아요. 또는 별도 설정 없이 안티봇 대응을 알아서 처리하는 Thunderbit 같은 도구를 써도 돼요.

더 알아보기

Ke
Ke
Thunderbit CTO | 시니어 데이터 사이언티스트 & ML 전문가 머신러닝과 데이터 과학 분야에서 약 10년에 가까운 경험을 쌓아온 Ke Shen은 컬럼비아 대학교 출신이며, 전 Walmart Labs의 시니어 데이터 사이언티스트였습니다. Python, R, Java, 통계 분야에서 동료들에게도 인정받는 깊은 전문성을 바탕으로, 복잡한 AI 알고리즘을 이론에서 실제 운영 수준의 아키텍처로 전환하는 데 필요한 실전 인사이트를 공유합니다.
목차

말 한마디로 웹페이지를 스크래핑하세요

원하는 걸 평범한 영어로 말하면 됩니다. 아니면 아무 말도 안 해도 괜찮아요.

Thunderbit 사용해보기 무료
AI로 데이터 추출하기
Google Sheets, Airtable, Notion으로 데이터를 손쉽게 منتقل하세요
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week