如何使用BeautifulSoup和Python3抓取网页
介绍
许多数据分析、大数据和机器学习项目都需要抓取网站来收集您将使用的数据。 Python 编程语言在数据科学社区中被广泛使用,因此拥有可以在自己的项目中使用的模块和工具生态系统。 在本教程中,我们将重点关注 Beautiful Soup 模块。
Beautiful Soup,暗指 Lewis Carroll 的 爱丽丝梦游仙境 第 10 章中的 Mock Turtle 的 歌曲,是一个 Python 库,可以快速打开网页抓取项目。 Beautiful Soup 目前以 Beautiful Soup 4 的形式提供,并且与 Python 2.7 和 Python 3 兼容,Beautiful Soup 从解析的 HTML 和 XML 文档(包括带有非封闭标签或 tag soup 和其他格式错误的标记的文档)创建解析树.
在本教程中,我们将收集和解析网页以获取文本数据并将我们收集的信息写入 CSV 文件。
先决条件
在学习本教程之前,您应该在您的机器上设置 local 或 基于服务器的 Python 编程环境。
您应该安装了 Requests 和 Beautiful Soup 模块 ',您可以按照我们的教程“如何使用请求和 Beautiful Soup with Python 3 处理 Web 数据”来实现。 熟悉这些模块也很有用。
此外,由于我们将使用从网络上抓取的数据,因此您应该熟悉 HTML 结构和标记。
了解数据
在本教程中,我们将使用来自美国 国家美术馆 官方网站的数据。 国家美术馆是位于华盛顿特区国家广场的艺术博物馆 它拥有超过 120,000 件从文艺复兴时期到现在的作品,由 13,000 多名艺术家完成。
我们想搜索艺术家索引,在更新本教程时,可通过 Internet Archive 的 Wayback Machine 获取,网址如下:
https://web.archive.org/web/20170131230332/https://www.nga.gov/collection/an.shtm
注:以上网址较长是由于本网站已被互联网档案馆存档。
互联网档案馆是一个非营利性数字图书馆,提供对互联网站点和其他数字媒体的免费访问。 该组织拍摄网站快照以保存网站历史,我们目前可以访问国家美术馆网站的旧版本,该网站在本教程首次编写时可用。 Internet Archive 是一个很好的工具,在进行任何类型的历史数据抓取时都要牢记,包括比较同一站点的迭代和可用数据。
在 Internet 档案的标题下方,您会看到如下所示的页面:
由于我们将做这个项目是为了学习使用 Beautiful Soup 进行网络抓取,我们不需要从网站中提取太多数据,所以让我们限制我们要抓取的艺术家数据的范围。 因此,让我们选择一个字母——在我们的示例中,我们将选择字母 Z——我们将看到一个如下所示的页面:
在上面的页面中,我们看到在撰写本文时列出的第一个艺术家是 Zabaglia, Niccola,这是我们开始提取数据时要注意的一件好事。 我们将从处理第一页开始,其中包含字母 Z 的以下 URL:
https://web.archive.org/web/20121007172955/http://www.nga.gov/collection/anZ1.htm
稍后请务必注意您选择列出的信件总共有多少页,您可以通过点击艺术家的最后一页来发现。 在这种情况下,总共有 4 页,在撰写本文时列出的最后一位艺术家是 Zykmund, Václav。 Z艺术家的最后一页有以下网址:
https://web.archive.org/web/20121010201041/http://www.nga.gov/collection/anZ4.htm
然而,您也可以使用与首页相同的 Internet Archive 数字字符串访问上述页面:
https://web.archive.org/web/20121007172955/http://www.nga.gov/collection/anZ4.htm
这一点很重要,因为我们将在本教程的后面部分遍历这些页面。
要开始熟悉此网页的设置方式,您可以查看其 DOM,这将帮助您了解 HTML 的结构。 为了检查 DOM,您可以打开浏览器的 开发者工具 。
导入库
要开始我们的编码项目,让我们激活 Python 3 编程环境。 确保您位于环境所在的目录中,然后运行以下命令:
. my_env/bin/activate
激活我们的编程环境后,我们将创建一个新文件,例如 nano。 您可以随意命名文件,在本教程中我们将其命名为 nga_z_artists.py
。
nano nga_z_artists.py
在这个文件中,我们可以开始导入我们将要使用的库——Requests 和 Beautiful Soup。
Requests 库允许您以人类可读的方式在 Python 程序中使用 HTTP,Beautiful Soup 模块旨在快速完成网络抓取。
我们将使用 import 语句 导入 Requests 和 Beautiful Soup。 对于 Beautiful Soup,我们将从 bs4
导入它,Beautiful Soup 4 所在的包是在其中找到的。
nga_z_artists.py
# Import libraries import requests from bs4 import BeautifulSoup
导入 Requests 和 Beautiful Soup 模块后,我们可以继续工作,首先收集页面然后解析它。
收集和解析网页
我们需要做的下一步是收集带有请求的第一个网页的 URL。 我们将使用 方法 requests.get() 将第一页的 URL 分配给 变量 page
。
nga_z_artists.py
import requests from bs4 import BeautifulSoup # Collect first page of artists’ list page = requests.get('https://web.archive.org/web/20121007172955/https://www.nga.gov/collection/anZ1.htm')
<$>[注] 笔记 : 因为 URL 很长,上面的代码和本教程中的代码都不会通过 PEP 8 E501 标记长度超过 79 个字符的行。 您可能希望将 URL 分配给变量,以使代码在最终版本中更具可读性。 本教程中的代码用于演示目的,允许您在自己的项目中替换较短的 URL。<$>
我们现在将创建一个 BeautifulSoup
对象或解析树。 该对象将 Requests 中的 page.text
文档(服务器响应的内容)作为其参数,然后从 Python 的内置 html.parser 中解析它。
nga_z_artists.py
import requests from bs4 import BeautifulSoup page = requests.get('https://web.archive.org/web/20121007172955/https://www.nga.gov/collection/anZ1.htm') # Create a BeautifulSoup object soup = BeautifulSoup(page.text, 'html.parser')
随着我们的页面被收集、解析并设置为 BeautifulSoup
对象,我们可以继续收集我们想要的数据。
从网页中提取文本
对于这个项目,我们将收集艺术家的姓名和网站上提供的相关链接。 您可能想要收集不同的数据,例如艺术家的国籍和日期。 无论您想收集什么数据,您都需要找出网页的 DOM 是如何描述它的。
为此,在您的网络浏览器中,右键单击 - 或 CTRL
+ 单击 macOS - 第一位艺术家的姓名 Zabaglia, Niccola。 在弹出的上下文菜单中,您应该会看到类似于 Inspect Element (Firefox) 或 Inspect (Chrome) 的菜单项。
单击相关的 Inspect 菜单项后,Web 开发人员的工具应该会出现在您的浏览器中。 我们希望在此列表中查找与艺术家姓名关联的类和标签。
我们将首先看到名称表位于 <div>
标记内,其中 class="BodyText"
。 请务必注意这一点,以便我们仅在网页的此部分中搜索文本。 我们还注意到名称 Zabaglia, Niccola 在链接标签中,因为该名称引用了描述艺术家的网页。 所以我们要引用链接的 <a>
标签。 每个艺术家的名字都是对链接的引用。
为此,我们将使用 Beautiful Soup 的 find()
和 find_all()
方法从 BodyText
<div>
中提取艺术家姓名的文本。
nga_z_artists.py
import requests from bs4 import BeautifulSoup # Collect and parse first page page = requests.get('https://web.archive.org/web/20121007172955/https://www.nga.gov/collection/anZ1.htm') soup = BeautifulSoup(page.text, 'html.parser') # Pull all text from the BodyText div artist_name_list = soup.find(class_='BodyText') # Pull text from all instances of <a> tag within BodyText div artist_name_list_items = artist_name_list.find_all('a')
接下来,在我们的程序文件的底部,我们将要创建一个 for 循环 以遍历我们刚刚放入 artist_name_list_items
变量的所有艺术家姓名。
我们将使用 prettify()
方法打印这些名称,以便将 Beautiful Soup 解析树转换为格式良好的 Unicode 字符串。
nga_z_artists.py
... artist_name_list = soup.find(class_='BodyText') artist_name_list_items = artist_name_list.find_all('a') # Create for loop to print out all artists' names for artist_name in artist_name_list_items: print(artist_name.prettify())
让我们按照目前的方式运行程序:
python nga_z_artists.py
一旦我们这样做,我们将收到以下输出:
Output<a href="/web/20121007172955/https://www.nga.gov/cgi-bin/tsearch?artistid=11630"> Zabaglia, Niccola </a> ... <a href="/web/20121007172955/https://www.nga.gov/cgi-bin/tsearch?artistid=3427"> Zao Wou-Ki </a> <a href="/web/20121007172955/https://www.nga.gov/collection/anZ2.htm"> Zas-Zie </a> <a href="/web/20121007172955/https://www.nga.gov/collection/anZ3.htm"> Zie-Zor </a> <a href="/web/20121007172955/https://www.nga.gov/collection/anZ4.htm"> <strong> next <br/> page </strong> </a>
我们此时在输出中看到的是与第一页 <div class="BodyText">
标记中的 <a>
标记中的所有艺术家姓名相关的全文和标记,以及底部的一些附加链接文本。 由于我们不想要这些额外的信息,让我们在下一节中删除它。
删除多余的数据
到目前为止,我们已经能够在我们网页的一个 <div>
部分中收集所有链接文本数据。 但是,我们不希望底部链接不引用艺术家姓名,因此让我们努力删除该部分。
为了删除页面底部的链接,让我们再次右键单击并 Inspect DOM。 我们将看到 <div class="BodyText">
部分底部的链接包含在 HTML 表中:<table class="AlphaNav">
:
因此,我们可以使用 Beautiful Soup 找到 AlphaNav
类并使用 decompose()
方法从解析树中删除标签,然后将其连同其内容一起销毁。
我们将使用变量 last_links
来引用这些底部链接并将它们添加到程序文件中:
nga_z_artists.py
import requests from bs4 import BeautifulSoup page = requests.get('https://web.archive.org/web/20121007172955/https://www.nga.gov/collection/anZ1.htm') soup = BeautifulSoup(page.text, 'html.parser') # Remove bottom links last_links = soup.find(class_='AlphaNav') last_links.decompose() artist_name_list = soup.find(class_='BodyText') artist_name_list_items = artist_name_list.find_all('a') for artist_name in artist_name_list_items: print(artist_name.prettify())
现在,当我们使用 python nga_z_artist.py
命令运行程序时,我们将收到以下输出:
Output<a href="/web/20121007172955/https://www.nga.gov/cgi-bin/tsearch?artistid=11630"> Zabaglia, Niccola </a> <a href="/web/20121007172955/https://www.nga.gov/cgi-bin/tsearch?artistid=34202"> Zaccone, Fabian </a> ... <a href="/web/20121007172955/http://www.nga.gov/cgi-bin/tsearch?artistid=11631"> Zanotti, Giampietro </a> <a href="/web/20121007172955/http://www.nga.gov/cgi-bin/tsearch?artistid=3427"> Zao Wou-Ki </a>
此时,我们看到输出不再包括网页底部的链接,现在只显示与艺术家姓名关联的链接。
到目前为止,我们已经专门针对带有艺术家姓名的链接,但我们拥有我们并不真正想要的额外标签数据。 让我们在下一节中删除它。
从标签中提取内容
为了仅访问实际艺术家的姓名,我们希望定位 <a>
标签的内容,而不是打印出整个链接标签。
我们可以使用 Beautiful Soup 的 .contents
来做到这一点,它将标签的子节点作为 Python 列表数据类型 返回。
让我们修改 for
循环,这样我们就不会打印整个链接及其标签,而是打印子列表(即 艺术家全名):
nga_z_artists.py
import requests from bs4 import BeautifulSoup page = requests.get('https://web.archive.org/web/20121007172955/https://www.nga.gov/collection/anZ1.htm') soup = BeautifulSoup(page.text, 'html.parser') last_links = soup.find(class_='AlphaNav') last_links.decompose() artist_name_list = soup.find(class_='BodyText') artist_name_list_items = artist_name_list.find_all('a') # Use .contents to pull out the <a> tag’s children for artist_name in artist_name_list_items: names = artist_name.contents[0] print(names)
请注意,我们通过调用每个项目的 索引号 来遍历上面的列表。
我们可以用python
命令运行程序,查看如下输出:
OutputZabaglia, Niccola Zaccone, Fabian Zadkine, Ossip ... Zanini-Viola, Giuseppe Zanotti, Giampietro Zao Wou-Ki
我们收到了一封回信 Z 第一页上所有可用艺术家姓名的列表。
但是,如果我们还想捕获与这些艺术家关联的 URL,该怎么办? 我们可以使用 Beautiful Soup 的 get('href')
方法提取在页面的 <a>
标签中找到的 URL。
从上面链接的输出,我们知道整个 URL 没有被捕获,所以我们将 连接 与 URL 字符串前面的链接字符串(在本例中为 https://web.archive.org/
) .
这些行我们还将添加到 for
循环中:
nga_z_artists.py
... for artist_name in artist_name_list_items: names = artist_name.contents[0] links = 'https://web.archive.org' + artist_name.get('href') print(names) print(links)
当我们运行上面的程序时,我们将收到 both 艺术家的名字和链接的 URL,这些链接告诉我们更多关于艺术家的信息:
OutputZabaglia, Niccola https://web.archive.org/web/20121007172955/https://www.nga.gov/cgi-bin/tsearch?artistid=11630 Zaccone, Fabian https://web.archive.org/web/20121007172955/https://www.nga.gov/cgi-bin/tsearch?artistid=34202 ... Zanotti, Giampietro https://web.archive.org/web/20121007172955/https://www.nga.gov/cgi-bin/tsearch?artistid=11631 Zao Wou-Ki https://web.archive.org/web/20121007172955/https://www.nga.gov/cgi-bin/tsearch?artistid=3427
虽然我们现在正在从网站获取信息,但它目前只是打印到我们的终端窗口。 相反,让我们捕获这些数据,以便我们可以通过将其写入文件来在其他地方使用它。
将数据写入 CSV 文件
收集仅存在于终端窗口中的数据并不是很有用。 逗号分隔值 (CSV) 文件允许我们以纯文本形式存储表格数据,并且是电子表格和数据库的常用格式。 在开始本节之前,您应该熟悉 如何在 Python 中处理纯文本文件。
首先,我们需要导入 Python 内置的 csv
模块以及 Python 编程文件顶部的其他模块:
import csv
接下来,我们将使用 'w'
模式。 我们还将编写顶行标题: Name
和 Link
我们将作为列表传递给 writerow()
方法:
f = csv.writer(open('z-artist-names.csv', 'w')) f.writerow(['Name', 'Link'])
最后,在我们的 for
循环中,我们将使用艺术家的 names
及其关联的 links
来编写每一行:
f.writerow([names, links])
您可以在下面的文件中看到每个任务的行:
nga_z_artists.py
import requests import csv from bs4 import BeautifulSoup page = requests.get('https://web.archive.org/web/20121007172955/http://www.nga.gov/collection/anZ1.htm') soup = BeautifulSoup(page.text, 'html.parser') last_links = soup.find(class_='AlphaNav') last_links.decompose() # Create a file to write to, add headers row f = csv.writer(open('z-artist-names.csv', 'w')) f.writerow(['Name', 'Link']) artist_name_list = soup.find(class_='BodyText') artist_name_list_items = artist_name_list.find_all('a') for artist_name in artist_name_list_items: names = artist_name.contents[0] links = 'https://web.archive.org' + artist_name.get('href') # Add each artist’s name and associated link to a row f.writerow([names, links])
当您现在使用 python
命令运行程序时,不会将任何输出返回到您的终端窗口。 相反,将在您正在工作的目录中创建一个名为 z-artist-names.csv
的文件。
根据您打开它的方式,它可能看起来像这样:
z-艺术家名称.csv
Name,Link "Zabaglia, Niccola",https://web.archive.org/web/20121007172955/http://www.nga.gov/cgi-bin/tsearch?artistid=11630 "Zaccone, Fabian",https://web.archive.org/web/20121007172955/http://www.nga.gov/cgi-bin/tsearch?artistid=34202 "Zadkine, Ossip",https://web.archive.org/web/20121007172955/http://www.nga.gov/cgi-bin/tsearch?artistid=3475w ...
或者,它可能看起来更像电子表格:
无论哪种情况,您现在都可以使用此文件以更有意义的方式处理数据,因为您收集的信息现在存储在您的计算机内存中。
检索相关页面
我们创建了一个程序,该程序将从姓氏以字母 Z 开头的艺术家列表的第一页中提取数据。 但是,网站上总共有 4 个页面可供这些艺术家使用。
为了收集所有这些页面,我们可以使用 for
循环执行更多迭代。 这将修改我们迄今为止编写的大部分代码,但将采用类似的概念。
首先,我们要初始化一个列表来保存页面:
pages = []
我们将使用以下 for
循环填充这个初始化列表:
for i in range(1, 5): url = 'https://web.archive.org/web/20121007172955/https://www.nga.gov/collection/anZ' + str(i) + '.htm' pages.append(url)
在本教程的前面,我们注意到我们应该注意包含以字母Z(或我们使用的任何字母)开头的艺术家姓名的总页数)。 由于字母 Z 有 4 页,我们构建了上面的 for
循环,范围为 1
到 5
以便它会迭代4 页中的每一页。
对于这个特定的网站,URL 以字符串 https://web.archive.org/web/20121007172955/https://www.nga.gov/collection/anZ
开头,然后是页面编号(这将是 for
循环中的整数 i
我们将 转换为字符串 ) 并以 .htm
结尾。 我们将这些字符串连接在一起,然后将结果附加到 pages
列表中。
除了这个循环之外,我们还有第二个循环,它将遍历上面的每个页面。 这个 for
循环中的代码看起来与我们迄今为止创建的代码相似,因为它正在执行我们为字母 Z 艺术家为每个共4页。 请注意,因为我们已将原始程序放入第二个 for
循环中,所以我们现在将原始循环作为 嵌套的 for loop 包含在其中。
两个 for
循环将如下所示:
pages = [] for i in range(1, 5): url = 'https://web.archive.org/web/20121007172955/https://www.nga.gov/collection/anZ' + str(i) + '.htm' pages.append(url) for item in pages: page = requests.get(item) soup = BeautifulSoup(page.text, 'html.parser') last_links = soup.find(class_='AlphaNav') last_links.decompose() artist_name_list = soup.find(class_='BodyText') artist_name_list_items = artist_name_list.find_all('a') for artist_name in artist_name_list_items: names = artist_name.contents[0] links = 'https://web.archive.org' + artist_name.get('href') f.writerow([names, links])
在上面的代码中,您应该看到第一个 for
循环正在遍历页面,第二个 for
循环正在从每个页面中抓取数据,然后添加艺术家的姓名和通过每一页的每一行逐行链接。
这两个 for
循环位于 import
语句、CSV 文件创建和写入器(带有用于写入文件头的行)以及 pages
的初始化变量(分配给列表)。
在编程文件的更大上下文中,完整的代码如下所示:
nga_z_artists.py
import requests import csv from bs4 import BeautifulSoup f = csv.writer(open('z-artist-names.csv', 'w')) f.writerow(['Name', 'Link']) pages = [] for i in range(1, 5): url = 'https://web.archive.org/web/20121007172955/https://www.nga.gov/collection/anZ' + str(i) + '.htm' pages.append(url) for item in pages: page = requests.get(item) soup = BeautifulSoup(page.text, 'html.parser') last_links = soup.find(class_='AlphaNav') last_links.decompose() artist_name_list = soup.find(class_='BodyText') artist_name_list_items = artist_name_list.find_all('a') for artist_name in artist_name_list_items: names = artist_name.contents[0] links = 'https://web.archive.org' + artist_name.get('href') f.writerow([names, links])
由于该程序正在做一些工作,因此创建 CSV 文件需要一些时间。 完成后,输出将完成,显示艺术家的姓名及其从 Zabaglia, Niccola 到 Zykmund, Václav 的关联链接。
体贴入微
抓取网页时,务必考虑您从中获取信息的服务器。
检查网站是否有与网络抓取相关的服务条款或使用条款。 此外,请检查网站是否具有允许您在自己抓取数据之前抓取数据的 API。
确保不要连续访问服务器来收集数据。 一旦你从一个站点收集了你需要的东西,运行将在本地检查数据的脚本,而不是给其他人的服务器增加负担。
此外,最好使用包含您的姓名和电子邮件的标题,以便网站可以识别您并在他们有任何问题时跟进。 可以与 Python Requests 库一起使用的标头示例如下:
import requests headers = { 'User-Agent': 'Your Name, example.com', 'From': 'email@example.com' } url = 'https://example.com' page = requests.get(url, headers = headers)
使用带有可识别信息的标头可确保查看服务器日志的人员可以联系到您。
结论
本教程介绍了使用 Python 和 Beautiful Soup 从网站抓取数据。 我们将收集到的文本存储在 CSV 文件中。
您可以通过收集更多数据并使您的 CSV 文件更加健壮来继续处理此项目。 例如,您可能希望包括每位艺术家的国籍和年份。 您还可以使用所学的知识从其他网站上抓取数据。
要继续学习如何从 Web 中提取信息,请阅读我们的教程“如何使用 Scrapy 和 Python 3 抓取网页”。