You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python与Selenium爬取动态内容:定位器选型及元素遍历咨询

Selenium定位动态加载商品卡片的实操方案(Dutchie站点适配)

定位方案选型:CSS与Xpath的决策逻辑

不存在Xpath一定优于CSS、或CSS效果更好的通用结论,选型只对照两个硬指标判断即可:

  • 优先选用CSS选择器的场景
    • 仅需要匹配元素属性、类名,不需要文本匹配/跨层级相邻节点查找时
    • CSS选择器的元素定位性能比原生Xpath高30%以上,写法更简洁
    • 针对你提供的HTML结构,注意所有类名是CSS-in-JS生成的哈希格式:类名由「语义前缀-哈希后缀」两部分组成,后缀(比如-ncbvk2-1 jpGhIo)是构建阶段随机生成的,每次站点发布版本都会变更,绝对不能直接拿完整类名做定位。
    • 你之前想靠类名匹配consumer-product-card元素的思路是对的,但写法错误:直接用By.CLASS_NAME, "consumer-product-card"会完全匹配不到元素,正确写法是匹配类名中固定的语义前缀,也就是a[class*="consumer-product-card__StyledLink"],这种写法不会因为类名后缀哈希变更失效。
  • 优先选用Xpath的场景
    • 需要基于元素的可见文本内容做匹配时(比如要找所有名称带“CBD Tincture”的卡片)
    • 需要匹配同父节点下的相邻元素、跨多层级查找关联内容时
    • 比如带文本匹配的定位,用Xpath实现为//a[contains(@class,"consumer-product-card__StyledLink") and contains(normalize-space(.),"CBD Tincture")],这个逻辑纯CSS是无法实现的。

动态列表的滚动触发逻辑

不需要提前强制滚到页面底部,但必须用渐进式滚动触发懒加载,一次性滚到底的操作反而会导致元素丢失。

  • 目标站点的商品列表采用前端虚拟滚动技术:浏览器只会渲染当前视口范围内的DOM节点,不在可视区域的节点会被从内存中回收移除。如果一开始就执行window.scrollTo(0, document.body.scrollHeight)直接跳到底部,前面已经加载过的卡片节点会被全部卸载,最后能拿到的元素只有最后一屏的3-4个商品,完全不符合遍历需求。
  • 正确的遍历流程:
    1. 初始化空集合存储已经处理过的商品href(每个商品的href是唯一固定的,用来做去重标识)
    2. 第一步先抓取当前DOM中所有匹配条件的商品卡片,逐个提取需要的字段,把已经处理过的href加入集合
    3. 执行小幅滚动(每次滚动当前视口高度的80%即可),等待300ms让新的商品卡片加载到DOM中
    4. 重复抓取、去重、提取、滚动的流程,直到连续两次滚动后没有抓到新的商品卡片,说明已经加载完全部内容,退出循环

可直接复用的遍历代码示例

from selenium import webdriver
from selenium.webdriver.common.by import By
import time

driver = webdriver.Chrome()
driver.get("https://dutchie.com/embedded-menu/revolutionary-clinics-somerville/menu")

processed_product_hrefs = set()
all_products = []

while True:
    # 获取当前DOM中所有的商品卡片元素
    product_cards = driver.find_elements(
        By.CSS_SELECTOR, 
        'a[class*="consumer-product-card__StyledLink"]'
    )
    new_card_count = 0
    for card in product_cards:
        href = card.get_attribute("href")
        # 跳过已经处理过的卡片
        if href in processed_product_hrefs:
            continue
        processed_product_hrefs.add(href)
        new_card_count +=1
        # 按需求提取卡片内的字段
        price = card.find_element(By.CSS_SELECTOR, 'div[class*="product-information__Price"]').text
        product_name = card.find_element(By.CSS_SELECTOR, 'div[class*="product-information__Title-sc"]').text
        all_products.append({"href": href, "name": product_name, "price": price})
    
    # 连续两次没有新卡片加载,退出循环
    if new_card_count ==0:
        break
    # 小幅滚动触发懒加载,不要一次性滚到底
    driver.execute_script("window.scrollBy(0, window.innerHeight * 0.8)")
    time.sleep(0.3)

print(f"总共提取到{len(all_products)}个商品")

实操避坑提醒

  • 不要用类名里的随机哈希后缀做定位:比如类名里的jpGhIo、iWHVJj这类字符串是构建时随机生成的,每次站点发版都会变,用这类值做定位会直接失效
  • 不要在遍历前一次性滚到底:虚拟滚动模式下的DOM节点是动态回收的,提前滚到底会导致前面的卡片节点被移除,无法获取完整列表
  • 遍历过程中必须做去重:滚动过程中DOM的卡片是动态增删的,不对已处理的href做去重会导致重复统计、重复操作的问题

内容的提问来源于stack exchange,提问作者T0ne

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.23 14:06:08