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

使用Jsoup抓取含__doPostBack的ASP.NET网站分页数据失败求助

问题

尝试用Java的Jsoup框架抓取https://pad.minem.gob.pe/REINFO_WEB/Index.aspx页面中名为stdregistro的表格数据并保存到数据库,但仅能获取第一页数据,无法获取后续分页数据。当前实现代码如下:

public class apiscraping {

    private static final String URL = "https://pad.minem.gob.pe/REINFO_WEB/Index.aspx";
    private static final String JDBC_URL = "jdbc:sqlserver://xxxxx:1433;databaseName=xxxxx;user=xxxx;password=xxxxx";
    
    public static void main(String[] args) {
        try {
            scrapeAndStore();
        } catch (Exception e) {
            System.out.println(e.getMessage());
            e.printStackTrace();
        }
    }
    
    private static void scrapeAndStore() throws Exception {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        int pageNumber = 1;
        boolean hasNextPage = true;

        while (hasNextPage) {
            Document doc = Jsoup.connect(URL).get();
            Elements rows = doc.select("#stdregistro tr");

            for (Element row : rows) {
                Elements columns = row.select("td");
                if (columns.size() > 0) {
                    String ruc = columns.get(2).text();
                    String mineroFormal = columns.get(3).text();
                    String codigoUnico = columns.get(4).text();
                    String nombre = columns.get(5).text();
                    String departamento = columns.get(6).text();
                    String provincia = columns.get(7).text();
                    String distrito = columns.get(8).text();
                    String estado = columns.get(9).text();

                    saveToDatabase(ruc, mineroFormal, codigoUnico, nombre, departamento, provincia, distrito, estado);
                }
            }

            hasNextPage = navigateToNextPage(httpClient, doc);
            System.out.println("Processed page: " + pageNumber);
            pageNumber++;
        }

        httpClient.close();
    }

    private static boolean navigateToNextPage(CloseableHttpClient httpClient, Document doc) throws IOException {
        Element nextButton = doc.select("#ImgBtnSiguiente").first();
        if (nextButton == null || nextButton.hasAttr("disabled")) {
            return false;
        }

        String viewState = doc.select("input[name=__VIEWSTATE]").val();
        String eventValidation = doc.select("input[name=__EVENTVALIDATION]").val();
        String viewStateGenerator = doc.select("input[name=__VIEWSTATEGENERATOR]").val();
        String txtpagina = doc.select("input[name=txtpagina]").val();
        int nextPage = Integer.parseInt(txtpagina) + 1;

        HttpPost post = new HttpPost(URL);
        List<BasicNameValuePair> params = new ArrayList<>();
        params.add(new BasicNameValuePair("__VIEWSTATE", viewState));
        params.add(new BasicNameValuePair("__EVENTVALIDATION", eventValidation));
        params.add(new BasicNameValuePair("__VIEWSTATEGENERATOR", viewStateGenerator));
        params.add(new BasicNameValuePair("txtpagina", String.valueOf(nextPage)));
        params.add(new BasicNameValuePair("ImgBtnSiguiente.x", "1"));
        params.add(new BasicNameValuePair("ImgBtnSiguiente.y", "1"));

        post.setEntity(new UrlEncodedFormEntity(params));
        CloseableHttpResponse response = httpClient.execute(post);

        Document nextDoc = Jsoup.parse(response.getEntity().getContent(), "UTF-8", URL);
        response.close();

        return nextDoc != null;
    }

    private static void saveToDatabase(String ruc, String mineroFormal, String codigoUnico, String nombre, String departamento, String provincia, String distrito, String estado) {
        String sql = "INSERT INTO OPE_REINFO (OPE_RUC, OPE_MINEROFORMAL, OPE_CODIGOUNICO, OPE_NOMBRE, OPE_DEPARTAMENTO, OPE_PROVINCIA, OPE_DISTRITO, OPE_ESTADO) VALUES (?, ?, ?, ?, ?, ?, ?, ?)";

        try (Connection conn = DriverManager.getConnection(JDBC_URL);
             PreparedStatement pstmt = conn.prepareStatement(sql)) {

            pstmt.setString(1, ruc);
            pstmt.setString(2, mineroFormal);
            pstmt.setString(3, codigoUnico);
            pstmt.setString(4, nombre);
            pstmt.setString(5, departamento);
            pstmt.setString(6, provincia);
            pstmt.setString(7, distrito);
            pstmt.setString(8, estado);

            pstmt.executeUpdate();
            System.out.println("Inserted record: RUC = " + ruc + ", Minero = " + mineroFormal + ", Código Único = " + codigoUnico);
        } catch (SQLException e) {
            System.out.println("Error inserting record: " + e.getMessage());
        }
    }
}

作为爬虫新手,认为问题出在表格数据的获取方式上,寻求解决建议与帮助。


解决方案

核心问题是每次循环都重新发起GET请求获取第一页,没有使用分页请求后的页面文档解析下一页数据。以下是具体修复方案:

1. 维护当前页面的文档对象

在scrapeAndStore方法中,初始获取第一页文档后,每次分页请求成功后更新当前文档,而非重复从原始URL拉取数据。

2. 修正分页逻辑的返回值

将navigateToNextPage方法改为返回下一页的Document对象(空值表示无下一页),替代原有的布尔值返回,确保后续循环能基于新页面解析数据。

3. 完善分页按钮的判断逻辑

部分ASP.NET页面的禁用按钮不会直接标记disabled属性,可能通过父元素或CSS类控制,需补充相关判断。

