原文:zh.annas-archive.org/md5/AB12C428C180E19BF921ADFBD1CC8C3E

译者:飞龙

协议:CC BY-NC-SA 4.0

第七章:使用基于 Web 的 API 进行数据提取

基于 Web 的 API 允许用户与网络上的信息进行交互。API 直接处理格式化模式易于使用和维护的数据。有些 API 在向用户提供数据之前还需要用户身份验证。本章将介绍使用 Python 和一些 Web API 与可用 API 进行交互和提取数据。通常,API 以可交换的文档格式(如 JSON、CSV 和 XML)提供数据。

在本章中,我们将涵盖以下主题:

  • Web API 简介

  • 使用 Python 编程语言访问 Web API

  • 通过 Web API 处理和提取数据

技术要求

本章需要使用 Web 浏览器(Google Chrome 或 Mozilla Firefox)。我们将使用以下 Python 库:

  • requests

  • json

  • collections

如果这些库在您当前的 Python 设置中不存在,请参考第二章,Python 和 Web-使用 urllib 和 Requests,在设置事项部分了解如何下载它们。

本章的代码文件可在本书的 GitHub 存储库中找到:github.com/PacktPublishing/Hands-On-Web-Scraping-with-Python/tree/master/Chapter07

Web API 简介

基于 Web 的应用程序编程信息基于 Web 的 API是网站提供的接口,用于返回接收到的请求的信息。Web API(或 API)实际上是网站为用户或第三方 Web 应用程序或自动化脚本提供的 Web 服务,以便共享和交换信息。

通常,这是通过 Web 浏览器处理的用户界面(UI),用于从已向网站或 Web 服务器发出的请求中检索特定信息。具有任何类型大量信息的网站可以为其用户提供 Web API,以便进行信息共享。

在软件应用领域,API 以其一组设施(如方法和库)而闻名,可用于进一步增强、构建或开发应用程序。这也被称为开发者 API。

Web API 不依赖于任何编程语言。它们使得以原始格式轻松访问基于 Web 的信息,并通常以 JSON、XML 或 CSV 格式返回结构化响应。

它们遵循 HTTP 原则(请求和响应循环),但只接受预定义格式的请求和参数集以生成响应。在安全方面,许多 API 还提供身份验证工具,如 API 密钥,这是向网站发出请求所必需的。

REST 和 SOAP

API 是由基于软件架构或原则的 Web 服务器提供的服务。简单对象访问协议SOAP)和表述状态转移REST)是访问 Web 服务的方法。虽然 REST 是一种架构,但 SOAP 是基于 Web 标准的协议。我们将在接下来的部分中处理 REST API。

REST

REST(www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm)是一种基于一组定义和解决网络原则的软件架构风格。REST 是一种软件架构,而不是一组标准。REST 使用标准的 HTTP 协议和方法,如GETPOSTPUTDELETE来提供服务。它是无状态的、多层的,也支持缓存。

Web API 通常被归类为 RESTful Web 服务;它们为用户和其他资源提供通信接口。RESTful Web 服务(REST API 或 Web API)(restfulapi.net/)是 Web 提供的适应 REST 架构的服务。

通过 REST 提供的服务无需适应新的标准、开发或框架。大多数情况下,它将使用 GET 请求,以及已发出到 API 的查询字符串,搜索其响应。通常会跟踪 HTTP 状态码(restfulapi.net/http-status-codes/)(404、200、304)以确定 API 的响应。响应也可以以 JSON、XML 和 CSV 等各种格式获取。

在选择 REST 和 SOAP 之间,REST 在处理方面比 SOAP 更容易和高效,并且被许多网站提供给公众。

SOAP

SOAP(www.w3.org/TR/soap/is)是由 W3C 指定的一组标准,也是 Web 服务中与 REST 相对应的选择。SOAP 使用 HTTP 和 SMTP(简单邮件传输协议),用于在互联网上交换文档,以及通过远程过程。

SOAP 使用 XML 作为消息服务,也被称为基于 XML 的协议。SOAP 请求包含描述发送到服务器的方法和参数的 XML 文档(带有信封和正文)。服务器将执行接收到的方法,以及参数,并将 SOAP 响应发送回发起请求的程序。

SOAP 具有高度的可扩展性,并包括内置的错误处理。它还与其他协议(如 SMTP)一起工作。SOAP 也独立于平台和编程语言,并且主要在分布式企业环境中实现。

Web API 的好处

信息需求与其在网络上的可用性一天比一天增长。信息来源、其可用性、设施和共享和交换技术已成为全球需求。API 是首选的数据来源之一,可用于检索数据。

API 不仅是通过 Web 浏览器与用户进行通信的一种方式-您还可以使用系统。API 允许系统和设备之间的通信,例如移动设备,尽管它们的基础系统或编程语言不同。许多移动应用程序会向某些 API 发出请求,并显示从响应中检索到的相关信息。API 不仅是用于检索数据的简单服务;它们用于交换和处理信息,甚至在不同平台和服务之间进行系统间通信。

从网络抓取的角度来看,通过 API 可用的响应或数据优于使用抓取脚本检索的数据。这是由于以下原因:

  • API 返回的数据完全特定于正在执行的请求,以及已应用于它的过滤器或参数。

  • 使用 Python 库(如 BeautifulSoup、pyquery 和 lxml)解析 HTML 或 XML 并不总是必需的。

  • 数据的格式是结构化的,易于处理。

  • 数据清理和处理最终列表将更容易或可能不需要。

  • 与编码、分析网页并应用 XPath 和 CSS 选择器来检索数据相比,处理时间会显著减少。

  • 它们易于处理。

在完全从抓取的角度转向 Web API 之前,还有一些因素需要考虑,包括以下内容:

  • 并非所有网站都向用户提供访问 Web API 的权限。

  • API 的响应是特定于预定义参数集的。这可能限制基于需求可以进行的确切请求,并限制立即获取的数据的可用性。

  • 返回的响应受限于一定的数量,例如每个请求返回的记录数以及允许的最大请求数量。

  • 尽管数据将以结构化格式可用,但它可能分布在键值对中,这可能需要一些额外的合并任务。

鉴于这些观点,我们可以看到 web API 是从网站获取信息的首选选择。

访问 web API 和数据格式

在本节中,我们将探讨在 web 上可用的各种 API,向它们发送请求并接收响应,然后解释它们如何通过 Python 编程语言工作。

让我们考虑以下示例 URL,https://www.someexampledomain.com。它提供的 API 带有参数,定位器和身份验证。通过使用这些,我们可以访问以下资源:

  • https://api.someexampledomain.com 

  • https://api.someexampledomain.com/resource?key1=value1&key2=value2

  • https://api.someexampledomain.com/resource?api_key=ACCESS_KEY&key1=value1&key2=value2

  • https://api.someexampledomain.com/resource/v1/2019/01

参数或键值对的集合实际上是由 web 提供的预定义变量集。通常,API 提供有关其用法、HTTP 方法、可用键和类型或允许键接收的值的基本指南或文档,以及有关 API 支持的功能的其他信息,如下图所示:

https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/hsn-web-scp-py/img/79440f5c-2a15-4d38-9fdd-7b7ccef19261.png

来自 https://sunrise-sunset.org/api 的 API 详细信息和链接

最终用户和系统只能使用提供者允许的 API 功能和功能。

以下是一些实际 API 链接和示例调用,显示了 URL 中使用的格式和参数:

参数,如keyapi_keyapiKeyapi-key,是为了安全和跟踪措施而需要的,并且在处理任何 API 请求之前需要获得。

本节中的 API 链接和示例调用与它们所列出的资源相关联。例如,api.twitter.com/1.1/search/tweets.json?q=nasa&result_type=populardeveloper.twitter.com/en/docs/tweets/search/api-reference/get-search-tweets上列出。

使用 web 浏览器向 web API 发出请求

获取通过查询字符串应用的参数信息和获取 API 密钥(如果需要)是获得 API 访问权限的初步步骤。与由 Google、Twitter 和 Facebook 提供的开发者 API 相比,大多数公共或免费 API 都非常简单易懂。

API 请求可以通过 Web 浏览器进行。但是,在这一部分,我们将尝试展示访问 API 时可能遇到的一些常见情况,同时展示 RESTful API 的一些重要属性。

案例 1 - 访问简单的 API(请求和响应)

在这一部分,我们将使用以下 URL:api.sunrise-sunset.org/json?lat=27.717245&lng=85.323959&date=2019-03-04

让我们通过一个简单的 API 处理一个请求,以获取尼泊尔加德满都的日出和日落时间(以 UTC 时间为准)。查询字符串需要为所选位置的lat(纬度)、lng(经度)和date提供值。如下面的截图所示,我们获得的响应是以 JSON 格式(使用浏览器扩展格式化)返回的,通过使用基于浏览器的开发者工具验证了成功的请求方法和 HTTP 状态码(200,即OK成功):

https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/hsn-web-scp-py/img/54ee6161-e2db-4047-a9e0-a6278f6a6825.png

来自api.sunrise-sunset.org/json?lat=27.717245&lng=85.323959&date=2019-03-04的响应状态码

响应以原始格式或 JSON 格式返回,如下面的代码所示。当正常获取 JSON 响应时,可以使用 Python 的json库进行处理。在下面的代码中,API 请求已经使用requests库进行处理。requests提供了处理 HTTP 的各种功能;例如,可以使用status_code获取 HTTP 状态码。可以使用headers获取头信息。在这里,我们对status_codeheaders特别感兴趣,特别是Content-Type,以便我们可以计划进一步处理和可能需要使用的库:

import requests
url = 'https://api.sunrise-sunset.org/json?lat=27.7172&lng=85.3239&date=2019-03-04'   results = requests.get(url) #request url
print("Status Code: ", results.status_code)
print("Headers-ContentType: ", results.headers['Content-Type'])
print("Headers: ", results.headers)

jsonResult = results.json() #read JSON content
print("Type JSON Results",type(jsonResult))
print(jsonResult)
print("SunRise & Sunset: ",jsonResult['results']['sunrise']," & ",jsonResult['results']['sunset'])

如我们所见,status_code200(即OK),Content-Type是 JSON 类型。这给了我们确认,我们可以使用与 JSON 相关的库继续前进。但是,在这种情况下,我们使用了requests库中的json()函数,这减少了我们对额外库的依赖,并将响应对象转换为dict对象。通过收到的dict,我们可以使用key:value对访问所需的元素:

Type Results <class 'requests.models.Response'>
Status Code: 200
Headers-ContentType: application/json

Headers: {'Access-Control-Allow-Origin':'*','Content-Type':'application/json','Vary':'Accept-Encoding', 'Server':'nginx','Connection':'keep-alive','Content-Encoding':'gzip','Transfer-Encoding':'chunked','Date': 'Mon, 04 Mar 2019 07:48:29 GMT'}

Type JSON Results <class 'dict'>

{'status':'OK','results':{'civil_twilight_end':'12:44:16 PM','astronomical_twilight_end':'1:38:31 PM', 'civil_twilight_begin':'12:16:32 AM','sunrise':'12:39:54 AM',......,'sunset':'12:20:54 PM','solar_noon': '6:30:24 AM','day_length':'11:41:00'}}

SunRise & Sunset: 12:39:54 AM & 12:20:54 PM** 

案例 2 - 展示 API 的状态码和信息响应

在这一部分,我们将使用以下 URL:api.twitter.com/1.1/search/tweets.json?q=

在这一部分,我们将处理来自 Twitter 的 API 请求。要请求的 URL 是api.twitter.com/1.1/search/tweets.json?q=。通过使用这个 URL,我们可以很容易地确定查询字符串q是空的,Twitter API 期望的值没有提供。完整的 URL 应该是类似于api.twitter.com/1.1/search/tweets.json?q=nasa&result_type=popular

返回的响应是不完整的 API 调用,如下面的截图所示,还有 HTTP 状态码(400Bad Request)以及 API 返回的消息,指出了“message”:“Bad Authentication data”的错误。有关 Twitter API 的搜索选项的更多信息,请参阅developer.twitter.com/en/docs/tweets/search/api-reference/get-search-tweets

https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/hsn-web-scp-py/img/f65a7397-c1e0-49bb-8491-7f0fa1d67053.png

向 Twitter API 发出的不完整请求

Twitter API 返回的响应实际上是信息,而不是错误。这种信息性的响应使 API 在被其他资源使用时更具可伸缩性和易于调试。这也是 RESTful web 服务的一个受欢迎的特性。这种信息可以通过部署 API 参数和其他要求轻松地克服。

以下代码将使用空查询字符串向 Twitter 发出请求并识别响应:

import requests
import json
url = 'https://api.twitter.com/1.1/search/tweets.json?q='  
results = requests.get(url)
print("Status Code: ", results.status_code)
print("Headers: Content-Type: ", results.headers['Content-Type'])

jsonResult = results.content    #jsonResult = results.json() print(jsonResult)

jsonFinal = json.loads(jsonResult.decode())
print(jsonFinal) #print(json.loads(requests.get(url).content.decode()))   if results.status_code==400:
 print(jsonFinal['errors'][0]['message'])
else:
 pass

前面的代码使用json Python 库加载了使用loads()函数获得的解码jsonResult。我们也可以像在案例 1 中那样使用requests中的json()jsonFinal现在是一个 Python 字典对象,可以被探索,以便我们可以找到它的'key:value'。最终输出如下:

Status Code: 400
Headers: Content-Type: application/json; charset=utf-8

b'{"errors":[{"code":215,"message":"Bad Authentication data."}]}'
{'errors': [{'message': 'Bad Authentication data.', 'code': 215}]}

Bad Authentication data.

案例 3 - 展示 RESTful API 缓存功能

在本节中,我们将使用以下 URL:api.github.com/

GitHUb(github.com/)是开发人员及其代码存储库的地方。GitHub API 在开发人员中非常有名,他们都来自不同的编程背景。正如我们在下面的截图中所看到的,响应是以 JSON 格式获得的。由于返回的 HTTP 状态码是200,即OK成功,因此请求是成功的:

https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/hsn-web-scp-py/img/0d2c1add-f32e-4612-85cb-40bced919ec5.png

来自 https://api.github.com 的响应,HTTP 状态码为 200

如您所见,我们对api.github.com进行了基本调用。返回的内容包含 API 的链接,以及一些参数供特定调用使用,例如{/gist_id}{/target}{query}

让我们再次向 API 发送请求,但这次参数值没有任何更改或更新。我们将收到的内容与之前的响应类似,但 HTTP状态码将有所不同;也就是说,与 200OK相比,我们将获得304 未修改

https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/hsn-web-scp-py/img/0d428fad-bd13-4bcf-8561-05a4dd29ed89.png

https://api.github.com 的 HTTP 状态码 304

这个 HTTP 状态码(304未修改)展示了 REST 的缓存功能。由于响应没有任何更新或更新的内容,客户端缓存功能开始发挥作用。这有助于处理时间,以及带宽时间和使用。缓存是 RESTful web 服务的重要属性之一。以下是 Python 代码,显示了 RESTful API 的缓存属性,通过传递外部标头,这些标头被提供给headers参数,同时使用requests.get()发出请求获得:

import requests
url = 'https://api.github.com'  #First Request results = requests.get(url)
print("Status Code: ", results.status_code)
print("Headers: ", results.headers)

#Second Request with 'headers'
etag = results.headers['ETag']
print("ETag: ",etag)

results = requests.get(url, headers={'If-None-Match': etag})
print("Status Code: ", results.status_code)

requests在代码中两次调用url。我们还可以看到第二个请求已经提供了etag作为头信息,即If-None-Match。这个特定的头部检查使用ETag键作为 HTTP 响应头获得的响应头。ETag用于跟踪目的,通常标识存在的资源。这展示了缓存能力。有关ETag的更多信息,请参阅developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag

ETag是从results.headers中收集的,并且随着获得 HTTP状态码:304的第二个请求一起转发。以下代码显示了输出:

Status Code: 200
Headers: Content-Type: application/json; charset=utf-8
Headers: {'X-GitHub-Request-Id': 'A195:073C:37F223:79CCB0:5C8144B4', 'Status': '200 OK','ETag': 'W/"7dc470913f1fe9bb6c7355b50a0737bc"', 'Content-Encoding': 'gzip','Date': 'Thu, 07 Mar 2019 16:20:05 GMT',........, 'Content-Type': 'application/json; charset=utf-8', ....., 'Server': 'GitHub.com'}

