使用Selenium+Python出现Timeout Exception,页面导航问题排查
爬虫问题诊断与修复方案
问题描述
爬虫逻辑为:访问指定商品搜索页,逐个点击商品链接进入详情页爬取数据,返回搜索页后继续处理下一个商品。实际运行时,常爬取1-2个商品(偶尔可爬5个),打印“exited”后抛出Timeout Exception,怀疑问题出在页面回退到循环开始的阶段。同时咨询当前自动滚动的实现是否正确。
核心问题诊断
- 页面回退后未等待加载完成:回退操作后直接操作页面元素,但页面尚未完全渲染,导致元素定位超时。
- Stale Element引用失效:回退后原有的商品列表元素引用已过期,未重新等待元素加载就复用,引发异常。
implicitly_wait用法错误:该方法是设置全局隐式等待时间(单位秒),不是暂停程序,无法替代显式等待确保页面稳定。- 尺码列表未清空:全局定义的
sizes列表会累积所有商品的尺码,导致数据混乱。 - “查看更多”按钮逻辑缺陷:按钮定位放在循环内部,且未判断按钮是否可点击,点击后未重新获取新的商品列表。
- PDF重复写入:每次爬取商品后遍历整个历史数据写入PDF,导致重复输出。
修复方案
1. 优化页面回退与加载等待
用driver.back()替代window.history.go(-1),回退后显式等待商品列表重新加载完成:
driver.back() # 显式等待搜索页商品列表重新渲染 WebDriverWait(driver, 20).until( EC.presence_of_all_elements_located((By.XPATH, "//div[@class = 'product-listing__grid']//div[@class = '_root_129ai_6 product-listing__grid-item']/a")) )
2. 重置尺码列表
每次进入商品详情页前清空sizes,避免数据累积:
# 进入详情页前添加 sizes = []
3. 修正implicitly_wait用法
移除错误的driver.implicitly_wait(5000),改用显式等待覆盖所有元素定位场景,必要时可补充time.sleep()(仅用于等待页面动画等显式等待无法覆盖的场景)。
4. 优化“查看更多”按钮逻辑
将按钮定位移到循环外,点击后重新获取商品列表:
# 放在while循环末尾,for循环之外 try: botao_vermais = WebDriverWait(driver, 10).until( EC.element_to_be_clickable((By.XPATH, "//div[@class = 'product-listing__view-more']/button")) ) botao_vermais.click() # 等待新商品加载完成 WebDriverWait(driver, 20).until( EC.staleness_of(product_list[0]) # 等待旧商品列表失效,确认新列表加载 ) except TimeoutException: # 无更多商品,退出循环 break
5. 修复PDF写入逻辑
仅写入当前爬取的商品数据,避免重复输出历史内容:
# 替换原有的PDF循环写入代码 current_data = pdf_data[-1] for data in current_data.values(): pdf.cell(1.6, 0.5, str(data)) pdf.ln()
6. 自动滚动实现的正确性与优化
当前的scrollIntoView写法是有效的,能将目标元素滚动到视口内。可优化为让元素居中显示,提升点击稳定性:
driver.execute_script("arguments[0].scrollIntoView({behavior: 'smooth', block: 'center'});", product_list[index])
完整修复后的核心代码片段
def extract_prodirectsports_data(): url_line = "https://www.prodirectsport.com/soccer/l/adults/departments-boots/activity-football/brand-adidas/silo-predator/" sheet_data = [] pdf_data = [] cotacao_libra() # 假设该函数已定义 driver.get(url_line) # 处理弹窗与Cookie WebDriverWait(driver, 100).until(EC.element_to_be_clickable((By.XPATH,"//button[@title='Accept all cookies']"))).click() WebDriverWait(driver, 20).until(EC.element_to_be_clickable((By.XPATH,"//div[@id='zonos']"))) close_button = driver.find_element(By.XPATH, "//a[@class = 'z-close']") driver.execute_script("arguments[0].click();", close_button) WebDriverWait(driver, 100).until(EC.element_to_be_clickable((By.XPATH,"//div[@class='global-popup']"))) button = driver.find_element(By.XPATH,"//button[@aria-label='Close']") driver.execute_script("arguments[0].click();", button) df_sheet = pd.DataFrame(columns=SHEET_COLUMNS) pdf = FPDF() pdf.add_page() pdf.set_font('Arial', 'B', 12) driver.maximize_window() while True: # 每次循环重新获取商品列表 product_list = WebDriverWait(driver, 20).until( EC.presence_of_all_elements_located((By.XPATH, "//div[@class = 'product-listing__grid']//div[@class = '_root_129ai_6 product-listing__grid-item']/a")) ) for index, product in enumerate(product_list): # 滚动到当前商品(居中显示) driver.execute_script("arguments[0].scrollIntoView({behavior: 'smooth', block: 'center'});", product) # 精准定位商品图片 image_element = WebDriverWait(driver, 20).until(EC.presence_of_element_located((By.XPATH, ".//img[@class='product-listing__image']"))) image = image_element.get_attribute("src") # 点击进入详情页 driver.execute_script("arguments[0].click();", product) WebDriverWait(driver, 20).until(EC.presence_of_element_located((By.CLASS_NAME, "ml-meta__title"))) # 爬取详情页数据 product_name = driver.find_element(By.CLASS_NAME, "ml-meta__title").text price = driver.find_element(By.CLASS_NAME, "ml-prices__price").text product_price = float(re.sub("£", "", price)) product_code = driver.current_url.split('-')[-1] # 爬取尺码(重置列表) sizes = [] try: size_list = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.XPATH, "//div[@class = 'ml-size__sizes']"))) driver.execute_script("arguments[0].scrollIntoView();", size_list) # 相对定位避免获取页面其他按钮 size_buttons = size_list.find_elements(By.XPATH, ".//button[@class='ml-size__size qa-size-item']") for size_btn in size_buttons: product_size = size_btn.text.strip() sizes.append(product_size) if "-" in product_size: sizes.append(product_size.split('-')[0].strip()) elif "(" in product_size: sizes.append(product_size.split('(')[0].strip()) except TimeoutException: # 无尺码信息时跳过 pass # 计算价格 price_in_reais = calculate_price_in_reais(product_price) sale_price = calculate_sale_price(product_price) # 保存数据 sheet_data.append({ 'Photo': image, 'Code': product_code, 'Description': product_name, 'Purchase': price_in_reais, 'Sale': sale_price, 'Sizes': sizes }) pdf_data.append({ 'Photo': image, 'Code': product_code, 'Description': product_name, 'Price': product_price }) # 写入PDF当前商品数据 current_data = pdf_data[-1] for data in current_data.values(): pdf.cell(1.6, 0.5, str(data)) pdf.ln() # 回退到搜索页并等待加载 driver.back() WebDriverWait(driver, 20).until( EC.presence_of_all_elements_located((By.XPATH, "//div[@class = 'product-listing__grid']//div[@class = '_root_129ai_6 product-listing__grid-item']/a")) ) print("回到搜索页,继续处理下一个商品") # 处理查看更多按钮 try: botao_vermais = WebDriverWait(driver, 10).until( EC.element_to_be_clickable((By.XPATH, "//div[@class = 'product-listing__view-more']/button")) ) botao_vermais.click() # 等待新商品加载完成 WebDriverWait(driver, 20).until( EC.staleness_of(product_list[0]) ) except TimeoutException: break # 生成文件 pdf.output(url_line + 'cátalogo.pdf') df_planilha = pd.DataFrame(sheet_data) df_planilha.to_excel(url_line + "catálogo.xlsx") driver.close()
内容的提问来源于stack exchange,提问作者André
相关产品推荐
相关产品推荐

