1. Scraping search results
Target's search results page shows product cards with prices, ratings, and availability. Use SnapRender with anti-bot bypass:
#E8A0BF">import requests
#E8A0BF">import json
API_KEY = #A8D4A0">"sr_live_YOUR_KEY"
#E8A0BF">def scrape_target_search(query, page=0):
#A8D4A0">""#A8D4A0">"Scrape Target search results"#A8D4A0">""
offset = page * 24
url = f#A8D4A0">"https://www.target.com/s?searchTerm={query}&Nao={offset}"
resp = requests.post(
#A8D4A0">"https://api.snaprender.dev/v1/extract",
headers={
#A8D4A0">"x-api-key": API_KEY,
#A8D4A0">"Content-Type": #A8D4A0">"application/json"
},
json={
#A8D4A0">"url": url,
#A8D4A0">"selectors": {
#A8D4A0">"names": #A8D4A0">"[data-test=#A8D4A0">'product-title'] a",
#A8D4A0">"prices": #A8D4A0">"[data-test=#A8D4A0">'current-price'] span",
#A8D4A0">"ratings": #A8D4A0">"[data-test=#A8D4A0">'ratings'] span:first-child",
#A8D4A0">"review_counts": #A8D4A0">"[data-test=#A8D4A0">'rating-count']",
#A8D4A0">"links": #A8D4A0">"[data-test=#A8D4A0">'product-title'] a::attr(href)",
#A8D4A0">"images": #A8D4A0">"picture img::attr(src)"
},
#A8D4A0">"use_flaresolverr": true
}
)
#E8A0BF">return resp.json()[#A8D4A0">"data"]
# Scrape #A8D4A0">"wireless headphones" across 3 pages
all_products = []
#E8A0BF">for page #E8A0BF">in range(3):
data = scrape_target_search(#A8D4A0">"wireless+headphones", page)
names = data.get(#A8D4A0">"names", [])
#E8A0BF">for i #E8A0BF">in range(len(names)):
all_products.append({
#A8D4A0">"name": names[i],
#A8D4A0">"price": data[#A8D4A0">"prices"][i] #E8A0BF">if i < len(data.get(#A8D4A0">"prices", [])) #E8A0BF">else #A8D4A0">"",
#A8D4A0">"rating": data[#A8D4A0">"ratings"][i] #E8A0BF">if i < len(data.get(#A8D4A0">"ratings", [])) #E8A0BF">else #A8D4A0">"",
#A8D4A0">"reviews": data[#A8D4A0">"review_counts"][i] #E8A0BF">if i < len(data.get(#A8D4A0">"review_counts", [])) #E8A0BF">else #A8D4A0">"",
})
#E8A0BF">print(f#A8D4A0">"Page {page + 1}: {len(names)} products")
#E8A0BF">print(f#A8D4A0">"Total: {len(all_products)} products scraped")2. Product detail extraction
Individual product pages contain specs, descriptions, UPC codes, and store availability:
#E8A0BF">def scrape_target_product(product_url):
#A8D4A0">""#A8D4A0">"Scrape detailed product data #E8A0BF">from Target"#A8D4A0">""
resp = requests.post(
#A8D4A0">"https://api.snaprender.dev/v1/extract",
headers={
#A8D4A0">"x-api-key": API_KEY,
#A8D4A0">"Content-Type": #A8D4A0">"application/json"
},
json={
#A8D4A0">"url": product_url,
#A8D4A0">"selectors": {
#A8D4A0">"name": #A8D4A0">"[data-test=#A8D4A0">'product-title']",
#A8D4A0">"price": #A8D4A0">"[data-test=#A8D4A0">'product-price']",
#A8D4A0">"description": #A8D4A0">"[data-test=#A8D4A0">'item-details-description']",
#A8D4A0">"specs": #A8D4A0">"[data-test=#A8D4A0">'item-details-specifications'] div",
#A8D4A0">"rating": #A8D4A0">"[data-test=#A8D4A0">'ratings'] span:first-child",
#A8D4A0">"review_count": #A8D4A0">"[data-test=#A8D4A0">'rating-count']",
#A8D4A0">"brand": #A8D4A0">"[data-test=#A8D4A0">'product-brand'] a",
#A8D4A0">"upc": #A8D4A0">"[data-test=#A8D4A0">'upc']",
#A8D4A0">"shipping": #A8D4A0">"[data-test=#A8D4A0">'shipping-info']",
#A8D4A0">"pickup": #A8D4A0">"[data-test=#A8D4A0">'store-availability']",
#A8D4A0">"images": #A8D4A0">"[data-test=#A8D4A0">'image-gallery'] img::attr(src)"
},
#A8D4A0">"use_flaresolverr": true
}
)
#E8A0BF">return resp.json()[#A8D4A0">"data"]
product = scrape_target_product(
#A8D4A0">"https://www.target.com/p/example-product/-/A-12345678"
)
#E8A0BF">print(json.dumps(product, indent=2))3. Price monitoring and alerts
Track prices over time and detect drops automatically:
#E8A0BF">import pandas #E8A0BF">as pd
#E8A0BF">import time
#E8A0BF">from datetime #E8A0BF">import datetime
#E8A0BF">class TargetPriceMonitor:
#E8A0BF">def __init__(self, api_key):
self.api_key = api_key
self.history_file = #A8D4A0">"target_price_history.csv"
#E8A0BF">def check_prices(self, product_urls):
results = []
timestamp = datetime.now().isoformat()
#E8A0BF">for url #E8A0BF">in product_urls:
#E8A0BF">try:
data = scrape_target_product(url)
price_str = data.get(#A8D4A0">"price", #A8D4A0">"")
results.append({
#A8D4A0">"timestamp": timestamp,
#A8D4A0">"url": url,
#A8D4A0">"name": data.get(#A8D4A0">"name", #A8D4A0">""),
#A8D4A0">"price": price_str,
#A8D4A0">"shipping": data.get(#A8D4A0">"shipping", #A8D4A0">""),
#A8D4A0">"pickup": data.get(#A8D4A0">"pickup", #A8D4A0">""),
})
time.sleep(2) # polite delay
#E8A0BF">except Exception #E8A0BF">as e:
#E8A0BF">print(f#A8D4A0">"Error: {url} - {e}")
# Save to CSV
df = pd.DataFrame(results)
df.to_csv(
self.history_file,
mode=#A8D4A0">"a",
header=#E8A0BF">not pd.io.common.file_exists(self.history_file),
index=#E8A0BF">False
)
#E8A0BF">return df
#E8A0BF">def detect_price_drops(self):
#A8D4A0">""#A8D4A0">"Compare latest prices to historical data"#A8D4A0">""
df = pd.read_csv(self.history_file)
df[#A8D4A0">"price_num"] = (
df[#A8D4A0">"price"]
.str.extract(r#A8D4A0">"([d.]+)")
.astype(float)
)
# Group by product, compare latest to previous
#E8A0BF">for url, group #E8A0BF">in df.groupby(#A8D4A0">"url"):
#E8A0BF">if len(group) < 2:
continue
latest = group.iloc[-1][#A8D4A0">"price_num"]
previous = group.iloc[-2][#A8D4A0">"price_num"]
#E8A0BF">if latest < previous:
drop = previous - latest
pct = (drop / previous) * 100
#E8A0BF">print(f#A8D4A0">"PRICE DROP: {group.iloc[-1][#A8D4A0">'name']}")
#E8A0BF">print(f#A8D4A0">" $" + f#A8D4A0">"{previous:.2f} -> $" + f#A8D4A0">"{latest:.2f} (-{pct:.1f}%)")
monitor = TargetPriceMonitor(API_KEY)
urls = [
#A8D4A0">"https://www.target.com/p/product-1/-/A-11111111",
#A8D4A0">"https://www.target.com/p/product-2/-/A-22222222",
]
monitor.check_prices(urls)
monitor.detect_price_drops()Pro tip
Run the price monitor via cron job at the same time each day for consistent data. Target prices can fluctuate intra-day, so consistent timing reduces noise.
4. Category analysis
Analyze pricing patterns and find the best value products:
#E8A0BF">import pandas #E8A0BF">as pd
# Analyze scraped Target product data
df = pd.DataFrame(all_products)
# Clean data
df[#A8D4A0">"price_num"] = (
df[#A8D4A0">"price"]
.str.extract(r#A8D4A0">"([d.]+)")
.astype(float)
)
df[#A8D4A0">"rating_num"] = pd.to_numeric(df[#A8D4A0">"rating"], errors=#A8D4A0">"coerce")
df[#A8D4A0">"review_num"] = (
df[#A8D4A0">"reviews"]
.str.extract(r#A8D4A0">"([d,]+)")
.replace(#A8D4A0">",", #A8D4A0">"", regex=#E8A0BF">True)
.astype(float)
)
#E8A0BF">print(#A8D4A0">"=== Target Wireless Headphones ===")
#E8A0BF">print(f#A8D4A0">"Products found: {len(df)}")
#E8A0BF">print(f#A8D4A0">"Price range: $" + f#A8D4A0">"{df[#A8D4A0">'price_num'].min():.2f} - $" + f#A8D4A0">"{df[#A8D4A0">'price_num'].max():.2f}")
#E8A0BF">print(f#A8D4A0">"Median price: $" + f#A8D4A0">"{df[#A8D4A0">'price_num'].median():.2f}")
#E8A0BF">print(f#A8D4A0">"Avg rating: {df[#A8D4A0">'rating_num'].mean():.1f}")
# Best value: high rating, many reviews, low price
df[#A8D4A0">"value_score"] = (df[#A8D4A0">"rating_num"] * df[#A8D4A0">"review_num"].fillna(0)) / df[#A8D4A0">"price_num"]
best_value = df.nlargest(5, #A8D4A0">"value_score")[[#A8D4A0">"name", #A8D4A0">"price", #A8D4A0">"rating", #A8D4A0">"reviews"]]
#E8A0BF">print(#A8D4A0">"\n=== Best Value Products ===")
#E8A0BF">print(best_value.to_string(index=#E8A0BF">False))
# Price tiers
tiers = pd.cut(
df[#A8D4A0">"price_num"],
bins=[0, 25, 50, 100, 200, float(#A8D4A0">"inf")],
labels=[#A8D4A0">"<$25", #A8D4A0">"$25-50", #A8D4A0">"$50-100", #A8D4A0">"$100-200", #A8D4A0">"$200+"]
)
#E8A0BF">print(#A8D4A0">"\n=== Price Tiers ===")
#E8A0BF">print(tiers.value_counts().sort_index())
df.to_csv(#A8D4A0">"target_headphones.csv", index=#E8A0BF">False)Scrape Target without getting blocked
SnapRender handles Akamai bot protection, JavaScript rendering, and structured data extraction. Get product data from Target with a single API call.
Get Your API Key — FreeFrequently asked questions
Target's Terms of Service prohibit automated data collection. Publicly displayed product data can be accessed, but use scraped data for personal research, price comparison, or market analysis only. Do not republish, resell, or use it to build a competing product listing service.
Target uses a React-based SPA, aggressive bot detection (Akamai), and frequently changes their DOM structure. Standard HTTP libraries get blocked immediately. The product data is loaded via JavaScript API calls, meaning the initial HTML contains no product information.
Target shows store availability on product pages for specific ZIP codes. You can extract this data by including the store/ZIP parameter in your scraping request. However, inventory data changes frequently, so real-time accuracy is limited to the time of the scrape.
Target uses dynamic pricing that varies by location, time, and user history. For consistent pricing data, scrape without cookies/session data and use a consistent geographic location. Track prices over time to identify patterns and promotional cycles.
All publicly listed categories: electronics, clothing, home goods, grocery, beauty, toys, sports, furniture, and more. Target has 200K+ products online. Category pages and search results are the best entry points for bulk data collection.