ETag: W/"7dc470913f1fe9bb6c7355b50a0737bc"
Status Code: 304

在本节中,我们已经学习了各种 API,通过使用功能访问它们,并演示了与网页抓取方法相关的一些重要概念。在下一节中,我们将使用 API 来抓取数据。

使用 API 进行网页抓取

在这一部分,我们将请求 API 并通过它们收集所需的数据。从技术上讲,通过 API 获取的数据并不类似于进行爬取活动,因为我们不能仅从 API 中提取所需的数据并进一步处理它。

示例 1 - 搜索和收集大学名称和 URL

在这个例子中,我们将使用 HIPO 提供的 API(hipolabs.com/)来搜索大学:universities.hipolabs.com/search?name=Wales

这个 API 使用一个名为name的查询参数,它将寻找大学名称。我们还将提供一个额外的参数country,其中包括美国和英国等国家名称。可以从以下 URL 请求此 API,更多信息可以在github.com/hipo/university-domains-list找到:

让我们导入所需的库并使用readUrl()函数来请求 API 并返回 JSON 响应,如下面的代码所示:

import requests
import json
dataSet = []
 def readUrl(search):
    results = requests.get(url+search)
    print("Status Code: ", results.status_code)
    print("Headers: Content-Type: ", results.headers['Content-Type'])
  return results.json()

通过返回的 JSON 响应,可以使用我们找到的键和索引检索所需的值,如下面的屏幕截图所示:

https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/hsn-web-scp-py/img/3d7a222c-075d-4d78-9049-aa13a01f34cc.png

从 API 中获取的 JSON(格式化)

nameurl被遍历并附加到dataSet中:

url = 'http://universities.hipolabs.com/search?name=' jsonResult = readUrl('Wales') # print(jsonResult)  for university in jsonResult:
    name = university['name']
    url = university['web_pages'][0]
    dataSet.append([name,url])
 print("Total Universities Found: ",len(dataSet))
print(dataSet)

最终输出如下:

Status Code: 200 Headers: Content-Type: application/json Total Universities Found: 10 [['University of Wales', 'http://www.wales.ac.uk/'], ['University of Wales Institute, Cardiff', 'http://www.uwic.ac.uk/'], ......., ['University of Wales, Lampeter', 'http://www.lamp.ac.uk/'], ['University of Wales, Bangor', 'http://www.bangor.ac.uk/']]  

示例 2 - 从 GitHub 事件中获取信息

在这个例子中,我们将收集关于type(事件类型)、created_at(事件创建日期)、id(事件标识代码)和repo(存储库名称)的信息。我们将使用以下 URL:api.github.com/events

GitHub“事件”列出了过去 90 天内执行的公共活动。这些事件以页面形式提供,每页 30 个项目,最多显示 300 个。事件中存在各种部分,所有这些部分都揭示了关于actorrepoorgcreated_attype等的描述。

有关更多详细信息,请参阅以下链接:developer.github.com/v3/activity/events/

以下是我们将要使用的代码:

if __name__ == "__main__":
    eventTypes=[] 
    #IssueCommentEvent,WatchEvent,PullRequestReviewCommentEvent,CreateEvent
  for page in range(1, 4): #First 3 pages
        events = readUrl('events?page=' + str(page))
  for event in events:
            id = event['id']
            type = event['type']
            actor = event['actor']['display_login']
            repoUrl = event['repo']['url']
            createdAt = event['created_at']
            eventTypes.append(type)
            dataSet.append([id, type, createdAt, repoUrl, actor])

    eventInfo = dict(Counter(eventTypes))

    print("Individual Event Counts:", eventInfo)
    print("CreateEvent Counts:", eventInfo['CreateEvent'])
    print("DeleteEvent Counts:", eventInfo['DeleteEvent'])

print("Total Events Found: ", len(dataSet))
print(dataSet)

上述代码给出了以下输出:

Status Code: 200
Headers: Content-Type: application/json; charset=utf-8
................
Status Code: 200
Headers: Content-Type: application/json; charset=utf-8

Individual Event Counts: {'IssueCommentEvent': 8, 'PushEvent': 42, 'CreateEvent': 12, 'WatchEvent': 9, 'PullRequestEvent': 10, 'IssuesEvent': 2, 'DeleteEvent': 2, 'PublicEvent': 2, 'MemberEvent': 2, 'PullRequestReviewCommentEvent': 1}

CreateEvent Counts: 12
DeleteEvent Counts: 2
Total Events Found: 90

[['9206862975','PushEvent','2019-03-08T14:53:46Z','https://api.github.com/repos/CornerYoung/MDN','CornerYoung'],'https://api.github.com/repos/OUP/INTEGRATION-ANSIBLE','peter-masters'],.....................,'2019-03-08T14:53:47Z','https://api.github.com/repos/learn-co-curriculum/hs-zhw-shoes-layout','maxwellbenton']]

collections Python 模块中的Counter类用于获取eventTypes中元素的个体计数:

from collections import Counter

总结

API 提供了几个好处,我们在本章中都已经涵盖了。RESTful Web 服务的需求正在增长,并且将来会比以往更多地促进数据请求和响应。结构化、易访问、基于参数的过滤器使 API 更方便使用,并且在节省时间方面表现出色。

在下一章中,我们将学习 Selenium 以及如何使用它从网络上爬取数据。

进一步阅读

第八章:使用 Selenium 进行 Web 抓取

到目前为止,我们已经学习了如何使用多种数据查找技术,并通过实现各种 Python 库来访问 Web 内容进行 Web 抓取。

Selenium 是一个 Web 应用程序测试框架,它自动化浏览操作,并可用于简单和复杂的 Web 抓取活动。 Selenium 提供了一个 Web 浏览器作为接口或自动化工具。使用 JavaScript、cookies、脚本等的动态或安全 Web 内容可以通过 Selenium 的帮助加载、测试,甚至抓取。

关于 Selenium 框架有很多东西要学习。在本章中,我们将介绍与 Web 抓取相关的框架的主要概念。

本章将涵盖以下主题:

  • Selenium 简介

  • 使用 Selenium 进行 Web 抓取

技术要求

本章需要一个 Web 浏览器(Google Chrome 或 Mozilla Firefox),我们将使用以下 Python 库:

  • selenium(Python 库)

  • re

如果您当前的 Python 设置中没有这些库,则可以通过参考第二章中的设置事物部分来设置或安装它们。

除了提到的 Python 库和 Web 浏览器之外,我们还将使用 WebDriver for Google Chrome。

代码文件可在github.com/PacktPublishing/Hands-On-Web-Scraping-with-Python/tree/master/Chapter08上找到。

Selenium 简介

正如我所提到的,Selenium 是一个可以用于 Web 抓取活动的 Web 应用程序框架。它也可以用作浏览器自动化工具。

与 Web 应用程序相关的任务或活动的自动化,例如以下列表中的任务,涉及在没有人类直接参与的情况下执行这些任务:

  • 浏览

  • 点击链接

  • 保存屏幕截图

  • 下载图像

  • 填写 HTML <form> 模板和许多其他活动

Selenium 提供了一个 Web 浏览器作为接口或自动化工具。通过浏览操作的自动化,Selenium 也可以用于 Web 抓取。使用 JavaScript、cookies、脚本等的动态或安全 Web 服务可以通过 Selenium 的帮助加载、测试,甚至爬取和抓取。

Selenium 是开源的,可以跨多个平台访问。可以使用各种 Web 浏览器进行测试,这些浏览器使用可用于编程语言(如 Java 和 Python)的库。使用库创建脚本与 Selenium 交互以执行基于浏览器的自动化。

尽管在应用程序测试中使用 Selenium 在爬行和抓取等操作方面具有许多优势,但它也有其缺点,例如时间和内存消耗。 Selenium 是可扩展和有效的,但在执行其操作时速度较慢,并且消耗大量内存空间。

有关 Selenium 的更详细信息,请访问www.seleniumhq.org/

在接下来的部分中,我们将设置 Selenium WebDriver 并使用 Python 库进行设置,该库可以在selenium-python.readthedocs.io/找到。

Selenium 是一个 Web 测试框架,而 Selenium (pypi.org/project/selenium/)是一个绑定 Selenium WebDriver 或用于创建与 Selenium 交互的脚本的 Python 库。

应用程序测试是为了确保应用程序满足要求,并检测错误和错误以确保产品质量而进行的。它可以通过手动(借助用户的帮助)或使用自动化工具(如 Selenium)进行。在互联网上发布应用程序之前,会对基于 Web 的应用程序进行测试。

Selenium 项目

Selenium 由多个组件或工具组成,也被称为 Selenium 项目,使其成为一个完整的基于 web 的应用程序测试框架。我们现在将看一些这些 Selenium 项目的主要组件。

Selenium WebDriver

Selenium WebDriver 是 Selenium 的一个组件,用于自动化浏览器。通过提供各种语言绑定,如 Java、Python、JavaScript 等,使用第三方驱动程序,如 Google Chrome 驱动程序、Mozilla Gecko 驱动程序和 Opera(github.com/mozilla/geckodriver/)来提供命令来进行浏览器自动化。Selenium WebDriver 不依赖于任何其他软件或服务器。

WebDriver 是一个面向对象的 API,具有更新的功能,克服并解决了之前 Selenium 版本和 Selenium Remote Control (RC) 的限制。请访问 Selenium WebDriver 网页(www.seleniumhq.org/projects/webdriver/)获取更多信息。

Selenium RC

Selenium RC 是一个用 Java 编程的服务器。它使用 HTTP 接受浏览器的命令,用于测试复杂的基于 AJAX 的 web 应用程序。

Selenium RC 在发布 Selenium 2(Selenium 版本 2)后已正式弃用。然而,WebDriver 包含了 Selenium RC 的主要功能。请访问www.seleniumhq.org/projects/remote-control/ 获取更多信息。

Selenium Grid

Selenium Grid 也是一个服务器,允许测试在多台机器上并行运行,跨多个浏览器和操作系统,分发系统负载并减少性能问题,如时间消耗。

复杂的测试用于同时处理 Selenium RC 和 Selenium Grid。自 2.0 版本发布以来,Selenium 服务器现在内置支持 WebDriver、Selenium RC 和 Selenium Grid。请访问 Selenium Grid 网页(www.seleniumhq.org/projects/grid/)获取更多信息。

Selenium IDE

一个开源的 Selenium 集成开发环境 (IDE) 用于使用 Selenium 构建测试用例。它基本上是一个网页浏览器扩展,具有诸如记录和通过图形用户 界面 (GUI) 回放网页自动化等功能。

以下是 Selenium IDE 的一些关键特性:

  • 可扩展且易于调试

  • 韧性测试

  • 跨浏览器支持

  • 可以创建可以运行命令并支持控制流结构的脚本

请访问 Selenium IDE 网页(www.seleniumhq.org/selenium-ide/)获取更多信息和安装程序。请访问 Selenium 项目网页(www.seleniumhq.org/projects/)获取有关 Selenium 组件的更多信息。

现在我们知道了 Selenium 的用途和一些主要组件,让我们看看如何安装和使用 Selenium WebDriver 进行一般测试。

设置事物

为了成功实现使用 Selenium 进行浏览器自动化和应用程序测试,需要设置 WebDriver。让我们通过以下步骤来设置 Google Chrome 的 WebDriver:

  1. 访问www.seleniumhq.org/

https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/hsn-web-scp-py/img/89dd533f-86a7-4e6c-903a-5c2871c08465.png SeleniumHQ 浏览器自动化主页

  1. 点击下载(或浏览至www.seleniumhq.org/download/)。

  2. 在第三方驱动程序、绑定和插件部分,点击 Google Chrome Driver(或浏览至sites.google.com/a/chromium.org/chromedriver/):

https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/hsn-web-scp-py/img/3c2a948b-705e-484b-aa17-09037bc7600b.png 第三方驱动程序,Selenium

  1. 从 ChromeDriver - WebDriver for Chrome (sites.google.com/a/chromium.org/chromedriver),下载适用于平台的最新稳定版本的 ChromeDriver:

https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/hsn-web-scp-py/img/1cb5ee93-dd5d-4666-97f0-9d19d5c799ed.png

ChromeDriver 列表

  1. 解压下载的chromedriver*.zip。应该出现一个名为chromedriver.exe的应用程序文件。我们可以将.exe文件放在包含代码的主文件夹中。

我们将在整个章节中使用谷歌浏览器和 ChromeDriver;有关使用其他浏览器的详细信息,或有关 Selenium 的更多信息,请访问 SeleniumHQ。有关安装的更多信息,请参阅selenium-python.readthedocs.io/installation.html

现在我们已经完成了 WebDriver 和 Selenium Python 库的设置,让我们通过 Python IDE 验证这个设置。如下面的屏幕截图所示,selenium包含webdriver模块,包括ChromeAndroidFirefoxIeOpera等子模块。当前版本是3.14.1

https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/hsn-web-scp-py/img/3c831b9c-ad14-4038-956d-7b7d7e81cc6d.png

打印 selenium.webdriver 版本

我们将使用 Selenium 与谷歌浏览器,因此让我们探索webdriverChrome的内容:

https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/hsn-web-scp-py/img/1c169341-d09d-4daf-ba71-52e874b3e558.png

从 Selenium WebDriver 探索 Chrome。

如前面的屏幕截图所示,有许多函数将被调用和用于实现浏览器自动化。您还可以看到许多函数名称以find_element*开头,类似于我们在早期章节中用于爬取活动的遍历和解析函数。

在下一节中,我们将学习关于selenium.webdriver

探索 Selenium

在本节中,我们将使用和介绍webdriverwebdriver.Chrome的各种属性,同时查看一些真实案例。接下来的章节将说明 Selenium 的使用并探索其主要属性。

访问浏览器属性

在本节中,我们将演示使用 Selenium 和 Chrome WebDriver 加载谷歌浏览器的 URL 并访问某些基于浏览器的功能。

首先,让我们从selenium中导入webdriver并设置到chromedriver.exe的路径,让我们称之为chromedriver_path。创建的路径将需要加载谷歌浏览器。根据应用程序位置,应提及chromedriver.exe的完整路径,并且对于成功实施是必需的:

from selenium import webdriver
import re

#setting up path to 'chromedriver.exe'
chromedriver_path='chromedriver' #C:\\Users\\....\\...\chromedriver.exe 

selenium.webdriver用于实现各种浏览器,在本例中是谷歌浏览器。webdriver.Chrome()短语提供了 Chrome WebDriver 的路径,以便chromedriver_path用于执行。

短语driverwebdriver.chrome.webdriver.WebDriver类的对象,使用webdriver.Chrome()创建,现在将提供对webdriver的各种属性和属性的访问:

driver = webdriver.Chrome(executable_path=chromedriver_path)

chromedriver.exe将在此实例或在driver对象创建时实例化。终端屏幕和空白的新窗口将加载谷歌浏览器,如下面的屏幕截图所示:

https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/hsn-web-scp-py/img/579126b7-c2b6-4201-8489-75cb356740da.png

终端屏幕和空白浏览器页面

如果您在执行到目前为止的代码时遇到任何错误,请按照以下步骤执行代码:

  1. 获取最新的 ChromeDriver 并替换现有的 ChromeDriver

  2. 更新和验证chromedriver_pathPATH

然后使用get()函数从webdriver为谷歌浏览器提供一个 URL。

get()短语接受要在浏览器上加载的 URL。让我们将www.python.org作为get()的参数;浏览器将开始加载 URL,如下面的屏幕截图所示:

driver.get('https://www.python.org')

如您在下面的截图中所见,地址栏下方显示了一个通知,其中包含消息Chrome is being controlled by automated test software。这条消息也确认了selenium.webdriver活动的成功执行,并且可以提供进一步的代码来操作或自动化加载的页面:

https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/hsn-web-scp-py/img/455fd250-2530-46ec-8fba-27ae3ff539ec.png

Chrome 浏览器加载了 https://www.python.org

在页面成功加载后,我们可以使用driver访问和探索其属性。为了说明这一点,让我们从 HTML <title>标签中提取或打印标题,并打印当前可访问的 URL:

print("Title: ",driver.title) #print <title> text
Title:  Welcome to Python.org

print("Current Page URL: ",driver.current_url) #print current url, loaded in the browser
Current Page URL:  https://www.python.org/

