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

登录后Selenium无法点击StoryWeaver书籍卡片(macOS+Python)

问题描述

我正在用Selenium+Python自动化下载StoryWeaver平台的书籍。登录流程完全正常,但进入对应等级页面后,UI里明明能看到书籍卡片,脚本却无法点击卡片进入单本书籍详情页(PDF下载入口在详情页)。

脚本精简版本(登录部分正常):

import os, time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from webdriver_manager.chrome import ChromeDriverManager

EMAIL      = "…"
PASSWORD   = "…"
LEVEL      = 4
MAX_BOOKS  = 10
OUTPUT_DIR = "storyweaver_pdfs"

HOME_URL = f"https://storyweaver.org.in/en/stories?level={LEVEL}&query=&sort=Ratings"

# — 登录选择器(全部可用) —
LOGIN_BTN      = (By.CSS_SELECTOR, "a[title='Sign Up/Log In']")
EMAIL_METHOD   = (By.CSS_SELECTOR, "a:has(div[title='Email'])")
EMAIL_INPUT    = (By.ID,    "auth-modal-email")
SUBMIT_BTN     = (By.CSS_SELECTOR, "button.pb-button--in-group-last[type='submit']:not([class*='disabled'])")
PASSWORD_INPUT = (By.ID,    "auth-modal-password")
LIBRARY_IND    = (By.CSS_SELECTOR, "a[href='/en/library']")

# 初始化驱动…
os.makedirs(OUTPUT_DIR, exist_ok=True)
prefs = {
  "download.default_directory": os.path.abspath(OUTPUT_DIR),
  "download.prompt_for_download": False,
  "plugins.always_open_pdf_externally": True,
}
opts = Options()
opts.add_argument("--start-maximized")
opts.add_experimental_option("prefs", prefs)
opts.add_experimental_option("excludeSwitches", ["enable-automation"])
opts.add_experimental_option("useAutomationExtension", False)

driver = webdriver.Chrome(
  service=Service(ChromeDriverManager().install()),
  options=opts
)
wait = WebDriverWait(driver, 30)

try:
    # 1) 登录(完全正常)
    driver.get(HOME_URL)
    wait.until(EC.element_to_be_clickable(LOGIN_BTN)).click()
    wait.until(EC.element_to_be_clickable(EMAIL_METHOD)).click()
    wait.until(EC.element_to_be_clickable(EMAIL_INPUT)).send_keys(EMAIL)
    wait.until(EC.element_to_be_clickable(SUBMIT_BTN)).click()
    wait.until(EC.element_to_be_clickable(PASSWORD_INPUT)).send_keys(PASSWORD)
    wait.until(EC.element_to_be_clickable(SUBMIT_BTN)).click()
    wait.until(EC.presence_of_element_located(LIBRARY_IND))
    print("✅ 登录成功 — 当前在等级页面")

    # 2) 查找书籍卡片并点击(此处失败)
    card_selector = (
        By.XPATH,
        "//a[contains(@href,'/stories/') and descendant::*[contains(@class,'story-card')]]"
    )
    wait.until(EC.presence_of_all_elements_located(card_selector))
    cards = driver.find_elements(*card_selector)[:MAX_BOOKS]
    print("找到", len(cards), "个卡片")
    for c in cards:
        c.click()   # <<--- 无任何反应,不跳转页面

finally:
    driver.quit()

浏览器中观察到的书籍卡片HTML结构:

<a href="/en/stories/34911-the-case-of-the-missing-water" class="pb-link pb-link--default pb-book-card__link">
  <div class="pb-book-card__container">
    <div class="pb-book-card__wrapper">
      <div class="pb-book-card__image-wrapper">
        <div class="pb-img__wrapper"><img …></div>
      </div>
      <div class="pb-book-card__meta-wrapper">
        <h3 class="pb-book-card__title">The Case of the Missing Water</h3>
        …
      </div>
    </div>
  </div>
</a>

Selenium的click()方法无任何反应(无异常、无页面跳转)。已尝试以下方案但均无效:

  • 使用JavaScriptExecutor执行点击:driver.execute_script("arguments[0].click()", c)
  • 等待元素可见而非仅存在或可点击
  • 点击内部<h3>标签或图片容器而非<a>标签
  • 将卡片滚动到可见区域
  • 点击前添加短暂等待(time.sleep(1))

疑问

  1. 为何元素可见且可用,但Selenium无法点击?
  2. 哪种定位器或点击策略能可靠打开书籍详情页?
  3. 能否提供从等级页跳转到书籍详情页的精简代码示例?

解决方案

1. 无法点击的原因

  • 定位器精度不足:原XPath选择器可能匹配到不可交互的隐藏元素,或者页面中存在多个符合条件的<a>标签,其中部分并非目标书籍卡片。
  • 页面交互状态未就绪:元素虽已存在,但页面可能未完成事件绑定,或存在隐形遮罩(如未完全加载的弹窗、广告层)阻挡点击区域。
  • Stale Element问题:页面动态更新后,之前定位的元素已失效,点击操作无法触发交互。

2. 可靠的定位器与点击策略

  • 使用精准CSS选择器:直接通过卡片的专属类定位,比如By.CSS_SELECTOR, "a.pb-book-card__link",避免模糊匹配带来的误判。
  • 优先等待元素可交互:用EC.element_to_be_clickable替代仅判断元素存在,确保元素处于可点击状态。
  • 循环中重新定位元素:每次处理卡片前重新查找元素,避免页面刷新导致的元素失效。
  • 直接跳转URL:若点击交互始终失败,可提取卡片的href属性,用driver.get()直接访问详情页,绕过点击环节。

3. 精简代码示例(替换原脚本第2部分)

方案1:优化点击交互

# 2) 查找书籍卡片并跳转详情页
card_selector = (By.CSS_SELECTOR, "a.pb-book-card__link")
# 等待至少一个卡片可点击
wait.until(EC.element_to_be_clickable(card_selector))

# 循环处理前MAX_BOOKS个卡片
for i in range(MAX_BOOKS):
    # 每次循环重新定位,避免stale element
    cards = wait.until(EC.presence_of_all_elements_located(card_selector))
    if i >= len(cards):
        break
    current_card = cards[i]
    
    # 滚动到元素可见区域
    driver.execute_script("arguments[0].scrollIntoView({behavior: 'smooth', block: 'center'});", current_card)
    # 等待元素完全可点击后执行点击
    wait.until(EC.element_to_be_clickable(current_card)).click()
    
    # 等待详情页加载完成(根据详情页特征元素判断)
    wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "div.pb-story-header__wrapper")))
    print(f"✅ 进入第{i+1}本书详情页")
    
    # 此处添加PDF下载逻辑...
    
    # 返回等级页面继续处理下一个
    driver.back()
    # 等待等级页面重新加载完成
    wait.until(EC.presence_of_element_located(card_selector))

方案2:直接跳转URL(更稳妥)

# 2) 提取卡片URL并直接跳转
card_selector = (By.CSS_SELECTOR, "a.pb-book-card__link")
wait.until(EC.presence_of_all_elements_located(card_selector))
cards = driver.find_elements(*card_selector)[:MAX_BOOKS]

for idx, card in enumerate(cards):
    book_url = card.get_attribute("href")
    driver.get(book_url)
    # 等待详情页加载完成
    wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "div.pb-story-header__wrapper")))
    print(f"✅ 进入第{idx+1}本书详情页")
    
    # 此处添加PDF下载逻辑...
    
    driver.back()
    wait.until(EC.presence_of_element_located(card_selector))

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 20:28:11