直接原因是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 字符串里的标签,不执行 JS —— 如果源码里没
,它就什么也读不到
- 即使源码里有
,但嵌套过深、
colspan过多、或表头写在里(而非),也可能导致解析失败或列错位- 默认 flavor 是
bs4,对某些编码或 malformed HTML 容错差;建议显式指定flavor='lxml'requests + BeautifulSoup 找不到 tbody 的真实原因
不是语法写错了,而是
soup.find('tbody')返回None,因为原始 HTML 中根本没这个标签——它被 JS 创建并 append 进去的。此时继续链式调用(如
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')另外注意:有些站点把表格结构故意拆成多个独立
,或用
模拟,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 或文本可查,才算稳。 - 即使源码里有
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











