【Web Crawler】Python 网页抓取的实用介绍

Web 抓取是从 Web 收集和解析原始数据的过程,Python 社区已经推出了一些非常强大的 Web 抓取工具。

互联网可能是地球上最大的信息来源。许多学科,例如数据科学、商业智能和调查报告,都可以从网站收集和分析数据中获益匪浅。

在本教程中,您将学习如何:

  • 使用字符串方法正则表达式解析网站数据

  • 使用HTML 解析器解析网站数据

  • 与表单和其他网站组件交互

注意:本教程改编自《 Python 基础:Python 3 实用介绍 》中的“与 Web 交互”一章。
本书使用 Python 的内置 IDLE 编辑器来创建和编辑 Python 文件并与 Python shell 交互,因此您会在本教程中偶尔看到对 IDLE 的引用。但是,从您选择的 编辑器 环境 运行示例代码应该没有问题。
源代码: 单击此处下载 您将用于从 Web 收集和解析数据的免费源代码。

从网站上抓取和解析文本

使用自动化过程从网站收集数据称为网络抓取。一些网站明确禁止用户使用自动化工具(例如您将在本教程中创建的工具)抓取他们的数据。网站这样做有两个可能的原因:

  1. 该站点有充分的理由保护其数据。例如,Google 地图不允许您过快地请求太多结果。

  1. 向网站服务器发出多次重复请求可能会耗尽带宽,降低其他用户的网站速度,并可能使服务器过载,从而使网站完全停止响应。

在使用 Python 技能进行网络抓取之前,您应该始终检查目标网站的可接受使用政策,看看使用自动化工具访问该网站是否违反了其使用条款。从法律上讲,违背网站意愿的网络抓取在很大程度上是一个灰色地带。

重要提示:请注意,在禁止网络抓取的网站上使用以下技术 可能是非法的。

对于本教程,您将使用托管在 Real Python 服务器上的页面。您将访问的页面已设置为与本教程一起使用。

现在您已经阅读了免责声明,您可以了解有趣的内容了。在下一节中,您将开始从单个网页中获取所有 HTML 代码。

构建您的第一个网络抓取工具

您可以在 Python 的标准库中找到一个有用的 Web 抓取包urllib,它包含用于处理 URL 的工具。特别是,该urllib.request模块包含一个名为的函数urlopen(),您可以使用该函数在程序中打开 URL。

在 IDLE 的交互式窗口中,键入以下内容以导入urlopen()

from urllib.request import urlopen

您将打开的网页位于以下 URL:

url = "http://olympus.realpython.org/profiles/aphrodite"

要打开网页,请传递urlurlopen()

page = urlopen(url)

urlopen()返回一个HTTPResponse对象:

page

<http.client.HTTPResponse object at 0x105fef820>

要从页面中提取 HTML,首先使用该HTTPResponse对象的.read()方法,该方法返回一个字节序列。然后使用UTF-8.decode()将字节解码为字符串:

html_bytes = page.read()
html = html_bytes.decode("utf-8")

现在您可以打印HTML 以查看网页内容:

print(html)

<html>

<head>

<title>Profile: Aphrodite</title>

</head>

<body bgcolor="yellow">

<center>

<br><br>

<img src="/static/aphrodite.gif" />

<h2>Name: Aphrodite</h2>

<br><br>

Favorite animal: Dove

<br><br>

Favorite color: Red

<br><br>

Hometown: Mount Olympus

</center>

</body>

</html>

您看到的输出是网站的HTML 代码,您的浏览器会在您访问时呈现该代码http://olympus.realpython.org/profiles/aphrodite

使用urllib,您可以像在浏览器中一样访问该网站。但是,您不是以可视化方式呈现内容,而是将源代码作为文本抓取。现在您已将 HTML 作为文本,您可以通过几种不同的方式从中提取信息。

使用字符串方法从 HTML 中提取文本

从网页的 HTML 中提取信息的一种方法是使用字符串方法。例如,您可以使用.find()它在 HTML 文本中搜索<title>标签并提取网页标题。

首先,您将提取在上一个示例中请求的网页的标题。如果您知道标题第一个字符的索引和结束</title>标记的第一个字符的索引,那么您可以使用字符串切片来提取标题。

因为.find()返回第一次出现的子字符串的索引,所以您可以通过将字符串传递给来获取开始标记<title>的索引:"<title>".find()

title_index = html.find("<title>")
title_index

14

但是,您不需要<title>标签的索引。您需要标题本身的索引。要获取标题中第一个字母的索引,可以将字符串的长度添加"<title>"title_index

start_index = title_index + len("<title>")
start_index

21

</title>现在通过将字符串传递给来获取结束标记"</title>"的索引.find()

end_index = html.find("</title>")
end_index

