
本文介绍如何利用 selenium 模拟浏览器行为,抓取巴西证券交易所(b3)官网中通过 javascript 动态加载、无 url 变化的分页表格数据,并合并为完整 dataframe。
本文介绍如何利用 selenium 模拟浏览器行为,抓取巴西证券交易所(b3)官网中通过 javascript 动态加载、无 url 变化的分页表格数据,并合并为完整 dataframe。
在处理现代 Web 应用时,许多表格数据(如 B3 官网的「Clube de Investimento」列表)并非静态 HTML 渲染,而是依赖 ASP.NET WebForms 的客户端事件(如 __doPostBack)实现无刷新分页。这类页面无法通过 requests + BeautifulSoup 直接翻页——因为分页按钮不改变 URL,也不发送独立 API 请求,而是触发 DOM 重绘与局部更新。
此时,Selenium 是更可靠的选择:它能真实驱动浏览器,等待元素加载、模拟点击、捕获动态渲染后的 HTML 源码,并支持显式等待机制应对异步加载不确定性。
✅ 推荐实现方案(Selenium + pandas)
以下是一个健壮、可复用的自动化爬取脚本:
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException, StaleElementReferenceException, NoSuchElementException
import pandas as pd
import time
# 配置无头模式(可选,提升效率)
chrome_options = Options()
chrome_options.add_argument("--headless")
chrome_options.add_argument("--no-sandbox")
chrome_options.add_argument("--disable-dev-shm-usage")
driver = webdriver.Chrome(options=chrome_options)
url = "https://bvmf.bmfbovespa.com.br/clube-de-investimento/clube-de-investimento.aspx?Idioma=pt-br"
driver.get(url)
data_frames = []
try:
# 确保进入目标标签页(如有多个 Tab)
tab_button = WebDriverWait(driver, 20).until(
EC.element_to_be_clickable((By.CSS_SELECTOR, 'a[id="ctl00_contentPlaceHolderConteudo_tabIbovespa_TabAdmnistradores"]'))
)
tab_button.click()
time.sleep(1) # 短暂等待表格渲染(可替换为更精准的等待条件)
while True:
# 使用 pd.read_html 提取当前页表格(自动识别 page_source 中的第一个合规 table)
tables = pd.read_html(driver.page_source, header=0, skiprows=0)
if not tables:
print("⚠️ 未检测到表格,请检查页面结构或等待逻辑")
break
current_df = tables[0]
data_frames.append(current_df)
try:
# 定位「下一页」按钮(ID 为 ASP.NET 自动生成,需确认实际值;示例中为 ctl00_...lnkNext)
next_btn = WebDriverWait(driver, 15).until(
EC.element_to_be_clickable((By.CSS_SELECTOR, 'input[id$="lnkNext"]')) # 使用属性结尾匹配提高鲁棒性
)
driver.execute_script("arguments[0].click();", next_btn)
time.sleep(1.5) # 给予页面足够时间更新(生产环境建议改用 WebDriverWait 等待 tbody 更新)
except TimeoutException:
print("✅ 已到达最后一页,停止翻页")
break
except StaleElementReferenceException:
print("? 页面 DOM 已更新,重试查找下一页按钮...")
continue
finally:
driver.quit()
# 合并所有页数据
if data_frames:
full_df = pd.concat(data_frames, ignore_index=True)
# 清理异常行(ASP.NET 分页常在首尾插入省略符占位行)
mask = ~(full_df.iloc[:, 1].str.startswith('...') | full_df.iloc[:, 1].str.endswith('...'))
clean_df = full_df[mask].reset_index(drop=True)
print(f"✅ 成功采集 {len(clean_df)} 条有效记录")
print(clean_df.head())
else:
print("❌ 未获取到任何数据,请检查网络、选择器或反爬策略")
⚠️ 关键注意事项
-
选择器稳定性:ASP.NET 自动生成的 ID(如
ctl00_contentPlaceHolderConteudo_grdAtivo_ctl01_ctl03_ctl01_ctl00_lnkNext)极易随页面结构变更。推荐使用CSS_SELECTOR的属性模糊匹配(如[id$="lnkNext"]表示 ID 以"lnkNext"结尾),或基于aria-label、title、文本内容等更稳定的定位方式。 -
显式等待优于
time.sleep():应优先使用WebDriverWait等待关键元素出现/可点击/文本变化,避免硬编码休眠导致效率低下或失败。 -
反爬与稳定性:B3 官网虽未启用强反爬,但仍建议添加随机延迟、User-Agent 轮换(通过
chrome_options.add_argument('--user-agent=...')),并在生产环境中加入异常重试逻辑与日志记录。 -
资源释放:务必调用
driver.quit()关闭浏览器进程,防止后台残留占用内存。
? 替代思路(进阶推荐)
若追求更高性能与可维护性,可尝试:
-
逆向分析请求:通过浏览器开发者工具(Network → XHR/Fetch)捕获分页时的真实 AJAX 请求(如
__EVENTTARGET=__Page&__EVENTARGUMENT=...),用requests模拟 POST,但需解析并维护 ViewState、EventValidation 等隐藏字段; - 使用 Playwright:比 Selenium 更现代、更快、内置等待策略,对动态内容支持更佳;
- 查阅官方 API:B3 提供 Dados Abertos 平台,部分数据(如基金注册信息)可能已有结构化 CSV/JSON 接口,应优先采用——合法、稳定、免解析。
最终,本方案成功将分散在数十页中的俱乐部投资数据整合为单一 Pandas DataFrame,为后续清洗、分析与可视化奠定坚实基础。










