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

如何修复Puppeteer Node.js截取Twitter推文的截图截断问题

修复Puppeteer抓取Twitter推文截图随机截断问题

问题现状

使用提供的Puppeteer代码滚动Twitter(X)时间线并截图推文时,截图会在随机位置被截断,无法稳定完整捕获每条推文。

核心修复点

  • 确保元素渲染稳定:替换固定延时,等待元素完全进入视口且布局稳定后再截图
  • 优化滚动加载逻辑:改用更可靠的滚动方式,等待新内容加载完成后再提取元素
  • 修正截图参数:直接使用元素真实的边界框,不手动限制宽高,避免截断
  • 改进重复判断机制:使用更可靠的推文唯一标识,避免重复处理

修复后完整代码

import puppeteer, { BoundingBox, ElementHandle, Page } from 'puppeteer';

export async function getTweets(url: string = "https://x.com/dell") {
    const processed: string[] = [];
    const browser = await puppeteer.launch({ headless: false });
    const page = await browser.newPage();
    await page.setViewport({ width: 1080, height: 1080 }); // 增大视口减少滚动次数
    await page.goto(url, { waitUntil: 'networkidle2' }); // 等待网络空闲确保页面加载完成

    // 等待推文容器加载
    await page.waitForSelector('article[data-testid="tweet"]');

    await scrollAndExtract(page, processed);
    let scrollCount = 0;
    const maxScrolls = 5; // 控制最大滚动次数

    while (scrollCount < maxScrolls) {
        await autoScroll(page);
        // 等待新推文加载
        await page.waitForFunction(
            (prevCount) => document.querySelectorAll('article[data-testid="tweet"]').length > prevCount,
            {},
            (await page.$$('article[data-testid="tweet"]')).length
        );
        await scrollAndExtract(page, processed);
        scrollCount++;
    }

    await browser.close();
}

async function scrollAndExtract(page: Page, processed: string[]) {
    const articleArray = await page.$$('article[data-testid="tweet"]');
    console.log(`当前已加载推文数量: ${articleArray.length}`);

    for (const element of articleArray) {
        // 获取推文唯一标识
        const tweetId = await page.evaluate(el => el.dataset.id || el.getAttribute('aria-labelledby')?.split(' ')[0], element);
        if (!tweetId || processed.includes(tweetId)) continue;

        processed.push(tweetId);
        // 滚动元素到视口中心,确保完全显示
        await element.scrollIntoView({ behavior: 'smooth', block: 'center' });
        // 等待元素布局稳定
        await page.waitForTimeout(500);
        // 验证元素是否在视口内
        const isInViewport = await page.evaluate(el => {
            const rect = el.getBoundingClientRect();
            return rect.top >= 0 && rect.bottom <= window.innerHeight;
        }, element);
        if (!isInViewport) continue;

        const imagePath = `./report/${tweetId}.jpg`;
        console.log(`截图推文: ${tweetId}`);
        await screenshot(page, element, imagePath);
    }
}

async function autoScroll(page: Page) {
    console.log("向下滚动加载更多推文...");
    // 滚动到页面底部,模拟用户滚动行为
    await page.evaluate(() => {
        window.scrollBy({ top: window.innerHeight * 1.5, behavior: 'smooth' });
    });
    // 等待滚动完成和内容加载
    await page.waitForTimeout(1000);
}

async function screenshot(page: Page, element: ElementHandle<HTMLElement>, path: string) {
    const boundingBox = await element.boundingBox();
    if (!boundingBox) return;

    // 直接使用元素完整的边界框,不做截断
    await page.screenshot({
        path: path,
        clip: {
            x: boundingBox.x,
            y: boundingBox.y,
            width: boundingBox.width,
            height: boundingBox.height,
        },
        omitBackground: true, // 可选:去除背景色,减小文件大小
    });
}

关键修复说明

  1. 页面加载优化:使用networkidle2等待网络空闲,确保页面基础资源加载完成
  2. 滚动逻辑改进:改用window.scrollBy滚动更大距离,等待新推文加载完成后再继续提取
  3. 元素稳定性处理:滚动到元素中心后等待布局稳定,验证元素在视口内再截图
  4. 唯一标识优化:优先使用推文的data-id属性,更可靠避免重复
  5. 截图参数修正:直接使用元素的完整边界框,不再手动限制宽高,确保截图完整

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 04:21:02