动态无URL关联分页的数据表爬取实现求助
动态无URL关联分页的数据表爬取实现求助
嘿,我之前也碰到过这种ASP.NET页面的动态分页问题,给你几个实用的思路和修改后的代码参考~
问题还原
我尝试从某个网站的简单表格获取数据,已经能拿到第一页的数据,但分页没有关联到URL,虽然找到了页面底部的“ProximoPaginacao”和“MeioPaginacao”按钮,但不知道怎么实现分页爬取,求指点!
附上的原代码:
import requests from bs4 import BeautifulSoup import pandas as pd def extract_table_data(url, table_id): try: response = requests.get(url,verify=False) response.raise_for_status() html_content = response.text soup = BeautifulSoup(html_content, 'html.parser') table = soup.find('table', id=table_id) if not table: print(f"Table with ID '{table_id}' not found.") return None # Extract header row header_row = [th.get_text(strip=True) for th in table.find_all('th')] # Extract data rows data_rows = [] for row in table.find('tbody').find_all('tr'): data_rows.append([td.get_text(strip=True) for td in row.find_all('td')]) # Create DataFrame df = pd.DataFrame(data_rows, columns=header_row) return df except requests.exceptions.RequestException as e: print(f"Error during requests: {e}") return None except Exception as e: print(f"An error occurred: {e}") return None # Example usage url = "https://bvmf.bmfbovespa.com.br/clube-de-investimento/clube-de-investimento.aspx?Idioma=pt-br" # Replace with the actual URL table_id = "ctl00_contentPlaceHolderConteudo_grdAtivo_ctl01" # Replace with the actual table ID table_data = extract_table_data(url, table_id) if table_data is not None: print(table_data)
核心原因分析
这个页面是ASP.NET架构,分页逻辑是通过表单提交实现的,不是URL参数控制——点击“下一页”时,页面会向服务器发送POST请求,携带__VIEWSTATE、__EVENTVALIDATION等ASP.NET专属的隐藏字段,以及分页按钮的触发事件参数,服务器验证后返回下一页的HTML。
解决方案步骤
抓包分析请求:打开浏览器开发者工具(F12)→ 切换到「Network」标签,点击“Próximo”按钮,观察发送的POST请求,重点看Form Data里的参数,比如:
__VIEWSTATE、__VIEWSTATEGENERATOR、__EVENTVALIDATION:这些是ASP.NET用来维护页面状态的隐藏字段,每次请求都要从当前页面提取__EVENTTARGET:对应分页按钮的ID,比如ctl00$contentPlaceHolderConteudo$grdAtivo$ctl01$ctl13(具体以抓包结果为准)
用Session保持会话:ASP.NET依赖会话Cookie,所以要用
requests.Session()来维持会话状态,避免每次请求都重新初始化。循环爬取所有页面:每次获取页面后,先提取表格数据,再检查是否有可用的下一页按钮,若有则构造POST请求获取下一页,直到分页按钮不可用(比如带有
disabled类)。
修改后的代码示例
import requests from bs4 import BeautifulSoup import pandas as pd import time def extract_all_pages(url, table_id): session = requests.Session() session.verify = False # 注意:生产环境建议移除或配置可信证书 all_data = [] header_row = None current_page = 1 while True: try: if current_page == 1: # 第一页用GET请求 response = session.get(url) else: # 后续页面用POST请求,构造表单数据 # 从当前页面提取ASP.NET隐藏字段 viewstate = soup.find('input', id='__VIEWSTATE')['value'] viewstategenerator = soup.find('input', id='__VIEWSTATEGENERATOR')['value'] eventvalidation = soup.find('input', id='__EVENTVALIDATION')['value'] # 构造表单数据(注意EventTarget要和抓包的一致) form_data = { '__VIEWSTATE': viewstate, '__VIEWSTATEGENERATOR': viewstategenerator, '__EVENTVALIDATION': eventvalidation, '__EVENTTARGET': 'ctl00$contentPlaceHolderConteudo$grdAtivo$ctl01$ctl13', # 下一页按钮的ID,以抓包为准 '__EVENTARGUMENT': '' } response = session.post(url, data=form_data) response.raise_for_status() soup = BeautifulSoup(response.text, 'html.parser') # 提取表格 table = soup.find('table', id=table_id) if not table: print(f"Table with ID '{table_id}' not found on page {current_page}.") break # 提取表头(仅第一页提取) if not header_row: header_row = [th.get_text(strip=True) for th in table.find_all('th')] # 提取数据行 data_rows = [] for row in table.find('tbody').find_all('tr'): data_rows.append([td.get_text(strip=True) for td in row.find_all('td')]) all_data.extend(data_rows) # 检查是否还有下一页 next_button = soup.find('a', id='ctl00_contentPlaceHolderConteudo_grdAtivo_ctl01_ctl13') # 下一页按钮ID,以抓包为准 if not next_button or 'disabled' in next_button.get('class', []): print(f"Reached last page (page {current_page}).") break current_page += 1 print(f"Successfully fetched page {current_page}.") time.sleep(1) # 加入延迟,避免触发反爬 except requests.exceptions.RequestException as e: print(f"Request error on page {current_page}: {e}") break except Exception as e: print(f"Unexpected error on page {current_page}: {e}") break # 构造完整DataFrame if all_data and header_row: return pd.DataFrame(all_data, columns=header_row) else: return None # 调用示例 url = "https://bvmf.bmfbovespa.com.br/clube-de-investimento/clube-de-investimento.aspx?Idioma=pt-br" table_id = "ctl00_contentPlaceHolderConteudo_grdAtivo_ctl01" full_table_data = extract_all_pages(url, table_id) if full_table_data is not None: print(full_table_data) # 可选:保存到CSV full_table_data.to_csv('investment_club_data.csv', index=False, encoding='utf-8-sig')
注意事项
- 按钮ID验证:代码中的
__EVENTTARGET和下一页按钮ID需要和你抓包的结果完全一致,ASP.NET的控件ID可能会根据页面结构变化,一定要自己抓包确认。 - 证书验证:
session.verify = False是为了跳过SSL证书验证,生产环境建议去掉这个参数,或者配置本地可信证书。 - 反爬机制:频繁请求可能会触发网站反爬,代码中加入了
time.sleep(1)的延迟,你可以根据实际情况调整时长。
备注:内容来源于stack exchange,提问作者André F
相关产品推荐
相关产品推荐

