Puppeteer与Selenium

Puppeteer与Selenium的主要区别以及对比

您是否坚持在 Puppeteer 和 Selenium 之间做出网络抓取的决定?我们得到你。两者都是出色的浏览器自动化框架,在做出决定时考虑您的抓取需求以及可用资源非常重要。

在下表中查看Puppeteer 和 Selenium 之间的主要区别,然后让我们深入了解细节。

标准 Puppeteer Selenium
兼容语言 仅官方支持 JavaScript,但有非官方的 PHP 和 Python 端口 Java、Python、C#、Ruby、PHP、JavaScript 和 Kotlin
浏览器支持 Chromium 和实验性 Firefox 支持 Chrome、Safari、Firefox、Opera、Edge 和 IE
表现 比 Selenium 快 60% 快速的
操作系统支持 Windows、Linux 和 macOS Windows、Linux、macOS 和 Solaris
建筑学 具有无头浏览器实例的事件驱动架构 Web 驱动程序上的 JSONWire 协议,用于控制浏览器实例
先决条件 JavaScript包就够了 Selenium Bindings(用于选择的编程语言)和浏览器网络驱动程序
社区 与 Selenium 相比的小型社区 建立了一个文档集合以及一个庞大的社区

让我们继续详细讨论这些库,并为每个库做一个抓取示例,以展示它们在从网页中提取数据方面的效率。

Puppeteer

Puppeteer是一个自动化 Node.js 库,支持通过 DevTools 协议使用无头 Chromium 或 Chrome。它提供了在 Chrome 或 Chromium 中自动执行任务的工具,例如截屏、生成 PDF 和导航页面。Puppeteer 还可用于通过模拟用户交互(例如单击按钮、填写表格和验证结果是否按预期显示)来测试网页。

Puppeteer有什么优势?

Puppeteer 的优点是:

  • 它易于使用。
  • 无需设置,因为它与 Chromium 捆绑在一起。
  • 它默认无头运行,但也可以禁用。
  • Puppeteer 具有事件驱动的架构,无需在代码中手动调用睡眠。
  • 它可以截取屏幕截图、生成 PDF 并自动执行浏览器上的每个操作。
  • 它提供了诸如记录运行时和加载性能等管理功能,这对于优化和调试你的爬虫很有用。
  • Puppeteer 可以爬取 SPA(Single Page Applications)并生成预渲染的内容(即 Server-side Rendering)。
  • 您可以使用 WebTools 控制台记录您在浏览器上的操作来创建 Puppeteer 脚本。

Puppeteer的缺点是什么?

Puppeteer 的缺点是:

  • 与 Selenium 相比,Puppeteer 支持的浏览器更少。
  • Puppeteer 只专注于 JavaScript,尽管有PythonPHP的非官方端口。

使用 Puppeteer 进行网页抓取示例

让我们快速浏览一下Puppeteer 网络抓取教程,以深入了解 Puppeteer 与 Selenium 之间的性能比较。我们将从Danube 网站的Crime and Thriller类别中提取表项:

danube_store_crime_and_thrillers

首先,导入Puppeteer模块并创建一个异步函数来运行 Puppeteer 代码:

const puppeteer = require('puppeteer'); 
async function main() { 
    // write code here 
}

puppeteer.launch()完成后,启动浏览器实例并使用和方法创建页面newPage()

const browser = await puppeteer.launch({ headless: true }) 
const page = await browser.newPage();

使用该goto方法导航到创建的页面并传递waitUntil: 'networkidle2'以等待所有网络流量完成。然后等待ul带有sidebar-list类的元素加载列表项:

await page.goto('https://danube-webshop.herokuapp.com/', { waitUntil: 'networkidle2' }) 
await page.waitForSelector('ul.sidebar-list');

单击第一个元素列表导航到Crime & Thrillers类别的页面,然后添加waitForNavigation()等待网页加载的方法。接下来,通过将它们包装在一个中来处理异步操作Promise

await Promise.all([ 
    page.waitForNavigation(), 
    page.click("ul[class='sidebar-list'] > li > a"), 
]);

使用该方法等待图书预览加载waitForSelector()。然后使用该方法提取书籍querySelectorAll()并将它们存储在books变量中。最后,抓取每个预览的标题、作者、价格和评级,并打印出来:

await page.waitForSelector("li[class='preview']"); 
 
const books = await page.evaluateHandle( 
    () => [...document.querySelectorAll("li[class='preview']")] 
) 
 
const processed_data = await page.evaluate(elements => { 
    let data = [] 
    elements.forEach( element => 
        { 
            let title = element.querySelector("div.preview-title").innerHTML; 
            let author = element.querySelector("div.preview-author").innerHTML; 
            let rating = element.querySelector("div.preview-details > p.preview-rating").innerHTML; 
            let price = element.querySelector("div.preview-details > p.preview-price").innerHTML; 
 
            let result = {title: title, author: author, rating: rating, price: price} 
            data.push(result); 
        }) 
    return data 
}, books) 
 
console.log(processed_data) 
 
await page.close(); 
await browser.close();

现在,让我们把它包装在main函数中,然后我们可以使用main()它来运行它:

// import the puppeteer library 
const puppeteer = require('puppeteer'); 
 
// create the asynchronous main function 
async function main() { 
    // launch a headless browser instance 
    const browser = await puppeteer.launch({ headless: true }) 
 
    // create a new page object 
    const page = await browser.newPage(); 
 
    // navigate to the target URL, wait until the loading finishes 
    await page.goto('https://danube-webshop.herokuapp.com/', { waitUntil: 'networkidle2' }) 
 
    // wait for left-side bar to load 
    await page.waitForSelector('ul.sidebar-list'); 
 
    // click to the first element and wait for the navigation to finish 
    await Promise.all([ 
        page.waitForNavigation(), 
        page.click("ul[class='sidebar-list'] > li > a"), 
    ]); 
 
    // wait for previews to load 
    await page.waitForSelector("li[class='preview']"); 
 
    // extract the book previews 
    const books = await page.evaluateHandle( 
        () => [...document.querySelectorAll("li[class='preview']")] 
    ) 
 
    // extract the relevant data using page.evaluate 
    // just pass the elements as the second argument and processing function as the first argument 
    const processed_data = await page.evaluate(elements => { 
        // define an array to store the extracted data 
        let data = [] 
        // use a forEach loop to loop through every preview 
        elements.forEach( element => 
            { 
                // get the HTMl text of title, author, rating, and price data, respectively. 
                let title = element.querySelector("div.preview-title").innerHTML; 
                let author = element.querySelector("div.preview-author").innerHTML; 
                let rating = element.querySelector("div.preview-details > p.preview-rating").innerHTML; 
                let price = element.querySelector("div.preview-details > p.preview-price").innerHTML; 
 
                // build a dictionary and store the data as key:value pairs 
                let result = {title: title, author: author, rating: rating, price: price} 
                // append the data to the `data` array 
                data.push(result); 
            }) 
        // return the result (it will be stored in `processed_data` variable) 
        return data 
    }, books) 
 
    // print out the extracted data 
    console.log(processed_data) 
    // close the page and browser respectively 
    await page.close(); 
    await browser.close(); 
} 
 
// run the main function to scrape the data 
main();

继续运行代码。您的输出应如下所示:

[ 
    { 
        title: 'Does the Sun Also Rise?', 
        author: 'Ernst Doubtingway', 
        rating: '★★★★☆', 
        price: '$9.95' 
    }, 
    { 
        title: 'The Insiders', 
        author: 'E. S. Hilton', 
        rating: '★★★★☆', 
        price: '$9.95' 
    }, 
    { 
        title: 'A Citrussy Clock', 
        author: 'Bethany Urges', 
        rating: '★★★★★', 
        price: '$9.95' 
    } 
]

Selenium

Selenium是一种开源的端到端测试和 Web 自动化工具,通常用于网页抓取,其主要组件有 Selenium IDE、Selenium Webdriver 和 Selenium Grid。

Selenium IDE 用于在自动化之前记录操作,Selenium Grid 用于并行执行,Selenium WebDriver 用于在浏览器中执行命令。

Selenium的优点是什么?

Selenium的优点是:

  • 它易于使用。
  • Selenium 支持多种编程语言,如 Python、Java、JavaScript、Ruby 和 C#。
  • 它能够自动化 Firefox、Edge、Safari 甚至自定义 QtWebKit 浏览器。
  • 可以使用不同的技术将 Selenium 扩展到数百个实例,例如使用不同的浏览器设置设置云服务器。
  • 它可以在 Windows、macOS 和 Linux 上运行。

