BeautifulSoup是网络抓取中最流行的库之一。它用于通过 Python 脚本或使用CSS 选择器来解析 HTML 文档中的数据。 在本实用教程中,我们将涵盖以下主题:
- HTML 结构概述以及如何导航它们。
- 如何使用 Beautifulsoup 的
find
和find_all
方法解析 HTML。 - 如何使用 Beautifulsoup 的 CSS 选择器
select
和select_one
方法。 - Beautifulsoup 附加功能,如:文本清理、漂亮的格式化和 HTML 树修改
最后,为了巩固所有这些,我们将看一个真实的网络抓取项目示例,并从remotepython.com抓取工作列表数据。
什么是网页抓取?
Web 抓取是从 Web 收集数据的过程。换句话说,它是一个从网站(通常是 HTML 页面)检索数据并将其解析为特定数据的程序。Web 抓取用于为市场研究、房地产分析、商业智能等收集数据集。 我们今天要介绍的工具 – beautifulsoup4 – 用于解析收集到的 HTML 数据,而且它非常擅长。一起来看看吧!
设置
在本文中,我们将使用 Python 3.7+ 和beautifulsoup4
. 我们还将requests
在示例中使用包来下载 Web 内容。所有这些都可以通过控制台命令安装pip install
:
$ pip install bs4 requests
或者,在使用poetry包管理器的新虚拟环境中:
$ mkdir bs4-project && cd bs4-project $ poetry init -n --dependency bs4 requests
快速开始
在我们开始之前,让我们看一个快速的 beautifulsoup 示例,了解这个 python 包的功能:
html = """ <div class="product"> <h2>Product Title</h2> <div class="price"> <span class="discount">12.99</span> <span class="full">19.99</span> </div> </div> """ from bs4 import BeautifulSoup soup = BeautifulSoup(html) product = { "title": soup.find(class_="product").find("h2").text, "full_price": soup.find(class_="product").find(class_="full").text, "price": soup.select_one(".price .discount").text, } print(product) { "title": "Product Title", "full_price": "19.99", "price": "12.99", }
这个例子说明了我们可以多么轻松地解析网页以获取产品数据和 beautifulsoup4 的一些关键功能。为了完全理解 HTML 解析,让我们来看看是什么让 HTML 成为如此强大的数据结构。
HTML 是如何解析的?
HTML(超文本标记语言)旨在易于机器阅读和解析。换句话说,HTML 遵循树状结构的节点(HTML 标记)及其属性,我们可以轻松地以编程方式导航它们。 让我们从一个小示例页面开始,并说明其结构:
<head> <title> </title> </head> <body> <h1>Introduction</h1> <div> <p>some description text: </p> <a class="link" href="http://example.com">example link</a> </div> </body>
在这个简单网页源代码的基本示例中,我们仅通过查看缩进就可以看到文档已经类似于数据树。 让我们更进一步说明这一点:
在这里,我们可以更容易地理解它——它是一棵节点树,每个节点包括:
- 节点名称 – 又名 HTML 标签,例如
<div>
- 自然属性 – 文本值和位置。
- 关键字属性 – 关键字值
class
,例如href
等。
有了这个基本的了解,我们就可以看到python和beautifulsoup是如何帮助我们遍历这棵树来提取我们需要的数据的。
使用 BeautifulSoup 解析 HTML
Beautifulsoup 是一个用于解析 HTML 文档的 python 库。使用它我们可以导航 HTML 数据以提取/删除/替换特定的 HTML 元素。它还带有实用功能,如可视化格式化和解析树清理。
提示:选择后端
Bs4 相当大,并带有几个后端,它们提供的 HTML 解析算法略有不同:
- html.parser – python 的内置解析器,它是用 python 编写的,这意味着它始终可用,尽管它有点慢。
- lxml – 用于 HTML 解析的基于 C 的库:非常快,但安装起来可能有点困难。
- html5lib – 另一个用 python 编写的解析器,旨在完全兼容 html5。
总而言之,最好坚持使用lxml
后端,因为它要快得多,但html.parser
对于较小的项目来说仍然是一个不错的选择。至于html5lib
它最适合需要符合 html5 规范的边缘情况。 每次我们创建Beautifulsoup对象时都可以选择后端:
from bs4 import BeautifulSoup html = "<h1>test</h1>" # automatically select the backend (not recommended as it makes code hard to share) soup = BeautifulSoup(html) # lxml - most commonly used backend soup = BeautifulSoup(html, "lxml") # html.parser - included with python soup = BeautifulSoup(html, "html.parser") # html5lib - parses pages same way modern browser does soup = BeautifulSoup(html, "html5lib")
现在,我们的soup已经热好了,让我们看看它能做什么!
基本导航
让我们从一段非常简单的 HTML 数据开始,它包含文章的一些基本元素:标题、副标题和一些文本段落:
from bs4 import BeautifulSoup # this is our HTML page: html = """ <head> <title class="page-title">Hello World!</title> </head> <body> <div id="content"> <h1>Title</h1> <p>first paragraph</p> <p>second paragraph</p> <h2>Subtitle</h2> <p>first paragraph of subtitle</p> </div> </body> """ # 1. build soup object from html text soup = BeautifulSoup(html, 'lxml') # then we can navigate the html tree via python API: # for example title is under `head` node: print(soup.head.title) '<title class="page-title">Hello World!</title>' # this gives us a whole HTML node but we can also just select the text: print(soup.head.title.text) "Hello World!" # or it's other attributes: print(soup.head.title["class"]) "page-title"
上面的示例探讨了所谓的“基于点的导航”——我们可以从上到下遍历整个 HTML 树,甚至可以选择特定的属性,如文本内容和类名或数据属性。 然而,在现实生活中,我们将处理更大的页面,所以想象一下,如果一个页面解析树有 7 个深度级别,我们将不得不编写如下内容:
soup.body.div.div.div.p.a['href']
这很不方便,因为这个Beautifulsoup引入了两个特殊的方法,称为find()
和find_all()
:
from bs4 import BeautifulSoup html = """ <head> <title class="page-title">Hello World!</title> </head> <body> <div id="content"> <h1>Title</h1> <p>first paragraph</p> <p>second paragraph</p> <h2>Subtitle</h2> <p>first paragraph of subtitle</p> </div> </body> """ soup = BeautifulSoup(html, 'lxml') soup.find('title').text "Hello World" # we can also perform searching by attribute values soup.find(class_='page-title').text "Hello World" # We can even combine these two approaches: soup.find('div', id='content').h2.text "Subtitle" # Finally, we can perform partial attribute matches using regular expressions # let's select paragraphs that contain the word "first" in it's text: soup.find_all('p', text=re.compile('first')) ["<p>first paragraph</p>", "<p>first paragraph of subtitle</p>"]
如您所见,通过将beautiful soups dot的导航与魔术find()
和find_all()
方法相结合,我们可以轻松可靠地导航 HTML 树以非常轻松地提取特定信息!
使用 CSS 选择器
另一种在页面结构深处查找特定元素的方法是通过 beautifulsoupselect()
和select_one()
函数使用 CSS 选择器:
from bs4 import BeautifulSoup html = """ <head> <title class="page-title">Hello World!</title> </head> <body> <div id="content"> <h1>Title</h1> <p>first paragraph</p> <p>second paragraph</p> <h2>Subtitle</h2> <p>first paragraph of subtitle</p> </div> </body> """ soup = BeautifulSoup(html, 'lxml') soup.select_one('title').text "Hello World" # we can also perform searching by attribute values such as class names soup.select_one('.page-title').text "Hello World" # We can also find _all_ amtching values: for paragraph in soup.select('#content p'): print(paragraph.text) "first paragraph" "second paragraph" "first paragraph of subtitile" # We can also combine CSS selectors with find functions: import re # select node with id=content and then find all paragraphs with text "first" that are under it: soup.select_one('#content').find_all('p', text=re.compile('first')) ["<p>first paragraph</p>", "<p>first paragraph of subtitle</p>"]
CSS 选择器是解析 HTML 网络数据的标准方式,结合 beautiful soup 的find
方法,我们甚至可以轻松解析最复杂的 HTML 数据结构。
接下来,让我们看看 bs4 的一些特殊附加功能和一些现实生活中的网络抓取场景。
Beautifulsoup 的附加功能
除了是一个出色的 HTML 解析器之外,bs4 还包含许多与 HTML 相关的实用程序和辅助函数。让我们快速概述一下网络抓取中经常使用的实用程序。
提取所有文本
复杂的文本结构通常通过多个难以提取的 HTML 节点表示。 为此,BeautifulSoup 的get_text()
方法可用于提取所有 HTML 元素的文本。例如:
from bs4 import BeautifulSoup html = """ <div> <a>The Avangers: </a> <a>End Game</a> <p>is one of the most popular Marvel movies</p> </div> """ soup = BeautifulSoup(html, 'lxml') # join all text values with space, and strip leading/trailing whitespace: soup.div.get_text(' ', strip=True) 'The Avangers: End Game is one of the most popular Marvel movies'
使用get_text()
方法,我们可以提取所选节点下的所有文本并对其进行良好的格式化
漂亮的格式化 HTML
另一个很棒的实用程序是 HTML 可视格式化程序,它可以美化 HTML 输出。 通常,在进行网络抓取时,我们希望在某处存储或显示 HTML 内容,以便使用其他工具获取或调试。 该.prettify()
方法重构 HTML 输出,使其更易于人类阅读:
from bs4 import BeautifulSoup html = """ <div><h1>The Avangers: </h1><a>End Game</a><p>is one of the most popular Marvel movies</p></div> """ soup = BeautifulSoup(html) soup.prettify() """ <html> <body> <div> <h1> The Avangers: </h1> <a> End Game </a> <p> is one of the most popular Marvel movies </p> </div> </body> </html> """
选择性解析
一些网络抓取工具可能不需要整个 HTML 文档来提取有价值的数据。例如,通常在网络爬取时,我们只想解析<a>
链接的节点。 对于这个 BeautifulSoup 提供的SoupStrainer
对象允许将我们的解析限制为仅特定的 HTML 元素:
from bs4 import BeautifulSoup, SoupStrainer html = """ <head><title>hello world</title></head> <body> <div> <a>Link 1</a> <a>Link 2</a> <div> <a>Link 3</a> /div> </div> </body> """ link_strainer = SoupStrainer('a') soup = BeautifulSoup(html, parse_only=link_strainer) print(soup) #<a>Link 1</a><a>Link 2</a><a>Link 3</a>
修改 HTML
由于 bs4 将所有 HTML 树加载为 Python 对象,我们可以轻松修改、删除或替换附加到它的每个节点:
from bs4 import BeautifulSoup html = """ <div> <button class="flat-button red">Subscribe</button> </div> """ soup = BeautifulSoup(html) soup.div.button['class'] = "shiny-button blue" soup.div.button.string = "Unsubscribe" print(soup.prettify()) # <html> # <body> # <div> # <button class="shiny-button blue"> # Unsubscribe # </button> # </div> # </body> # </html>
在本节中,我们介绍了 4 个最常见的附加功能。如您所见,这个库不仅仅是一个 HTML 解析库,而是一个完整的 HTML 套件!最后,让我们用一个真实世界的例子来结束这篇文章。
示例项目
让我们把我们学到的东西用起来。在这个真实世界的例子中,我们将从https://www.remotepython.com/jobs/抓取 Python 工作列表。
我们想刮这些字段,但是我们如何用Beautifulsoup找到它们呢? 为此,我们可以使用网络浏览器的开发人员工具轻松了解我们将要抓取的网站的 HTML 结构。每个浏览器都带有一个开发工具套件——让我们快速了解一下如何在网络抓取中使用它。
import re import json from urllib.parse import urljoin import requests from bs4 import BeautifulSoup url = "https://www.remotepython.com/jobs/" response = requests.get(url) soup = BeautifulSoup(response.text, "lxml") results = [] # first find all job listing boxes: job_listing_boxes = soup.find_all(class_="item") # then extract listing from each box: for item in job_listing_boxes: parsed = {} if title := item.find("h3"): parsed["title"] = title.get_text(strip=True) if item_url := item.find("h3").a["href"]: parsed["url"] = urljoin(url, item_url) if company := item.find("h5").find("span", class_="color-black"): parsed["company"] = company.text if location := item.select_one("h5 .color-white-mute"): parsed["location"] = location.text if posted_on := item.find("span", class_="color-white-mute", text=re.compile("posted:", re.I)): parsed["posted_on"] = posted_on.text.split("Posted:")[-1].strip() results.append(parsed) print(results) [{ "title": "Hiring Senior Python / DJANGO Developer", "url": "https://www.remotepython.com/jobs/3edf4109d642494d81719fc9fe8dd5d6/", "company": "Mathieu Holding sarl", "location": "Rennes, France", "posted_on": "Sept. 1, 2022" }, ... # etc. ]在上面的爬虫中,我们使用请求来检索页面数据并将其加载到Beautifulsoup中。然后,我们找到所有包含工作列表数据的框,遍历它们并解析工作详细信息。
常问问题
让我们通过看一些关于使用 beautifulsoup 进行网页抓取的常见问题来结束这个 beautifulsoup 教程,我们无法将其纳入本指南:BeautifulSoup 有哪些替代品?
对于 Python,有一些替代方案,例如scrapy使用的parsel(基于lxml ) 。另一种选择是html5lib,它可以被 beautifulsoup4 用作后端。 其他语言也有类似的库,如Ruby 中的nokogiri、PHP 中的DomCrawler 、 R 中的rvest等。如何使用beautifulsoup解析HTML表格数据?
最常见的解析目标之一是 HTML 表格,它可以很容易地被 bs4 解析。让我们看一下这个例子:from bs4 import BeautifulSoup import requests soup = BeautifulSoup(requests.get("https://www.w3schools.com/html/html_tables.asp").text) # first we should find our table object: table = soup.find('table', {"id": "customers"}) # then we can iterate through each row and extract either header or row values: header = [] rows = [] for i, row in enumerate(table.find_all('tr')): if i == 0: header = [el.text.strip() for el in row.find_all('th')] else: rows.append([el.text.strip() for el in row.find_all('td')]) print(header) ['Company', 'Contact', 'Country'] for row in rows: print(row) ['Alfreds Futterkiste', 'Maria Anders', 'Germany'] ['Centro comercial Moctezuma', 'Francisco Chang', 'Mexico'] ['Ernst Handel', 'Roland Mendel', 'Austria'] ['Island Trading', 'Helen Bennett', 'UK'] ['Laughing Bacchus Winecellars', 'Yoshi Tannamuri', 'Canada'] ['Magazzini Alimentari Riuniti', 'Giovanni Rovelli', 'Italy']上面,我们首先使用
find
函数来查找表本身。然后,我们找到所有的表行并遍历它们以提取它们的文本内容。请注意,第一行可能是表格标题。
BeautifulSoup 可以和 Scrapy 一起使用吗?
是的,虽然 scrapy 有自己的 HTML 解析库,称为parsel,它比 beautifulsoup4 更受欢迎。为什么 beautifulsoup 中的 HTML 与网络浏览器中的 HTML 不同?
我们经常可以看到 BeautifulSoup 和网络浏览器之间的 HTML 树不匹配。 例如,如果我们在浏览器中打开开发者工具(F12 键),页面包含更复杂的结构,如表格标签(例如这个 w3school 表格演示页面)),我们可能会注意到 HTML 树略有不同。 这是由于各个后端的 HTML 解释规则不同造成的。为了绕过这个使用html5lib
后端,它使 HTML 树与 Web 浏览器保持一致。
为什么 beautifulsoup 看不到一些 HTML 元素?
这些元素很可能是由 javascript 动态加载的。由于 python 不执行 javascript,因此它不会看到该内容。有关更多信息,请参阅我们关于如何使用无头 Web 浏览器抓取动态网站的文章BeautifulSoup 是一个网络抓取库吗?
不完全是,Beautifulsoup 是一个 HTML 解析库,因此虽然它用于网络抓取,但它不是像scrapy这样的完整网络抓取套件/框架。Beautifulsoup HTML 解析器需要与Requests库(或其他替代方案,如httpx、selenium )等 HTTP 客户端库配对才能检索 HTML 页面。总结和进一步阅读
在本教程中,我们介绍了使用 python 和 beautifulsoup 进行网络抓取。 我们快速了解了什么是 HTML 结构,如何使用 bs4 的find
函数和点符号解析它们,以及如何使用select
函数使用 CSS 选择器。 我们还查看了一些实用功能,Beautifulsoup包提供了干净的文本提取和 HTML 格式化——所有这些都是非常有用的网络抓取功能。 最后,我们通过从remotepython.com抓取工作列表信息,用一个带有 beautifulsoup 示例的真实 python 包装了所有内容。 有关解析的更多信息,请参阅我们标有解析关键字的文章,例如: