如何使用Python抓取JavaScript动态内容

如何使用Python抓取JavaScript动态内容

有没有试过用 Python 抓取 JavaScript 渲染的网页,但你碰壁了?嗯,这是可以理解的。抓取 JavaScript 呈现的网页可能很困难,因为网页上的数据是动态加载的。还有大量使用 React.js、Angular 和 Vue.js 等框架的 Web 应用程序,因此基于请求的抓取器很有可能在抓取 JS 呈现的页面时中断

如果您希望从这些网页中抓取 JavaScript 生成的内容,那么常规库和方法是不够的

在本教程中,我们将讨论如何使用 Python 抓取 JavaScript 呈现的网页。

为什么抓取 JavaScript 呈现的网页很困难?

当您向网页发送请求时,客户端会下载网站内容,这与 JavaScript 呈现的网站不同。如果客户端支持 JS,它将运行 JavaScript 代码来填充呈现的 HTML 内容。

JavaScript 呈现的网页并不会真正产生有价值的静态 HTML 内容,因此,纯 HTTP 请求是不够的,因为必须首先填充请求的内容。

这意味着您必须专门为每个要抓取的网站编写代码,这使得抓取 JavaScript 生成的内容变得困难。

当然,情况并非总是如此。呈现网页的方式有多种:

  • 服务器呈现:它为服务器上的页面生成完整的 HTML 以响应导航。这避免了在客户端获取额外的数据,因为它是在浏览器获得响应之前处理的。就像静态网站一样,我们可以通过发送简单的 HTTP 请求作为从服务器返回的完整内容来提取信息。
  • 静态渲染:这发生在构建时,为用户提供快速体验。主要缺点是必须为每个可能的 URL 生成单独的 HTML 文件。众所周知,从静态网站抓取数据非常容易。
  • 客户端呈现:页面使用 JavaScript 直接在浏览器中呈现。逻辑、数据获取和路由是在客户端而不是服务器上处理的。

如今,许多现代 Web 应用程序结合了这两种方法。这通常被称为通用渲染。

Universal Rendering 试图结合客户端和服务器端渲染来克服它们的缺点。React JS 和 Angular 等流行框架也支持它。例如,React 解析 HTML 并动态更新呈现的页面。这称为水合作用。

如何抓取 JavaScript 生成的内容

有多种方法可用于从网页中抓取 JavaScript 生成的内容,其中包括:

  • 使用后端查询。
  • 在 HTML 脚本标记中使用隐藏数据。

使用后端查询来抓取 JavaScript 呈现的网页

有时,诸如 React 之类的框架会使用后端查询来填充网页。可以在您的应用程序中使用这些 API 调用来直接从服务器获取数据。

由于这不是一种有保证的方法,您需要检查浏览器发出的请求以了解是否有可用的 API 后端。如果有,那么您可以使用与自定义查询相同的设置来从服务器获取数据。

使用脚本标签

可以使用 JSON 文件形式的脚本标记中的隐藏数据来抓取 JS 呈现的页面。不过,此方法可能需要深入搜索,因为您将检查加载网页中的 HTML 标记。

动态网页的 JS 代码可以在脚本标签中找到,并使用 BeautifulSoup Python 包提取。

Web 应用程序通常使用不同的身份验证方法来保护 API 端点,因此可能难以使用 API 来抓取 JavaScript 呈现的网页。

如果静态内容中存在编码的隐藏数据,您可能无法对其进行解码。在这种情况下,您需要一个可以呈现 JavaScript 以进行抓取的工具。

您可以使用基于浏览器的自动化工具,例如 Selenium、Playwright 和 Puppeteer。

在本指南中,我们将在 Python 中使用 Selenium,它也可用于 JavaScript 和 Node JS。

如何使用 Selenium 构建网络爬虫

Selenium 是一种浏览器自动化工具,主要用于 Web 测试。它像实际浏览器一样工作的能力使其成为网络抓取目的的最佳选择之一。由于它支持 JavaScript,因此使用 Selenium 抓取 JavaScript 呈现的网页应该不是问题。

我们不会深入研究和使用复杂的方法,但您可以查看我们完整的Selenium 指南以了解更多信息!

在本文中,我们将从Instacart中刮取Sprouts 的面包

首先,instacart 在服务器上呈现一个模板页面,然后在客户端由 JavaScript 填充它。

这是加载屏幕模板的样子:

loading-template

在填充 HTML 内容后,我们得到如下内容:

