是什么原因导致 BeautifulSoup 函数产生“None”结果?如何使用 BeautifulSoup 避免“AttributeError:'NoneType' 对象没有属性...”?

2024-12-30 08:41:00
admin
原创
60
摘要:问题描述:通常,当我尝试使用 BeautifulSoup 来解析网页时,我会None从 BeautifulSoup 函数中得到一个结果,或者AttributeError引发一个。这里有一些独立的(即不需要互联网访问,因为数据是硬编码的)示例,基于文档中的示例,不需要互联网访问:>>> htm...

问题描述:

通常,当我尝试使用 BeautifulSoup 来解析网页时,我会None从 BeautifulSoup 函数中得到一个结果,或者AttributeError引发一个。

这里有一些独立的(即不需要互联网访问,因为数据是硬编码的)示例,基于文档中的示例,不需要互联网访问:

>>> html_doc = """
... <html><head><title>The Dormouse's story</title></head>
... <body>
... <p class="title"><b>The Dormouse's story</b></p>
... 
... <p class="story">Once upon a time there were three little sisters; and their names were
... <a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
... <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
... <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
... and they lived at the bottom of a well.</p>
... 
... <p class="story">...</p>
... """
>>> 
>>> from bs4 import BeautifulSoup
>>> soup = BeautifulSoup(html_doc, 'html.parser')
>>> print(soup.sister)
None
>>> print(soup.find('a', class_='brother'))
None
>>> print(soup.select_one('a.brother'))
None
>>> soup.select_one('a.brother').text
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'text'

我知道这None 是 Python 中的特殊值,也是NoneType 它的类型;但是...现在怎么办?为什么我会得到这些结果,我该如何正确处理它们?


这个问题专门针对寻找单个结果(如.find)的 BeautifulSoup 方法。如果您使用.find_all通常返回列表的方法获得此结果,则可能是由于 HTML 解析器存在问题。有关详细信息,请参阅Python Beautiful Soup 'NoneType' 对象错误。


解决方案 1:

概述

一般来说,BeautifulSoup 提供两种查询:查找单个特定元素(标签、属性、文本等)的查询和查找满足要求的每个元素的查询。

对于后一组(这类函数.find_all可以给出多个结果),返回值将是一个列表。如果没有结果,则列表为空。简单又好用。

但是,对于像这样只能给出一个结果的**方法,.find`.select_one****如果在 HTML 中找不到任何内容,则结果将为None**。BeautifulSoup**不会**直接引发异常来解释问题。相反,AttributeError通常会出现在*下面的*代码中,该代码试图不恰当地*使用*None(因为它期望接收其他内容 - 通常是TagBeautifulSoup 定义的类的实例)。发生这种情况是因为 None根本不支持该操作;它被称为 是AttributeError因为.语法意味着访问左侧的任何*属性*AttributeError`。[TODO:一旦存在适当的规范,链接到对属性是什么以及什么是的解释。]

示例

让我们逐一考虑问题中不起作用的代码示例:

>>> print(soup.sister)
None

<sister>这会尝试在 HTML 中查找标记(而不是class具有或id其他属性等于 的其他标记sister)。没有标记,因此结果为“无”。

>>> print(soup.find('a', class_='brother'))
None

这将尝试查找<a>具有class等于 的属性的标签brother,例如<a href="https://example.com/bobby" class="brother">Bobby</a>。文档不包含任何类似内容;所有标签均不a具有该类(它们均具有sister类)。

>>> print(soup.select_one('a.brother'))
None

这是另一种方式,使用不同的方法完成与上一个示例相同的操作。(我们传递 CSS 查询选择器,而不是传递标签名称和一些属性值。)结果是相同的。

>>> soup.select_one('a.brother').text
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'text'

由于soup.select_one('a.brother')返回了None,这与尝试执行 相同None.text。错误的意思正是它所说的:None没有 来text访问。事实上,它没有任何“普通”属性;该类NoneType只定义了特殊方法__str__(将其转换None为字符串,以便它在打印时'None'看起来像实际文本)。None

解决方案 2:

真实世界数据的常见问题

当然,使用一个小的硬编码文本示例可以清楚地说明为什么某些对find等方法的调用会失败 - 内容根本不存在,只需阅读几行数据就可以立即发现。任何调试代码的尝试都应从仔细检查拼写错误开始

