现在您已经了解了Headless Chrome和Puppeteer的工作原理基础知识,让我们看一个更复杂的示例,其中我们实际上可以抓取一些数据。
首先,请查看此处的Puppeteer API文档。如您所见,有大量不同的方法我们可以使用不仅可以在网站上点击,还可以填写表单、输入内容并读取数据。
在本教程中,我们将抓取Books To Scrape,这是一家专门用于帮助人们练习抓取的假书店。
在同一目录中创建一个名为scrape.js
的文件,并插入以下样板代码:
const puppeteer = require('puppeteer');
let scrape = async () => {
// Actual Scraping goes Here...
// Return a value
};
scrape().then((value) => {
console.log(value); // Success!
});
理想情况下,经过第一个示例的学习,上述代码对您来说是有意义的。 如果不是,没关系!
我们上面所做的就是要求之前安装的 puppeteer 依赖项。然后我们有 scrape() 函数,我们将在其中输入抓取代码。该函数将返回一个值。最后,我们调用 scrape 函数并处理返回值(将其记录到控制台)。
我们可以通过在 scrape 函数中添加一行代码来测试上述代码。试试这个:
let scrape = async () => {
return 'test';
};
现在在控制台中运行 node scrape.js
。您应该会看到 test 被返回!完美,我们的返回值被记录到了控制台。现在我们可以开始填充 scrape 函数了。
步骤 1:设置
我们需要做的第一件事是创建浏览器的实例,打开一个新页面,并导航到一个 URL。我们是这样做的:
let scrape = async () => {
const browser = await puppeteer.launch({headless: false});
const page = await browser.newPage();
await page.goto('http://books.toscrape.com/');
await page.waitFor(1000);
// 抓取
browser.close();
return result;
};
很棒!让我们一行一行地分解它:
首先,我们创建浏览器并将 headless 模式设置为 false。这使我们可以完全看到正在发生的事情:
const browser = await puppeteer.launch({headless: false});
然后,我们在浏览器中创建一个新页面:
const page = await browser.newPage();
接下来,我们转到 books.toscrape.com URL:
await page.goto('http://books.toscrape.com/');
另外,我添加了 1000 毫秒的延迟。虽然通常不必要,但这可以确保页面上的所有内容都加载:
await page.waitFor(1000);
最后,完成所有工作后,我们将关闭浏览器并返回结果。
browser.close();
return result;
设置完成。现在,让我们开始抓取!
步骤 2:抓取
如您到目前所了解的,Books to Scrape 有大量真实书籍和这些书籍的虚拟数据。我们要做的是选择页面上的第一本书,并返回该书的标题和价格。这是 Books to Scrape 的主页。我有兴趣单击第一本书(如下红色突出显示)
查看 Puppeteer API,我们可以找到允许我们在页面上点击的方法:
page.click(selector[, options])
selector
要搜索要单击的元素的选择器。如果有多个元素满足选择器,将单击第一个元素。幸运的是,Google Chrome 开发者工具可以非常轻松地确定特定元素的选择器。只需右键单击图像并选择检查:
这将打开元素面板,元素被突出显示。您现在可以在左侧单击三个点,选择复制,然后选择复制选择器:
太棒了!我们现在已经复制了选择器,可以将点击方法插入程序中。就是这样:
await page.click('#default > div > div > div > div > section > div:nth-child(2) > ol > li:nth-child(1) > article > div.image_container > a > img');
我们的窗口现在将点击第一张产品图片并导航到该产品页面!
在新页面上,我们对产品标题和产品价格感兴趣 —— 如下红色部分所示
为了检索这些值,我们将使用 page.evaluate() 方法。该方法允许我们使用内置的 DOM 选择器,如 querySelector()。
我们要做的第一件事是创建 page.evaluate() 函数并将返回的值保存到名为 result 的变量中:
const result = await page.evaluate(() => {
// 返回一些内容
});
在我们的函数中,我们可以选择所需的元素。我们将再次使用 Google 开发者工具来解决这个问题。右键单击标题并选择检查:
如您在元素面板中所见,标题只是一个 h1 元素。我们现在可以使用以下代码选择该元素:
let title = document.querySelector('h1');
由于我们想要该元素中包含的文本,因此我们需要添加 .innerText —— 这是最终代码的样子:
let title = document.querySelector('h1').innerText;
类似地,我们可以通过右键单击并检查元素来选择价格:
如您所见,我们的价格具有 price_color 类。我们可以使用此类来选择元素及其内部文本。代码如下:
let price = document.querySelector('.price_color').innerText;
现在我们已经获取了所需的文本,可以在对象中返回它:
return {
title,
price
}
很棒!我们现在正在选择标题和价格,将它们保存到对象中,并将该对象的值返回到 result 变量中。将所有部分组合在一起的样子如下:
const result = await page.evaluate(() => {
let title = document.querySelector('h1').innerText;
let price = document.querySelector('.price_color').innerText;
return {
title,
price
}
});
现在唯一要做的就是返回我们的 result,以便将其记录到控制台:
return result;
您的最终代码应如下所示:
onst puppeteer = require('puppeteer');
let scrape = async () => {
const browser = await puppeteer.launch({headless: false});
const page = await browser.newPage();
await page.goto('http://books.toscrape.com/');
await page.click('#default > div > div > div > div > section > div:nth-child(2) > ol > li:nth-child(1) > article > div.image_container > a > img');
await page.waitFor(1000);
const result = await page.evaluate(() => {
let title = document.querySelector('h1').innerText;
let price = document.querySelector('.price_color').innerText;
return {
title,
price
}
});
browser.close();
return result;
};
scrape().then((value) => {
console.log(value); // Success!
});
您现在可以通过在控制台中输入以下命令来运行 Node 文件:
node scrape.js
// { title: 'A Light in the Attic', price: '£51.77' }
您应该会在屏幕上看到所选书籍的标题和价格被返回!您刚刚抓取了网络!
现在您可能会问自己,为什么我们要点击书籍,而标题和价格都显示在主页上? 为什么不从那里抓取它们? 而且,为什么不抓取所有书籍的标题和价格呢?
因为抓取网站有很多种方法! (另外,如果我们留在主页上,我们的标题会被截断)。 然而,这为您提供了练习新抓取技能的绝佳机会!
挑战
目标 —— 从主页抓取所有书籍的标题和价格,并将它们返回到数组中。 这是我的最终输出样子:
去吧! 看看您是否可以自己完成这项工作。 它与我们刚刚创建的程序非常相似。 如果您遇到困难,请向下滚动…
提示:
与前面的示例相比,此挑战的主要区别在于需要循环大量结果。 您可以按照以下方式设置代码以执行此操作:
const result = await page.evaluate(() => {
let data = []; // 创建一个空数组
let elements = document.querySelectorAll('xxx'); // 选择所有元素
// 循环每个产品
// 选择标题
// 选择价格
data.push({title, price}); // 将数据推送到我们的数组
return data; // 返回我们的数据数组
});
解决方案:
const puppeteer = require('puppeteer');
let scrape = async () => {
const browser = await puppeteer.launch({headless: false});
const page = await browser.newPage();
await page.goto('http://books.toscrape.com/');
const result = await page.evaluate(() => {
let data = []; // Create an empty array that will store our data
let elements = document.querySelectorAll('.product_pod'); // Select all Products
for (var element of elements){ // Loop through each proudct
let title = element.childNodes[5].innerText; // Select the title
let price = element.childNodes[7].children[0].innerText; // Select the price
data.push({title, price}); // Push an object with the data onto our array
}
return data; // Return our data array
});
browser.close();
return result; // Return the data
};
scrape().then((value) => {
console.log(value); // Success!
});