Selenium的缺点是什么?

Selenium的缺点是:

  • Selenium 设置方法很复杂。

使用 Selenium 的 Web 抓取示例

正如我们对 Puppeteer 所做的那样,让我们​​使用相同的目标站点运行一个关于使用 Selenium 进行网络抓取的教程。首先导入必要的模块,然后配置 Selenium 实例:

# for timing calculations and sleeping the scraper 
import time 
 
# import the Selenium 
from selenium import webdriver 
from selenium.webdriver.chrome.service import Service 
from selenium.webdriver.common.by import By 
# web driver manager: https://github.com/SergeyPirogov/webdriver_manager 
# will help us automatically download the web driver binaries 
# then we can use `Service` to manage the web driver's state. 
from webdriver_manager.chrome import ChromeDriverManager 
 
options = webdriver.ChromeOptions() 
options.headless = True

使用 WebDriverManager 安装 Chrome 网络驱动实例,然后定义 Chrome 服务并初始化网络驱动:

# this returns the path web driver downloaded 
chrome_path = ChromeDriverManager().install() 
chrome_service = Service(chrome_path) 
driver = webdriver.Chrome(service=chrome_service, options=options)

使用方法指向目标网站driver.get()

url = "https://danube-webshop.herokuapp.com/" 
driver.get(url)

导航到并单击 Crime & Thrillers 类别元素,然后添加time.sleep(1)以让抓取器休眠一秒钟。然后使用find_elements提取图书预览的方法:

time.sleep(1) 
crime_n_thrillers = driver.find_element(By.CSS_SELECTOR, "ul[class='sidebar-list'] > li") 
crime_n_thrillers.click() 
time.sleep(1) 
books = driver.find_elements(By.CSS_SELECTOR, "div.shop-content li.preview")

使用 BeautifulSoup 分别从元素中提取标题、作者、评级和定价,并将其包装在函数中extract

def extract(element): 
    title = element.find_element(By.CSS_SELECTOR, "div.preview-title").text 
    author = element.find_element(By.CSS_SELECTOR, "div.preview-author").text 
    rating = element.find_element(By.CSS_SELECTOR, "div.preview-details p.preview-rating").text 
    price = element.find_element(By.CSS_SELECTOR, "div.preview-details p.preview-price").text 
    # return the extracted data as a dictionary 
    return {"title": title, "author": author, "rating": rating, "price": price}

循环预览并提取数据,然后退出驱动程序:

extracted_data = [] 
for element in books: 
    data = extract(element) 
    extracted_data.append(data) 
driver.quit()

输出如下所示:

[ 
    {'title': 'Does the Sun Also Rise?', 'author': 'Ernst Doubtingway', 'rating': '★★★★☆', 'price': '$9.95'}, 
    {'title': 'The Insiders', 'author': 'E. S. Hilton', 'rating': '★★★★☆', 'price': '$9.95'}, 
    {'title': 'A Citrussy Clock', 'author': 'Bethany Urges', 'rating': '★★★★★', 'price': '$9.95'} 
]

Puppeteer 与 Selenium:速度比较

Puppeteer 比 Selenium 快吗?这是一个经常被问到的问题,直截了当地说:Puppeteer 比 Selenium 快。

为了比较 Puppeteer 与 Selenium 的速度,我们使用了 danube-store 沙盒并运行了我们在上面展示的脚本 20 次,并对执行时间取平均值。

为了找到运行 Selenium 脚本所需的时间,我们使用时间模块来设置开始和结束时间。我们把它start_time = time.time()放在脚本的开头和end_time = time.time()结尾。然后计算开始时间和结束时间之间的差异end_time - start_time

这是用于 Selenium 的完整脚本:

import time 
 
from selenium import webdriver 
from selenium.webdriver.chrome.service import Service 
from selenium.webdriver.common.by import By 
from selenium.webdriver.support import expected_conditions as EC 
from selenium.webdriver.support.ui import WebDriverWait 
# web driver manager: https://github.com/SergeyPirogov/webdriver_manager 
# will help us automatically download the web driver binaries 
# then we can use `Service` to manage the web driver's state. 
from webdriver_manager.chrome import ChromeDriverManager 
 