instacart

让我们开始使用 Python 上的 Selenium 抓取 JavaScript 呈现的网页!

安装要求

Selenium 用于控制网络驱动程序实例,因此我们需要浏览器的网络驱动程序。

我们将为此任务使用WebDriver Manager ,它将自动下载所需的 WebDriver。数据将CSV使用 Pandas 模块以某种格式存储。

首先,让我们使用以下命令安装软件包pip

pip install webdriver-manager selenium pandas

现在我们可以开始从网站上抓取一些 JavaScript 生成的内容。

首先导入必要的模块:

import time 
 
import pandas as pd 
from selenium import webdriver 
from selenium.webdriver import Chrome 
from selenium.webdriver.chrome.service import Service 
from selenium.webdriver.common.by import By 
from webdriver_manager.chrome import ChromeDriverManager

现在,让我们初始化 headless chrome 网络驱动程序:

# start by defining the options 
options = webdriver.ChromeOptions() 
options.headless = True # it's more scalable to work in headless mode 
# normally, selenium waits for all resources to download 
# we don't need it as the page also populated with the running javascript code. 
options.page_load_strategy = 'none' 
# this returns the path web driver downloaded 
chrome_path = ChromeDriverManager().install() 
chrome_service = Service(chrome_path) 
# pass the defined options and service objects to initialize the web driver 
driver = Chrome(options=options, service=chrome_service) 
driver.implicitly_wait(5)

初始化完成后,让我们连接到网站:

url = "https://www.instacart.com/store/sprouts/collections/bread?guest=True" 
 
driver.get(url) 
time.sleep(10)

你会注意到我们在连接到网站后添加了10 秒的延迟,这样做是为了让网络驱动程序完全加载网站。

在从单个列表中提取数据之前,我们需要找出产品的存储位置。

产品作为一个li元素存储在 中ul,它也在一个元素中div

main-content

我们可以div通过按子字符串过滤它们的类来过滤掉元素。

检查元素的class属性是否有ItemsGridWithPostAtcRecommendations文本。

可以为此使用 CSS 选择器,就像我们在这里所做的那样:

content = driver.find_element(By.CSS_SELECTOR, "div[class*='ItemsGridWithPostAtcRecommendations'")

我们可以用来*=检查特定子字符串是否在属性中。

li由于父级之外没有任何元素ul,让我们li从中提取元素content

breads = content.find_elements(By.TAG_NAME, "li")

继续,我们将从每个li元素中分别抓取 JavaScript 生成的数据:

bread

让我们从提取产品图像开始。

img中只有一个元素li。我们还可以在属性中看到图像 URL srcset

image

我们需要处理提取的数据。

经过一番挖掘,您可以看到图像存储在Cloudfront 的 CDN中。所以我们可以从那里提取 URL。

通过[注意逗号后的空格]拆分整个元素并处理第一个元素。

我们将 URL 拆分并/连接从 URL 开始的部分Cloudfront

def parse_img_url(url): 
    # get the first url 
    url = url.split(', ')[0] 
    # split it by '/' 
    splitted_url = url.split('/') 
    # loop over the elements to find where 'cloudfront' url begins 
    for idx, part in enumerate(splitted_url): 
        if 'cloudfront' in part: 
            # add the HTTP scheme and concatenate the rest of the URL 
            # then return the processed url 
            return 'https://' + '/'.join(splitted_url[idx:]) 
 
    # as we don't know if that's the only measurement to take, 
    # return None if the cloudfront couldn't be found 
    return None

现在我们可以使用以下函数提取 URL parse_img_url

img = element.find_element(By.TAG_NAME, "img").get_attribute("srcset") 
img = parse_img_url(img)

产品也有饮食属性。但正如您从绿色矩形中看到的那样,并非所有产品都有:

dietary-attributes

我们还可以利用 CSS 选择器div先获取元素,然后提取span其中的 s。

由于我们将在 Selenium 中使用该方法,因此如果没有任何元素,find_elements它将返回:Nonespan

# A>B means the B elements where A is the parent element. 
dietary_attrs = element.find_elements(By.CSS_SELECTOR, "div[class*='DietaryAttributes']>span") 
# if there aren't any, then 'dietary_attrs' will be None and 'if' block won't work 
# but if there are any dietary attributes, extract the text from them 
if dietary_attrs: 
    dietary_attrs = [attr.text for attr in dietary_attrs] 