39

html最后,您可以通过对字符串进行切片来提取标题:

title = html[start_index:end_index]
title

title'Profile: Aphrodite'

真实世界的 HTML 可能比 Aphrodite 个人资料页面上的 HTML 复杂得多,也更难预测。这是另一个个人资料页面,其中包含一些您可以抓取的更混乱的 HTML:

url = "http://olympus.realpython.org/profiles/poseidon"

尝试使用与上一个示例相同的方法从此新 URL 中提取标题:

url = "http://olympus.realpython.org/profiles/poseidon"
page = urlopen(url)
html = page.read().decode("utf-8")
start_index = html.find("<title>") + len("<title>")
end_index = html.find("</title>")
title = html[start_index:end_index]
title

'\n<head>\n<title >Profile: Poseidon'

哎呀!标题中混合了一些 HTML。为什么?

/profiles/poseidon页面的 HTML 看起来与该页面相似/profiles/aphrodite,但存在细微差别。开始<title>标记在结束尖括号 ( ) 之前有一个额外的空间>,将其呈现为<title >.

html.find("<title>")返回-1,因为确切的子字符串"<title>"不存在。当-1添加到len("<title>")时,即为7变量start_index赋值6

字符串索引6处的字符是标签左尖括号 ( )之前的html换行符 ( )。这意味着返回所有以该换行符开始并在标记之前结束的 HTML 。\n<<head>html[start_index:end_index]</title>

这类问题可能以无数不可预测的方式发生。您需要一种更可靠的方法来从 HTML 中提取文本。

了解正则表达式

正则表达式(或简称为正则表达式)是可用于在字符串中搜索文本的模式。rePython 通过标准库的模块支持正则表达式。

注意:正则表达式不是 Python 特有的。它们是一个通用的编程概念,并在许多编程语言中得到支持。

要使用正则表达式,您需要做的第一件事是导入re模块:

import re

正则表达式使用称为元字符的特殊字符来表示不同的模式。例如,星号字符 ( *) 代表星号之前出现的任何内容的零个或多个实例。

在以下示例中,您可以使用它.findall()来查找字符串中与给定正则表达式匹配的任何文本:

re.findall("ab*c","ac")['ac']

第一个参数re.findall()是要匹配的正则表达式,第二个参数是要测试的字符串。"ab*c"在上面的示例中,您在字符串中搜索模式"ac"

正则表达式"ab*c"匹配字符串中以 开头"a"、以 结尾"c"并且在两者之间有零个或多个实例的任何部分"b"re.findall()返回所有匹配项的列表。该字符串"ac"与此模式匹配,因此它在列表中返回。

这是应用于不同字符串的相同模式:

>>> re.findall("ab*c", "abcd")
['abc']

>>> re.findall("ab*c", "acc")
['ac']

>>> re.findall("ab*c", "abcac")
['abc', 'ac']

>>> re.findall("ab*c", "abdc")
[]

请注意,如果未找到匹配项,则.findall()返回一个空列表。

模式匹配区分大小写。如果你想不管大小写都匹配这个模式,那么你可以传递第三个参数re.IGNORECASE

>>> re.findall("ab*c", "ABC")
[]

>>> re.findall("ab*c", "ABC", re.IGNORECASE)
['ABC']

您可以使用句点 ( .) 来代表正则表达式中的任何单个字符。例如,您可以找到包含字母并由单个字符分隔的所有字符串,"a"如下"c"所示:

>>> re.findall("a.c", "abc")
['abc']

>>> re.findall("a.c", "abbc")
[]

>>> re.findall("a.c", "ac")
[]

>>> re.findall("a.c", "acc")
['acc']

正则表达式中的模式.*代表任何重复任意次数的字符。例如,您可以使用"a.*c"来查找以 开头"a"和结尾的每个子字符串"c",而不管中间是哪个字母或哪些字母:

>>> re.findall("a.*c", "abc")
['abc']

>>> re.findall("a.*c", "abbc")
['abbc']

>>> re.findall("a.*c", "ac")
['ac']

>>> re.findall("a.*c", "acc")
['acc']

通常,您re.search()用于在字符串中搜索特定模式。这个函数比re.findall()因为它返回一个MatchObject存储不同数据组的对象调用要复杂一些。这是因为其他匹配项中可能存在匹配项,并re.search()返回所有可能的结果。

的细节在MatchObject这里无关紧要。现在,只需知道调用.group()onMatchObject将返回第一个也是最具包容性的结果,在大多数情况下这正是您想要的:

match_results = re.search("ab*c", "ABC", re.IGNORECASE)
match_results.group()

'ABC'

该模块中还有一项功能re可用于解析文本。re.sub()substitute的缩写,允许您用新文本替换与正则表达式匹配的字符串中的文本。它的行为有点像.replace()字符串方法。

