卓越飞翔博客卓越飞翔博客

卓越飞翔 - 您值得收藏的技术分享站
技术文章17051本站已运行3324

Python中使用Beautiful Soup进行网页抓取:基础知识探究

Python中使用Beautiful Soup进行网页抓取:基础知识探究

在之前的教程中,我向您展示了如何使用 Requests 模块通过 Python 访问网页。本教程涵盖了很多主题,例如发出 GET/POST 请求以及以编程方式下载图像或 PDF 等内容。该教程缺少的一件事是如何抓取您使用请求访问的网页以提取所需信息的指南。

在本教程中,您将了解 Beautiful Soup,它是一个用于从 HTML 文件中提取数据的 Python 库。本教程的重点是学习该库的基础知识,下一个教程将介绍更高级的主题。请注意,本教程的所有示例均使用 Beautiful Soup 4。

安装

您可以使用 pip 安装 Beautiful Soup 4。包名称为 beautifulsoup4。它应该适用于 Python 2 和 Python 3。

'
$ pip install beautifulsoup4

如果您的系统上没有安装 pip,您可以直接下载 Beautiful Soup 4 源代码 tarball 并使用 setup.py 进行安装。

'
$ python setup.py install

Beautiful Soup 最初打包为 Python 2 代码。当您安装它以与 Python 3 一起使用时,它会自动更新为 Python 3 代码。除非您安装该软件包,否则代码不会被转换。以下是您可能会注意到的一些常见错误:

  • 当您在 Python 3 下运行 Python 2 版本的代码时,会出现“没有名为 HTMLParser 的模块”ImportError
  • 当您在 Python 2 下运行 Python 3 版本的代码时,会出现“没有名为 html.parser 的模块”ImportError

上述两个错误都可以通过卸载并重新安装 Beautiful Soup 来纠正。

安装解析器

在讨论 Beautiful Soup 可以使用的不同解析器之间的差异之前,让我们编写代码来创建一个 soup。

'
from bs4 import BeautifulSoup

soup = BeautifulSoup("<html><p>This is <b>invalid HTML</p></html>", "html.parser")

BeautifulSoup 对象可以接受两个参数。第一个参数是实际标记,第二个参数是您要使用的解析器。不同的解析器是 html.parser、lxml 和 html5lib。 lxml 解析器有两个版本:HTML 解析器和 XML 解析器。

html.parser 是一个内置解析器,它在旧版本的 Python 中工作得不太好。您可以使用以下命令安装其他解析器:

'
$ pip install lxml
$ pip install html5lib

lxml 解析器非常快,可用于快速解析给定的 HTML。另一方面,html5lib 解析器非常慢,但也非常宽松。以下是使用每个解析器的示例:

'
soup = BeautifulSoup("<html><p>This is <b>invalid HTML</p></html>", "html.parser")
print(soup)
# <html><p>This is <b>invalid HTML</b></p></html>

soup = BeautifulSoup("<html><p>This is <b>invalid HTML</p></html>", "lxml")
print(soup)
# <html><body><p>This is <b>invalid HTML</b></p></body></html>

soup = BeautifulSoup("<html><p>This is <b>invalid HTML</p></html>", "xml")
print(soup)
# <?xml version="1.0" encoding="utf-8"?>
# <html><p>This is <b>invalid HTML</b></p></html>

soup = BeautifulSoup("<html><p>This is <b>invalid HTML</p></html>", "html5lib")
print(soup)
# <html><head></head><body><p>This is <b>invalid HTML</b></p></body></html>

只有当您解析无效的 HTML 时,上面示例中概述的差异才有意义。然而,网络上的大多数 HTML 都是格式错误的,了解这些差异将帮助您调试一些解析错误并决定要在项目中使用哪个解析器。一般来说,lxml 解析器是一个非常好的选择。

美丽汤中的物体

Beautiful Soup 将给定的 HTML 文档解析为 Python 对象树。您需要了解四个主要的 Python 对象:TagNavigableStringBeautifulSoupComment

Tag 对象指的是文档中的实际 XML 或 HTML 标记。您可以使用 tag.name 访问标签的名称。您还可以将标签的名称设置为其他名称。名称更改将在 Beautiful Soup 生成的标记中可见。