>>> html_doc = """
... <html><head><title>The Dormouse's story</title></head>
... <body>
... <p class="title"><b>The Dormouse's story</b></p>
... 
... <p class="story">Once upon a time there were three little sisters; and their names were
... <a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
... <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
... <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
... and they lived at the bottom of a well.</p>
... 
... <p class="story">...</p>
... """
>>> from bs4 import BeautifulSoup
>>> soup = BeautifulSoup(html_doc, 'html.parser')
>>> print(soup.find('a', class_='sistre')) # note the typo
None
>>> print(soup.find('a', class_='sister')) # corrected
<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>

然而,在现实世界中,网页的文本很容易就达到几千字节甚至几兆字节,因此这种视觉检查并不实用。一般来说,对于更复杂的任务,值得先花时间检查给定网页是否提供访问数据的API,而不是从页面内容中抓取数据。许多网站都乐于直接提供数据,格式更易于使用(因为它是专门设计为作为数据使用,而不是填充“模板”网页的空白)。

粗略地概括一下:API 由端点组成- 可以像网页 URL 一样直接访问的 URI,但响应不是网页。目前最常见的格式是 JSON,尽管可以根据确切的用例使用任何数据格式 - 例如,数据表可能以 CSV 形式返回。要使用标准 JSON 端点,请编写代码找出要使用的确切 URI,正常加载它,读取和解析 JSON 响应,然后处理该数据。(在某些情况下,“API 密钥”是必需的;一些公司使用这些密钥来收取高级数据访问费用,但通常只是为了将信息请求与特定用户绑定。)

通常,这比使用 BeautifulSoup 可以完成的任何操作都容易得多,并且还可以节省带宽。为其网页提供公开文档 API 的公司希望您使用它们;这通常对涉及的每个人都有好处。

综上所述,以下是 BeautifulSoup 解析的 Web 响应不包含预期内容无法直接处理的一些常见原因。

动态(客户端)生成的内容

请记住,BeautifulSoup 处理的是 静态 HTML,而不是 JavaScript。它只能使用在禁用 JavaScript 的情况下访问网页时可见的数据。

现代网页通常通过在客户端的 Web 浏览器中运行 JavaScript 来生成大量页面数据。在典型情况下,此 JavaScript 代码将发出更多 HTTP 请求来获取数据、格式化数据并有效地动态编辑页面(更改 DOM)。BeautifulSoup无法处理所有这些。它只是将网页中的 JavaScript 代码视为更多文本

要抓取动态网站,可以考虑使用 Selenium来模拟与网页的交互。

或者,调查正常使用网站时会发生什么。通常,页面上的 JavaScript 代码会调用 API 端点,这些端点可以在 Web 浏览器的开发者控制台的“网络”(或类似名称)选项卡上看到。即使不容易找到好的文档,这也可以成为理解网站 API 的一个很好的提示。

用户代理检查

每个 HTTP 请求都包含标头,这些标头会向服务器提供信息以帮助服务器处理请求。这些标头包括有关缓存的信息(以便服务器可以决定是否可以使用数据的缓存版本)、可接受的数据格式(以便服务器可以对响应应用压缩以节省带宽)以及有关客户端的信息(以便服务器可以调整输出以使其在每个 Web 浏览器中都正确显示)。

最后一部分是使用标头的“用户代理”部分完成的。但是,默认情况下,HTML 库(如urllibrequests)通常不会声明任何 Web 浏览器- 在服务器端,这是一个大红旗,表示“此用户正在运行程序来抓取网页,而不是实际使用 Web 浏览器”。

大多数公司都不太喜欢这种情况。他们宁愿让您看到实际的网页(包括广告)。因此,服务器可能会生成某种虚拟页面(或 HTTP 错误)。(注意:这可能包括“请求过多”错误,否则将指向速率限制,如下一节所述。)

为了解决这个问题,请以适当的方式为 HTTP 库设置标头:

  • 使用 Python 中的 Requests 库发送“用户代理”

  • 在 Python 3 中更改 urrlib.request.urlopen 的用户代理

速率限制