传递给的参数re.sub()是正则表达式,后跟替换文本,再后跟字符串。这是一个例子:

string = "Everything is <replaced> if it's in <tags>."
string = re.sub("<.*>", "ELEPHANTS", string)
string

'Everything is ELEPHANTS.'

也许那不是您所期望的。

re.sub()使用正则表达式"<.*>"查找和替换第一个<和最后一个之间的所有内容>,跨越从开头<replaced>到结尾<tags>。这是因为 Python 的正则表达式是贪婪的,这意味着当使用像这样的字符时,它们会尝试找到最长的可能匹配项*

或者,您可以使用非贪婪匹配模式*?,它的工作方式与 相同,*只是它匹配最短的可能文本字符串:

string = "Everything is <replaced> if it's in <tags>."
string = re.sub("<.*?>", "ELEPHANTS", string)
string

"Everything is ELEPHANTS if it's in ELEPHANTS."

这一次,re.sub()找到两个匹配项,<replaced><tags>,并将字符串替换"ELEPHANTS"为两个匹配项。

使用正则表达式从 HTML 中提取文本

具备所有这些知识后,现在尝试从另一个个人资料页面解析出标题,其中包括这段相当粗心的 HTML 行:

<TITLE >Profile: Dionysus</title  / >

.find()方法将很难处理这里的不一致,但是通过巧妙地使用正则表达式,您可以快速有效地处理这段代码:

# regex_soup.py

import re
from urllib.request import urlopen

url = "http://olympus.realpython.org/profiles/dionysus"
page = urlopen(url)
html = page.read().decode("utf-8")

pattern = "<title.*?>.*?</title.*?>"
match_results = re.search(pattern, html, re.IGNORECASE)
title = match_results.group()
title = re.sub("<.*?>", "", title) # Remove HTML tags

print(title)

pattern将字符串中的第一个正则表达式分解为三个部分,仔细查看:

  1. <title.*?>匹配. <TITLE >_ html模式的<title部分与匹配,<TITLE因为re.search()调用与re.IGNORECASE,并匹配直到第一个实例.*?>之后的任何文本。<TITLE>

  1. .*?非贪婪地匹配开头后的所有文本<TITLE >,在第一个匹配处停止</title.*?>

  1. </title.*?>与第一个模式的不同之处仅在于它对字符的使用,/因此它与.</title / >html

第二个正则表达式 string"<.*?>"也使用非贪婪.*?匹配字符串中的所有 HTML 标记title。通过将任何匹配项替换为""re.sub()删除所有标签并仅返回文本。

注意:使用 Python 或任何其他语言进行 Web 抓取可能很乏味。没有两个网站的组织方式是一样的,而且 HTML 常常很乱。此外,网站会随着时间而改变。今天能用的网络抓取工具并不能保证明年或下周也能用!

如果使用得当,正则表达式是一个强大的工具。在本简介中,您只了解了皮毛。有关正则表达式及其使用方法的更多信息,请查看由两部分组成的正则表达式系列:Python 中的正则表达式。

检查你的理解

展开下面的方块来检查你的理解。

编写一个程序,从以下 URL 抓取完整的 HTML:

url = "http://olympus.realpython.org/profiles/dionysus"

然后用于.find()显示Name:Favorite Color:之后的文本(不包括可能出现在同一行的任何前导空格或尾随 HTML 标记)。

您可以展开下面的块以查看解决方案。

首先,从模块中导入urlopen函数:urlib.request

from urllib.request import urlopen

然后打开 URL 并使用返回的对象的.read()方法来读取页面的 HTML:HTTPResponseurlopen()

url = "http://olympus.realpython.org/profiles/dionysus"
html_page = urlopen(url)
html_text = html_page.read().decode("utf-8")

.read()方法返回一个字节字符串,因此您可以.decode()使用 UTF-8 编码对字节进行解码。

现在您已将网页的 HTML 源代码作为分配给html_text变量的字符串,您可以从他的个人资料中提取 Dionysus 的名字和最喜欢的颜色。Dionysus 个人资料的 HTML 结构与您之前看到的 Aphrodite 个人资料的 HTML 结构相同。

您可以通过"Name:"在文本中查找字符串并提取字符串第一次出现之后和下一个 HTML 标记之前的所有内容来获取名称。也就是说,您需要提取冒号 ( :) 之后和第一个尖括号 ( <) 之前的所有内容。您可以使用相同的技术来提取最喜欢的颜色。

以下for循环为名称和最喜欢的颜色提取此文本:

