如何使用Python抓取动态网页数据
在抓取动态网页内容时,您是否得到了糟糕的结果?不仅仅是你。爬取动态数据对于标准爬虫来说是一项具有挑战性的工作(至少可以说)。这是因为当发出 HTTP 请求时,JavaScript 在后台运行。
抓取动态网站需要在浏览器中渲染整个页面并提取目标信息。
加入我们这个循序渐进的教程,学习使用 Python 进行动态网页抓取所需的一切——注意事项、挑战和解决方案,以及介于两者之间的一切。
什么是动态网站?
动态网站是一种不直接在静态 HTML 中包含所有内容的网站。它使用服务器端或客户端来显示数据,有时基于用户的操作(例如,单击、滚动等)。
简而言之,这些网站会根据每个服务器请求显示不同的内容或布局。这有助于缩短加载时间,因为无需在用户每次想要查看“新”内容时都重新加载相同的信息。
如何识别它们?一种方法是在浏览器的命令面板中禁用 JavaScript。如果网站是动态的,内容就会消失。
让我们以Saleor React Storefront 为例。这是它的首页的样子:
注意标题、图像和艺术家的名字。
现在,让我们使用以下步骤禁用 JavaScript:
- 检查页面:右键单击并选择“检查”以打开 DevTools 窗口。
- 导航到命令面板:CTRL/CMD + SHIFT + P。
- 搜索“JavaScript”。
- 单击禁用 JavaScript。
- 点击刷新。
禁用 JavaScript 会删除所有动态 Web 内容。
使用 Python 进行动态 Web 抓取的替代方案
所以,你想用 Python 抓取动态网站……
由于Beautiful Soup或Requests等库不会自动获取动态内容,因此您有两种选择来完成任务:
- 将内容提供给标准库。
- 抓取时执行页面的内部 JavaScript。
然而,并不是所有的动态页面都是一样的。有些通过 JS API 呈现内容,可以通过检查“网络”选项卡访问这些内容。其他人将 JS 呈现的内容作为 JSON 存储在 DOM(文档对象模型)的某处。
好消息是,在这两种情况下,我们都可以解析 JSON 字符串以提取必要的数据。
请记住,有些情况下这些解决方案不适用。对于此类网站,您可以使用无头浏览器来呈现页面并提取所需数据。
使用 Python 爬取动态网页的替代方案是:
- 手动定位数据并解析 JSON 字符串。
- 使用无头浏览器执行页面的内部 JavaScript(例如,Selenium 或Pyppeteer,Puppeteer 的非官方 Python 端口)。
在 Python 中抓取动态网站的最简单方法是什么?
的确,无头浏览器可能很慢且性能密集。但是,他们取消了对网页抓取的所有限制。也就是说,如果您不计算反机器人检测。你不应该,因为我们已经告诉你如何绕过这些保护。
手动定位数据和解析 JSON 字符串假定可以访问动态数据的 JSON 版本。不幸的是,情况并非总是如此,尤其是在涉及高级单页应用程序 (SPA) 时。
更不用说模仿 API 请求是不可扩展的。他们通常需要 cookie 和身份验证以及其他可以轻松阻止您的限制。
在 Python 中抓取动态网页的最佳方式取决于您的目标和资源。如果您有权访问网站的 JSON 并希望提取单个页面的数据,则可能不需要无头浏览器。
然而,除了这一小部分情况,大多数时候使用 Beautiful Soup 和 Selenium 是最好和最简单的选择。
是时候动手了!准备好编写一些代码并准确了解如何使用 Python 抓取动态网站!
准备工作
要学习本教程,您需要满足一些要求。我们将使用以下工具:
- Python 3:最新版本的 Python 效果最好。在撰写本文时,即 3.11.2。
- selenium
- Webdriver Manager:这将确保浏览器和驱动程序的版本匹配。您不必为此手动下载 WebDriver。
pip install selenium webdriver-manager
方法#1:使用 Beautiful Soup 使用 Python 进行动态 Web 抓取
Beautiful Soup 可以说是最流行的用于抓取 HTML 数据的 Python 库。
要用它提取信息,我们需要目标页面的 HTML 字符串。但是,动态内容并不直接出现在网站的静态 HTML 中。这意味着Beautiful Soup 无法访问 JavaScript 生成的数据。
这是一个解决方案:如果网站使用 AJAX 请求加载内容,则可以从 XHR 请求中提取数据。
方法#2:使用 Selenium 在 Python 中抓取动态网页
要了解 Selenium 如何帮助您抓取动态网站,首先,我们需要检查常规库(例如 )如何Requests
与它们交互。
我们将使用Angular作为我们的目标网站:
让我们尝试抓取它Requests
并查看结果。在此之前,我们必须安装Requests
可以使用pip
命令执行的库。
pip install requests
下面是我们的代码:
import requests url = 'https://angular.io/' response = requests.get(url) html = response.text print(html)
如您所见,仅提取了以下 HTML:
<noscript> <div class="background-sky hero"></div> <section id="intro" style="text-shadow: 1px 1px #1976d2;"> <div class="hero-logo"></div> <div class="homepage-container"> <div class="hero-headline">The modern web<br>developer's platform</div> </div> </section> <h2 style="color: red; margin-top: 40px; position: relative; text-align: center; text-shadow: 1px 1px #fafafa; border-top: none;"> <b><i>This website requires JavaScript.</i></b> </h2> </noscript>
但是,检查该网站显示的内容比检索到的内容多。
这正是Requests
能够返回的。该库在从网站的静态 HTML 解析数据时没有发现任何错误,这正是它创建的目的。
在这种情况下,不可能达到与网站上显示的结果相同的结果。你能猜出为什么吗?没错,因为这是一个动态网页。
要访问全部内容并提取我们的目标数据,我们必须呈现 JavaScript。
是时候使用 Selenium 动态网页抓取来解决这个问题了。
我们将使用以下脚本来快速抓取我们的目标网站:
from selenium import webdriver from selenium.webdriver.chrome.service import Service as ChromeService from webdriver_manager.chrome import ChromeDriverManager url = 'https://angular.io/' driver = webdriver.Chrome(service=ChromeService( ChromeDriverManager().install())) driver.get(url) print(driver.page_source)
选择selenium中的元素
有多种方法可以访问 Selenium 中的元素。我们在使用 Python 中的 Selenium 进行网络抓取指南中深入讨论了这个问题。
尽管如此,我们还是会用一个例子来解释这一点。让我们只选择目标网站上的 H2:
在此之前,我们需要检查网站并确定我们要提取的元素的位置。
我们可以看到,class="text-container"
这些标题很常见。我们复制它并映射 H2 以使用 Chrome 驱动程序获取元素。
粘贴此代码:
from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.chrome.service import Service as ChromeService from webdriver_manager.chrome import ChromeDriverManager # instantiate options options = webdriver.ChromeOptions() # run browser in headless mode options.headless = True # instantiate driver driver = webdriver.Chrome(service=ChromeService( ChromeDriverManager().install()), options=options) # load website url = 'https://angular.io/' # get the entire website content driver.get(url) # select elements by class name elements = driver.find_elements(By.CLASS_NAME, 'text-container') for title in elements: # select H2s, within element, by tag name heading = title.find_element(By.TAG_NAME, 'h2').text # print H2s print(heading)
您将获得以下内容:
"DEVELOP ACROSS ALL PLATFORMS" "SPEED & PERFORMANCE" "INCREDIBLE TOOLING" "LOVED BY MILLIONS"
如何使用 Selenium 抓取无限滚动网页
当用户向下滚动到页面底部时,一些动态页面会加载更多内容。这些被称为“无限滚动网站”。爬行它们更具挑战性。为此,我们需要指示我们的蜘蛛滚动到底部,等待所有新内容加载,然后才开始抓取。
用一个例子来理解这一点。让我们使用Scraping Club的示例页面。
此脚本将滚动浏览前 20 个结果并提取其标题:
from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.chrome.service import Service as ChromeService from webdriver_manager.chrome import ChromeDriverManager import time options = webdriver.ChromeOptions() options.headless = True driver = webdriver.Chrome(service=ChromeService( ChromeDriverManager().install()), options=options) # load target website url = 'https://scrapingclub.com/exercise/list_infinite_scroll/' # get website content driver.get(url) # instantiate items items = [] # instantiate height of webpage last_height = driver.execute_script('return document.body.scrollHeight') # set target count itemTargetCount = 20 # scroll to bottom of webpage while itemTargetCount > len(items): driver.execute_script('window.scrollTo(0, document.body.scrollHeight);') # wait for content to load time.sleep(1) new_height = driver.execute_script('return document.body.scrollHeight') if new_height == last_height: break last_height == new_height # select elements by XPath elements = driver.find_elements(By.XPATH, "//div[@class='card-body']/h4/a") h4_texts = [element.text for element in elements] items.extend(h4_texts) # print title print(h4_texts)
备注:为无限滚动页面设置目标计数很重要,这样您就可以在某个时候结束脚本。_
在前面的示例中,我们使用了另一个选择器:By.XPath
。如前所述,它将基于 XPath 而不是类和 ID 来定位元素。检查页面,右键单击<div>
包含要抓取的元素的 a 并选择Copy Path。
你的结果应该是这样的:
['Short Dress', 'Patterned Slacks', 'Short Chiffon Dress', 'Off-the-shoulder Dress', ...]
这就是前 20 个产品的 H4!
_Remark:使用 Selenium 进行动态 Web 抓取可能会因连续的Selenium 更新而变得棘手。好好经历最新的变化。_
结论
动态网页无处不在。因此,您很有可能会在数据提取工作中遇到它们。请记住,熟悉它们的结构将帮助您确定检索目标信息的最佳方法。