修改后的完整代码

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.io.IOException;

public class apiscraping {

    private static final String URL = "https://pad.minem.gob.pe/REINFO_WEB/Index.aspx";
    private static final String JDBC_URL = "jdbc:sqlserver://xxxxx:1433;databaseName=xxxxx;user=xxxx;password=xxxxx";
    
    public static void main(String[] args) {
        try {
            scrapeAndStore();
        } catch (Exception e) {
            System.out.println(e.getMessage());
            e.printStackTrace();
        }
    }
    
    private static void scrapeAndStore() throws Exception {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        int pageNumber = 1;
        Document currentDoc = Jsoup.connect(URL).get();
        boolean hasNextPage = true;

        while (hasNextPage) {
            // 解析当前页面的表格数据
            Elements rows = currentDoc.select("#stdregistro tr");
            for (Element row : rows) {
                Elements columns = row.select("td");
                if (columns.size() > 0) {
                    String ruc = columns.get(2).text();
                    String mineroFormal = columns.get(3).text();
                    String codigoUnico = columns.get(4).text();
                    String nombre = columns.get(5).text();
                    String departamento = columns.get(6).text();
                    String provincia = columns.get(7).text();
                    String distrito = columns.get(8).text();
                    String estado = columns.get(9).text();

                    saveToDatabase(ruc, mineroFormal, codigoUnico, nombre, departamento, provincia, distrito, estado);
                }
            }

            // 尝试获取下一页文档
            Document nextDoc = navigateToNextPage(httpClient, currentDoc);
            hasNextPage = nextDoc != null;
            currentDoc = nextDoc;
            
            System.out.println("Processed page: " + pageNumber);
            pageNumber++;
        }

        httpClient.close();
    }

    private static Document navigateToNextPage(CloseableHttpClient httpClient, Document doc) throws IOException {
        Element nextButton = doc.select("#ImgBtnSiguiente").first();
        // 检查按钮是否存在,以及是否可点击(兼容父元素禁用的情况)
        if (nextButton == null || nextButton.hasAttr("disabled") || nextButton.parent().hasAttr("disabled")) {
            return null;
        }

        // 获取ASP.NET表单所需的隐藏字段
        String viewState = doc.select("input[name=__VIEWSTATE]").val();
        String eventValidation = doc.select("input[name=__EVENTVALIDATION]").val();
        String viewStateGenerator = doc.select("input[name=__VIEWSTATEGENERATOR]").val();
        String txtpagina = doc.select("input[name=txtpagina]").val();
        int nextPage = Integer.parseInt(txtpagina) + 1;

        // 构造POST请求参数,随机x/y坐标更贴近真实用户操作
        HttpPost post = new HttpPost(URL);
        List<NameValuePair> params = new ArrayList<>();
        params.add(new BasicNameValuePair("__VIEWSTATE", viewState));
        params.add(new BasicNameValuePair("__EVENTVALIDATION", eventValidation));
        params.add(new BasicNameValuePair("__VIEWSTATEGENERATOR", viewStateGenerator));
        params.add(new BasicNameValuePair("txtpagina", String.valueOf(nextPage)));
        params.add(new BasicNameValuePair("ImgBtnSiguiente.x", String.valueOf((int)(Math.random()*100))));
        params.add(new BasicNameValuePair("ImgBtnSiguiente.y", String.valueOf((int)(Math.random()*100))));

        post.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
        CloseableHttpResponse response = httpClient.execute(post);

        // 解析下一页文档
        Document nextDoc = Jsoup.parse(response.getEntity().getContent(), "UTF-8", URL);
        response.close();

        // 验证下一页有效性(检查表格是否存在)
        return nextDoc.select("#stdregistro").size() > 0 ? nextDoc : null;
    }

    private static void saveToDatabase(String ruc, String mineroFormal, String codigoUnico, String nombre, String departamento, String provincia, String distrito, String estado) {
        String sql = "INSERT INTO OPE_REINFO (OPE_RUC, OPE_MINEROFORMAL, OPE_CODIGOUNICO, OPE_NOMBRE, OPE_DEPARTAMENTO, OPE_PROVINCIA, OPE_DISTRITO, OPE_ESTADO) VALUES (?, ?, ?, ?, ?, ?, ?, ?)";

        try (Connection conn = DriverManager.getConnection(JDBC_URL);
             PreparedStatement pstmt = conn.prepareStatement(sql)) {

            pstmt.setString(1, ruc);
            pstmt.setString(2, mineroFormal);
            pstmt.setString(3, codigoUnico);
            pstmt.setString(4, nombre);
            pstmt.setString(5, departamento);
            pstmt.setString(6, provincia);
            pstmt.setString(7, distrito);
            pstmt.setString(8, estado);

            pstmt.executeUpdate();
            System.out.println("Inserted record: RUC = " + ruc + ", Minero = " + mineroFormal + ", Código Único = " + codigoUnico);
        } catch (SQLException e) {
            System.out.println("Error inserting record: " + e.getMessage());
        }
    }
}

额外建议

  • 添加请求延迟:在分页请求之间添加1-2秒延迟,避免给服务器造成压力,降低被反爬拦截的风险。
  • 处理重复数据:给数据库中OPE_RUC或OPE_CODIGOUNICO字段添加唯一约束,避免重复插入。
  • 替换日志方式:使用SLF4J等日志框架替代System.out.println,便于后续排查问题。

内容的提问来源于stack exchange,提问作者DrGun Gun

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 14:15:54