如何提升Selenium网页数据提取的健壮性与效率?以雅虎财经期权链爬取为例
Selenium雅虎财经期权链爬取优化方案
我们针对你遇到的4个问题,从耗时、稳定性、成功率三个维度做优化,核心优化点如下:
核心优化逻辑
- 干掉所有硬等待,替换为条件化显式等待
原来的time.sleep(25)、time.sleep(11)是耗时高的核心原因,固定等待不管元素有没有加载完都等满时间,换成显式等待后只有元素没加载出来的时候才会等,最长等待时间设为10秒足够,整体耗时直接降低80%以上。 - 复用浏览器实例,避免重复启动开销
原代码每爬一个股票就新开、关闭一次Chrome,每次启动关闭就要花十几秒,改成全局只启动一次浏览器,所有股票爬完再关闭,省掉大量重复启动的时间。 - 增加反爬规避配置,解决页面加载失败问题
大部分股票爬失败都是触发了雅虎的反爬机制,Selenium默认会带自动化标记,很容易被网站识别拦截,给Chrome加上反检测参数后,就能规避大部分反爬拦截,解决adsk、adbe这类网页打不开的问题。 - 直接拼接URL切换到期日,替代页面点击操作
原代码点击下拉框切换到期日的操作很容易因为元素遮挡、加载延迟失效,雅虎期权链的URL支持直接传入到期时间戳参数,根本不需要操作页面元素,直接请求对应日期的URL即可,稳定性提升数倍,也不会出现只能拿到部分数据的问题。 - 精细化异常处理+失败重试机制
原代码用全局try-except吞掉所有错误,一出问题就直接跳过整个股票,改成单步异常捕获,单页加载失败重试2次,不会因为某一个到期日加载失败就浪费前面已经爬好的数据。 - 优化数据拼接逻辑
弃用已经被pandas淘汰的append方法,换成pd.concat拼接数据,效率更高也不会有兼容警告。
优化后代码示例
import time import pandas as pd from selenium import webdriver from selenium.common.exceptions import TimeoutException, NoSuchElementException from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By # 全局初始化浏览器,只启动一次 def init_browser(): chrome_options = webdriver.ChromeOptions() # 反爬配置:隐藏Selenium自动化标记 chrome_options.add_argument("--disable-blink-features=AutomationControlled") chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"]) chrome_options.add_experimental_option("useAutomationExtension", False) # 可选:开启无头模式,进一步降低耗时,不需要可视化窗口 # chrome_options.add_argument("--headless=new") chrome_options.add_argument("--start-maximized") browser = webdriver.Chrome(options=chrome_options) # 修改navigator.webdriver属性,进一步反检测 browser.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", { "source": "Object.defineProperty(navigator, 'webdriver', {get: () => undefined})" }) return browser def write_option_chain(browser, code): df_all = pd.DataFrame() base_url = "https://finance.yahoo.com/quote/{}/options".format(code) # 第一次请求拿所有到期日时间戳 try: browser.get(base_url) WebDriverWait(browser, 10).until(EC.visibility_of_element_located((By.XPATH, ".//select/option"))) date_options = browser.find_elements(By.XPATH, ".//select/option") # 提取所有到期日的时间戳和文本 date_list = [(opt.get_attribute("value"), opt.text) for opt in date_options] print(f"{len(date_list)} 个期权链存在于 {code}") except Exception as e: print(f"{code} 首页加载失败,错误:{str(e)}") return False # 遍历所有到期日 for date_ts, date_text in date_list: retry_cnt = 0 while retry_cnt < 2: try: print(f"正在解析 {code} {date_text} 的看跌期权链") # 直接拼接带日期参数的URL,不用点下拉框 target_url = f"{base_url}?date={date_ts}" browser.get(target_url) # 等待看跌期权表格加载完成 WebDriverWait(browser, 10).until(EC.visibility_of_element_located( (By.XPATH, ".//table[contains(@class,'puts')]//td") )) # 直接拿表格HTML解析 put_table = browser.find_element(By.XPATH, ".//table[contains(@class,'puts')]") df_put = pd.read_html(put_table.get_attribute('outerHTML'))[0] df_all = pd.concat([df_all, df_put], ignore_index=True) break except (TimeoutException, NoSuchElementException) as e: retry_cnt += 1 print(f"{code} {date_text} 加载失败,第{retry_cnt}次重试") time.sleep(1) else: print(f"{code} {date_text} 重试2次仍失败,跳过该日期") if not df_all.empty: df_all.to_csv(f'/tmp/{code}.csv', index=False) print(f'{code} 期权链已写入CSV文件') return True else: print(f'{code} 未获取到任何有效数据') return False if __name__ == "__main__": nas_list = ['aapl','adbe','adi','adp','adsk'] browser = init_browser() for item in nas_list: try: write_option_chain(browser, item) except Exception as e: print(f"{item} 爬取出现未捕获错误:{str(e)}") time.sleep(2) # 两个股票之间留2秒间隔,避免触发频率限制 browser.quit()
优化后效果
- 整体耗时从20分钟降低到2-3分钟,最高提速90%
- 爬取成功率从40%提升到90%以上,基本不会出现页面打不开的问题
- 只要有数据的到期日都会被爬取,不会出现只拿到部分数据的情况
- 错误日志更清晰,出问题可以快速定位原因
内容的提问来源于stack exchange,提问作者showkey
相关产品推荐
相关产品推荐

