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

基于Puppeteer的墨西哥房产网站爬虫运行缓慢求助排查

问题:墨西哥房产网站爬虫速度过慢排查

我用Node.js的Puppeteer开发了针对墨西哥房产网站vivanuncios.com.mx的爬虫,但本地运行时速度极慢。尝试并发处理后速度更差,检查浏览器网络面板也没找到能获取所需信息的API接口。附上完整代码,求有经验的开发者帮忙排查问题:

const mysql = require('mysql2');
const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');

puppeteer.use(StealthPlugin());

function delay(time) {
    return new Promise(function(resolve) { 
        setTimeout(resolve, time)
    });
}

async function createInmueble(page, info){

    return await page.evaluate((info) => {
        let inmueble = {};

        inmueble.titulo = info.title;
        inmueble.precios = info?.priceOperationTypes?.[0]?.prices?.[0]?.amount.toString() || document.querySelector(".price-items").firstElementChild.textContent.trim().split(" ")[1];
        inmueble.moneda = info?.priceOperationTypes?.[0]?.prices?.[0]?.isoCode || document.querySelector(".price-items").firstElementChild.textContent.trim().split(" ")[0];
        inmueble.inmobiliaria = info?.publisher?.name || null;
        inmueble.tipo = info?.realEstateType?.name || null;
        inmueble.estado = info?.postingLocation?.location?.parent?.parent?.name || null;
        inmueble.municipio = info?.postingLocation?.location?.parent?.name || null;
        inmueble.zona = info?.postingLocation?.location?.name || null;
        inmueble.latitud = info?.postingLocation?.postingGeolocation?.geolocation?.latitude.toString() || null
        inmueble.longitud = info?.postingLocation?.postingGeolocation?.geolocation?.longitude.toString() || null
        inmueble.imagenes = info?.pictures?.map(picture => picture.url1200x1200) || null;
        inmueble.metros_totales = info?.mainFeatures?.CFT100?.value || "0";
        inmueble.metros_construidos = info?.mainFeatures?.CFT101?.value || "0";
        inmueble.banios = info?.mainFeatures?.CFT3?.value || "0";
        inmueble.estacionamientos = info?.mainFeatures?.CFT7?.value || "0";
        inmueble.recamaras = info?.mainFeatures?.CFT2?.value || "0";
        inmueble.antiguedad = info?.mainFeatures?.CFT5?.value || "0";
        inmueble.medio_banio = info?.mainFeatures?.CFT4?.value || "0";
        inmueble.direcciones = info?.postingLocation?.address?.name || null;
        inmueble.new_latitud = inmueble?.latitud || null;
        inmueble.new_longitud = inmueble?.longitud || null;
        inmueble.url = info?.seoAttributes?.canonical || null;
        inmueble.telefono = info?.whatsApp;
        inmueble.nombre_asesor = null;
        inmueble.email = null;

        return inmueble;
    }, info);
}

async function getInfoInmueble(page){
    console.log(page.url());

    const info = await page.evaluate(() => {
        return POSTING;
    });

    return info;
}

async function scopeEnlaces(page, enlaces){
    let info;
    let inmuebles = [];
    for(const enlace of enlaces){
        try{
            await page.goto(enlace);
            info = await getInfoInmueble(page);
            const inmueble = await createInmueble(page, info);
            inmuebles.push(inmueble);
        }
        catch(err){
            console.error("error", enlace);
        }
    }

    return inmuebles;
}

async function getEnlaces(page){
    const enlaces = await page.evaluate(() => {
        try{
            let div_padre = document.querySelector(".postings-container");
            const div_hijo = Array.from(div_padre.children);

            const enlaces = div_hijo.map(div => {
                const link_base = "https://www.vivanuncios.com.mx";
                const div_enlace = div.firstElementChild;
                const enlace = div_enlace.getAttribute("data-to-posting");

                return link_base+enlace;
            }).filter(enlace => enlace !== null);

            return enlaces;
        }
        catch(err){
            console.log("Error en page:", err);
            return [];
        }
    });

    return enlaces;
}

async function getInmuebles(page, url) {
    await page.goto(url);
    const enlaces = await getEnlaces(page);
    return await scopeEnlaces(page, enlaces);
}

async function postInmueble(inmueble){
    let query = `INSERT INTO inmueblesvivanuncio (titulo, precios, moneda, inmobiliaria, tipo, estado, municipio, zona, latitud, longitud, imagenes, metros_totales, metros_construidos, banios, estacionamientos, recamaras, antiguedad, medio_banio, direcciones, new_latitud, new_longitud, url, telefono, nombre_asesor, email) VALUES ('${inmueble.titulo}', '${inmueble.precios}', '${inmueble.moneda}', '${inmueble.inmobiliaria}', '${inmueble.tipo}', '${inmueble.estado}', '${inmueble.municipio}', '${inmueble.zona}', '${inmueble.latitud}', '${inmueble.longitud}', '${JSON.stringify(inmueble.imagenes)}', '${inmueble.metros_totales}', '${inmueble.metros_construidos}', '${inmueble.banios}', '${inmueble.estacionamientos}', '${inmueble.recamaras}', '${inmueble.antiguedad}', '${inmueble.medio_banio}', '${inmueble.direcciones}', '${inmueble.new_latitud}', '${inmueble.new_longitud}', '${inmueble.url}', '${inmueble.telefono}', '${inmueble.nombre_asesor}', '${inmueble.email}')`;
    
    connection.query(query, (error, results, fields) => {
        if (error){
            console.log("error", error);
        }
        else{
            console.log('Fila insertada con éxito');
        }
    });
}