for string in ["Name: ", "Favorite Color:"]:
    string_start_idx = html_text.find(string)
    text_start_idx = string_start_idx + len(string)

    next_html_tag_offset = html_text[text_start_idx:].find("<")
    text_end_idx = text_start_idx + next_html_tag_offset

    raw_text = html_text[text_start_idx : text_end_idx]
    clean_text = raw_text.strip(" \r\n\t")
    print(clean_text)

看起来这个for循环中发生了很多事情,但计算提取所需文本的正确索引只是一点点算术。继续分解:

  1. 您使用或html_text.find()来查找字符串的起始索引,然后将索引分配给。"Name:""Favorite Color:"string_start_idx

  1. 由于要提取的文本刚好在"Name:"or中的冒号之后开始"Favorite Color:",因此您可以通过将字符串的长度添加到 来获得冒号之后字符的索引,start_string_idx然后将结果分配给text_start_idx。

  1. 您可以通过确定第一个尖括号 ( <) 相对于的索引text_start_idx并将该值分配给来计算要提取的文本的结束索引next_html_tag_offset。然后将该值添加到text_start_idx并将结果分配给text_end_idx。

  1. html_text您通过从text_start_idxto切片text_end_idx并将此字符串分配给来提取文本raw_text。

  1. raw_text您从using的开头和结尾删除所有空格,并将.strip()结果分配给clean_text。

在循环结束时,您使用print()来显示提取的文本。最终输出如下所示:

Dionysus
Wine

这个解决方案是解决这个问题的众多解决方案之一,所以如果您使用不同的解决方案获得相同的输出,那么您就做得很好!

准备就绪后,您可以继续下一部分。

在 Python 中使用 HTML 解析器进行网页抓取

虽然正则表达式通常非常适合模式匹配,但有时使用专门为解析 HTML 页面而设计的 HTML 解析器会更容易。有许多为此目的编写的 Python 工具,但Beautiful Soup库是一个很好的起点。

安装Beautiful Soup

要安装 Beautiful Soup,您可以在终端中运行以下命令:

$ python -m pip install beautifulsoup4

使用此命令,您可以将最新版本的 Beautiful Soup 安装到您的全局 Python 环境中。

创建BeautifulSoup对象

在新的编辑器窗口中键入以下程序:

# beauty_soup.py

from bs4 import BeautifulSoup
from urllib.request import urlopen

url = "http://olympus.realpython.org/profiles/dionysus"
page = urlopen(url)
html = page.read().decode("utf-8")
soup = BeautifulSoup(html, "html.parser")

这个程序做了三件事:

  1. http://olympus.realpython.org/profiles/dionysus使用urlopen()fromurllib.request模块打开 URL

  1. 从页面读取 HTML 作为字符串并将其分配给html变量

  1. 创建一个BeautifulSoup对象并将其分配给soup变量

BeautifulSoup分配给的对象soup是用两个参数创建的。第一个参数是要解析的 HTML,第二个参数 string"html.parser"告诉对象在幕后使用哪个解析器。"html.parser"代表 Python 的内置 HTML 解析器。

使用BeautifulSoup对象

保存并运行上面的程序。当它运行完成后,您可以使用交互窗口中的变量以各种方式soup解析其中的内容。html

注意:如果你没有使用 IDLE,那么你可以使用-i 标志运行你的程序以进入交互模式。类似的东西python -i beauty_soup.py 将首先运行您的程序,然后将您留在 REPL 中,您可以在其中探索您的对象。

例如,BeautifulSoup对象有一个.get_text()方法,您可以使用该方法从文档中提取所有文本并自动删除任何 HTML 标记。

在 IDLE 的交互窗口或编辑器中的代码末尾键入以下代码:

print(soup.get_text())

Profile: Dionysus

Name: Dionysus

Hometown: Mount Olympus

Favorite animal: Leopard

Favorite Color: Wine

此输出中有很多空行。这些是 HTML 文档文本中换行符的结果。.replace()如果需要,您可以使用字符串方法删除它们。

通常,您只需要从 HTML 文档中获取特定文本。首先使用 Beautiful Soup 提取文本,然后使用.find()字符串方法有时比使用正则表达式更容易。

然而,有时 HTML 标签本身就是指出您要检索的数据的元素。例如,您可能想要检索页面上所有图像的 URL。这些链接包含在HTML 标签的src属性中。<img>

在这种情况下,您可以使用find_all()返回该特定标签的所有实例的列表:

soup.find_all("img")

[<img src="/static/dionysus.jpg"/>, <img src="/static/grapes.png"/>]

<img>这将返回HTML 文档中所有标签的列表。列表中的对象看起来可能是代表标签的字符串,但它们实际上是TagBeautiful Soup 提供的对象的实例。Tag对象提供了一个简单的接口来处理它们包含的信息。

Tag您可以通过首先从列表中解包对象来稍微探索一下:

image1, image2 = soup.find_all("img")

