Итак, я пытаюсь очистить страницу в Твиттере, чтобы получить твиты:
Я хочу получить элементы; текст, изображение, видео отдельно, но я продолжаю получать пустой массив
//Scraper.js
const puppeteer = require('puppeteer');
const fs = require('fs');
async function scrapeTwitter() {
try {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://twitter.com/coindesk');
await page.waitForLoadState('networkidle2');
const html = await page.content();
const $ = cheerio.load(html);
const tweets = $('[data-testid = "tweet"]');
const posts = [];
tweets.each(function () {
const text = $(this).find('.tweet-text').text().trim();
const image = $(this).find('.tweet-image').attr('src');
const video = $(this).find('.tweet-video').attr('src');
posts.push({ text, image, video });
});
await browser.close();
return posts;
} catch (error) {
console.error('Error scraping Twitter:', error);
return [];
}
}
module.exports = scrapeTwitter;





Я бы не стал использовать Cheerio с Puppeteer. Puppeteer уже работает с живой страницей, поэтому нет смысла сериализовать все это, чтобы выгрузить в статический анализатор HTML. Если вы хотите очистить больше твитов, вам нужно прокрутить вниз, а затем повторно сделать снимок всей страницы, чтобы синхронизировать Cheerio с динамическим сайтом.
Кроме того, избегайте ложных вызовов waitForLoadState. У goto уже есть опция {waitFor: "networkidle2"}, поэтому я бы использовал ее, а не добавлял вторую после нее, что может вызвать странные проблемы.
Кроме того, ваши селекторы мне ничего не возвращают. Возможно, попробуйте что-то вроде:
const puppeteer = require("puppeteer"); // ^22.6.0
const url = "<Your URL>";
let browser;
(async () => {
browser = await puppeteer.launch();
const [page] = await browser.pages();
const ua =
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36";
await page.setUserAgent(ua);
await page.setRequestInterception(true);
const blockedResources = ["stylesheet", "font"];
page.on("request", req => {
if (blockedResources.includes(req.resourceType())) {
req.abort();
} else {
req.continue();
}
});
await page.goto(url, {waitUntil: "domcontentloaded"});
const tweetSel = '[data-testid = "tweet"]';
await page.waitForSelector(tweetSel);
const data = [];
for (let i = 0; i < 50 && data.length < 20; i++) {
const preLen = await page.$$eval(
tweetSel,
els => els.length
);
await page.keyboard.press("PageDown");
try {
await page.waitForFunction(
`document.querySelectorAll('${tweetSel}').length > ${preLen}`,
{timeout: 2_000}
);
} catch (err) {
// ...
}
const chunk = await page.$$eval(tweetSel, els =>
els.map(el => ({
text: el
.querySelector('[data-testid = "tweetText"]')
.textContent.trim(),
photo: el
.querySelector('[data-testid = "tweetPhoto"] img')
?.getAttribute("src"),
}))
);
for (const e of chunk) {
if (data.every(f => f.text !== e.text)) {
data.push(e);
}
}
}
console.info(data);
console.info(data.length);
})()
.catch(err => console.error(err))
.finally(() => browser?.close());
Перехват ответов API, вероятно, более надежен, чем обращение к DOM; здесь есть много возможностей для улучшения, и время является несколько произвольным предположением.
Disclosure: I'm the author of the linked blog posts.
Если я зайду на эту страницу в Твиттере и запрошу
[data-testid], я не получу результатов, так что это кажется ожидаемым поведением. Вам нужно выбрать то, что существует.