async function scopeInmuebles(all_inmuebles){
    await all_inmuebles.forEach(async inmueble => {
        try{
            await postInmueble(inmueble);
        }
        catch(err){
            console.error("surgio un error");
        }
    });
}

async function getBotonSiguiente(page){
    const boton = await page.evaluate(() => {
        const boton = document.querySelector(".sc-n5babu-2.kNwjYM");
        if(boton){
            return true;
        }
        return false;
    });
    return boton
}

async function scanPage(page){
    const BASE_URL = 'https://www.vivanuncios.com.mx/s-casas-en-venta';
    let estado = "chihuahua";
    let i = 16;
    let all_inmuebles = [];
    let boton;

    do{
        let url = `${BASE_URL}/${estado}/page-${i}`;
        console.log("Pagina nueva: ", url, "\n");

        const inmuebles = await getInmuebles(page, url);
        all_inmuebles = all_inmuebles.concat(inmuebles);

        await page.goto(url);
        
        if(i%5 == 0){ 
            await scopeInmuebles(all_inmuebles);
            all_inmuebles = [];
        }

        boton = await getBotonSiguiente(page);
        if (boton) {
            i++;
        } else {
            console.log("No hay boton");
            await scopeInmuebles(all_inmuebles);
        }

    } while(boton && i <= 1000);

    return all_inmuebles;
}

async function scrapeo(){
    const browser = await puppeteer.launch({headless: false, executablePath: "C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe"});
    const page = await browser.newPage();
    
    const inmuebles = await scanPage(page);

    await browser.close();

    return inmuebles;
}

async function main(){
    const all_inmuebles = await scrapeo();
    connection.end();
}

const connection = mysql.createConnection({
    host: '127.0.0.1',
    user: 'root',
    password: 'CocacolA&_2016',
    database: 'InmobiliariaPrueba'
});

connection.connect();

main();

核心问题与优化方案

1. 单页面串行访问是速度瓶颈

当前用单个页面逐个打开房源链接,每个链接的加载、渲染都要耗时,这是最慢的模式。改成多页面并发处理,用浏览器创建多个页面同时处理不同房源,但要控制并发数(建议4-6个),避免触发反爬:

// 改写scopeEnlaces为并发模式,限制并发数
async function scopeEnlaces(browser, enlaces, concurrency = 4) {
    let inmuebles = [];
    for (let i = 0; i < enlaces.length; i += concurrency) {
        const batch = enlaces.slice(i, i + concurrency);
        const promises = batch.map(async (enlace) => {
            const page = await browser.newPage();
            try {
                await page.goto(enlace);
                const info = await getInfoInmueble(page);
                const inmueble = createInmueble(null, info); // 直接用info生成,无需再调用page.evaluate
                return inmueble;
            } catch (err) {
                console.error("error", enlace);
                return null;
            } finally {
                await page.close();
            }
        });
        const results = await Promise.all(promises);
        inmuebles.push(...results.filter(Boolean));
    }
    return inmuebles;
}

2. 禁用不必要的资源加载

Puppeteer默认加载所有资源(图片、CSS、视频等),极大拖慢速度。在页面初始化时设置拦截:

// 在创建页面后添加拦截逻辑
await page.setRequestInterception(true);
page.on('request', (req) => {
    const blockedTypes = ['image', 'stylesheet', 'font', 'media', 'script'];
    // 只保留必要的HTML和关键JS
    if (blockedTypes.includes(req.resourceType()) && !req.url().includes('POSTING')) {
        req.abort();
    } else {
        req.continue();
    }
});

3. 数据库插入逻辑错误

scopeInmuebles中用forEach+async/await无效,forEach不会等待异步操作完成,导致大量并发数据库请求。改用批量插入或for...of控制并发:

// 批量插入示例
async function postInmueblesBatch(inmuebles) {
    const values = inmuebles.map(inmueble => `('${inmueble.titulo}', '${inmueble.precios}', ...)`).join(',');
    const query = `INSERT INTO inmueblesvivanuncio (...) VALUES ${values}`;
    await connection.promise().query(query);
}

4. 重复的页面跳转

scanPage中调用getInmuebles已经执行了page.goto(url),之后又重复跳转,直接去掉第二次page.goto(url)即可。

5. 启用高效无头模式

当前用headless: false可视化浏览器,速度远低于无头模式。改成新无头模式并添加优化参数:

const browser = await puppeteer.launch({
    headless: 'new',
    executablePath: "C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe",
    args: [
        '--no-sandbox',
        '--disable-setuid-sandbox',
        '--disable-dev-shm-usage',
        '--disable-gpu',
        '--disable-extensions'
    ]
});

6. 去掉冗余的DOM查询

已经从页面拿到POSTING全局变量,createInmueble中的DOM查询完全多余,直接从info提取数据即可,无需再调用page.evaluate。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 19:12:02