“机器人”的另一个明显迹象是,同一个用户以互联网连接允许的最快速度请求多个网页,甚至不等一个网页加载完毕就请求另一个网页。即使不需要登录,服务器也会通过 IP(也可能通过其他“指纹”信息)跟踪谁在发出请求,并且可能会简单地拒绝向请求网页速度过快的人提供网页内容。

此类限制通常同样适用于 API(如果可用)——服务器正在保护自己免受拒绝服务攻击。因此,通常唯一的解决方法是修复代码以减少请求频率,例如在请求之间暂停程序。

例如,参见如何避免 HTTP 错误 429(请求过多)python。

需要登录

这非常简单:如果内容通常仅对登录用户可用,那么抓取脚本将必须模拟网站使用的任何登录程序。

服务器端动态/随机名称

请记住,服务器决定为每个请求发送什么。它不必每次都是相同的内容,也不必与服务器永久存储中的任何实际文件相对应。

例如,它可能包括随机生成的类名或 ID,每次访问页面时,这些名称可能会有所不同。更棘手的是:由于缓存,名称可能看起来是一致的……直到缓存过期。

如果 HTML 源代码中的类名或 ID 似乎包含一堆无意义的垃圾字符,请考虑不要依赖该名称保持一致 - 考虑另一种方法来识别必要的数据。或者,可以通过查看HTML 中的其他标签如何引用它来动态地找出标签 ID。

结构不规则的数据

例如,假设一家公司网站的“关于”页面显示了几个主要员工的联系信息,<div class="staff">每个员工的信息都用一个标签包裹起来。其中一些列出了电子邮件地址,而另一些则没有;当没有列出地址时,相应的标签完全不存在,而不仅仅是没有任何文本:

soup = BeautifulSoup("""<html>
<head><title>Company staff</title></head><body>
<div class="staff">Name: <span class="name">Alice A.</span> Email: <span class="email">alice@example.com</span></div>
<div class="staff">Name: <span class="name">Bob B.</span> Email: <span class="email">bob@example.com</span></div>
<div class="staff">Name: <span class="name">Cameron C.</span></div>
</body>
</html>""", 'html.parser')

由于缺少电子邮件,尝试迭代并打印每个姓名和电子邮件将失败:

>>> for staff in soup.select('div.staff'):
...     print('Name:', staff.find('span', class_='name').text)
...     print('Email:', staff.find('span', class_='email').text)
... 
Name: Alice A.
Email: alice@example.com
Name: Bob B.
Email: bob@example.com
Name: Cameron C.
Traceback (most recent call last):
  File "<stdin>", line 3, in <module>
AttributeError: 'NoneType' object has no attribute 'text'

这只是一种必须预料到并处理的异常现象。

但是,根据具体要求,可能会有更优雅的方法。例如,如果目标只是收集所有电子邮件地址(不担心姓名),我们可能首先尝试使用列表推导处理子标签的代码:

>>> [staff.find('span', class_='email').text for staff in soup.select('div.staff')]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <listcomp>
AttributeError: 'NoneType' object has no attribute 'text'

我们可以解决这个问题,方法是获取每个名称的电子邮件列表(其中将有 0 个或 1 个元素),并使用为获得平坦结果而设计的嵌套列表推导:

>>> [email.text for staff in soup.select('div.staff') for email in staff.find_all('span', class_='email')]
['alice@example.com', 'bob@example.com']

或者我们可以简单地使用一个更好的查询:

>>> # maybe we don't need to check for the div tags at all?
>>> [email.text for email in soup.select('span.email')]
['alice@example.com', 'bob@example.com']
>>> # Or if we do, use a fancy CSS selector:
>>> # look for the span anywhere inside the div
>>> [email.text for email in soup.select('div.staff span.email')]
['alice@example.com', 'bob@example.com']
>>> # require the div as an immediate parent of the span
>>> [email.text for email in soup.select('div.staff > span.email')]
['alice@example.com', 'bob@example.com']

浏览器“纠正”了无效的 HTML

HTML 很复杂,现实世界中的 HTML 经常充斥着错别字和浏览器会忽略的小错误。如果页面源代码没有 100% 完全符合标准(无论是在开始时还是在每次 JavaScript 操作之后),没有人会使用只会弹出错误消息的繁琐浏览器 - 因为如此大一部分网络内容会从视野中消失。