else: 
    # set the variable to None if there aren't any dietary attributes found. 
    dietary_attrs = None

让我们转向价格……

它们存储在属性中div带有子字符串的元素中。ItemBCardDefaultclass

但它不是唯一的,所以我们将span使用 CSS 选择器直接获取其中的元素:

price

在抓取网页上的价格时检查元素是否已加载总是一个好主意。

一个简单的方法就是find_elements方法。

它返回一个空列表,这在构建用于数据提取的 API 时很有用:

# get the span elements where the parent is a 'div' element that 
# has 'ItemBCardDefault' substring in the 'class' attribute 
price = element.find_elements(By.CSS_SELECTOR, "div[class*='ItemBCardDefault']>span") 
# extract the price text if we could find the price span 
if price: 
    price = price[0].text 
else: 
    price = None

总结一下,让我们提取产品的名称和尺寸。

名称存储在唯一h2元素中。我们可以使用 CSS 选择器提取大小,因为它位于div具有子字符串的Size子字符串中:

name-and-size

现在完成后,让我们添加代码,如下所示:

name = element.find_element(By.TAG_NAME, "h2").text 
size = element.find_element(By.CSS_SELECTOR, "div[class*='Size']").text

最后,我们可以将所有这些包装在一个extract_data函数中:

def extract_data(element): 
    img = element.find_element(By.TAG_NAME, "img").get_attribute("srcset") 
    img = parse_img_url(img) 
 
    # A>B means the B elements where A is the parent element. 
    dietary_attrs = element.find_elements(By.CSS_SELECTOR, "div[class*='DietaryAttributes']>span") 
    # if there aren't any, then 'dietary_attrs' will be None and 'if' block won't work 
    # but if there are any dietary attributes, extract the text from them 
    if dietary_attrs: 
        dietary_attrs = [attr.text for attr in dietary_attrs] 
    else: 
        # set the variable to None if there aren't any dietary attributes found. 
        dietary_attrs = None 
 
    # get the span elements where the parent is a 'div' element that 
    # has 'ItemBCardDefault' substring in the 'class' attribute 
    price = element.find_elements(By.CSS_SELECTOR, "div[class*='ItemBCardDefault']>span") 
    # extract the price text if we could find the price span 
    if price: 
        price = price[0].text 
    else: 
        price = None 
 
    name = element.find_element(By.TAG_NAME, "h2").text 
    size = element.find_element(By.CSS_SELECTOR, "div[class*='Size']").text 
 
    return { 
        "price": price, 
        "name": name, 
        "size": size, 
        "attrs": dietary_attrs, 
        "img": img 
    }

让我们使用该函数来处理li在 main content 中找到的所有元素div

可以将结果存储在列表中并使用 Pandas 将它们转换为 DataFrame!

data = [] 
 
for bread in breads: 
    extracted_data = extract_data(bread) 
    data.append(extracted_data) 
 
df = pd.DataFrame(data) 
df.to_csv("result.csv", index=False)

一个能够从 JavaScript 呈现的网站中抓取数据的 Selenium 抓取工具!

现在,如果您一步一步地按照本教程进行操作,那么您的最终结果应该如下所示:

result

使用 Python 从 JavaScript 呈现的网页中抓取的数据。

此 GitHub 要点是本指南中使用的代码的完整版本。

使用Selenium的缺点

由于我们正在运行 Web 驱动程序实例,因此很难扩展应用程序。

更多实例将需要更多资源,这通常会使生产环境超载。

此外,与基于请求的解决方案相比,使用 Web 驱动程序更耗时。因此,通常建议在万不得已时使用浏览器自动化工具,例如 Selenium。

结论

本指南的目的是向您展示如何从动态加载的页面中抓取 JavaScript 生成的内容。

我们介绍了 JavaScript 渲染网站的工作原理。我们使用 Selenium 构建了一个工具来从动态加载的元素中提取数据。

快速回顾:

  1. 安装 Selenium 和 WebDriver Manager。
  2. 连接到目标 URL。
  3. 使用 CSS 选择器或Selenium 支持的其他方法抓取相关数据。
  4. 将数据保存并导出为 CSV 文件以备后用。

当然,您始终可以编写自己的代码并构建自己的网络抓取工具。但是网站采取了许多预防措施来阻止机器人。

在更大的范围内,抓取数十种产品既困难又耗时。最好的选择是使用ZenRows,它可以让您通过简单的 API 调用来抓取数据。它还自动处理反机器人措施。

类似文章