每个Tag对象都有一个.name返回包含 HTML 标签类型的字符串的属性:

image1.name

'img'

您可以通过将名称放在方括号中来访问对象的 HTML 属性Tag,就像属性是字典中的键一样。

例如,<img src="/static/dionysus.jpg"/>标记具有单个属性 ,src其值为"/static/dionysus.jpg"。同样,像链接这样的 HTML 标签<a href="https://realpython.com" target="_blank">有两个属性,hreftarget.

要获取 Dionysus 个人资料页面中图像的来源,您可以src使用上述字典符号访问该属性:

>>> image1["src"]
'/static/dionysus.jpg'

>>> image2["src"]
'/static/grapes.png'

HTML 文档中的某些标签可以通过Tag对象的属性访问。例如,要获取<title>文档中的标签,您可以使用.title属性:

soup.title

<title>Profile: Dionysus</title>

如果您通过导航到个人资料页面查看狄俄尼索斯个人资料的来源,右键单击该页面并选择查看页面来源,您会注意到<title>标签全部大写并带有空格:

Beautiful Soup 会自动为您清理标签,方法是删除开始标签中的多余空格和/结束标签中多余的正斜杠 ( )。

您还可以仅检索具有对象.string属性的标题标签之间的字符串Tag

soup.title.string

'Profile: Dionysus'

Beautiful Soup 的功能之一是能够搜索其属性与特定值匹配的特定类型的标签。例如,如果您想查找属性等于 value<img>的所有标签,则可以向 提供以下附加参数:src/static/dionysus.jpg.find_all()

soup.find_all("img", src="/static/dionysus.jpg")

[<img src="/static/dionysus.jpg"/>]

这个例子有些武断,从这个例子中可能看不出这种技术的用处。如果您花一些时间浏览各种网站并查看它们的页面源代码,那么您会注意到许多网站的 HTML 结构极其复杂。

当使用 Python 从网站抓取数据时,您通常会对页面的特定部分感兴趣。通过花一些时间浏览 HTML 文档,您可以识别具有独特属性的标签,您可以使用这些标签来提取所需的数据。

然后,无需依赖复杂的正则表达式或使用.find()搜索文档,您可以直接访问您感兴趣的特定标签并提取所需数据。

在某些情况下,您可能会发现 Beautiful Soup 无法提供您需要的功能。lxml库在开始时有点棘手,但在解析 HTML 文档方面比 Beautiful Soup 提供了更大的灵活性。一旦您习惯使用 Beautiful Soup,您可能想检查一下。

注意:在查找网页中的特定数据时,像 Beautiful Soup 这样的 HTML 解析器可以为您节省大量时间和精力。然而,有时 HTML 写得如此糟糕和混乱,以至于即使像 Beautiful Soup 这样复杂的解析器也无法正确解释 HTML 标签。
在这种情况下,您通常只能使用.find() 正则表达式技术来尝试解析出您需要的信息。

Beautiful Soup 非常适合从网站的 HTML 中抓取数据,但它不提供任何处理 HTML 表单的方法。例如,如果您需要在网站上搜索一些查询,然后抓取结果,那么仅靠 Beautiful Soup 不会让您走得太远。

检查你的理解

展开下面的方块来检查你的理解。

编写一个程序,从位于 URL的页面http://olympus.realpython.org/profiles中获取完整的 HTML 。

a使用 Beautiful Soup,通过查找带有名称的 HTML 标签并检索href每个标签的属性所采用的值,打印出页面上所有链接的列表。

最终输出应如下所示:

http://olympus.realpython.org/profiles/aphrodite
http://olympus.realpython.org/profiles/poseidon
http://olympus.realpython.org/profiles/dionysus

/确保基本 URL 和相对 URL 之间只有一个斜杠 ( )。

您可以展开下面的块以查看解决方案:

首先,从模块中导入urlopen函数,从包中导入类:urlib.requestBeautifulSoupbs4

from urllib.request import urlopen
from bs4 import BeautifulSoup

页面上的每个链接 URL/profiles都是一个相对 URL,因此使用网站的基本 URL 创建一个base_url变量:

base_url = "http://olympus.realpython.org"

base_url您可以通过与相对 URL连接来构建完整URL。

现在打开/profiles页面urlopen()并使用它.read()来获取 HTML 源代码:

html_page = urlopen(base_url + "/profiles")
html_text = html_page.read().decode("utf-8")

下载并解码 HTML 源代码后,您可以创建一个新BeautifulSoup对象来解析 HTML:

soup = BeautifulSoup(html_text, "html.parser")

soup.find_all("a")返回 HTML 源中所有链接的列表。您可以遍历此列表以打印出网页上的所有链接:

for link in soup.find_all("a"):
    link_url = base_url + link["href"]
    print(link_url)