BeautifulSoup 允许 HTML 解析器处理此问题,如果除了标准库解析器之外还安装了其他 HTML 解析器,则允许用户选择 HTML 解析器。另一方面,Web 浏览器有自己的内置 HTML 解析器,这些解析器可能更加宽松,并且还采用更重的方法来“纠正”错误。

在这个例子中,OP 的浏览器在“检查元素”视图中显示了<tbody>内的标签,尽管实际页面源代码中并不存在该标签。另一方面,BeautifulSoup 使用的 HTML 解析器却没有这样做;它只是接受直接嵌套在 内的标签。因此,BeautifulSoup 创建的用于表示表的相应元素会报告其属性。<table>`<tr><table>TagNonetbody`

通常,可以通过在汤的子部分内搜索(例如使用 CSS 选择器)来解决此类问题,而不是尝试“进入”每个嵌套标签。这类似于不规则结构数据的问题。

根本不是 HTML

因为它有时会出现,而且也与上面的警告有关:并非每个 Web 请求都会生成网页。例如,图像无法用 BeautifulSoup 处理;它甚至不代表文本,更不用说 HTML 了。不太明显的是,中间有类似内容的 URL/api/v1/很可能是 API 端点,而不是网页;响应很可能是 JSON 格式的数据,而不是 HTML。BeautifulSoup 不是解析这些数据的合适工具。

现代网络浏览器通常会为此类数据生成一个“包装器”HTML 文档。例如,如果我在 Imgur 上查看图片,使用直接图片 URL(而不是 Imgur 自己的“图库”页面之一),然后打开浏览器的 Web 检查器视图,我会看到类似以下内容(替换了一些占位符):

<html>
    <head>
        <meta name="viewport" content="width=device-width; height=device-height;">
        <link rel="stylesheet" href="resource://content-accessible/ImageDocument.css">
        <link rel="stylesheet" href="resource://content-accessible/TopLevelImageDocument.css">
        <title>[image name] ([format] Image, [width]×[height] pixels) — Scaled ([scale factor])</title>
    </head>
    <body>
        <img src="[url]" alt="[url]" class="transparent shrinkToFit" width="[width]" height="[height]">
    </body>
</html>

对于 JSON,会生成一个更复杂的包装器 - 这实际上是浏览器的 JSON 查看器实现方式的一部分。

这里要注意的重要一点是,当 Python 代码发出 Web 请求时, BeautifulSoup 不会看到任何此类 HTML - 该请求从未通过 Web 浏览器过滤,并且创建此 HTML 的是本地浏览器,而不是远程服务器。

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   997  
  在项目管理领域,CDCP(Certified Data Center Professional)认证评审是一个至关重要的环节,它不仅验证了项目团队的专业能力,还直接关系到项目的成功与否。在这一评审过程中,沟通技巧的运用至关重要。有效的沟通不仅能够确保信息的准确传递,还能增强团队协作,提升评审效率。本文将深入探讨CDCP...
华为IPD流程   34  
  IPD(Integrated Product Development,集成产品开发)是一种以客户需求为核心、跨部门协同的产品开发模式,旨在通过高效的资源整合和流程优化,提升产品开发的成功率和市场竞争力。在IPD培训课程中,掌握关键成功因素是确保团队能够有效实施这一模式的核心。以下将从五个关键成功因素展开讨论,帮助企业和...
IPD项目流程图   40  
  华为IPD(Integrated Product Development,集成产品开发)流程是华为公司在其全球化进程中逐步构建和完善的一套高效产品开发管理体系。这一流程不仅帮助华为在技术创新和产品交付上实现了质的飞跃,还为其在全球市场中赢得了显著的竞争优势。IPD的核心在于通过跨部门协作、阶段性评审和市场需求驱动,确保...
华为IPD   39  
  华为作为全球领先的通信技术解决方案提供商,其成功的背后离不开一套成熟的管理体系——集成产品开发(IPD)。IPD不仅是一种产品开发流程,更是一种系统化的管理思想,它通过跨职能团队的协作、阶段评审机制和市场需求驱动的开发模式,帮助华为在全球市场中脱颖而出。从最初的国内市场到如今的全球化布局,华为的IPD体系在多个领域展现...
IPD管理流程   71  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用