You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

动态无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。

解决方案步骤

  1. 抓包分析请求:打开浏览器开发者工具(F12)→ 切换到「Network」标签,点击“Próximo”按钮,观察发送的POST请求,重点看Form Data里的参数,比如:

    • __VIEWSTATE、__VIEWSTATEGENERATOR、__EVENTVALIDATION:这些是ASP.NET用来维护页面状态的隐藏字段,每次请求都要从当前页面提取
    • __EVENTTARGET:对应分页按钮的ID,比如ctl00$contentPlaceHolderConteudo$grdAtivo$ctl01$ctl13(具体以抓包结果为准)
  2. 用Session保持会话:ASP.NET依赖会话Cookie,所以要用requests.Session()来维持会话状态,避免每次请求都重新初始化。

  3. 循环爬取所有页面:每次获取页面后,先提取表格数据,再检查是否有可用的下一页按钮,若有则构造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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.14 14:24:39