如前面的代码所示,可以使用driver.title获取页面标题,使用driver.current_url找到当前页面的 URL。current_url短语可用于验证在加载初始 URL 后是否发生了任何 URL 重定向。让我们使用 Python 库research()保存页面截图:

#check if pattern matches the current url loaded

if re.search(r'python.org',driver.current_url):
    driver.save_screenshot("pythonorg.png") #save screenshot with provided name
    print("Python Screenshot Saved!")

save_screenshot()短语以文件名作为图像的参数,并创建一个 PNG 图像。图像将保存在当前代码位置;也可以提供完整的目标或所需路径。

为了进一步探索,让我们从www.python.org收集网页 cookies。使用get_cookies()短语来检索 cookies,如下所示:

#get cookie information
cookies = driver.get_cookies() 
print("Cookies obtained from python.org")
print(cookies)

Cookies obtained from python.org
[{'domain': '.python.org', 'expiry': 1619415025, 'httpOnly': False, 'name': '__utma', 'path': '/', 'secure': False, 'value': '32101439.1226541417.1556343026.1556343026.1556343026.1'},........ {'domain': '.python.org', 'expiry': 1556343625, 'httpOnly': False, 'name': '__utmt', 'path': '/', 'secure': False, 'value': '1'}]

可以使用driver.page_source获取页面源。

要手动获取页面源,请右键单击页面,然后单击“查看页面源”,或按Ctrl + U

print(driver.page_source) #page source

可以使用driver.refresh()重新加载或刷新页面。

要手动刷新页面源,请右键单击页面,然后单击“重新加载”,或按Ctrl + R

driver.refresh() #reload or refresh the browser

使用前面代码中的driver访问的功能,让我们继续加载、截图和访问www.google.com的 cookies,使用以下代码:

driver.get('https://www.google.com')
print("Title: ",driver.title)
print("Current Page URL: ",driver.current_url)

if re.search(r'google.com',driver.current_url):
    driver.save_screenshot("google.png")
    print("Google Screenshot Saved!")

cookies = driver.get_cookies()

使用google.com执行的操作将在用于访问python.org的同一浏览器窗口上进行。有了这个,我们现在可以使用浏览器历史记录执行操作(即,我们将使用 Web 浏览器中可用的“返回”和“前进”按钮),并检索 URL,如下面的代码所示:

print("Current Page URL: ",driver.current_url)

driver.back() #History back action
print("Page URL (Back): ",driver.current_url)

driver.forward() #History forward action
print("Page URL (Forward): ",driver.current_url)

在上述代码中,back()将浏览器返回到上一页,而forward()将其沿着浏览器历史向前移动一步。收到的输出如下:

Current Page URL: https://www.google.com/
Page URL (Back): https://www.python.org/
Page URL (Forward): https://www.google.com/

在成功执行代码后,建议您关闭并退出驱动程序以释放系统资源。我们可以使用以下功能执行终止操作:

driver.close() #close browser
driver.quit()  #quit webdriver

上述代码包含以下两个短语:

  • close()终止加载的浏览器窗口

  • quit()结束 WebDriver 应用程序

到目前为止,在本节中我们执行的完整代码如下:

from selenium import webdriver
import re
chrome_path='chromedriver'
driver = webdriver.Chrome(executable_path=chrome_path)  #print(type(driver))
driver.get('https://www.python.org')  
print("Title: ",driver.title)
print("Current Page URL: ",driver.current_url)

if re.search(r'python.org',driver.current_url):
    driver.save_screenshot("pythonorg.png")
    print("Python Screenshot Saved!")
cookies = driver.get_cookies()

print(driver.page_source)
driver.refresh()

driver.get('https://www.google.com')
print("Title: ",driver.title)
print("Current Page URL: ",driver.current_url)
if re.search(r'google.com',driver.current_url):
    driver.save_screenshot("google.png")
    print("Google Screenshot Saved!")
cookies = driver.get_cookies()

print("Current Page URL: ",driver.current_url)
driver.back()
print("Page URL (Back): ",driver.current_url)
driver.forward()
print("Page URL (Forward): ",driver.current_url)

driver.close()
driver.quit()

上述代码演示了selenium.webdriver及其各种属性的使用。在下一节中,我们将演示webdriver和网页元素(网页中的元素)的使用。

定位网页元素

在本节中,我们将在automationpractice.com上进行搜索,以获取与搜索查询匹配的产品列表,演示selenium.webdriver的使用。网页元素是列在网页上或在页面源中找到的元素。我们还看一下一个名为WebElement的类,它被用作selenium.webdriver.remote.webelement.WebElement

自动化实践网站(automationpractice.com/)是来自www.seleniumframework.com的一个示例电子商务网站,您可以用来练习。

首先,让我们从selenium中导入webdriver,设置chromedriver.exe的路径,创建webdriver的对象——也就是在前一节访问浏览器属性中实现的driver,并加载 URL,automationpractice.com

driver.get('http://automationpractice.com')

新的 Google Chrome 窗口将加载提供的 URL。如下图所示,找到位于购物车上方的搜索(输入)框:

https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/hsn-web-scp-py/img/43ae769c-80e3-431a-807d-2097da75cebd.png

从 http://automationpractice.com 检查元素(搜索框)

要继续通过脚本搜索,我们需要识别具有 HTML <input>的元素。请参阅第三章中的使用 Web 浏览器开发者工具访问 Web 内容部分,使用 LXML、XPath 和 CSS 选择器

在我们的情况下,搜索框可以通过前面截图中显示的属性来识别,甚至可以使用 XPath 或 CSS 选择器:

  • id="search_query_top"

  • name="search_query"

  • class="search_query"

selenium.webdriver提供了许多定位器(用于定位元素的方法),可以方便地应用于遇到的情况。

定位器返回单个、多个或 WebElement 实例的列表,写作selenium.webdriver.remote.webelement.WebElement。以下是一些定位器以及简要描述:

  • find_element_by_id(): 通过其id属性来查找元素。此方法返回单个 WebElement。

  • find_element_by_name(): 通过其name属性来查找单个元素。可以使用find_elements_by_name()来找到或定位多个 WebElement。

  • find_element_by_tag_name(): 通过其 HTML 标签的名称来查找单个元素。可以使用find_elements_by_tag_name()来定位多个 WebElement。

  • find_element_by_class_name(): 通过其class属性来查找单个元素。可以使用find_elements_by_class_name()来定位多个 WebElement。

  • find_element_by_link_text(): 通过链接文本标识的链接来查找单个元素。可以使用find_elements_by_link_text()来定位多个 WebElement。

  • find_element_by_partial_link_text(): 通过元素携带的部分文本来查找单个元素的链接。可以使用find_elements_by_partial_link_text()来定位多个 WebElement。

  • find_element_by_xpath(): 通过提供 XPath 表达式来查找单个元素。可以使用find_elements_by_xpath()来定位多个 WebElement。

  • find_element_by_css_selector(): 通过提供 CSS 选择器来查找单个元素。可以使用find_elements_by_css_selector()来定位多个 WebElement。

现在,让我们使用find_element_by_id()来找到输入框:

searchBox = driver.find_element_by_id('search_query_top')
#searchBox = driver.find_element_by_xpath('//*[@id="search_query_top"]')
#searchBox = driver.find_element_by_css_selector('#search_query_top')

如前面的代码所示,searchBox可以使用任何方便的定位器来定位,这些定位器都提供了它们各自的参数。

获得的 WebElement 可以访问以下属性和一般方法,以及许多其他方法:

  • get_attribute(): 返回提供的键参数的属性值,例如valueidnameclass

  • tag_name: 返回特定 WebElement 的 HTML 标签名称。

  • text: 返回 WebElement 的文本。

  • clear(): 这会清除 HTML 表单元素的文本。

  • send_keys(): 用于填充文本并提供键效果,例如按下ENTERBACKSPACEDELETE,可从selenium.webdriver.common模块中的selenium.webdriver.common.keys模块中获得,应用于 HTML 表单元素。

  • click(): 执行单击操作到 WebElement。用于 HTML 元素,如提交按钮。

在下面的代码中,我们将使用前面列出的searchBox中的函数和属性:

print("Type :",type(searchBox))
<class 'selenium.webdriver.remote.webelement.WebElement'>

print("Attribute Value :",searchBox.get_attribute("value")) #is empty
Attribute Value *:* 

print("Attribute Class :",searchBox.get_attribute("class"))
Attribute Class : search_query form-control ac_input

print("Tag Name :",searchBox.tag_name)
Tag Name : input

让我们清除searchBox内的文本并输入要搜索的文本Dress。我们还需要提交位于searchBox右侧的按钮,并点击它以使用 WebElement 方法click()执行搜索:

searchBox.clear() 
searchBox.send_keys("Dress")
submitButton = driver.find_element_by_name("submit_search")
submitButton.click()

浏览器将处理提交的文本Dress的搜索操作并加载结果页面。

现在搜索操作完成,为了验证成功的搜索,我们将使用以下代码提取有关产品数量和计数的信息:

#find text or provided class name
resultsShowing = driver.find_element_by_class_name("product-count")
print("Results Showing: ",resultsShowing.text) 

Results Showing: Showing 1-7 of 7 items

#find results text using XPath
resultsFound = driver.find_element_by_xpath('//*[@id="center_column"]//span[@class="heading-counter"]')
print("Results Found: ",resultsFound.text)

Results Found: 7 results have been found.

通过找到的项目数量和产品数量,这传达了我们搜索过程的成功信息。现在,我们可以继续使用 XPath、CSS 选择器等查找产品:

#Using XPath
products = driver.find_elements_by_xpath('//*[@id="center_column"]//a[@class="product-name"]')

#Using CSS Selector
#products = driver.find_elements_by_css_selector('ul.product_list li.ajax_block_product a.product-name')

foundProducts=[]
for product in products:
    foundProducts.append([product.text,product.get_attribute("href")])

从前面的代码中,对获得的products进行迭代,并将单个项目添加到 Python 列表foundProducts中。product是 WebElement 对象,换句话说,是selenium.webdriver.remote.webelement.WebElement,而属性是使用textget_attribute()收集的:

print(foundProducts) 

[['Printed Summer Dress',
'http://automationpractice.com/index.php?id_product=5&controller=product&search_query=Dress&results=7'],
['Printed Dress',
'http://automationpractice.com/index.php?id_product=4&controller=product&search_query=Dress&results=7'],
['Printed Summer Dress',
'http://automationpractice.com/index.php?id_product=6&controller=product&search_query=Dress&results=7'],
['Printed Chiffon Dress',
'http://automationpractice.com/index.php?id_product=7&controller=product&search_query=Dress&results=7'],['PrintedDress',
'http://automationpractice.com/index.php?id_product=3&controller=product&search_query=Dress&results=7'],
['Faded Short Sleeve T-shirts',
'http://automationpractice.com/index.php?id_product=1&controller=product&search_query=Dress&results=7'],['Blouse',
'http://automationpractice.com/index.php?id_product=2&controller=product&search_query=Dress&results=7']]

在本节中,我们探索了selenium.webdriver中用于处理浏览器、使用 HTML 表单、读取页面内容等的各种属性和方法。请访问selenium-python.readthedocs.io了解有关 Python Selenium 及其模块的更详细信息。在下一节中,我们将使用本节中使用的大部分方法来从网页中抓取信息。

使用 Selenium 进行网页抓取

Selenium 用于测试 Web 应用程序。它主要用于使用各种基于编程语言的库和浏览器驱动程序执行浏览器自动化。正如我们在前面的探索 Selenium部分中看到的,我们可以使用 Selenium 导航和定位页面中的元素,并执行爬取和抓取相关的活动。

让我们看一些使用 Selenium 从网页中抓取内容的示例。

示例 1 - 抓取产品信息

在这个例子中,我们将继续使用探索 Selenium部分获得的foundProducts的搜索结果。

我们将从foundProducts中找到的每个单独的产品链接中提取一些特定信息,列举如下:

  • product_name:产品名称

  • product_price:列出的价格

  • image_url:产品主要图片的 URL

  • item_condition:产品的状态

  • product_description:产品的简短描述

使用driver.get()加载foundProducts中的每个单独的产品链接:

dataSet=[]
if len(foundProducts)>0:
   for foundProduct in foundProducts:
       driver.get(foundProduct[1])

       product_url = driver.current_url
       product_name = driver.find_element_by_xpath('//*[@id="center_column"]//h1[@itemprop="name"]').text
       short_description = driver.find_element_by_xpath('//*[@id="short_description_content"]').text
       product_price = driver.find_element_by_xpath('//*[@id="our_price_display"]').text
       image_url = driver.find_element_by_xpath('//*[@id="bigpic"]').get_attribute('src')
       condition = driver.find_element_by_xpath('//*[@id="product_condition"]/span').text
       dataSet.append([product_name,product_price,condition,short_description,image_url,product_url])

print(dataSet)

使用 XPath 获取要提取的目标字段或信息,并将其附加到dataSet。请参考第三章中的使用 Web 浏览器开发者工具访问 Web 内容部分,使用 LXML、XPath 和 CSS 选择器

dataSet中获取的输出如下:

[['Printed Summer Dress','$28.98','New','Long printed dress with thin adjustable straps. V-neckline and wiring under the bust with ruffles at the bottom of the dress.', 'http://automationpractice.com/img/p/1/2/12-large_default.jpg', 'http://automationpractice.com/index.php?id_product=5&controller=product&search_query=Dress&results=7'],
['Printed Dress','$50.99','New','Printed evening dress with straight sleeves with black .............,
['Blouse','$27.00','New','Short sleeved blouse with feminine draped sleeve detail.', 'http://automationpractice.com/img/p/7/7-large_default.jpg','http://automationpractice.com/index.php?id_product=2&controller=product&search_query=Dress&results=7']]

最后,使用close()quit()保持系统资源空闲。此示例的完整代码如下:

from selenium import webdriver
chrome_path='chromedriver' driver = webdriver.Chrome(executable_path=chrome_path)
driver.get('http://automationpractice.com')

searchBox = driver.find_element_by_id('search_query_top')
searchBox.clear()
searchBox.send_keys("Dress")
submitButton = driver.find_element_by_name("submit_search")
submitButton.click()

resultsShowing = driver.find_element_by_class_name("product-count")
resultsFound = driver.find_element_by_xpath('//*[@id="center_column"]//span[@class="heading-counter"]')

products = driver.find_elements_by_xpath('//*[@id="center_column"]//a[@class="product-name"]')
foundProducts=[]
for product in products:
    foundProducts.append([product.text,product.get_attribute("href")])

dataSet=[]
if len(foundProducts)>0:
   for foundProduct in foundProducts:
       driver.get(foundProduct[1])
       product_url = driver.current_url
       product_name = driver.find_element_by_xpath('//*[@id="center_column"]//h1[@itemprop="name"]').text
       short_description = driver.find_element_by_xpath('//*[@id="short_description_content"]').text
       product_price = driver.find_element_by_xpath('//*[@id="our_price_display"]').text
       image_url = driver.find_element_by_xpath('//*[@id="bigpic"]').get_attribute('src')
       condition = driver.find_element_by_xpath('//*[@id="product_condition"]/span').text
       dataSet.append([product_name,product_price,condition,short_description,image_url,product_url])

driver.close()
driver.quit()

在这个例子中,我们执行了基于 HTML <form>的操作,并从每个单独的页面中提取所需的细节。表单处理是在测试 Web 应用程序期间执行的主要任务之一。

示例 2 - 抓取书籍信息

在这个例子中,我们将自动化浏览器来处理主 URL 提供的类别和分页链接。我们有兴趣从books.toscrape.com/index.html跨多个页面提取食品和饮料类别的详细信息。

类别中的单个页面包含产品(书籍)的列表,其中包含以下某些信息:

  • title:书籍的标题

  • titleLarge:列出的书籍标题(完整标题,作为title属性的值找到)

  • price:列出的书籍价格

  • stock:与列出的书籍相关的库存信息

  • image:书籍图片的 URL

  • starRating:评级(找到的星星数量)

  • url:列出每本书的 URL。

在第三章中还展示了一个类似的例子,使用 LXML、XPath 和 CSS 选择器中的使用 LXML 进行网页抓取部分,名称为示例 2 - 使用 XPath 循环并从多个页面抓取数据。在那里,我们使用了 Python 库lxml

导入selenium.webdriver并设置 Chrome 驱动程序路径后,让我们开始加载books.toscrape.com/index.html。当主页面加载时,我们将看到各种类别依次列出。

目标类别包含文本“食品和饮料”,可以使用find_element_by_link_text()找到(我们可以使用任何适用的find_element...方法来找到特定类别)。找到的元素进一步使用click()进行处理 - 即点击返回的元素。此操作将在浏览器中加载特定类别的 URL:

driver.get('http://books.toscrape.com/index.html')

driver.find_element_by_link_text("Food and Drink").click()
print("Current Page URL: ", driver.current_url)
totalBooks = driver.find_element_by_xpath("//*[@id='default']//form/strong[1]")
print("Found: ", totalBooks.text)

为了处理在迭代过程中找到的多个页面,将从selenium.common.exceptions导入NoSuchElementException

from selenium.common.exceptions import NoSuchElementException

由于我们将使用分页按钮 next,NoSuchElementException将有助于处理如果没有找到进一步的 next 或页面的情况。

如下代码所示,分页选项 next 位于页面中,并使用click()操作进行处理。此操作将加载它包含的 URL 到浏览器中,并且迭代将继续直到在页面中找不到或找到 next,被代码中的except块捕获:

try:
 #Check for Pagination with text 'next'  driver.find_element_by_link_text('next').click()
    continue except NoSuchElementException:
    page = False

此示例的完整代码如下所示:

from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException
chrome_path = 'chromedriver' driver = webdriver.Chrome(executable_path=chrome_path)
driver.get('http://books.toscrape.com/index.html')

dataSet = []
driver.find_element_by_link_text("Food and Drink").click()
totalBooks = driver.find_element_by_xpath("//*[@id='default']//form/strong[1]")

page = True while page:
    listings = driver.find_elements_by_xpath("//*[@id='default']//ol/li[position()>0]")
    for listing in listings:
        url=listing.find_element_by_xpath(".//article[contains(@class,'product_pod')]/h3/a"). get_attribute('href')
        title=listing.find_element_by_xpath(".//article[contains(@class,'product_pod')]/h3/a").text
        titleLarge=listing.find_element_by_xpath(".//article[contains(@class,'product_pod')]/h3/a"). get_attribute('title')
        price=listing.find_element_by_xpath(".//article/div[2]/p[contains(@class,'price_color')]").text
        stock=listing.find_element_by_xpath(".//article/div[2]/p[2][contains(@class,'availability')]"). text
        image=listing.find_element_by_xpath(".//article/div[1][contains(@class,'image_container')]/a/img") .get_attribute('src')
        starRating=listing.find_element_by_xpath(".//article/p[contains(@class,'star-rating')]"). get_attribute('class')
        dataSet.append([titleLarge,title,price,stock,image,starRating.replace('star-rating ',''),url])

    try:
  driver.find_element_by_link_text('next').click()
        continue
 except NoSuchElementException:
        page = False 
driver.close()
driver.quit()

最后,在迭代完成后,dataSet将包含所有页面的列表数据,如下所示:

[['Foolproof Preserving: A Guide to Small Batch Jams, Jellies, Pickles, Condiments, and More: A Foolproof Guide to Making Small Batch Jams, Jellies, Pickles, Condiments, and More', 'Foolproof Preserving: A Guide ...','£30.52','In stock', 'http://books.toscrape.com/media/cache/9f/59/9f59f01fa916a7bb8f0b28a4012179a4.jpg','Three','http://books.toscrape.com/catalogue/foolproof-preserving-a-guide-to-small-batch-jams-jellies-pickles-condiments-and-more-a-foolproof-guide-to-making-small-batch-jams-jellies-pickles-condiments-and-more_978/index.html'], ['The Pioneer Woman Cooks: Dinnertime: Comfort Classics, Freezer Food, 16-Minute Meals, and Other Delicious Ways to Solve Supper!', 'The Pioneer Woman Cooks: ...', '£56.41', 'In stock', 'http://books.toscrape.com/media/cache/b7/f4/b7f4843dbe062d44be1ffcfa16b2faa4.jpg', 'One', 'http://books.toscrape.com/catalogue/the-pioneer-woman-cooks-dinnertime-comfort-classics-freezer-food-16-minute-meals-and-other-delicious-ways-to-solve-supper_943/index.html'],................, 
['Hungry Girl Clean & Hungry: Easy All-Natural Recipes for Healthy Eating in the Real World', 'Hungry Girl Clean & ...', '£33.14', 'In stock', 'http://books.toscrape.com/media/cache/6f/c4/6fc450625cd672e871a6176f74909be2.jpg', 'Three', 'http://books.toscrape.com/catalogue/hungry-girl-clean-hungry-easy-all-natural-recipes-for-healthy-eating-in-the-real-world_171/index.html']]

在本节中,我们探索了来自selenium.webdriver的方法和属性,并将其用于网页抓取活动。

摘要

在本章中,我们学习了关于 Selenium 以及使用 Python 库进行浏览器自动化、网页内容抓取、基于浏览器的活动和 HTML <form> 处理。 Selenium 可以用于处理多种活动,这是 Selenium 相对于 Python 专用库(如lxmlpyquerybs4scrapy)的主要优势之一。

在下一章中,我们将学习更多关于使用正则表达式进行网页抓取的技术。

进一步阅读

第九章:使用正则表达式提取数据

如果您当前的 Python 设置中不存在这些库,请参考第二章,Python 和 Web - 使用 urllib 和 Requests设置事项部分,了解有关其安装和设置的更多信息。到目前为止,我们已经学习了关于 Web 技术、数据查找技术以及如何使用 Python 库访问 Web 内容的知识。

正则表达式Regexregex)实际上是使用预定义命令和格式构建的模式,以匹配所需内容。在数据提取过程中,当没有特定的布局或标记模式可供选择时,正则表达式提供了很大的价值,并且可以与 XPath、CSS 选择器等其他技术一起应用。

复杂的网页内容和一般文本或字符格式的数据可能需要使用正则表达式来完成匹配和提取等活动,还包括函数替换、拆分等。

在本章中,我们将学习以下主题:

  • 正则表达式概述

  • 使用正则表达式提取数据

技术要求

本章需要一个 Web 浏览器(Google Chrome 或 Mozilla Firefox)。我们将使用以下 Python 库:

  • 请求

  • re

  • bs4

如果您当前的 Python 设置中不存在这些库,请参考第二章,Python 和 Web - 使用 urllib 和 Requests设置事项部分,了解有关其安装和设置的更多信息。

本章的代码文件可在本书的 GitHub 存储库中找到:github.com/PacktPublishing/Hands-On-Web-Scraping-with-Python/tree/master/Chapter09

那些已经使用re的人可以参考使用正则表达式提取数据部分。

正则表达式概述

正则表达式用于匹配文本或字符串中找到的模式。正则表达式可以用于根据需要对文本或网页内容进行测试和查找模式。正则表达式包含各种定义模式和特殊符号的方法,例如转义代码,以应用一些预定义规则。有关正则表达式的更多信息,请参考进一步阅读部分。

有各种情况下,正则表达式可以非常有效和快速地获得所需的结果。正则表达式可以仅应用于内容(文本或网页源代码),并且可以用于针对不易使用 XPath、CSS 选择器、BS4*、*PyQuery 等提取的特定信息模式。

有时,可能会出现需要同时使用正则表达式和 XPath 或 CSS 选择器才能获得所需输出的情况。然后可以使用正则表达式对输出进行测试,以查找模式或清理和管理数据。代码编辑器、文档编写器和阅读器还提供了嵌入式基于正则表达式的实用工具。

正则表达式可以应用于任何包含正确或不正确格式的文本或字符字符串、HTML 源代码等。正则表达式可以用于各种应用,例如以下内容:

  • 基于特定模式的内容

  • 页面链接

  • 图像标题和链接

  • 链接内的文本

  • 匹配和验证电子邮件地址

  • 从地址字符串中匹配邮政编码或邮政编码

  • 验证电话号码等

使用搜索、查找、拆分、替换、匹配和迭代等工具,无论是否有其他技术干扰,都可以适用。

在接下来的章节中,我们将使用re Python 模块并探索其方法,然后将其应用于正则表达式。

正则表达式和 Python

re是一个标准的 Python 库,用于处理正则表达式。每个默认的 Python 安装都包含re库。如果该库不存在,请参考第二章,Python 和 Web - 使用 urllib 和 Requests*,* 设置事物部分,了解如何设置它。

>>> 在代码中表示使用 Python IDE。它接受给定的代码或指令,并在下一行显示输出。

让我们开始通过 Python IDE 导入re并使用dir()函数列出其属性:

>>> import re
>>> print(dir(re)) #listing features from re

以下是前面命令的输出:

['A', 'ASCII', 'DEBUG', 'DOTALL', 'I', 'IGNORECASE', 'L', 'LOCALE', 'M', 'MULTILINE', 'S', 'Scanner', 'T', 'TEMPLATE', 'U', 'UNICODE', 'VERBOSE', 'X', '_MAXCACHE', '__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', '__versio n__', '_alphanum_bytes', '_alphanum_str', '_cache', '_cache_repl', '_compile', '_compile_repl', '_expand', '_locale', '_pattern_type', '_pickle', '_subx', 'compile', 'copyreg', 'error', 'escape', 'findall', 'finditer', 'fullmatch', 'match', 'purge', 'search', 'split', 'sre_compile', 'sre_parse', 'sub', 'subn', 'sys', 'template']

从前面的输出中可以看出,在re中有各种可用的函数。我们将从内容提取的角度使用其中的一些函数,并通过使用以下示例来解释正则表达式的基础知识:

>>> sentence = """Brief information about Jobs in Python. Programming and Scripting experience in some language (such as Python R, MATLAB, SAS, Mathematica, Java, C, C++, VB, JavaScript or FORTRAN) is expected. Participants should be comfortable with basic programming concepts like variables, loops, and functions."""

我们之前声明的sentence包含有关 Python 工作和工作描述的简要信息。我们将使用这个句子来解释基本的正则表达式功能。

split()函数将字符串分解并返回由空格字符默认分隔的单词列表。我们也可以使用re.split()来拆分字符串对象。在这种情况下,split()接受正则表达式模式来拆分句子,例如re.split(r'\s+',sentence)

>>> splitSentence = sentence.split() #split sentence or re.split(r'\s',sentence) >>> print("Length of Sentence: ",len(sentence), '& splitSentence: ',len(splitSentence))
Length of Sentence: 297 & splitSentence: 42 >>> print(splitSentence) #List of words obtained using split() 
['Brief', 'information', 'about', 'Jobs', 'in', 'Python.', 'Programming', 'and', 'Scripting', 'experience', 'in', 'some', 'language', '(such', 'as', 'Python', 'R,', 'MATLAB,', 'SAS,', 'Mathematica,', 'Java,', 'C,', 'C++,', 'VB,', 'JavaScript', 'or', 'FORTRAN)', 'is', 'expected.', 'Participants', 'should', 'be', 'comfortable', 'with', 'basic', 'programming', 'concepts', 'like', 'variables,', 'loops,', 'and', 'functions.']

使用前面的代码获取并打印sentence的长度和 Python 的splitSentence列表对象的长度。这些元素和字符的计数将有助于比较从以下示例返回的答案:

>>> matches = re.findall(r"([A-Z+]+)\,",sentence) #finding pattern with [A-Z+] and comma behind >>> print("Findall found total ",len(matches)," Matches >> ",matches) **Findall found total  6  Matches >>  ['R', 'MATLAB', 'SAS', 'C', 'C++', 'VB']** >>> matches = re.findall(r"([A-Z]+)\,",sentence) #finding pattern with [A-Z] and comma behind >>> print("Findall found total ",len(matches)," Matches >> ",matches) Findall found total 5 Matches >> ['R', 'MATLAB', 'SAS', 'C', 'VB']

re.findall()接受要搜索的模式和要查找的与提供的模式相关的内容。通常,模式可以直接作为参数提供给函数,并且作为原始字符串前面带有r,例如r'([A-Z]+)',或包含原始字符串的变量。

在前面的代码中,我们可以看到类似的模式,提供了一些额外的字符,但它们的输出不同。以下是一些这些模式的一般解释:

  • [A-Z]:模式中的方括号匹配一组字符,并且区分大小写。在这里,它匹配从AZ的字符,但不匹配az的字符。我们可以提供一组字符,例如[A-Za-z0-9],它匹配从AZaz的任何字符,以及从09的数字字符。如果需要,可以在集合中传递其他字符,例如[A-Z+]+字符可以与AZ的字符一起存在,例如 C++或 C。

  • (): 模式中的圆括号包含匹配的值组。

  • +(用于重复):在字符集之外找到时,它匹配模式的一个或多个出现。[A-Z]+将匹配至少一个或多个AZ的字符组合,例如,前面代码中的RMATLAB。还有一些用于指定重复或出现次数的其他字符,也称为正则表达式量词:

  • * 匹配零次或多次模式

  • ? 匹配模式的零次或一次出现

  • {m,n} 分别匹配重复的最小m和最大n次数:

  • {2,5}:最少 2 次或最多 5 次

  • {2,}:最少 2 次或更多

  • {,5}:最多 5 次

  • {3}:3 次出现

  • \,(逗号):在正则表达式中,除了[A-Za-z0-9]之外的字符通常被写为转义字符,以便提及特定的字符(\,代表逗号,\.代表句号,\?代表问号等)。

正则表达式量词也分为以下几类:

  • 贪婪量词:这些量词尽可能多地匹配任何元素。

  • 懒惰或非贪婪量词:这些量词尽可能少地匹配任何元素。通常,通过在贪婪量词后添加?将其转换为懒惰量词。

诸如 ([A-Z+]+)\, 的模式匹配从 A 到 Z 和 + 中至少一个或多个字符,后跟,。在前面的代码中的sentence中,我们可以找到RMATLABSASMathematicaJavaCC++VBJavaScript(还有FORTRAN),即名称后跟,(但不适用于FORTRAN的情况;这就是为什么它在提供的模式的输出中被排除的原因)。

在下面的代码中,我们试图匹配在sentence中找到的FORTRAN,并使用先前在代码中尝试的模式进行省略:

>>> matches = re.findall(r"\s*([\sorA-Z+]+)\)",sentence) #r'\s*([A-Z]+)\)' matches 'FORTRAN' 
>>> print("Findall found total ",len(matches)," Matches >> ",matches)

Findall found total  1  Matches >>  ['or FORTRAN']

>>> fortran = matches[0] # 'or FORTRAN'
>>> if re.match(r'or',fortran): 
 fortran = re.sub(r'or\s*','',fortran) #substitute 'or ' with empty string >>> print(fortran)

FORTRAN

>>> if re.search(r'^F.*N$',fortran):  #using beginning and end of line searching pattern 
 print("True")
 True

如前面的代码块所示,Python 库re具有各种函数,如下所示:

  • re.match(): 这匹配提供的模式在字符串的开头,并返回匹配的对象。

  • re.sub(): 这会找到一个模式并用提供的字符串替换它。它类似于文本中的查找和替换。

  • re.search(): 这在字符串中匹配模式并返回找到的匹配对象。

  • \s: 这表示空格制表符换行符。在这里,[\sorA-Z+]+\)匹配一个或多个字符,包括A-Zor\s+,后跟\)(右括号)。在正则表达式中还有一些其他转义代码,如下所示:

  • \d: 匹配数字

  • \D: 匹配非数字

  • \s: 匹配空白

  • \S: 匹配非空白

  • \w: 匹配字母数字字符

  • \W: 匹配非字母数字字符

  • \b: 匹配单词边界

  • \B: 匹配非单词边界

  • ^: 这匹配字符串的开头。

注意:r'[^a-z]'(插入符号或^)在字符集内使用时起否定作用。这意味着除了排除[a-z]

  • $: 这匹配字符串的结尾。

  • |: 这在模式中实现逻辑表达式OR。例如,r'a|b'将匹配任何真实表达式,即ab

以下代码显示了一些这些正则表达式模式和findall()函数的使用,以及它们的输出:

>>> matches  = re.findall(r'\s(MAT.*?)\,',sentence,flags=re.IGNORECASE)
>>> print("(MAT.*?)\,: ",matches)  #r'(?i)\s(MAT.*?)\,' can also be used
 (MAT.*?)\,: ['MATLAB', 'Mathematica']   >>> matches = re.findall(r'\s(MAT.*?)\,',sentence) #findall with 'MAT' case-sensitive
>>> print("(MAT.*?)\,: ",matches)
 (MAT.*?)\,: ['MATLAB']   >>> matches = re.findall(r'\s(C.*?)\,',sentence)