"href"您可以通过下标访问每个链接的相对 URL 。将此值与base_url创建完整的link_url.

准备就绪后,您可以继续下一部分。

与 HTML 表单交互

urllib到目前为止,您在本教程中使用的模块非常适合请求网页内容。但有时,您需要与网页进行交互以获取所需的内容。例如,您可能需要提交表单或单击按钮以显示隐藏内容。

注意:本教程改编自《 Python 基础:Python 3 实用介绍 》中的“与 Web 交互”一章。如果您喜欢正在阅读的内容,请务必查看 本书的其余部分

Python 标准库不提供以交互方式处理网页的内置方法,但可以从PyPI获得许多第三方包。其中,MechanicalSoup是一个流行且使用起来相对简单的包。

从本质上讲,MechanicalSoup 安装了所谓的无头浏览器,这是一种没有图形用户界面的网络浏览器。该浏览器通过 Python 程序以编程方式控制。

安装 MechanicalSoup

pip您可以在终端中安装 MechanicalSoup :

$ python -m pip install MechanicalSoup

您需要关闭并重新启动您的 IDLE 会话,以便 MechanicalSoup 加载并在安装后被识别。

创建Browser对象

在 IDLE 的交互窗口中输入以下内容:

import mechanicalsoup
browser = mechanicalsoup.Browser()

Browser对象代表无头网络浏览器。您可以使用它们通过将 URL 传递给它们的.get()方法来从 Internet 请求页面:

url = "http://olympus.realpython.org/login"
page = browser.get(url)

page是一个Response对象,用于存储从浏览器请求 URL 的响应:

page

<Response [200]>

该数字200表示请求返回的状态代码。状态码200表示请求成功。404如果 URL 不存在或500发出请求时出现服务器错误,则不成功的请求可能会显示状态代码。

MechanicalSoup 使用 Beautiful Soup 从请求中解析 HTML,并且page有一个.soup表示BeautifulSoup对象的属性:

type(page.soup)

<class 'bs4.BeautifulSoup'>

您可以通过检查.soup属性来查看 HTML:

page.soup

<html>

<head>

<title>Log In</title>

</head>

<body bgcolor="yellow">

<center>

<br/><br/>

<h2>Please log in to access Mount Olympus:</h2>

<br/><br/>

<form action="/login" method="post" name="login">

Username: <input name="user" type="text"/><br/>

Password: <input name="pwd" type="password"/><br/><br/>

<input type="submit" value="Submit"/>

</form>

</center>

</body>

</html>

请注意,此页面上有一个<form>包含<input>用户名和密码的元素。

使用 MechanicalSoup 提交表格

在浏览器中打开/login上一个示例中的页面,并在继续之前自行查看:

尝试输入随机的用户名和密码组合。如果您猜错了,则会显示错误的用户名或密码!显示在页面底部。

但是,如果您提供了正确的登录凭据,您将被重定向到该/profiles页面:

用户名

密码

zeus

ThunderDude

在下一个示例中,您将看到如何使用 MechanicalSoup 使用 Python 填写和提交此表单!

HTML 代码的重要部分是登录表单,即<form>标签内的所有内容。此<form>页面上的name属性设置为login。此表单包含两个<input>元素,一个名为user,另一个名为pwd。第三个<input>元素是提交按钮。

既然您了解了登录表单的基本结构以及登录所需的凭据,请查看一个填写并提交表单的程序。

在新的编辑器窗口中,输入以下程序:

import mechanicalsoup

# 1
browser = mechanicalsoup.Browser()
url = "http://olympus.realpython.org/login"
login_page = browser.get(url)
login_html = login_page.soup

# 2
form = login_html.select("form")[0]
form.select("input")[0]["value"] = "zeus"
form.select("input")[1]["value"] = "ThunderDude"

# 3
profiles_page = browser.submit(form, login_page.url)

保存文件并按F5运行它。要确认您已成功登录,请在交互窗口中输入以下内容:

profiles_page.url

'http://olympus.realpython.org/profiles'

现在分解上面的例子:

  1. 您创建一个Browser实例并使用它来请求 URL http://olympus.realpython.org/login。您可以使用该属性将页面的 HTML 内容分配给login_html变量。.soup

  1. login_html.select("form")返回页面上所有<form>元素的列表。因为页面只有一个<form>元素,所以您可以通过检索列表索引处的元素来访问表单0。当页面上只有一个表单时,也可以使用login_html.form. 接下来的两行选择用户名和密码输入,并将它们的值分别设置为"zeus"和。"ThunderDude"

  1. 您使用 提交表格browser.submit()。请注意,您将两个参数传递给此方法,form对象和 的 URL login_page,您可以通过 访问它们login_page.url。

