出于兴趣,我决定为自己从该网站收集数据(名称、每晚价格、评级),但遇到了误解。我没有得到任何输出。我重写了其他库,但他们说这个更好。
const cheerio = require("cheerio");
let fs = require('fs');
const base = "https://ostrovok.ru/hotel/russia/adler/";
(async () => {
let url = "?page=1";
const data = [];
for (let i = 0; i < 176; i++) {
try {
console.log(base + url);
const res = await fetch(base + url);
if (!res.ok) {
break;
}
const $ = cheerio.load(await res.text());
const chunk = [...$("")].map(e =>
$(e).text().trim()
);
data.push(chunk);
url = $("#__next > div > div:nth-child(2) > div > div > div.Layout_content__9ap_g > div:nth-child(3) > div > div.HotelCard_headerArea__hlQPk > div > div.HotelCard_mainInfo__pNKYU > div.HotelCard_wrapTitle__t742O > h2 > a").attr("TEXT");
}
catch (err) {
console.error(err);
break;
}
}
console.log(JSON.stringify(data, null, 2));
fs.writeFile('numbers.txt', data.join('\n'), function(err) {
if (err) {
console.log(err);
}
});
})();
我本想看到一个数据列表,但我得到了[]。
base + url
始终使用 ?page=1
。尝试将索引变量插入:${base}?page=${i}
。
.attr("TEXT")
看起来不正确。我假设您希望每个页面上显示全部 20 个酒店名称,因此使用 [...$("...")].map(e => $(e).text())
将每个名称收集为单独的数组元素。
至于选择器,浏览器生成的超长选择器很容易出错。如果该链条中的任何假设发生变化,整个事情就会崩溃。使用
".HotelCard_title__cpfvk"
更安全,这就是识别您想要的元素所需的全部内容,仅此而已。
!res.ok
不足以确定分页何时结束。当结果列表为空时中断。
放在一起:
const cheerio = require("cheerio"); // ^1.0.0-rc.12
const {writeFile} = require("node:fs/promises");
const url = "<Your URL>";
(async () => {
const data = [];
for (let i = 1; i <= 1000; i++) {
const res = await fetch(`${url}?page=${i}`);
if (!res.ok) {
break;
}
const $ = cheerio.load(await res.text());
const names = [...$(".HotelCard_title__cpfvk")]
.map(e => $(e).text());
if (!names.length) {
break;
}
data.push(...names);
}
console.log(data);
await writeFile("numbers.txt", JSON.stringify(data));
})();
这需要一段时间才能运行,因此您可以并行化请求(冒着激怒服务器的风险),或者简单地添加一些日志以确保每个块都正常通过。
披露:我是链接博客文章的作者。
您传递了一个空选择器:
$("")
...不会选择任何内容。
您应该指定要选择哪些元素。例如,如果您想要酒店名称,那么也许:
$(".HotelCard_title__cpfvk")
或酒店名称和价格的组合:
$(".HotelCard_title__cpfvk,.HotelCard_ratePriceValue__s3HvW")
请注意,该网站具有国际化功能,因此您可能需要传递参数才能使用您选择的语言。但这取决于第三方网站...