在先前的教學中,我向您展示如何使用 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 程式碼。除非您安裝軟體包,否則代碼不會被轉換。以下是您可能會注意到的一些常見錯誤:
ImportError
。 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 物件:Tag
、NavigableString
、BeautifulSoup
和 Comment
。
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 树中导航。链接这些标签名称可以帮助您更深入地导航树。例如,您可以使用 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。
以上是Python中使用Beautiful Soup進行網頁抓取:基礎知識探究的詳細內容。更多資訊請關注PHP中文網其他相關文章!