>>> print("\s(C.*?)\,: ",matches)
 \s(C.*?)\,: ['C', 'C++']

在前面的代码中找到了以下函数:

  • re 函数还支持可选的flags 参数。这些标志也有缩写形式(i代表re.IGNORECASEs代表re.DOTALLM代表re.MULTILINE)。它们可以通过在表达式开头包含它们来在模式中使用。例如,r'(?i)\s(MAT.*?)\,将返回[MATLAB, Mathematica]。以下是在代码中找到的一些其他re函数:

  • re.IGNORECASE : 忽略提供的模式中发现的大小写敏感性

  • re.DOTALL : 允许. (句号)匹配换行符,并且适用于包含多行的字符串

  • re.MULTILINE : 与多行字符串一起使用,并搜索包括换行符("\n")在内的模式

  • . 或句号: 这匹配任何单个字符,但不包括换行符("\n")。它通常与重复字符一起在模式中使用。句号或. 需要在字符串中匹配,并且应该使用\.

>>> matchesOne = re.split(r"\W+",sentence)  #split by word, \w (word characters, \W - nonword) >>> print("Regular Split '\W+' found total: ",len(matchesOne ),"\n",matchesOne)  Regular Split '\W+' found total: 43 
['Brief', 'information', 'about', 'Jobs', 'in', 'Python', 'Programming', 'and', 'Scripting', 'experience', 'in', 'some', 'language', 'such', 'as', 'Python', 'R', 'MATLAB', 'SAS', 'Mathematica', 'Java', 'C', 'C', 'VB', 'JavaScript', 'or', 'FORTRAN', 'is', 'expected', 'Participants', 'should', 'be', 'comfortable', 'with', 'basic', 'programming', 'concepts', 'like', 'variables', 'loops', 'and', 'functions', ''] >>> matchesTwo = re.split(r"\s",sentence) #split by space
>>> print("Regular Split '\s' found total: ",len(matchesTwo),"\n", matchesTwo) **Regular Split '\s' found total: 42** 
['Brief', 'information', 'about', 'Jobs', 'in', 'Python.', 'Programming', 'and', 'Scripting', 'experience', 'in', 'some', 'language', '(such', 'as', 'Python', 'R,', 'MATLAB,', 'SAS,', 'Mathematica,', 'Java,', 'C,', 'C++,', 'VB,', 'JavaScript', 'or', 'FORTRAN)', 'is', 'expected.', 'Participants', 'should', 'be', 'comfortable', 'with', 'basic', 'programming', 'concepts', 'like', 'variables,', 'loops,', 'and', 'functions.']
  • re.split(): 这根据模式拆分提供的内容并返回带有结果的列表。还有一个split(),它可以与字符串一起使用以使用默认或提供的字符进行分割。它的使用方式与本节中稍早的splitSentence类似。

建议您比较此部分中matchesOnematchesTwo的结果**。**

在下面的代码中,我们尝试应用 datetime 属性中找到的值的正则表达式模式。定义的模式将被编译,然后用于在代码块中搜索:

>>> timeDate= '''<time datetime="2019-02-11T18:00:00+00:00"></time> <time datetime="2018-02-11T13:59:00+00:00"></time> <time datetime="2019-02-06T13:44:00.000002+00:00"></time> <time datetime="2019-02-05T17:39:00.000001+00:00"></time> <time datetime="2019-02-04T12:53:00+00:00"></time>''' >>> pattern = r'(20\d+)([-]+)(0[1-9]|1[012])([-]+)(0[1-9]|[12][0-9]|3[01])' >>> recompiled = re.compile(pattern)  # <class '_sre.SRE_Pattern'>
>>> dateMatches = recompiled.search(timeDate)
  • re.compile(): 用于编译正则表达式模式并接收模式对象(_sre.SRE_Pattern)。接收到的对象可以与其他正则表达式功能一起使用。

可以通过使用group()方法单独探索组匹配,如下面的代码所示:

>>> print("Group : ",dateMatches.group()) 
Group : 2019-02-11
 >>> print("Groups : ",dateMatches.groups())
Groups : ('2019', '-', '02', '-', '11')
 >>> print("Group 1 : ",dateMatches.group(1))
Group 1 : 2019
 >>> print("Group 5 : ",dateMatches.group(5))
Group 5 : 11

正如我们所看到的,尽管该模式已经针对多行 timeDate 进行了搜索,但结果是一个单独的分组;也可以使用索引返回单个分组。一个与 re 相关的匹配对象包含了 groups()group() 函数;groups(0) 的结果与 groups() 相同。groups() 中的单个元素将需要从 1 开始的索引。

  • re.finditer(): 用于迭代在提供的内容中找到的模式或模式对象的结果匹配。它返回一个从 re.match() 中找到的匹配(_sre.SRE_Match)对象。

re.match() 返回一个包含在代码示例中使用的各种函数和属性的对象。这些如下:

  • start(): 返回与表达式匹配的起始字符索引

  • end(): 返回与表达式匹配的结束字符索引

  • span(): 返回匹配表达式的起始和结束字符索引

  • lastindex: 返回最后匹配表达式的索引

  • groupdict(): 返回匹配组字典与模式字符串和匹配值

  • groups(): 返回所有匹配的元素

  • group(): 返回一个单独的分组,并可以通过分组名称访问

  • lastgroup: 返回最后一个组的名称

>>> for match in re.finditer(pattern, timeDate): # <class '_sre.SRE_Match'>
 #for match in re.finditer(recompiled, timeDate):
 s = match.start()
 e = match.end()
 l = match.lastindex
 g = match.groups()

 print('Found {} at {}:{}, groups{} lastindex:{}'.format(timeDate[s:e], s, e,g,l))

Found 2019-02-11 at 16:26, groups('2019', '-', '02', '-', '11') lastindex:5
Found 2018-02-11 at 67:77, groups('2018', '-', '02', '-', '11') lastindex:5
Found 2019-02-06 at 118:128, groups('2019', '-', '02', '-', '06') lastindex:5
Found 2019-02-05 at 176:186, groups('2019', '-', '02', '-', '05') lastindex:5
Found 2019-02-04 at 234:244, groups('2019', '-', '02', '-', '04') lastindex:5

模式也可以为它们所在的组指定字符串名称;例如,r'(?P<year>[0-9]{4})' 匹配 year 组。在正则表达式中使用基于组的模式可以帮助我们更准确地读取模式并管理输出;这意味着我们不必担心索引。

让我们考虑模式 pDate(实现 group(), groupdict(), start(), end(), lastgroup, 和 lastindex)与一个分组名称和代码,分别展示日期和时间的输出:

>>> pDate = r'(?P<year>[0-9]{4})(?P<sep>[-])(?P<month>0[1-9]|1[012])-(?P<day>0[1-9]|[12][0-9]|3[01])' >>> recompiled = re.compile(pDate) #compiles the pattern >>> for match in re.finditer(recompiled,timeDate): #apply pattern on timeDate
 s = match.start()
 e = match.end()
 l = match.lastindex

 print("Group ALL or 0: ",match.groups(0)) #or match.groups() that is all
 print("Group Year: ",match.group('year')) #return year
 print("Group Month: ",match.group('month')) #return month
 print("Group Day: ",match.group('day')) #return day

 print("Group Delimiter: ",match.group('sep')) #return seperator
 print('Found {} at {}:{}, lastindex: {}'.format(timeDate[s:e], s, e,l))

 print('year :',match.groupdict()['year']) #accessing groupdict()
 print('day :',match.groupdict()['day'])

 print('lastgroup :',match.lastgroup) #lastgroup name

前面的代码将产生以下输出:

Group ALL or 0: ('2019', '-', '02', '11')
Group Year: 2019
Group Month: 02
Group Day: 11
Group Delimiter: -
Found 2019-02-11 at 16:26, lastindex: 4
year : 2019
day : 11
lastgroup : day

以下代码显示了使用 pTime(实现 span()):

>>> pTime = r'(?P<hour>[0-9]{2})(?P<sep>[:])(?P<min>[0-9]{2}):(?P<sec_mil>[0-9.:+]+)'
>>> recompiled = re.compile(pTime)

>>> for match in re.finditer(recompiled,timeDate):
 print("Group String: ",match.group()) #groups
 print("Group ALL or 0: ",match.groups())

 print("Group Span: ",match.span()) #using span()
 print("Group Span 1: ",match.span(1))
 print("Group Span 4: ",match.span(4))

 print('hour :',match.groupdict()['hour']) #accessing groupdict()
 print('minute :',match.groupdict()['min'])
 print('second :',match.groupdict()['sec_mil'])

 print('lastgroup :',match.lastgroup) #lastgroup name

前面的代码将产生以下输出:

Group String: 12:53:00+00:00
Group ALL or 0: ('12', ':', '53', '00+00:00')
Group Span: (245, 259)
Group Span 1: (245, 247)
Group Span 4: (251, 259)
hour : 12
minute : 53
second : 00+00:00
lastgroup : sec_mil

在本节中,我们已经介绍了正则表达式的一般概述和 re Python 库的特性,以及一些实际示例。请参考进一步阅读部分以获取有关正则表达式的更多信息。在下一节中,我们将应用正则表达式来从基于 web 的内容中提取数据。

使用正则表达式提取数据

现在我们已经介绍了基础知识并概述了正则表达式,我们将使用正则表达式以类似于使用 XPath、CSS 选择器、pyquerybs4 等的方式批量抓取(提取)数据,通过选择在正则表达式、XPath、pyquery 等之间的实现来满足网页访问的要求和可行性以及内容的可用性。

并不总是要求内容应该是无结构的才能应用正则表达式并提取数据。正则表达式可以用于结构化和非结构化的网页内容,以提取所需的数据。在本节中,我们将探讨一些示例,同时使用正则表达式及其各种属性。

示例 1 - 提取基于 HTML 的内容