在交互窗口中,您确认提交已成功重定向到/profiles页面。如果出了什么问题,那么 的值profiles_page.url仍然是"http://olympus.realpython.org/login"

注意:黑客可以使用像上面那样的自动化程序通过快速尝试许多不同的用户名和密码来暴力破解登录,直到他们找到一个有效的组合。
除了这是高度违法的,现在几乎所有的网站都会在看到你发出太多失败的请求时将你拒之门外并报告你的 IP 地址,所以不要尝试!

现在您已经profiles_page设置了变量,是时候以编程方式获取/profiles页面上每个链接的 URL 了。

为此,您.select()再次使用,这次传递字符串"a"以选择<a>页面上的所有锚点元素:

links = profiles_page.soup.select("a")

现在您可以遍历每个链接并打印href属性:

for link in links:
    address = link["href"]
    text = link.text
    print(f"{text}: {address}")

Aphrodite: /profiles/aphrodite

Poseidon: /profiles/poseidon

Dionysus: /profiles/dionysus

每个href属性中包含的 URL 都是相对 URL,如果您想稍后使用 MechanicalSoup 导航到它们,这不是很有帮助。如果您碰巧知道完整的 URL,那么您可以分配构建完整 URL 所需的部分。

在这种情况下,基本 URL 就是http://olympus.realpython.org. 然后您可以将基本 URL 与在src属性中找到的相对 URL 连接起来:

base_url = "http://olympus.realpython.org"
for link in links:
    address = base_url + link["href"]
    text = link.text
    print(f"{text}: {address}")

Aphrodite: http://olympus.realpython.org/profiles/aphrodite

Poseidon: http://olympus.realpython.org/profiles/poseidon

Dionysus: http://olympus.realpython.org/profiles/dionysus

.get()您可以使用、.select()和做很多事情.submit()。也就是说,MechanicalSoup 的功能远不止于此。要了解有关 MechanicalSoup 的更多信息,请查看官方文档

检查你的理解

展开下面的块以检查您的理解

使用 MechanicalSoup 向位于 URL的登录表单zeus提供正确的用户名 ( ) 和密码 ( ThunderDude) 。http://olympus.realpython.org/login

提交表单后,显示当前页面的标题以确定您已被重定向到该/profiles页面。

您的程序应该打印文本<title>All Profiles</title>

您可以展开下面的块以查看解决方案。

首先,导入mechanicalsoup包并创建一个Broswer对象:

import mechanicalsoup

browser = mechanicalsoup.Browser()

通过将 URL 传递给并获取具有以下属性browser.get()的 HTML,将浏览器指向登录页面:.soup

login_url = "http://olympus.realpython.org/login"
login_page = browser.get(login_url)
login_html = login_page.soup

login_html是一个BeautifulSoup实例。因为该页面上只有一个表单,所以您可以通过 访问该表单login_html.form。使用.select(),选择用户名和密码输入并填写用户名"zeus"和密码"ThunderDude"

form = login_html.form
form.select("input")[0]["value"] = "zeus"
form.select("input")[1]["value"] = "ThunderDude"

现在表单已填写,您可以使用以下方式提交browser.submit()

profiles_page = browser.submit(form, login_page.url)

如果您用正确的用户名和密码填写了表格,那么profiles_page实际上应该指向该/profiles页面。您可以通过打印分配给的页面标题来确认这一点profiles_page:

print(profiles_page.soup.title)

您应该看到显示以下文本:

<title>All Profiles</title>

相反,如果您看到的是文本Log In或其他内容,则表示表单提交失败。

准备就绪后,您可以继续下一部分。

与网站实时互动

有时您希望能够从提供不断更新信息的网站获取实时数据。

在您学习 Python 编程之前的黑暗日子里,您必须坐在浏览器前,每次要检查更新的内容是否可用时单击“刷新”按钮重新加载页面。但现在您可以使用.get()MechanicalSoupBrowser对象的方法自动执行此过程。

打开您选择的浏览器并导航到 URL http://olympus.realpython.org/dice

/dice页面模拟六面骰子的滚动,每次刷新浏览器时都会更新结果。下面,您将编写一个程序,重复抓取页面以获得新结果。

您需要做的第一件事是确定页面上的哪个元素包含掷骰子的结果。现在通过右键单击页面上的任意位置并选择View page source来执行此操作。HTML 代码的一半多一点是一个<h2>看起来像这样的标签:

<h2 id="result">3</h2>

标签的文本<h2>可能与您不同,但这是抓取结果所需的页面元素。

注意:对于此示例,您可以轻松检查页面上是否只有一个元素带有id="result" . 尽管该id 属性应该是唯一的,但实际上您应该始终检查您感兴趣的元素是否具有唯一标识。

