in

使用Python和BeautifulSoup进行网页爬取的教程

Python和BeautifulSoup使用教程

BeautifulSoup是网络抓取中最流行的库之一。它用于通过 Python 脚本或使用CSS 选择器来解析 HTML 文档中的数据。 在本实用教程中,我们将涵盖以下主题:

  • HTML 结构概述以及如何导航它们。
  • 如何使用 Beautifulsoup 的findfind_all方法解析 HTML。
  • 如何使用 Beautifulsoup 的 CSS 选择器selectselect_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 树的插图

在这里,我们可以更容易地理解它——它是一棵节点树,每个节点包括:

  • 节点名称 – 又名 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 工作列表。

remotepython.com 上工作列表的屏幕截图
remotepython.com 上的职位列表

我们想刮这些字段,但是我们如何用Beautifulsoup找到它们呢? 为此,我们可以使用网络浏览器的开发人员工具轻松了解我们将要抓取的网站的 HTML 结构。每个浏览器都带有一个开发工具套件——让我们快速了解一下如何在网络抓取中使用它。

提示:关闭浏览器中的 javascript 以查看由 beautifulsoup 驱动的网络抓取工具看到的内容
我们可以右键单击标题元素并选择“检查”,然后准确查看职位名称在 HTML 树中的位置。Chrome devtools 是可视化网络废弃的 HTML 树的好方法。 现在我们可以编写我们的抓取器,它使用 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库(或其他替代方案,如httpxselenium )等 HTTP 客户端库配对才能检索 HTML 页面。

总结和进一步阅读

在本教程中,我们介绍了使用 python 和 beautifulsoup 进行网络抓取。 我们快速了解了什么是 HTML 结构,如何使用 bs4 的find函数和点符号解析它们,以及如何使用select函数使用 CSS 选择器。 我们还查看了一些实用功能,Beautifulsoup包提供了干净的文本提取和 HTML 格式化——所有这些都是非常有用的网络抓取功能。 最后,我们通过从remotepython.com抓取工作列表信息,用一个带有 beautifulsoup 示例的真实 python 包装了所有内容 有关解析的更多信息,请参阅我们标有解析关键字的文章,例如:

Written by 河小马

河小马是一位杰出的数字营销行业领袖,广告中国论坛的重要成员,其专业技能涵盖了PPC广告、域名停放、网站开发、联盟营销以及跨境电商咨询等多个领域。作为一位资深程序开发者,他不仅具备强大的技术能力,而且在出海网络营销方面拥有超过13年的经验。