在这个例子中,我们将使用来自 regexHTML.html 文件的 HTML 内容,并应用正则表达式模式来提取以下信息:

  • HTML 元素

  • 元素的属性(key 和 values

  • 元素的内容

这个例子将为您提供一个如何处理网页内容中存在的各种元素、值等以及如何应用正则表达式来提取内容的概述。我们将在接下来的代码中应用以下步骤来处理 HTML 和类似内容:

<html>
<head>
   <title>Welcome to Web Scraping: Example</title>
   <style type="text/css">
        ....
   </style>
</head>
<body>
    <h1 style="color:orange;">Welcome to Web Scraping</h1>
     Links:
    <a href="https://www.google.com" style="color:red;">Google</a>   <a class="classOne" href="https://www.yahoo.com">Yahoo</a>   <a id="idOne" href="https://www.wikipedia.org" style="color:blue;">Wikipedia</a>
    <div>
        <p id="mainContent" class="content">
            <i>Paragraph contents</i>
            <img src="mylogo.png" id="pageLogo" class="logo"/>
        </p>
        <p class="content" id="subContent">
            <i style="color:red">Sub paragraph content</i>
            <h1 itemprop="subheading">Sub heading Content!</h1>
        </p>
    </div>
</body>
</html>

前面的代码是我们将要使用的 HTML 页面源代码。这里的内容是结构化的,我们可以用多种方式处理它。

在下面的代码中,我们将使用以下函数:

  • read_file(): 这将读取 HTML 文件并返回页面源代码以供进一步处理。

  • applyPattern(): 这个函数接受一个pattern参数,即用于查找内容的正则表达式模式,它使用re.findall()应用于 HTML 源代码,并打印诸如搜索元素列表和它们的计数之类的信息。

首先,让我们导入rebs4

import re
from bs4 import BeautifulSoup

def read_file():
   ''' Read and return content from file (.html). '''  content = open("regexHTML.html", "r")
    pageSource = content.read()
    return pageSource

def applyPattern(pattern):
'''Applies regex pattern provided to Source and prints count and contents'''
    elements = re.findall(pattern, page) #apply pattern to source
    print("Pattern r'{}' ,Found total: {}".format(pattern,len(elements)))
    print(elements) #print all found tags
    return   if __name__ == "__main__":
    page = read_file() #read HTML file 

在这里,page是从 HTML 文件中使用read_file()读取的 HTML 页面源。我们还在前面的代码中导入了BeautifulSoup,以提取单独的 HTML 标签名称,并通过使用soup.find_all()和我们将应用的正则表达式模式来比较代码的实现和结果:

soup = BeautifulSoup(page, 'lxml')
print([element.name for element in soup.find_all()])
['html', 'head', 'title', 'style', 'body', 'h1', 'a', 'a', 'a', 'div', 'p', 'i', 'img', 'p', 'i', 'h1']

为了找到page中存在的所有 HTML 标签,我们使用了find_all()方法,soup作为BeautifulSoup的对象,使用lxml解析器。

有关 Beautiful Soup 的更多信息,请访问第五章,使用 Scrapy 和 Beautiful Soup 进行 Web 抓取使用 Beautiful Soup 进行 Web 抓取部分。

在这里,我们正在查找所有没有任何属性的 HTML 标签名称。\w+匹配任何一个或多个字符的单词:

applyPattern(r'<(\w+)>') #Finding Elements without attributes 
Pattern r'<(\w+)>' ,Found total: 6
['html', 'head', 'title', 'body', 'div', 'i']

可以使用空格字符\s来查找所有不以>结尾或包含某些属性的 HTML 标签或元素:

applyPattern(r'<(\w+)\s') #Finding Elements with attributes 
Pattern r'<(\w+)\s' ,Found total: 10
['style', 'h1', 'a', 'a', 'a', 'p', 'img', 'p', 'i', 'h1']

现在,通过结合所有这些模式,我们正在列出在页面源中找到的所有 HTML 标签。通过使用soup.find_all()name属性,前面的代码也得到了相同的结果:

applyPattern(r'<(\w+)\s?') #Finding all HTML element

Pattern r'<(\w+)\s?' ,Found total: 16
['html', 'head', 'title', 'style', 'body', 'h1', 'a', 'a', 'a', 'div', 'p', 'i', 'img', 'p', 'i', 'h1']

让我们找到 HTML 元素中的属性名称:

applyPattern(r'<\w+\s+(.*?)=') #Finding attributes name Pattern r'<\w+\s+(.*?)=' ,Found total: 10
['type', 'style', 'href', 'class', 'id', 'id', 'src', 'class', 'style', 'itemprop']

正如我们所看到的,只列出了 10 个属性。在 HTML 源代码中,一些标签包含多个属性,比如<a href="https://www.google.com" style="color:red;">Google</a>,只有使用提供的模式找到了第一个属性。

让我们纠正这一点。我们可以使用r'(\w+)='模式选择紧跟着=字符的单词,这将导致返回页面源中找到的所有属性:

applyPattern(r'(\w+)=') #Finding names of all attributes Pattern r'(\w+)=' ,Found total: 18
['type', 'style', 'href', 'style', 'class', 'href', 'id', 'href', 'style', 'id', 'class', 'src', 'id', 'class', 'class', 'id', 'style', 'itemprop']

同样,让我们找到我们找到的属性的所有值。以下代码列出了属性的值,并比较了我们之前列出的18个属性。只找到了9个值。使用的模式r'=\"(\w+)\"'只会找到单词字符。一些属性值包含非单词字符,比如<a href="https://www.google.com" style="color:red;">

applyPattern(r'=\"(\w+)\"')

Pattern r'=\"(\w+)\"' ,Found total: 9
['classOne', 'idOne', 'mainContent', 'content', 'pageLogo', 'logo', 'content', 'subContent', 'subheading']

通过使用我们分析的适当模式列出了完整的属性值。内容属性值还包含非单词字符,如;/:.。在正则表达式中,我们可以单独包含这些字符,但这种方法可能并不适用于所有情况。

在这种情况下,包括\w和非空白字符\S的模式非常合适,即r'=\"([\w\S]+)\"

applyPattern(r'=\"([\w\S]+)\"')

Pattern r'=\"([\w\S]+)\"' ,Found total: 18
['text/css', 'color:orange;', 'https://www.google.com', 'color:red;', 'classOne', 'https://www.yahoo.com', 'idOne', 'https://www.wikipedia.org', 'color:blue;', 'mainContent', 'content', 'mylogo.png', 'pageLogo', 'logo', 'content', 'subContent', 'color:red', 'subheading']

最后,让我们收集在 HTML 标签的开头和结尾之间找到的所有文本:

applyPattern(r'\>(.*)\<')
Pattern r'\>(.*)\<' ,Found total: 8
['Welcome to Web Scraping: Example', 'Welcome to Web Scraping', 'Google', 'Yahoo', 'Wikipedia', 'Paragraph contents', 'Sub paragraph content', 'Sub heading Content!']  

在对内容应用正则表达式时,必须进行内容类型和要提取的值的初步分析。这将有助于在一次尝试中获得所需的结果。

示例 2 - 提取经销商位置

在这个例子中,我们将从godfreysfeed.com/dealersandlocations.php提取内容。这个网站包含经销商位置信息,如下面的屏幕截图所示:

import re
import requests
 def read_url(url):
'''
Handles URL Request and Response
Loads the URL provided using requests and returns the text of page source
'''
  pageSource = requests.get(url).text
    return pageSource

if __name__ == "__main__":

在本节和其他示例中,我们将使用rerequests库来检索页面源代码,即pageSource。在这里,我们将使用read_url()函数来实现。

页面包含 HTML<form>元素,以便我们可以根据输入的zipcode搜索经销商。还有一个带有标记的地理地图:

https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/hsn-web-scp-py/img/6a8edbca-0e35-4112-8341-44add5a180f2.png

Godfreysfeed 经销商首页

您可以使用zipcode进行表单提交,也可以从地图中提取内容。

通过分析页面源,我们将发现没有包含经销商信息的 HTML 元素。实现 Regex 非常适合这种情况。在这里,经销商的信息是在 JavaScript 代码中找到的,其中包含latLnginfoWindowContent等变量,如下截图所示:

https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/hsn-web-scp-py/img/5026c2bb-c5ec-42a4-ad46-e7d3c9d53192.png

Godfreysfeed 经销商页面源

我们现在将继续加载所需 URL 的页面源,并实现 Regex 来查找数据:

dataSet=list() #collecting data extracted
sourceUrl = 'http://godfreysfeed.com/dealersandlocations.php' page = read_url(sourceUrl) #load sourceUrl and return the page source

通过从read_url()获取的页面源,让我们进行基本分析并构建一个模式来收集纬度和经度信息。我们需要两个不同的模式来分别获取经销商的地址和坐标值。从这两个模式的输出可以合并以获得最终结果:

#Defining pattern matching latitude and longitude as found in page.
pLatLng= r'var latLng = new google.maps.LatLng\((?P<lat>.*)\,\s*(?P<lng>.*)\)\;'

#applying pattern to page source latlngs = re.findall(pLatLng,page) 
print("Findall found total *LatLngs:* ", len(latlngs))

#Print coordinates found
print(latlngs)

通过使用pLatLng模式,共找到了55个坐标值:

Findall found total LatLngs: 55 
[('33.2509855','-84.2633946'),('31.0426107','-84.8821949'),('34.8761989','-83.9582412'),('32.43158','-81.749293'),('33.8192864','-83.4387722'),('34.2959968','-83.0062267'),
('32.6537561','-83.7596295'),('31.462497','-82.5866503'),('33.7340136','-82.7472304')
,................................................................., 
('32.5444125','-82.8945945'),('32.7302168','-82.7117232'),('34.0082425','-81.7729772'),
('34.6639864', '-82.5126743'),('31.525261','-83.06603'),('34.2068698','-83.4689814'),
('32.9765932','-84.98978'),('34.0412765','-83.2001394'),('33.3066615','-83.6976187'), 
('31.3441482','-83.3002373'),('30.02116','-82.329495'),('34.58403','-83.760829')]

现在我们已经得到了经销商的坐标,让我们找出经销商的名称、地址等信息:

#Defining pattern to find dealer from page.
pDealers = r'infoWindowContent = infoWindowContent\+\s*\"(.*?)\"\;'

#applying dealers pattern to page source dealers = re.findall(pDealers, page)
print("Findall found total Address: ", len(dealers))

#Print dealers information found
print(dealers)

还有55个基于地址的信息,是通过使用pDealers模式找到的。请注意,经销商的内容是以 HTML 格式呈现的,需要进一步实现 Regex 以获取诸如nameaddresscity等个别标题:

Findall found total Address: 55

["<strong><span style='color:#e5011c;'>Akins Feed & Seed</span></strong><br><strong>206 N Hill Street </strong><br><strong>Griffin, GA</strong><br><strong>30223</strong><br><br>", "<strong><span style='color:#e5011c;'>Alf&apos;s Farm and Garden</span></strong><br><strong>101 East 1st Street</strong><br><strong>Donalsonville, GA</strong><br><strong>39845</strong><br><br>", "<strong><span style='color:#e5011c;'>American Cowboy Shop</span></strong><br><strong>513 D Murphy Hwy</strong><br><strong>Blairsville, GA</strong><br><strong>30512</strong><br><br>",................................... ....................................,"<strong><span style='color:#e5011c;'>White Co. Farmers Exchange </span></strong><br><strong>951 S Main St</strong><br><strong>Cleveland, GA</strong><br><strong>30528 </strong><br><br>"]

现在我们已经得到了latlngsdealers的结果,让我们收集经销商地址的各个部分。经销商的原始数据包含一些 HTML 标签,已被用于拆分和清理经销商的地址信息。由于re.findall()返回 Python 列表,索引也可以用于检索地址组件:

d=0 #maintaining loop counter for dealer in dealers:
    dealerInfo = re.split(r'<br>',re.sub(r'<br><br>','',dealer))

    #extract individual item from dealerInfo
    name = re.findall(r'\'>(.*?)</span',dealerInfo[0])[0]
    address = re.findall(r'>(.*)<',dealerInfo[1])[0]
    city = re.findall(r'>(.*),\s*(.*)<',dealerInfo[2])[0][0]
    state = re.findall(r'>(.*),\s*(.*)<',dealerInfo[2])[0][1]
    zip = re.findall(r'>(.*)<',dealerInfo[3])[0]
    lat = latlngs[d][0]
    lng = latlngs[d][1]
    d+=1

    #appending items to dataset
  dataSet.append([name,address,city,state,zip,lat,lng])
 print(dataSet)  #[[name,address, city, state, zip, lat,lng],]

最后,dataSet将包含从dealerslatlngs中合并的单个经销商信息:

[['Akins Feed & Seed', '206 N Hill Street', 'Griffin', 'GA', '30223', '33.2509855', '-84.2633946'], ['Alf&apos;s Farm and Garden', '101 East 1st Street', 'Donalsonville', 'GA', '39845', '31.0426107', '-84.8821949'],...................................., 
['Twisted Fitterz', '10329 Nashville Enigma Rd', 'Alapaha', 'GA', '31622', '31.3441482', '-83.3002373'], 
['Westside Feed II', '230 SE 7th Avenue', 'Lake Butler', 'FL', '32054', '30.02116', '-82.329495'],
['White Co. Farmers Exchange', '951 S Main St', 'Cleveland', 'GA', '30528', '34.58403', '-83.760829']]

在这个例子中,我们尝试使用不同的模式提取数据,并从提供的 URL 中检索了经销商的信息。

示例 3 - 提取 XML 内容

在这个例子中,我们将从sitemap.xml文件中提取内容,可以从**webscraping.com/sitemap.xml**下载:

https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/hsn-web-scp-py/img/060d674a-0e48-4325-b8a3-720edd7f4a0a.png

来自 https://webscraping.com 的 sitemap.xml 文件

通过分析 XML 内容,我们可以看到不同类型的 URL 存在于子节点中,即<loc>。我们将从这些 URL 中提取以下内容:

从代码中获取的博客标题和类别标题是从 URL 或实际可用的内容的表示中检索出来的。实际标题可能会有所不同。

首先,让我们导入re Python 库并读取文件内容,以及创建一些 Python 列表以收集相关数据:

import re

filename = 'sitemap.xml' dataSetBlog = [] # collect Blog title information from URLs except 'category' dataSetBlogURL = [] # collects Blog URLs dataSetCategory = [] # collect Category title dataSetCategoryURL = [] # collect Category URLs   page = open(filename, 'r').read()

从 XML 内容,也就是page中,我们需要找到 URL 模式。代码中使用的pattern匹配并返回<loc>节点内的所有 URL。urlPatterns<class 'list'>)是一个包含搜索 URL 的 Python 列表对象,可以迭代收集和处理所需的信息:

#Pattern to be searched, found inside <loc>(.*)</loc>
pattern = r"loc>(.*)</loc" urlPatterns = re.findall(pattern, page) #finding pattern on page

for url in urlPatterns: #iterating individual url inside urlPatterns

现在,让我们匹配一个url,比如webscraping.com/blog/Google-App-Engine-limitations/,其中包含一个blog字符串,并将其附加到dataSetBlogURL。还有一些其他 URL,比如webscraping.com/blog/8/,在我们提取blogTitle时将被忽略。

此外,任何作为文本等于categoryblogTitle都将被忽略。r'blog/([A-Za-z0-9\-]+)模式匹配包含-字符的字母和数字值:

if re.match(r'.*blog', url): #Blog related
    dataSetBlogURL.append(url)
 if re.match(r'[\w\-]', url):
        blogTitle = re.findall(r'blog/([A-Za-z0-9\-]+)', url)

        if len(blogTitle) > 0 and not re.match('(category)', blogTitle[0]):
            #blogTitle is a List, so index is applied.
            dataSetBlog.append(blogTitle[0]) 

以下是dataSetBlogURL的输出:

print("Blogs URL: ", len(dataSetBlogURL))
print(dataSetBlogURL)

Blogs URL: 80
['https://webscraping.com/blog', 'https://webscraping.com/blog/10/', 
'https://webscraping.com/blog/11/', .......,
'https://webscraping.com/blog/category/screenshot', 'https://webscraping.com/blog/category/sitescraper', 'https://webscraping.com/blog/category/sqlite', 'https://webscraping.com/blog/category/user-agent', 'https://webscraping.com/blog/category/web2py', 'https://webscraping.com/blog/category/webkit', 'https://webscraping.com/blog/category/website/', 'https://webscraping.com/blog/category/xpath']

dataSetBlog将包含以下标题(URL 部分)。将set()方法应用于dataSetBlog时,将从dataSetBlog返回唯一元素。如下所示,dataSetBlog中没有重复的标题:

print**("Blogs Title: ", len(dataSetBlog))
print("Unique Blog Count: ", len(set(dataSetBlog)))
print(dataSetBlog)
#print(set(dataSetBlog)) #returns unique element from List similar to dataSetBlog.

Blogs Title: 24
Unique Blog Count: 24
 ['Android-Apps-Update', 'Apple-Apps-Update', 'Automating-CAPTCHAs', 'Automating-webkit', 'Bitcoin', 'Client-Feedback', 'Fixed-fee-or-hourly', 'Google-Storage', 'Google-interview', 'How-to-use-proxies', 'I-love-AJAX', 'Image-efficiencies', 'Luminati', 'Reverse-Geocode', 'Services', 'Solving-CAPTCHA', 'Startup', 'UPC-Database-Update', 'User-agents', 'Web-Scrapping', 'What-is-CSV', 'What-is-web-scraping', 'Why-Python', 'Why-web']

现在,让我们通过使用category来提取与 URL 相关的信息。r'.*category'正则表达式模式匹配迭代中的url,并将其收集或附加到datasetCategoryURL。从与r'category/([\w\s\-]+)模式匹配的url中提取categoryTitle,并将其添加到dataSetCategory

if re.match(r'.*category', url): #Category Related
    dataSetCategoryURL.append(url)
    categoryTitle = re.findall(r'category/([\w\s\-]+)', url)
    dataSetCategory.append(categoryTitle[0])

print("Category URL Count: ", len(dataSetCategoryURL))
print(dataSetCategoryURL)

dataSetCategoryURL将产生以下值:

Category URL Count: 43
['https://webscraping.com/blog/category/ajax', 'https://webscraping.com/blog/category/android/', 'https://webscraping.com/blog/category/big picture', 'https://webscraping.com/blog/category/business/', 'https://webscraping.com/blog/category/cache', 'https://webscraping.com/blog/category/captcha', ..................................., 'https://webscraping.com/blog/category/sitescraper', 'https://webscraping.com/blog/category/sqlite', 'https://webscraping.com/blog/category/user-agent', 'https://webscraping.com/blog/category/web2py', 'https://webscraping.com/blog/category/webkit', 'https://webscraping.com/blog/category/website/', 'https://webscraping.com/blog/category/xpath']

最后,以下输出显示了从dataSetCategory中检索到的标题,以及其计数:

print("Category Title Count: ", len(dataSetCategory))
print("Unique Category Count: ", len(set(dataSetCategory)))
print(dataSetCategory)
#returns unique element from List similar to dataSetCategory.
#print(set(dataSetCategory)) 

Category Title Count: 43
Unique Category Count: 43 
['ajax', 'android', 'big picture', 'business', 'cache', 'captcha', 'chickenfoot', 'concurrent', 'cookies', 'crawling', 'database', 'efficiency', 'elance', 'example', 'flash', 'freelancing', 'gae', 'google', 'html', 'image', 'ip', 'ir', 'javascript', 'learn', 'linux', 'lxml', 'mobile', 'mobile apps', 'ocr', 'opensource', 'proxies', 'python', 'qt', 'regex', 'scrapy', 'screenshot', 'sitescraper', 'sqlite', 'user-agent', 'web2py', 'webkit', 'website', 'xpath']

从这些示例中,我们可以看到,通过使用正则表达式,我们可以编写针对来自网页、HTML 或 XML 等来源的特定数据的模式。

搜索、分割和迭代等正则表达式功能可以通过re Python 库中的各种函数来实现。尽管正则表达式可以应用于任何类型的内容,但首选非结构化内容。使用 XPath 和 CSS 选择器时,首选带有属性的结构化网页内容。

摘要

在本章中,我们学习了正则表达式及其在re Python 库中的实现。

到目前为止,我们已经了解了各种基于抓取的工具和技术。当涉及到提取任务时,正则表达式可以提供更多的灵活性,并且可以与其他工具一起使用。

在下一章中,我们将学习进一步的步骤和主题,这些对于学习环境可能是有益的,比如管理抓取的数据,可视化和分析,以及机器学习和数据挖掘的介绍,以及探索一些相关资源。

进一步阅读

第四部分:结论

在本节中,您将了解一些适用于收集或抓取数据的主题,并了解一些值得从信息和职业角度了解的高级概念。

本节包括以下章节:

  • 第十章,下一步

第十章:下一步

到目前为止,我们已经通过使用 Python 编程语言探索了有关网页抓取的各种工具和技术。

网页抓取或网络收集是为了从网站中提取和收集数据。网页抓取在模型开发方面非常有用,因为需要实时收集真实、与主题相关和准确的数据。这是可取的,因为与实施数据集相比,需要的时间更少。收集的数据以各种格式存储,如 JSON、CSV、XML 等,写入数据库以供以后使用,并且也作为数据集在线提供。

网站还提供带有用户界面的 Web API,用于与网络上的信息进行交互。这些数据可以用于计算机科学、管理、医学等领域的研究、分析、营销、机器学习(ML)模型、信息构建、知识发现等。我们还可以对通过 API 和公开或免费提供的数据集获得的数据进行分析,并生成结果,但这个过程不被归类为网页抓取。

在本章中,我们将学习与收集或抓取的数据相关的主题,并了解一些值得从信息和职业角度了解的高级概念:

  • 管理抓取的数据

  • 使用 pandas 和 matplotlib 进行分析和可视化

  • ML

  • 数据挖掘

  • 接下来是什么?

技术要求

需要使用网络浏览器(Google Chrome 或 Mozilla Firefox)。在本章中,我们将使用以下 Python 库:

  • pandas

  • matplotlib

  • csv

  • json

如果这些库在您当前的 Python 设置中不存在,请参考第二章,Python 和 Web - 使用 urllib 和 Requests,在设置事项部分,获取安装和设置它们的说明。

本章的代码文件可在本书的 GitHub 存储库中找到:github.com/PacktPublishing/Hands-On-Web-Scraping-with-Python/tree/master/Chapter10

管理抓取的数据

在本节中,我们将探索一些工具,并了解如何处理和管理我们从某些网站上抓取或提取的数据。

