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

Rust中用headless-chrome爬取网页:下一页按钮点击无响应

问题分析与解决方案

你的问题核心是点击下一页按钮后页面未触发导航,而是通过AJAX动态加载内容,但代码中用了wait_until_navigated()等待页面跳转,这会导致代码在内容更新前就继续执行,从而爬取到重复的旧数据。此外还有几个细节问题需要调整,下面逐一说明:

主要原因

  1. 页面是SPA动态加载:目标网站点击下一页后URL不会变化,而是通过前端JS异步加载新房源数据,没有触发浏览器的导航事件。此时wait_until_navigated()会超时或无效,因为根本没有页面跳转发生。
  2. 按钮可能未处于可交互状态:比如按钮不在视口内,或者前端框架拦截了原生click()事件,导致直接调用元素的click()方法无法触发正确行为。

解决方案

1. 替换页面导航等待为内容更新等待

既然没有页面跳转,就需要等待新的房源内容加载完成。可以通过等待当前页面的房源数量变化、页码更新,或者按钮状态变化(如变为不可点击)来判断内容是否更新。

2. 确保按钮可交互(滚动到视口+JS模拟点击)

有些网站的按钮需要在视口内才能触发点击事件,且部分前端框架会拦截原生DOM的click()方法,此时用JS模拟点击更可靠。

修改后的代码示例

use headless_chrome::Tab;
use std::error::Error;
use std::sync::Arc;
use std::{thread, time};

pub fn scrape(tab: Arc<Tab>) {
    let url = "https://www.immowelt.at/liste/bezirk-bruck-muerzzuschlag/haeuser/kaufen?ami=125&d=true&lids=513958&lids=513960&lids=513966&pami=750&pma=500000&pmi=10000&sd=DESC&sf=TIMESTAMP";

    if let Err(_) = tab.navigate_to(url) {
        println!("Failed to navigate to {}", url);
        return;
    }

    if let Err(e) = tab.wait_until_navigated() {
        println!("Failed to wait for navigation: {}", e);
        return;
    }

    if let Ok(gdpr_accept_button) = tab.wait_for_element(".sc-gsDKAQ.fILFKg") {
        if let Err(e) = gdpr_accept_button.click() {
            println!("Failed to click GDPR accept button: {}", e);
            return;
        }
        thread::sleep(time::Duration::from_secs(1));
    } else {
        println!("No GDPR popup to acknowledge found.");
    }

    let mut links = Vec::<String>::new();
    loop {
        let mut skipped: usize = 0;
        let new_urls_count: usize;
        match parse_list(&tab) {
            Ok(urls) => {
                new_urls_count = urls.len();
                for url in urls {
                    if !links.contains(&url) {
                        links.push(url);
                    } else {
                        skipped += 1;
                    }
                }
            }
            Err(_) => {
                println!("No more houses found: stopping");
                break;
            }
        }

        if skipped == new_urls_count {
            println!("Only previously loaded houses found: stopping");
            break;
        }

        match tab.wait_for_element("[class=\"arrowButton-20ae5\"]") {
            Ok(button) => {
                // 滚动到按钮可见位置
                if let Err(e) = button.call_js_fn("function() { this.scrollIntoView({ behavior: 'auto', block: 'center' }); }", vec![], true) {
                    println!("Failed to scroll to next button: {}", e);
                    break;
                }
                thread::sleep(time::Duration::from_millis(500));

                // 用JS模拟点击,替代原生click()
                if let Err(e) = button.call_js_fn("function() { this.click(); }", vec![], true) {
                    println!("Failed to click next page button via JS: {}", e);
                    break;
                } else {
                    println!("Clicked next page button");
                }

                // 等待新内容加载(生产环境可替换为等待新元素出现的逻辑)
                thread::sleep(time::Duration::from_secs(2));

                // 检查是否到达最后一页
                let is_disabled = button.call_js_fn("function() { return this.disabled || this.getAttribute('aria-disabled') === 'true'; }", vec![], true)
                    .ok()
                    .and_then(|res| res.value.as_bool())
                    .unwrap_or(false);
                if is_disabled {
                    println!("Reached last page: stopping");
                    break;
                }
            }
            Err(_) => {
                println!("No next page button found: stopping");
                break;
            }
        }
    }

    println!("Found {} houses:", links.len());
    for link in links {
        println!("\t{}", link);
    }
}

fn parse_list(tab: &Arc<Tab>) -> Result<Vec<String>, Box<dyn Error>> {
    let elements = tab.find_elements("div[class*=\"EstateItem\"] > a")?;

    let mut links = Vec::new();
    for element in elements {
        if let Some(url) = element
            .call_js_fn(
                &"function() { return this.getAttribute(\"href\"); }",
                vec![],
                true,
            )?
            .value
        {
            links.push(url.to_string());
        }
    }

    Ok(links)
}

关键修改说明

  • 移除wait_until_navigated(),替换为等待内容更新的逻辑(示例用固定等待,生产环境可改为等待新元素出现)。
  • 增加滚动到按钮的操作,确保按钮在视口内。
  • 用JS模拟点击替代原生click(),避免前端框架拦截事件。
  • 增加按钮状态检查,判断是否到达最后一页。

额外建议

  • 尽量避免固定等待时间,优先用wait_for_element等待新的房源元素或页码元素更新,这样更可靠。
  • 可以查看网站的网络请求,若存在直接获取房源数据的API接口,直接调用接口爬取效率会远高于模拟浏览器操作(需注意接口的认证规则)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 10:45:14