现在开始编写一个简单的程序来打开/dice页面、抓取结果并将其打印到控制台:

# mech_soup.py

import mechanicalsoup

browser = mechanicalsoup.Browser()
page = browser.get("http://olympus.realpython.org/dice")
tag = page.soup.select("#result")[0]
result = tag.text

print(f"The result of your dice roll is: {result}")

此示例使用BeautifulSoup对象的.select()方法来查找带有 的元素id=result"#result"您传递给的字符串.select()使用CSS ID 选择器 #来指示它result是一个id值。

要定期获得新结果,您需要创建一个循环以在每一步加载页面。所以browser = mechanicalsoup.Browser()上面代码中行下面的所有内容都需要进入循环体。

对于此示例,您需要以 10 秒为间隔掷四次骰子。为此,代码的最后一行需要告诉 Python 暂停运行十秒钟。您可以使用.sleep()来自 Python 的time模块来执行此操作。该.sleep()方法采用单个参数表示以秒为单位的休眠时间。

这是一个说明sleep()工作原理的示例:

import time

print("I'm about to wait for five seconds...")
time.sleep(5)
print("Done waiting!")

当您运行这段代码时,您会看到直到第一个函数执行"Done waiting!"五秒后才显示该消息。print()

对于掷骰子示例,您需要将数字传递10sleep(). 这是更新的程序:

# mech_soup.py

import time
import mechanicalsoup

browser = mechanicalsoup.Browser()

for i in range(4):
    page = browser.get("http://olympus.realpython.org/dice")
    tag = page.soup.select("#result")[0]
    result = tag.text
    print(f"The result of your dice roll is: {result}")
    time.sleep(10)

当您运行该程序时,您会立即看到第一个结果打印到控制台。十秒后,显示第二个结果,然后是第三个,最后是第四个。打印第四个结果后会发生什么?

该程序在最终停止之前继续运行了十秒钟。那是在浪费时间!您可以使用仅针对前三个请求运行的if语句来阻止它执行此操作:time.sleep()

# mech_soup.py

import time
import mechanicalsoup

browser = mechanicalsoup.Browser()

for i in range(4):
    page = browser.get("http://olympus.realpython.org/dice")
    tag = page.soup.select("#result")[0]
    result = tag.text
    print(f"The result of your dice roll is: {result}")

    # Wait 10 seconds if this isn't the last request
    if i < 3:
        time.sleep(10)

使用这样的技术,您可以从定期更新数据的网站上抓取数据。但是,您应该知道,快速连续多次请求一个页面可能会被视为对网站的可疑使用,甚至是恶意使用。

重要提示:大多数网站都会发布使用条款文档。您通常可以在网站的页脚中找到它的链接。
在尝试从网站上抓取数据之前,请务必阅读本文档。如果找不到使用条款,请尝试联系网站所有者并询问他们是否有任何关于请求量的政策。
不遵守使用条款可能会导致您的 IP 被封锁,所以要小心!

甚至有可能因请求过多而导致服务器崩溃,因此您可以想象许多网站都在关注对其服务器的请求量!向网站发送多个请求时,请务必查看使用条款并保持尊重。

结论

尽管可以使用 Python 标准库中的工具解析来自 Web 的数据,但 PyPI 上有许多工具可以帮助简化该过程。

在本教程中,您学习了如何:

  • 使用Python内置urllib模块请求网页

  • 使用Beautiful Soup解析 HTML

  • 使用MechanicalSoup与 Web 表单交互

  • 反复从网站请求数据以检查更新

编写自动网络抓取程序很有趣,互联网上不乏可以促成各种令人兴奋的项目的内容。

请记住,并不是每个人都希望您从他们的网络服务器中提取数据。在开始抓取之前,请务必检查网站的使用条款,并尊重您对网络请求的时间安排,以免服务器流量过大。

  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 15
    评论
Python是一种非常适合用于实现网页爬虫的编程语言。使用Python可以编写爬虫程序,从网站上抓取数据。网页爬虫(Web Crawler)也被称为网络爬虫(Web Spider),它是一种按照一定的规则自动地抓取万维网信息的程序或者脚本。通过Python编写的爬虫程序可以实现从网站上抓取所需的代码并保存到本地。你可以使用Python的一些库或框架来实现这样一个功能,如Beautiful Soup、Scrapy等。这些工具提供了一些方便的方法和函数来帮助你解析网页内容,并提取出你所需要的数据。因此,如果你想要实现一个简单的网页爬虫,Python是一个不错的选择。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [Python小姿势 - Python爬虫:如何使用Python实现网页爬虫](https://blog.csdn.net/weixin_39032019/article/details/130479738)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [python实现简单爬虫功能](https://blog.csdn.net/weixin_34384915/article/details/85608775)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Sonhhxg_柒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值