使用抓取脚本从网站收集的数据称为原始数据。这些数据可能需要进行一些额外的任务,然后才能进一步处理,以便我们可以对其进行深入的了解。因此,原始数据应该经过验证和处理(如果需要),可以通过以下方式进行:

  • 清理:顾名思义,此步骤用于删除不需要的信息,例如空格和空白字符以及不需要的文本部分。以下代码显示了在先前章节的示例中使用的一些相关步骤,例如第九章,使用正则表达式提取数据,和第三章,使用 LXML、XPath 和 CSS 选择器。在许多地方使用sub()(即re.sub())、strip()replace()等函数,也可以用于清理的目的:
dealerInfo = re.split(r'<br>', re.sub(r'<br><br>', '', dealer))

stock = list(map(lambda stock:stock.strip(),availability))

availability = stockPath(row)[0].strip()

article['lastUpdated'] = article['lastUpdated'].replace('This page was last edited on', '')

title = row.find(attrs={'itemprop':'text'}).text.strip()

re.sub(r'or\s*','',fortran)

dealerInfo = re.split(r'<br>',re.sub(r'<br><br>','',dealer))
  • 格式化:此步骤用于从数据中获取所需的格式。例如,我们可能需要在收到的价格中获得固定的小数位,我们可能需要将大浮点值转换或四舍五入为固定的小数位,将大字符串拆分为较小的单元等,然后将它们写入数据集。还可能出现将十进制数或整数提取为字符串并需要格式化的情况。通常,转换数据类型和呈现数据被视为格式化:
>>> price = 1234.567801
>>> newprice = round(price,2)
>>> print(newprice)
1234.57

>>> totalsum="200.35"
>>> print(type(totalsum))
<class 'str'>

#For large precision use: https://docs.python.org/2/library/decimal.html
>>> totalsum = float(totalsum) 
>>> print(type(totalsum))
<class 'float'>

>>> totalsum
200.35
>>> ratings = 5.5
>>> print(int(rating))
5

这些额外的步骤也可以在提取特定数据的同时在脚本中执行,并且已经在本书中的示例中完成。在许多情况下,清理和格式化是一起进行的,或者是并行进行的。

写入文件

在整本书中,我们需要提取数据行。您可能已经注意到,在大多数示例中,我们使用了一个数据集(用于收集数据的 Python 列表对象),该数据集附加了 Python 列表中的各种字段,如下面的代码所示(从本书的各个示例中收集):

dataSet.append([year,month,day,game_date,team1,team1_score,team2,team2_score,game_status])
..
dataSet.append([title,price,availability,image.replace('../../../..',baseUrl),rating.replace('star-rating ','')])
...
dataSet.append([link, atype, adate, title, excerpt,",".join(categories)])
...
dataSet.append([titleLarge, title, price, stock, image, starRating.replace('star-rating ', ''), url])

有了这样的数据集,我们可以将这些信息写入外部文件,也可以写入数据库。在将数据集写入文件之前,需要列名来描述数据集中的数据。考虑以下代码,其中keys是一个单独的列表,包含一个字符串标题,即列的名称,将其附加到数据集的相应列表项中:

keys = ['year','month','day','game_date','team1', 'team1_score', 'team2', 'team2_score', 'game_status']
......
dataSet.append([year,month,day,game_date,team1,team1_score,team2,team2_score,game_status])

让我们考虑以下示例,其中包含要使用的列的colNames,以及清理和格式化数据的dataSet

import csv
import json

colNames = ['Title','Price','Stock','Rating']
dataSet= [['Rip it Up and ...', 35.02, 'In stock', 5],['Our Band Could Be ...', 57.25, 'In stock', 4],
    ['How Music Works', 37.32, 'In stock', 2],['Love Is a Mix ...', 18.03, 'Out of stock',1],
    ['Please Kill Me: The ...', 31.19, 'In stock', 4],["Kill 'Em and Leave: ...", 45.0, 'In stock',5],
    ['Chronicles, Vol. 1', 52.60, 'Out of stock',2],['This Is Your Brain ...', 38.4, 'In stock',1],
    ['Orchestra of Exiles: The ...', 12.36, 'In stock',3],['No One Here Gets ...', 20.02, 'In stock',5],
   ['Life', 31.58, 'In stock',5],['Old Records Never Die: ...', 55.66, 'Out of Stock',2],
    ['Forever Rockers (The Rocker ...', 28.80, 'In stock',3]]

现在我们将上述dataSet写入 CSV 文件。CSV 文件的第一行应始终包含列名。在本例中,我们将使用colNames作为列名:

fileCsv = open('bookdetails.csv', 'w', newline='', encoding='utf-8')
writer = csv.writer(fileCsv) #csv.writer object created

writer.writerow(colNames)  #write columns from colNames
for data in dataSet:       #iterate through dataSet and write to file
    writer.writerow(data)

fileCsv.close() #closes the file handler

上述代码将导致bookdetails.csv文件,其内容如下:

Title,Price,Stock,Rating Rip it Up and ...,35.02,In stock,5 Our Band Could Be ...,57.25,In stock,4 ........... Life,31.58,In stock,5 Old Records Never Die: ...,55.66,Out of Stock,2 Forever Rockers (The Rocker ...,28.8,In stock,3

同样,让我们创建一个包含colNamesdataSets的 JSON 文件。JSON 类似于 Python 字典,其中每个数据或值都具有一个键;也就是说,它存在于键值对中:

finalDataSet=list() #empty DataSet 
for data in dataSet:
    finalDataSet.append(dict(zip(colNames,data))) 

print(finalDataSet)

[{'Price': 35.02, 'Stock': 'In stock', 'Title': 'Rip it Up and ...', 'Rating': 5}, {'Price': 57.25, 'Stock': 'In stock', ..........'Title': 'Old Records Never Die: ...', 'Rating': 2}, {'Price': 28.8, 'Stock': 'In stock', 'Title': 'Forever Rockers (The Rocker ...', 'Rating': 3}]

正如我们所看到的,finalDataSet是通过从dataSet中添加数据并使用zip() Python 函数形成的。zip()将列表中的每个单独元素组合在一起。然后将这个压缩对象转换为 Python 字典。例如,考虑以下代码:

#first iteration from loop above dict(zip(colNames,data)) will generate
{'Rating': 5, 'Title': 'Rip it Up and ...', 'Price': 35.02, 'Stock': 'In stock'}

现在,有了可用的finalDataSet,我们可以使用json模块的dump()函数将数据转储或添加到 JSON 文件中:

with open('bookdetails.json', 'w') as jsonfile:
    json.dump(finalDataSet,jsonfile)

上述代码将导致bookdetails.json文件。其内容如下:

[
  {
    "Price": 35.02,
    "Stock": "In stock",
    "Title": "Rip it Up and ...",
    "Rating": 5
  },
  ................
  {
    "Price": 28.8,
    "Stock": "In stock",
    "Title": "Forever Rockers (The Rocker ...",
    "Rating": 3
  }
]

在本节中,我们已经介绍了管理原始数据的基本步骤。我们获得的文件可以在各种独立系统之间轻松共享和交换,用作 ML 的模型,并且可以作为应用程序中的数据源导入。此外,我们还可以使用数据库管理系统DBMS)如 MySQL、PostgreSQL 等来存储数据,并使用必要的 Python 库执行结构化查询语言SQL)。

使用 pandas 和 matplotlib 进行分析和可视化

在本节中,我们将探讨使用 pandas 分析数据和使用 matplotlib 绘制通用图表的一些基本概念。

pandas 是近年来最受欢迎的数据分析库之一。数据分析和可视化是主要任务,可以借助 pandas 和其他库(如 matplotlib)来完成。

有关 pandas 和 matplotlib 的更多详细信息和文档,请访问它们的官方网站pandas.pydata.org/matplotlib.org/

pandas 也被称为原始电子表格,并支持数学、统计和查询类型的语句,并允许您从各种文件中读取和写入。它也受到开发人员和分析师的欢迎,因为它具有易于使用的函数和属性,可以帮助您处理以行和列结构存在的数据:

https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/hsn-web-scp-py/img/6a141fdd-e7fa-4ce0-9e71-7120be45a431.png

使用 Python IDE 探索 pandas

在本节中,我们将从bookdetails.csv文件中读取数据,并使用该文件的数据进行分析和可视化。让我们导入所需的库,即 pandas 和matplotlib.pyplot。我们将分别使用pdplt别名,并从文件中读取数据:

import pandas as pd
import matplotlib.pyplot as plt

dataSet = pd.read_csv('bookdetails.csv') #loads the file content as dataframe.

print(type(dataSet)) #<class 'pandas.core.frame.DataFrame'>

正如我们所看到的,read_csv()函数从 CSV 文件中读取内容并生成一个 DataFrame 对象。pandas 还通过使用read_html()read_excel()read_json()read_sql_table()等函数支持各种数据文件。

在这里,dataSet是 pandas DataFrame 的一个对象。DataFrame 表示具有行、列和索引的二维表格结构。DataFrame 支持针对行和列中的数据的查询级别分析、条件语句、过滤、分组等操作:

print(dataSet)

以下屏幕截图显示了现在在dataSet中可用的内容:

https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/hsn-web-scp-py/img/a640b1d4-77ce-4ee3-8188-d698f08bfe09.png

来自 CSV 文件的数据集内容

行索引也显示出来,所有的行索引都以0(零)开头。可以使用describe()函数获得一般的统计输出:

print(dataSet.describe()) 
#print(dataSet.describe('price') will only generate values for column price

      Price      Rating
count 13.000000  13.000000
mean  35.633077  3.230769
std   14.239014  1.535895
min   12.360000  1.000000
25%   28.800000  2.000000
50%   35.020000  3.000000
75%   45.000000  5.000000
max   57.250000  5.000000

正如我们所看到的,默认情况下,describe()选择适用于统计函数的列,并返回以下函数的计算结果:

  • count: 行数

  • mean: 相关列的平均值

  • min: 找到的最小值

  • max: 找到的最大值

  • std: 计算的标准偏差

  • 25%: 返回第 25 个百分位数

  • 50%: 返回第 50 个百分位数

  • 75%: 返回第 75 个百分位数

在以下代码中,我们选择了一个名为Price的单独列作为price_group。可以使用dataSet.columns列出数据集中的所有列。可以使用以下格式选择多个列dataSet[['Price','Rating']]

print(dataSet.columns)
Index(['Title', 'Price', 'Stock', 'Rating'], dtype='object')

print(sum(dataSet['Price']))
463.23

print(sum(dataSet['Rating']))
42

print(dataSet['Price'][0:5])
0 35.02
1 57.25
2 37.32
3 18.03
4 31.19
Name: Price, dtype: float64

以下代码显示了Price列的单独数据:

#dataSet[['Price','Rating']] will select both column
price_group = dataSet[['Price']] #selecting 'Price' column only.
print(price_group) 

Index(['Title', 'Price', 'Stock', 'Rating'], dtype='object')
  Price
0 35.02
1 57.25
2 37.32
.....
11 55.66
12 28.80

pandas DataFrame 也接受对列使用条件或过滤操作。如您所见,筛选应用于Rating,其值为>=4.0,并且只返回TitlePrice

 print(dataSet[dataSet['Rating']>=4.0][['Title','Price']])

  Title                  Price
0 Rip it Up and ...      35.02
1 Our Band Could Be ...  57.25
4 Please Kill Me: The ...31.19
5 Kill 'Em and Leave: ...45.00
9 No One Here Gets ...   20.02
10 Life                  31.58

同样,也可以应用基于字符串的过滤。包含Out文本的Stock被过滤,输出返回满足Out文本的所有列。contains()函数接受正则表达式和字符串:

print(dataSet[dataSet.Stock.str.contains(r'Out')])

   Title                     Price Stock        Rating
3  Love Is a Mix ...         18.03 Out of stock 1
6  Chronicles, Vol. 1        52.60 Out of stock 2
11 Old Records Never Die: ...55.66 Out of Stock 2#will return only column 'Price'
#print(dataSet[dataSet.Stock.str.contains(r'Out')]['Price'])

between()函数提供了与Rating相关的值,以过滤和返回书籍的Title

print(dataSet[dataSet.Rating.between(3.5,4.5)]['Title'])

1 Our Band Could Be ...
4 Please Kill Me: The ...

由于我们有price_group数据,我们可以使用show()函数在数据上调用plot()函数:

 bar_plot = price_group.plot()  #default plot
 bar_plot.set_xlabel("No of Books") #set X axis: label
 bar_plot.set_ylabel("Price") #set Y axis: label
 plt.show() #displays the plot or chart created

上述代码将生成一个带有默认属性的线图,如颜色和图例位置:

https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/hsn-web-scp-py/img/88032012-24fe-458b-bce4-311805f499a0.png

Price 列的默认线图

我们还可以更改图表的类型,即线图、柱状图等。

访问 matplotlib matplotlib.org/gallery/index.html 了解更多有关各种功能图表类型及其附加属性的信息。

在以下代码中,kind='bar'覆盖了默认的线型:

bar_plot = price_group.plot(kind='bar') #kind='bar'
bar_plot.set_xlabel("No of Books")  #Label for X-Axis
bar_plot.set_ylabel("Price") #label for Y-Axis
plt.show() 

上述代码生成了以下柱状图:

https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/hsn-web-scp-py/img/53896187-3e46-4cf5-8b19-32d0fb0272e5.png

Price 列的柱状图

到目前为止,我们已经使用了基本的图表类型和单个列。在以下代码中,我们将使用PriceRating值绘制柱状图:

price_group = dataSet[['Price','Rating']]  #obtain both columns
#title: generates a title for plot
bar_plot = price_group.plot(kind='bar',title="Book Price ad Rating")
bar_plot.set_xlabel("No of Books")
bar_plot.set_ylabel("Price")
plt.show()

我们收到以下输出:

https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/hsn-web-scp-py/img/1b71ada2-29dd-4b1c-909f-6153340ceef6.png

具有 Price 和 Rating 列的柱状图

到目前为止,我们已成功绘制了线图和柱状图。以下代码为Price列的前六个项目绘制了一个饼图,并使用dataSet中可用的前六个Title标签它们:

prices = dataSet['Price'][0:6] #Price from first 6 items
labels = dataSet['Title'][0:6] #Book Titles from first 6 items
legends,ax1 = plt.pie(prices, labels=labels, shadow=True, startangle=45)
plt.legend(legends, prices, loc="best") #legend built using Prices
plt.show() 

来自Price的值被用作图例。我们收到以下输出:

https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/hsn-web-scp-py/img/48838041-1f20-43c6-8ed4-55baf156f677.png

带有 Price 和 Title 列数据的饼图

在使用 pandas 和 matplotlib 方面还有很多可以探索的地方。在本节中,我们展示了这两个库中可用的基本功能。现在,我们将看看机器学习。

机器学习

机器学习是人工智能的一个分支,涉及研究数学和统计算法,以处理和开发能够从数据中学习的自动化系统,减少人类干预。机器学习的预测和决策模型依赖于数据。网络抓取是使数据可用于机器学习模型的资源之一。

如今,许多推荐引擎实现了机器学习,以便实时提供营销广告和推荐,比如谷歌的 AdSense 和 AdWords。机器学习中实施的过程类似于数据挖掘和预测建模。这两个概念都在浏览数据并根据要求修改程序的行为时寻找模式。因此,机器学习在探索商业、营销、零售、股票价格、视频监控、人脸识别、医学诊断、天气预测、在线客户支持、在线欺诈检测等领域时是一个方便的工具。

随着新的和改进的机器学习算法、数据捕获方法以及更快的计算机和网络,机器学习领域正在加速发展。

机器学习和人工智能

人工智能是一个广泛的范畴,涵盖了诸多主题,如神经网络、专家系统、机器人技术、模糊逻辑等等。机器学习是人工智能的一个子集,它探索了构建一个能够自主学习的机器的理念,从而超越了对不断推测的需求。因此,机器学习已经取得了实现人工智能的重大突破。

机器学习包括使用多种算法,从而使软件能够提供准确的结果。从一组解析数据中进行有用的预测是机器学习概念的目标。机器学习的最主要优势是它可以不知疲倦地学习和预测,而无需硬编码的软件体系。训练包括将大量数据集作为输入。这使得算法能够学习、处理和进行预测,然后将预测结果作为输出。

在衡量任何模型的潜力时,会采用几个重要参数。准确性是其中之一,也是衡量任何开发模型成功的重要参数。在机器学习中,80%的准确性就是成功。如果模型的准确性达到 80%,那么我们就节省了 80%的时间,提高了生产率。然而,如果数据不平衡,准确性并不总是评估分类模型的最佳指标。

总的来说,准确性被称为一种直观的度量。在使用准确性时,对假阳性和假阴性分配了相等的成本。对于不平衡的数据(比如 94%属于一种情况,6%属于另一种情况),有许多降低成本的好方法;做一个模糊的预测,即每个实例都属于多数类,证明整体准确性为 94%,然后完成任务。同样,如果我们讨论的是一种罕见且致命的疾病,问题就会出现。未能正确检查患病者的疾病的成本高于将健康个体推向更多检查的成本。

总之,没有最佳的度量标准。两个人选择不同的度量标准来达到他们的目标是很常见的。

Python 和机器学习

荷兰程序员(Guido Van Rossum)将 Python 作为他的副业项目推出,但没有意识到它会加速他的成功。Python 在开发人员中被广泛采用,当涉及到快速原型设计时尤其如此。它因其可读性、多功能性和易用性而在所有机器学习工具中备受欢迎。

作为机器学习工程师、计算机视觉工程师、数据科学家或数据工程师,我们必须在线性代数和微积分的概念中游刃有余,一旦深入研究,这些概念往往变得复杂。然而,Python 通过其快速实现来解救我们,从而绕过了最大努力的障碍。对这一理念的快速验证使得 Python 编程语言更加受欢迎。

对于 ML 来说,数据就是一切。原始数据是非结构化的、庞大的、不完整的,并且存在缺失值。数据清洗是 ML 中最关键的步骤之一,这样我们才能继续处理我们的数据。Python 中有许多重要的库,使 ML 的实施变得更简单。Python 中的各种开源存储库帮助改变现有的方法。Web 抓取是这些方法之一,它处理存在于网络上的数据,然后进一步处理为 ML 模型的输入。

以下是一些最常见和广泛使用的库,如果我们决定使用 Python 和 ML,值得一看:

  • scikit-learn:用于处理经典 ML 算法

  • NumPy(数值 Python):设计用于科学计算

  • SciPy:包含线性代数、优化、积分和统计模块

  • pandas:用于数据聚合、操作和可视化

  • matplotlibSeaborn:用于数据可视化

  • BokehPlotly:用于交互式可视化

  • TensorFlowTheano:用于深度学习

  • Beautiful Soup, LXML, PyQueryScrapy:用于从 HTML 和 XML 文档中提取数据

一旦我们对 Python 有了基本的了解,就可以导入并实施这些库。或者,我们也可以从头开始应用这些功能,这是大多数开发人员所做的。

Python 在编写和调试代码方面比其他编程语言节省时间。这正是 AI 和 ML 程序员想要的:专注于理解架构方面,而不是花费所有时间在调试上。因此,Python 可以很容易地被不太懂编程的人处理,因为它提供了人类水平的可读性的语法。

除了 Python,还有其他几种用于 ML 的工具,如 Microsoft Excel、SAS、MATLAB 和 R。由于缺乏足够的社区服务和无法处理大型数据集,这些工具经常被忽视。MATLAB 还提供了用于图像处理和分析的复杂库和包。与 Python 相比,执行时间适中,功能仅限于原型设计,而不是部署。

R 是另一个用于统计分析的工具。Python 通过提供各种开发工具来执行数据操作,可以与其他系统协作。然而,R 只能处理特定形式的数据集,因此预定义的函数需要预定义的输入。R 为数据提供了一个原始的基础,而 Python 允许我们探索数据。

ML 算法的类型

一般来说,有三种 ML 算法,如下所示:

  • 监督学习:

  • 分类

  • 回归

  • 无监督学习:

  • 关联

  • 聚类

  • 强化学习

监督学习

监督学习是观察或指导执行某事。输入给模型的是我们想要做出的预测。标记的数据是对特定输入实例的明确预测。监督学习需要标记的数据,这需要一些专业知识。然而,这些条件并不总是满足的。我们并不总是拥有标记的数据集。例如,欺诈预测是一个迅速发展的领域,攻击者不断寻找可用的漏洞。这些新攻击不可能在带有标记攻击的数据集下得到维护。

数学上,输入到输出的映射函数可以表示为Y = f(X)。这里,Y是输出变量,X是输入变量。

分类

分类根据其属性确定或分类模型,并且根据成员类别来确定新观察结果所属的流派的过程,这是事先已知的。这是一种根据一个或多个自变量确定因变量属于哪个类别的技术。分类问题中的输出变量是一个组或类别。

一些例子包括信用评分(根据收入和储蓄区分高风险和低风险)、医学诊断(预测疾病风险)、网络广告(预测用户是否会点击广告)等。

分类模型的能力可以通过模型评估程序和模型评估指标来确定。

模型评估程序

模型评估程序可以帮助您找出模型对样本数据的适应程度:

  • 训练和测试数据:训练数据用于训练模型,使其适应参数。测试数据是一个掩盖的数据集,需要进行预测。

  • 训练和测试分离:通常情况下,当数据被分离时,大部分数据用于训练,而一小部分数据用于测试。

  • K 折交叉验证:创建 K 个训练和测试分离,并将它们平均在一起。该过程比训练和测试分离运行 k 倍慢。

模型评估指标

模型评估指标用于量化模型的性能。以下指标可用于衡量分类预测模型的能力。

评估指标是通过以下方式进行管理:

  • 混淆矩阵:这是一个 2x2 矩阵,也称为错误矩阵。它有助于描述算法的性能,通常是监督学习算法,通过分类准确度、分类错误、灵敏度、精确度和预测。指标的选择取决于业务目标。因此,有必要确定根据要求是否可以减少假阳性或假阴性。

  • 逻辑回归:逻辑回归是一种用于分析数据集的统计模型。它有几个独立变量负责确定输出。输出用双倍体变量(涉及两种可能的结果)来衡量。逻辑回归的目标是找到最佳拟合模型,描述双倍体变量(因变量)和一组独立变量(预测变量)之间的关系。因此,它也被称为预测学习模型。

  • 朴素贝叶斯:这是基于条件概率的概念工作,由贝叶斯定理给出。贝叶斯定理根据可能与事件相关的先验知识计算事件的条件概率。这种方法广泛应用于人脸识别、医学诊断、新闻分类等。朴素贝叶斯分类器基于贝叶斯定理,可以计算A给定B的条件概率如下:

P(A | B) = ( P(B | A) * P( A ))/ P( B  )
Given:
P(A | B) = Conditional probability of A given B
P(B | A) = Conditional probability of B given A
P( A )= Probability of occurrence of event A
P( B  )= Probability of occurrence of event B
  • 决策树:决策树是一种监督学习模型,最终结果可以以树的形式呈现。决策树包括叶节点、决策节点和根节点。决策节点有两个或更多的分支,而叶节点代表分类或决策。决策树进一步将数据集分解为更小的子集,从而逐步发展相关的树。它易于理解,可以轻松处理分类和数值数据集。

  • 随机森林算法:这个算法是一种易于使用且即使没有超参数调整也能提供出色结果的监督式机器学习算法。由于其简单性,它可以用于回归和分类任务。它可以处理更大的数据集以保持缺失值。与回归相比,该算法被认为是执行与分类相关任务最好的。

  • 神经网络:虽然我们已经有了线性和分类算法,但神经网络是许多机器学习问题的最先进技术。神经网络由单元组成,即神经元,它们排列成层。它们负责将输入向量转换为某种输出。每个单元接受输入,应用函数,并将输出传递到下一层。通常,对该算法应用非线性函数。

  • 支持向量机(SVM)算法:SVM 学习算法是一种监督式机器学习模型。它用于分类和回归分析,并被广泛认为是一个受限制的优化问题。SVM 可以通过核技巧(线性、径向基函数、多项式和 Sigmoid)变得更加强大。然而,SVM 方法的局限性在于核的选择。

回归

回归是一种有助于估计变量之间关系的统计测量。一般来说,分类侧重于标签的预测,而回归侧重于数量的预测。回归在金融、投资和其他领域中被管理者用来估值他们的资产。在同一条线上,它试图确定因变量和一系列其他变化的自变量之间关系的强度;例如,商品价格与经营这些商品的企业之间的关系。

回归模型具有两个主要特征。回归问题中的输出变量是实数或数量性质的。模型的创建考虑了过去的数据。从数学上讲,预测模型将输入变量(X)映射到连续的输出变量(Y)。连续的输出变量是整数或浮点值。

回归预测模型的能力可以通过计算均方根误差(RMSE)来衡量。例如,总共,回归预测模型做出了两次预测,即 1.5 和 3.3,而预期值分别为 1.0 和 3.0。因此,RMSE 可以计算如下:

RMSE = sqrt(average(error²))
RMSE = sqrt(((1.0 - 1.5)² + (3.0 - 3.3)²) / 2)
RMSE = sqrt((0.25 + 0.09) / 2)
RMSE = sqrt(0.17)
RMSE = 0.412

无监督学习

无监督学习是一类机器学习技术,其中作为输入的数据没有标签。此外,只提供输入变量(X),没有对应的输出变量(Y)。在无监督学习中,算法被留在孤立中自行学习和探索,没有真正的早期期望。这种缺乏标记教会我们关于使用表示或嵌入重建输入数据。在数据挖掘和特征提取方面非常有益。

无监督学习可以帮助您发现隐藏的趋势和模式。一些现实世界的例子包括预测或理解手写数字、纳米摄像头制造技术、普朗克量子光谱等。

从数学上讲,无监督学习具有没有相应输出值的输入值(X)。与监督学习相比,无监督学习的任务处理非常复杂。无监督学习的实现可以在自动或自动驾驶汽车、面部识别程序、专家系统、生物信息学等领域找到。

关联和聚类是无监督学习的两个部分。

关联

这是一种用于在大型数据集中发现新模式的技术。关联被认为是根据新闻价值程度从数据集中识别强规则的。在对数据进行长时间分析时,会生成更多的新规则。

关联规则在市场篮分析中被广泛应用。这种技术有助于确定购买产品对之间的关联强度以及在观察中的共同发生频率。

市场篮分析是零售商用来发现商品之间关联的建模技术之一。该理论围绕着这样一个事实展开,即如果我们购买某些商品,我们更有可能购买类似的商品。

在数学上,它表示为P(A|B),其中购买A的人也购买B。也可以写成如果*{A},那么{B}*。换句话说,如果 A 发生的概率,那么 B 也会发生的概率。例如,P(牛奶 | 面包 ) = 0.7

聚类

簇是属于同一标签的对象的集合,被视为一个整体。聚类是将对象分组到其相应类别的技术。这包括将多个对象分类到它们的特定组中,如果它属于同一组,则其关联度最大,否则最小。

最流行的聚类算法之一是 k 均值聚类算法。该算法要求预定义的 k 值。K 代表我们想要将数据分成的簇的数量。当簇是超球形时,如二维空间中的圆或三维空间中的球时,才能获得真正的性能。

聚类的主要优势在于它帮助你从数据中找出独特、有用的特征,并且它对变化具有灵活性。

强化学习

强化学习是机器学习的一部分,它处理采取必要行动以增加特定情况奖励的问题。它利用多个软件和机器来找到特定情况的最佳路径。

强化学习与监督学习不同。在监督学习中,提供带有标签的训练数据,基于这些数据进行训练。在强化学习的情况下,强化代理人做出决定来解决分配给他们的任务。

强化学习有两种类型:

  • 正强化:最大化性能并维持更长时间的变化

  • 负强化:最小化性能并维持更短时间的变化

数据挖掘

从大型数据集或数据库中发现隐藏或预测信息的过程被称为数据挖掘。数据挖掘是一种在数据上进行的分析形式,以发现新的模式和事实。这些事实被用来发现知识,也被认为是朝着数据库知识发现KDD)的一步。

通常结合人工智能、机器学习、统计学、数据库管理系统等各种过程和步骤来寻找新的模式。随着数据量和机器学习算法的增长,总是有发现数据库中新的或隐藏事实的趋势。发现或搜索到的事实和模式随后被用来预测特定结果,并且也可以应用于统计学、数据可视化、营销、管理、医学、决策系统等许多领域。

数据分析和数据挖掘经常被比较或并列讨论。数据挖掘被认为是数据分析过程的一部分。在进行数据分析时,我们需要一些预定义的假设,因为这是组织数据以开发模型并确定一些见解的过程。在应用实践方面,数据挖掘主要是针对结构化数据进行的,而数据分析可以针对结构化、非结构化或半结构化数据进行。

数据挖掘基于科学和数学方法,而数据分析使用分析模型和智能系统。从远处看,数据分析和数据挖掘都是数据科学的子集,数据挖掘实施预测算法来发现模式,而数据分析实施活动来从数据集中获得一些见解。

数据挖掘的一个主要好处是能够在短时间内处理大量数据。它还可以在新平台或现有平台上实施,预测隐藏的模式或帮助发现它们,帮助决策、知识发现等等。

数据挖掘的任务

一般来说,数据挖掘任务分为两种类型,也称为数据挖掘分析或数据挖掘建模。如下所示,两者都可以进一步分类:

  • 预测:

  • 分类

  • 回归

  • 预测

  • 描述性:

    • 聚类
  • 总结

  • 关联规则

预测

这使用统计分析将数据转化为有价值的信息。它预测可能发生情况的未来结果。通过分析当前和历史事实生成输出的与预测相关的技术属于这种模型。

分类

这是最常见的挖掘技术之一,在处理样本之前对其进行分类和归类以找到事实。有关分类和模型评估程序的更多信息,请参阅ML 算法类型部分。

回归

这种技术用于预测、预测和分析信息趋势和变量之间的关系。有关回归的更多信息,请参阅ML 算法类型部分。

预测

这种技术分析过去的事件,并通过使用其他数据挖掘技术(如聚类、分类等)的参考来预测可能缺失或未来的值。

描述性

也称为数据处理的初步阶段,它使用商业智能和许多其他系统。这种形式的分析是有限的,因为它只分析过去的数据,并且通常提供有关已经发生的事情的信息。

聚类

聚类是一种用于识别彼此相似的数据的技术。有关聚类的更多信息,请参阅ML 算法类型部分。

总结

这提供了数据集的更紧凑表示,并包括可视化和报告生成。大多数关于销售和营销的管理报告使用这种技术。

关联规则

有关关联的更多信息,请参阅ML 算法类型部分。

接下来是什么?

Web 抓取是动态的、要求高的,也是一项具有挑战性的任务。在进行此任务之前,我们需要遵守法律的角度,这是在网站的服务条款(ToS)和隐私政策中提出的。Python 编程,以其支持性、简单的语法、简短可读的代码形式以及库和工具的可用性,是用于 Web 抓取的最佳语言之一。

然而,挑战依然存在,通用脚本可能无法满足需求。有时,抓取任务可能需要大量资源,个人 PC 或笔记本电脑在考虑时间、机器资源等方面可能不值得实施。有许多功能和程序可以使抓取任务变得更加复杂和具有挑战性。让我们来看看其中一些:

  • 采用不断增长的基于网络的安全措施

  • 动态加载数据和脚本语言的参与使得抓取变得复杂

  • 存在 CAPTCHA,可以在www.captcha.net/找到

  • 阻止用户的 IP 地址(用于同时请求)

  • 阻止来自世界某些地区的请求(使用和切换代理可能会有所帮助)

对于这种情况,我们可以从正在进行与抓取相关工作的组织那里获得帮助。这些组织可以通过收取一定费用并为我们提供一个网络界面来帮助我们满足数据需求。这样的公司可以在谷歌上搜索“网络抓取服务”或“网络抓取软件”来寻找。还有各种基于浏览器的扩展程序可供搜索“抓取扩展”来找到。

总结

在本章中,我们探讨并学习了使用文件进行数据管理、分析和可视化的基本概念,使用了 pandas 和 matplotlib。我们还介绍了机器学习和数据挖掘,并探讨了一些相关资源,这些资源对进一步学习和职业发展可能有帮助。

通过本章,我们完成了本书!网络抓取是一个广泛的主题,直接或间接与许多技术和开发技术相关。在整本书中,我们通过使用 Python 编程语言学习了这一领域的许多概念。我们还可以探索与网络抓取相关的更多主题,如机器学习、数据挖掘、网络抓取、人工智能和 Python 编程。从知识和职业发展的角度来看,这些主题都值得探索。

进一步阅读

Logo

这里是“一人公司”的成长家园。我们提供从产品曝光、技术变现到法律财税的全栈内容,并连接云服务、办公空间等稀缺资源,助你专注创造,无忧运营。

更多推荐