本文详解如何使用python结合requests与beautifulsoup,通过模拟ajax请求获取电商页面中依赖前序选项的多级下拉菜单数据(如选中“size”后动态加载对应的“capacity”和“pack pieces”选项),解决scrapy静态解析无法捕获动态内容的问题。
本文详解如何使用python结合requests与beautifulsoup,通过模拟ajax请求获取电商页面中依赖前序选项的多级下拉菜单数据(如选中“size”后动态加载对应的“capacity”和“pack pieces”选项),解决scrapy静态解析无法捕获动态内容的问题。
在电商网站(如BCF Australia)中,产品变体常采用级联式下拉菜单:用户选择“Size”后,“Capacity”选项才根据所选尺寸动态加载;进一步选择“Capacity”,“Pack Pieces”又随之更新。这类交互通常由前端JavaScript发起AJAX请求,从后端API实时获取HTML片段或JSON数据——而Scrapy等基于静态HTML解析的工具无法自动执行JS或追踪后续请求,因此直接css('.variation-select option')只能捕获初始页面的空/默认选项,导致“Scenario 3”(Size选中但Capacity/Pack为空)失败。
✅ 正确方案:主动构造并调用变体API
BCF网站实际通过/on/demandware.store/Sites-bcf-au-Site/en_AU/Product-Variation接口返回变体HTML片段。关键在于:
- 提取商品ID(如M675158);
- 遍历所有一级选项(如.swatchanchor链接中的Size文本);
- 对每个Size,拼接含dwvar_{pid}_size参数的GET请求,触发后端返回对应二级选项(Capacity)的
- 解析返回HTML中.capacity option(跳过首项);
- 同理,可对每个Capacity再发起新请求,传入dwvar_{pid}_capacity参数,获取三级Pack Pieces选项。
以下为完整可运行示例(支持二级+三级联动):
import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin
def scrape_multi_level_variants(product_url: str):
# Step 1: 获取商品页基础信息
response = requests.get(product_url, headers={"User-Agent": "Mozilla/5.0"})
soup = BeautifulSoup(response.content, "html.parser")
# 提取商品ID(从URL或页面meta)
pid = product_url.split("/")[-2] # 如 M675158
api_base = "https://www.bcf.com.au/on/demandware.store/Sites-bcf-au-Site/en_AU/Product-Variation"
results = {}
# Step 2: 遍历所有Size选项(一级)
for size_link in soup.select("li > a.swatchanchor"):
size_text = size_link.text.strip()
if not size_text:
continue
# 构造Size参数请求
params = {
"pid": pid,
f"dwvar_{pid}_size": size_text,
"source": "detail",
"variant_order": "size",
"Quantity": "1",
"productlistid": "undefined",
"format": "ajax"
}
try:
api_resp = requests.get(api_base, params=params, timeout=10)
api_soup = BeautifulSoup(api_resp.content, "html.parser")
# Step 3: 提取该Size下的所有Capacity选项(二级)
capacity_options = api_soup.select(".capacity option:not([value=''])")
capacities = [opt.text.strip() for opt in capacity_options]
# Step 4: 对每个Capacity,请求其对应的Pack Pieces(三级)
pack_pieces_map = {}
for cap_opt in capacity_options:
cap_value = cap_opt.get("value", "").strip()
if not cap_value:
continue
# 构造Capacity参数请求
cap_params = params.copy()
cap_params[f"dwvar_{pid}_capacity"] = cap_value
cap_params["variant_order"] = "capacity"
try:
cap_resp = requests.get(api_base, params=cap_params, timeout=10)
cap_soup = BeautifulSoup(cap_resp.content, "html.parser")
pack_opts = cap_soup.select(".pack-pieces option:not([value=''])")
pack_pieces_map[cap_opt.text.strip()] = [p.text.strip() for p in pack_opts]
except Exception as e:
print(f"⚠️ 获取 {size_text} → {cap_opt.text.strip()} 的Pack Pieces失败: {e}")
pack_pieces_map[cap_opt.text.strip()] = []
results[size_text] = {
"capacities": capacities,
"pack_pieces_by_capacity": pack_pieces_map
}
except Exception as e:
print(f"❌ 获取 {size_text} 的Capacity失败: {e}")
return results
# 使用示例
if __name__ == "__main__":
url = "https://www.bcf.com.au/p/daiwa-23-td-black-spinning-rod/M675158.html"
data = scrape_multi_level_variants(url)
# 打印结构化结果
for size, variants in data.items():
print(f"\n✅ Size: {size}")
for cap, packs in variants["pack_pieces_by_capacity"].items():
print(f" ├─ Capacity: {cap}")
print(f" │ └─ Pack Pieces: {', '.join(packs) if packs else 'N/A'}")
⚠️ 注意事项与最佳实践
- Headers与User-Agent:务必添加User-Agent,部分站点会拦截无头请求;必要时添加Referer或Cookie。
- 请求频率控制:避免高频请求触发反爬,建议加入time.sleep(1)或使用requests.Session()复用连接。
- 错误处理:网络超时、HTTP 404/500、DOM结构变更均需捕获,防止程序中断。
- 参数命名动态化:dwvar_{pid}_size中的size字段名可能因产品而异(如color、length),需先检查页面源码确认
- 替代方案:若需更高可靠性,可改用Playwright或Selenium执行真实浏览器渲染,但性能开销显著增加。
通过主动模拟AJAX请求,而非依赖静态HTML解析,即可精准捕获所有层级的动态变体选项,彻底解决“多级下拉仅返回空值”的核心痛点。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!