def extract(element): 
    title = element.find_element(By.CSS_SELECTOR, "div.preview-title").text 
    author = element.find_element(By.CSS_SELECTOR, "div.preview-author").text 
    rating = element.find_element(By.CSS_SELECTOR, "div.preview-details p.preview-rating").text 
    price = element.find_element(By.CSS_SELECTOR, "div.preview-details p.preview-price").text 
 
    return {"title": title, "author": author, "rating": rating, "price": price} 
 
# start the timer 
start = time.time() 
 
options = webdriver.ChromeOptions() 
options.headless = True 
# this returns the path web driver downloaded 
chrome_path = ChromeDriverManager().install() 
# define the chrome service and pass it to the driver instance 
chrome_service = Service(chrome_path) 
driver = webdriver.Chrome(service=chrome_service, options=options) 
 
url = "https://danube-webshop.herokuapp.com/" 
 
driver.get(url) 
# get the first page and click to the its link 
# first element will be the Crime & Thrillers category 
time.sleep(1) 
crime_n_thrillers = driver.find_element(By.CSS_SELECTOR, "ul[class='sidebar-list'] > li") 
print(crime_n_thrillers) 
crime_n_thrillers.click() 
time.sleep(1) 
# get the data div and extract the data using beautifulsoup 
books = driver.find_elements(By.CSS_SELECTOR, "div.shop-content li.preview") 
 
extracted_data = [] 
print(books) 
for element in books: 
    data = extract(element) 
    extracted_data.append(data) 
    print(data) 
 
end = time.time() 
 
print(f"The whole script took: {end-start:.4f}") 
 
driver.quit()

对于 Puppeteer 脚本,我们使用了Date对象。const start = Date.now();只需在脚本的开头和const end = Date.now();结尾添加即可。然后可以用 计算差值end-start

这是用于 Puppeteer 的脚本:

const puppeteer = require('puppeteer'); 
 
async function main() { 
    const start = Date.now(); 
 
    const browser = await puppeteer.launch({ headless: true }) 
    const page = await browser.newPage(); 
    await page.goto('https://danube-webshop.herokuapp.com/', { waitUntil: 'networkidle2' }) 
 
    await page.waitForSelector('ul.sidebar-list'); 
 
    await Promise.all([ 
        page.waitForNavigation(), 
        page.click("ul[class='sidebar-list'] > li > a"), 
    ]); 
 
    await page.waitForSelector("li[class='preview']"); 
    const books = await page.evaluateHandle( 
        () => [...document.querySelectorAll("li[class='preview']")] 
    ) 
 
    const processed_data = await page.evaluate(elements => { 
        let data = [] 
        elements.forEach( element => 
            { 
                let title = element.querySelector("div.preview-title").innerHTML; 
                let author = element.querySelector("div.preview-author").innerHTML; 
                let rating = element.querySelector("div.preview-details > p.preview-rating").innerHTML; 
                let price = element.querySelector("div.preview-details > p.preview-price").innerHTML; 
 
                let result = {title: title, author: author, rating: rating, price: price} 
                data.push(result); 
            }) 
        return data 
    }, books) 
 
    console.log(processed_data) 
    await page.close(); 
    await browser.close(); 
 
    const end = Date.now(); 
    console.log(`Execution time: ${(end - start) / 1000} s`); 
} 
 
main();

您可以在下面看到 Selenium 与 Puppeteer 的性能测试结果:

results

该图表显示Puppeteer 比 Selenium 快约 60%。因此,在这方面,为需要 Chromium 的项目扩展 Puppeteer 应用程序是网络抓取的最佳选择。

Puppeteer 与 Selenium:哪个更好?

那么在抓取方面,Selenium 和 Puppeteer 哪个更好?这个问题没有直接的答案,因为它取决于多种因素,比如长期库支持、跨浏览器支持和您的网络抓取需求。Puppeteer 速度很快,但与 Selenium 相比,它支持的浏览器更少。与 Puppeteer 相比,Selenium 还支持更多语言。

尽管使用 Puppeteer 或 Selenium 是网络抓取工具的不错选择,但扩展和优化您的网络抓取项目可能很困难,因为高级反机器人能够检测和阻止这些库。避免这种情况的最佳方法是使用网络抓取 API。

类似文章