您可以分别使用 tag[\'class\']tag[\'id\'] 访问不同的属性,例如标签的类和 id。您还可以使用 tag.attrs 访问整个属性字典。您还可以添加、删除或修改标签的属性。像元素的 class 这样的属性可以采用多个值,存储为列表。

标签内的文本在 Beautiful Soup 中存储为 NavigableString。它有一些有用的方法,例如 replace_with("string") 来替换标签内的文本。您还可以使用 unicode()NavigableString 转换为 unicode 字符串。

美丽汤还允许您访问网页中的评论。这些注释存储为 Comment 对象,该对象基本上也是一个 NavigableString

您已经在上一节中了解了 BeautifulSoup 对象。它用于表示整个文档。由于它不是一个实际的对象,因此它没有任何名称或属性。

获取标题、标题和链接

您可以使用 Beautiful Soup 轻松提取页面标题和其他此类数据。让我们抓取有关 Python 的维基百科页面。首先,您必须根据请求模块教程使用以下代码获取页面标记才能访问网页。

'
import requests
from bs4 import BeautifulSoup

req = requests.get(\'https://en.wikipedia.org/wiki/Python_(programming_language)\')
soup = BeautifulSoup(req.text, "lxml")

现在您已经创建了汤,您可以使用以下代码获取网页的标题:

'
soup.title
# <title>Python (programming language) - Wikipedia</title>

soup.title.name
# \'title\'

soup.title.string
# \'Python (programming language) - Wikipedia\'

您还可以抓取网页以获取其他信息,例如主标题或第一段、它们的类或 id 属性。

'
soup.h1
# <h1 class="firstHeading" id="firstHeading" lang="en">Python (programming language)</h1>

soup.h1.string
# \'Python (programming language)\'

soup.h1[\'class\']
# [\'firstHeading\']

soup.h1[\'id\']
# \'firstHeading\'

soup.h1.attrs
# {\'class\': [\'firstHeading\'], \'id\': \'firstHeading\', \'lang\': \'en\'}

soup.h1[\'class\'] = \'firstHeading, mainHeading\'
soup.h1.string.replace_with("Python - Programming Language")
del soup.h1[\'lang\']
del soup.h1[\'id\']

soup.h1
# <h1 class="firstHeading, mainHeading">Python - Programming Language</h1>

同样,您可以使用以下代码遍历文档中的所有链接或副标题:

'
for sub_heading in soup.find_all(\'h2\'):
    print(sub_heading.text)
    
# all the sub-headings like Contents, History[edit]...

处理多值和重复属性

HTML 文档中的不同元素使用各种属性来实现不同的目的。例如,您可以将 class 或 id 属性添加到样式、组或标识元素。同样,您可以使用数据属性来存储任何附加信息。并非所有属性都可以接受多个值,但有一些可以。 HTML 规范对这些情况有一套明确的规则,Beautiful Soup 试图遵循所有这些规则。但是,它还允许您指定如何处理多值属性返回的数据。该功能是在4.8版本中添加的,因此在使用之前请确保您已经安装了正确的版本。

默认情况下,像 class 这样可以有多个值的属性将返回一个列表,但像 id 这样的属性将返回单个字符串值。您可以在 BeautifulSoup 构造函数中传递名为 multi_valued_attributes 的参数,并将其值设置为 None。这将确保所有属性返回的值都是字符串。

这是一个例子:

'
from bs4 import BeautifulSoup

markup = \'\'\'
<a class="notice light" id="recent-posts" data-links="1 5 20" href="/recent-posts/">Recent Posts</a>
\'\'\'

soup = BeautifulSoup(markup, \'html.parser\')
print(soup.a[\'class\'])
print(soup.a[\'id\'])
print(soup.a[\'data-links\'] + "\n")
\'\'\' 
Output:
[\'notice\', \'light\']
recent-posts
1 5 20
\'\'\'


soup = BeautifulSoup(markup, \'html.parser\', multi_valued_attributes=None)

print(soup.a[\'class\'])
print(soup.a[\'id\'])
print(soup.a[\'data-links\'] + "\n")
\'\'\'
Output:
notice light
recent-posts
1 5 20
\'\'\'

无法保证您从不同网站获得的 HTML 始终完全有效。它可能存在许多不同的问题,例如重复的属性。从版本 4.9.1 开始,Beautiful Soup 允许您通过为 on_duplicate_attribute 参数设置值来指定在这种情况下应该执行的操作。不同的解析器以不同的方式处理此问题,您将需要使用内置的 html.parser 来强制执行特定行为。

'
from bs4 import BeautifulSoup

markup = \'\'\'
<a class="notice light" href="/recent-posts/" class="important dark">Recent Posts</a>
\'\'\'

soup = BeautifulSoup(markup, \'lxml\')
print(soup.a[\'class\'])
# [\'notice\', \'light\']

soup = BeautifulSoup(markup, \'html.parser\', on_duplicate_attribute=\'ignore\')
print(soup.a[\'class\'])
# [\'notice\', \'light\']

soup = BeautifulSoup(markup, \'html.parser\', on_duplicate_attribute=\'replace\')
print(soup.a[\'class\'])
# [\'important\', \'dark\']

浏览 DOM

您可以使用常规标签名称在 DOM 树中导航。链接这些标签名称可以帮助您更深入地导航树。例如,您可以使用 soup.p.a 获取给定维基百科页面第一段中的第一个链接。第一段中的所有链接都可以使用 soup.p.find_all(\'a\') 访问。

您还可以使用 tag.contents 以列表形式访问标记的所有子级。要获取特定索引处的子项,您可以使用 tag.contents[index]。您还可以使用 .children 属性来迭代标记的子级。

仅当您想要访问标记的直接或第一级后代时,.children.contents 才有用。要获取所有后代,您可以使用 .descendants 属性。

'
print(soup.p.contents)
# [<b>Python</b>, \' is a widely used \',.....the full list]

print(soup.p.contents[10])
# <a href="/wiki/Readability" title="Readability">readability</a>

for child in soup.p.children:
    print(child.name)
# b
# None
# a
# None
# a
# None
# ... and so on.

您还可以使用 .parent 属性访问元素的父元素。同样,您可以使用 .parents 属性访问元素的所有祖先。顶级 标签的父级是 BeautifulSoup 对象本身,其父级为 None。

'
print(soup.p.parent.name)
# div

for parent in soup.p.parents:
    print(parent.name)
# div
# div
# div
# body
# html
# [document]

您可以使用 .previous_sibling.next_sibling 属性访问元素的上一个和下一个同级元素。

要使两个元素成为兄弟元素,它们应该具有相同的父元素。这意味着元素的第一个子元素不会有前一个同级元素。类似地,元素的最后一个子元素不会有下一个同级元素。在实际的网页中,元素的上一个和下一个同级元素很可能是换行符。

您还可以使用 .previous_siblings.next_siblings 迭代元素的所有同级元素。

'
soup.head.next_sibling
# \'\n\'

soup.p.a.next_sibling
# \' for \'

soup.p.a.previous_sibling
# \' is a widely used \'

print(soup.p.b.previous_sibling)
# None

您可以使用 .next_element 属性转到紧随当前元素之后的元素。要访问紧邻当前元素之前的元素,请使用 .previous_element 属性。

同样,您可以分别使用 .previous_elements.next_elements 迭代当前元素之前和之后的所有元素。

仅解析文档的一部分

假设您在查找特定内容时需要处理大量数据,并且节省一些处理时间或内存对您来说很重要。在这种情况下,您可以利用 Beautiful Soup 中的 SoupStrainer 类。此类允许您仅关注特定元素,而忽略文档的其余部分。例如,您可以通过在 SoupStrainer 构造函数中传递适当的选择器,使用它来忽略网页上除图像之外的所有其他内容。

请记住,汤过滤器不能与 html5lib 解析器一起使用。但是,您可以将其与 lxml 和内置解析器一起使用。下面是一个示例,我们解析美国的维基百科页面并获取类为 thumbimage 的所有图像。

'
import requests
from bs4 import BeautifulSoup, SoupStrainer

req = requests.get(\'https://en.wikipedia.org/wiki/United_States\')

thumb_images = SoupStrainer(class_="thumbimage")

soup = BeautifulSoup(req.text, "lxml", parse_only=thumb_images)

for image in soup.find_all("img"):
    print(image[\'src\'])
\'\'\'
Output:
//upload.wikimedia.org/wikipedia/commons/thumb/7/7b/Mesa_Verde_National_Park_-_Cliff_Palace.jpg/220px-Mesa_Verde_National_Park_-_Cliff_Palace.jpg
//upload.wikimedia.org/wikipedia/commons/thumb/3/38/Map_of_territorial_growth_1775.svg/260px-Map_of_territorial_growth_1775.svg.png
//upload.wikimedia.org/wikipedia/commons/thumb/f/f9/Declaration_of_Independence_%281819%29%2C_by_John_Trumbull.jpg/220px-Declaration_of_Independence_%281819%29%2C_by_John_Trumbull.jpg
//upload.wikimedia.org/wikipedia/commons/thumb/9/94/U.S._Territorial_Acquisitions.png/310px-U.S._Territorial_Acquisitions.png
...and many more images
\'\'\'

您应该注意,我使用 class_ 而不是 class 来获取这些元素,因为 class 是 Python 中的保留关键字。

最终想法

完成本教程后,您现在应该能够很好地理解不同 HTML 解析器之间的主要差异。您现在还应该能够浏览网页并提取重要数据。当您想要分析给定网站上的所有标题或链接时,这会很有帮助。

在本系列的下一部分中,您将学习如何使用 Beautiful Soup 库来搜索和修改 DOM。

卓越飞翔博客
上一篇: 整合HTML5 WordPress音乐播放器和设置页面
下一篇: 返回列表
留言与评论(共有 0 条评论)
   
验证码:
隐藏边栏