直接原因是requests.get()获取的是原始HTML,而表格由JavaScript动态插入,原始响应中不存在;确认方法是右键“查看网页源代码”搜索关键词,若搜不到但Elements中有,则属JS渲染,同时Network中查XHR/Fetch请求可进一步验证。

直接原因是:你用 requests.get() 拿到的 HTML 里压根就没有那个表格——它由 JavaScript 动态插入,原始响应中不存在。
怎么确认表格是否在原始 HTML 里
别信浏览器 Elements 面板里看到的内容,那是 JS 渲染后的 DOM。右键网页 →「查看网页源代码」,Ctrl+F 搜索表格中的任意一个明确文本(比如“2025年Q3营收”或某个唯一 ID)。如果搜不到,但 Elements 里有,基本可断定是 JS 渲染或懒加载。
同时打开开发者工具 → Network → XHR/Fetch 标签页,刷新页面,滚动或触发操作,观察是否有新请求返回 JSON 或 HTML 片段;若看到带 /api/table、?format=json 或含 cursor/offset 的请求,且响应体里含你要的字段,就坐实了动态加载。
pd.read_html() 返回空列表的典型原因
-
pd.read_html()只解析原始 HTML 字符串里的<table> 标签,不执行 JS —— 如果源码里没 <code><table>,它就什么也读不到<li>即使源码里有 <code><table>,但嵌套过深、<code>colspan过多、或表头写在<tbody> 里(而非 <code><thead>),也可能导致解析失败或列错位<li>默认 flavor 是 <code>bs4,对某些编码或 malformed HTML 容错差;建议显式指定flavor='lxml'
requests + BeautifulSoup 找不到 tbody 的真实原因
不是语法写错了,而是 soup.find('tbody') 返回 None,因为原始 HTML 中根本没这个标签——它被 JS 创建并 append 进去的。
立即学习“Python免费学习笔记(深入)”;
此时继续链式调用(如 soup.find('tbody').find_all('tr'))会直接抛 AttributeError。正确做法是先判空:
tbody = soup.find('tbody')
if tbody is None:
print("tbody not present in raw HTML — likely JS-rendered")
# 切换策略:找 API 或换 Playwright
else:
rows = tbody.find_all('tr')
另外注意:有些站点把表格结构故意拆成多个独立 <table>,或用 <code><div role="table"> 模拟,<code>pd.read_html() 和常规 find('table') 都会失效。
真正能拿到数据的三类路径
根据实际渲染机制选一种,别混用:
- 若 Network 里抓到了返回 JSON 的懒加载接口:用
requests.Session()复用 Cookie,严格复制User-Agent、Referer、X-Requested-With: XMLHttpRequest,手动构造参数(注意offset起始值、中文需urllib.parse.quote()) - 若表格由完整页面级 JS 渲染(比如 React/Vue 单页应用),且无稳定 API:用
playwright.sync_api.sync_playwright()启动浏览器,page.goto(url)后等page.wait_for_selector('table', state='attached'),再取page.content()传给pd.read_html() - 若需绕过
navigator.webdriver检测:Playwright 可设context = browser.new_context(bypass_csp=True, user_agent=...),Selenium 则需加options.add_experimental_option("excludeSwitches", ["enable-automation"])等隐藏特征
最常被忽略的一点:哪怕用了 Playwright,如果没等真实数据节点出现(比如只等 table 标签存在,但里面还是 loading skeleton),拿到的仍是空内容。得等具体数据行的 class 或文本可查,才算稳。



















