Skip to content

Latest commit

 

History

History
848 lines (557 loc) · 45.4 KB

File metadata and controls

848 lines (557 loc) · 45.4 KB

二、Python 和 Web——使用urllib和 Requests

从上一章开始,我们现在了解了什么是网络抓取,现有的核心开发技术是什么,我们可以计划在哪里或如何找到我们正在寻找的信息。

Web 抓取需要使用脚本或程序来实现和部署工具和技术。Python 编程语言由一组庞大的库组成,这些库适合与 web 进行交互并用于抓取;我们还将探索和搜索要从 web 中提取的内容

本章还将详细介绍如何使用 Python 库,如requestsurllib

我们将特别了解以下主题:

  • 设置 Python 及其所需库requestsurllib以加载 URL
  • requestsurllib的详细概述
  • 实现 HTTP 方法(GET/POST

We assume that you have some prior basic experience of using the Python programming language. If not, then please refer to Python tutorials from W3schools (https://www.w3schools.com/python/default.asp), Python course (https://python-course.eu/), or search Google for learn Python programming.

技术要求

我们将使用已安装在 Windows 操作系统上的 Python 3.7.0。代码编辑器有很多选择;选择一个便于使用和处理本章代码示例中使用的库的库。我们将使用 PyCharm(社区版https://www.jetbrains.com/pycharm/download/download-thanks.html?platform=windows 来自 JetBrains 和 Python IDLE(的&代码=PCChttps://www.python.org/downloads/ )并排。

要按照本章操作,您需要安装以下应用程序:

本章所需的 Python 库如下:

  • requests
  • urllib

本章的代码文件可在 GitHub 上在线获取:https://github.com/PacktPublishing/Hands-On-Web-Scraping-with-Python/tree/master/Chapter02

用 Python 访问 web

Python 是一种编程语言,用于编写各种类型的应用程序,从简单脚本到 AI 算法和 web 框架。我们将用 Python 编写脚本,从数据提取或抓取的角度访问我们感兴趣的 URL。

有许多 Python 库用于 HTTP 通信和 web 相关用途(包括httpcookieliburllibrequestshtmlsocketjsonxmlrpchttplib2urllib3)。我们将探索并使用其中一些在 HTTP 访问或客户机-服务器通信方面受到程序员社区赞扬的方法。urllibrequestsPython 模块是我们感兴趣使用的模块。这些库具有各种功能,可用于使用 Python 与 web 进行通信,并处理 HTTP 请求和响应。

为了开始一些编码任务并直接探索基于 Python 的模块,在继续之前,让我们验证一下我们已经安装了所需的所有 Python 资源。

摆设

假设 Python 已经预装。如果没有,请访问https://www.python.org/downloads/https://www.python.org/download/other/ 获取适用于您的操作系统的最新 Python 版本。关于一般设置和安装程序,请访问https://realpython.com/installing-python/ 了解如何在您选择的平台上安装 Python。我们将在这里使用 Windows 操作系统。

为了验证我们是否拥有所有可用的必需工具,让我们检查一下 Python 和pip是否已安装并且是最新的。

pip package management system is used to install and manage software packages written in Python. More on installing Python packages and pip can be found at https://packaging.python.org/tutorials/installing-packages/.

我们将在 Windows 操作系统上使用 Python 3.7。按 Windows+R打开运行框,输入cmd进入命令行界面:

Opening the command-line interface on the Windows operating system

现在,移动到根目录并键入以下命令:

C:\> pythonversion
Python 3.7.0

前面的命令将为我们提供当前系统上的 Python 版本。让我们来了解一下我们正在使用的pip版本。以下命令将显示当前pip版本及其位置:

C:\> pip --version

pip 18.1 from c:\python37\lib\site-packages\pip (python 3.7)

我们很高兴在看到前面的答复后继续进行。如果您遇到一个错误,说明找不到应用程序或not recognized as an internal or external command,那么我们需要重新安装 Python 或检查安装过程中使用的驱动器是否正确

It's always advisable to check for the system and library version and keep them updated unless a specific version is required.

要将pip更新为其最新版本,请使用以下命令:

C:\> python -m pip install --upgrade pip

您可以从命令行或通过导入 Python IDE 并使用help()方法获取包的详细信息来验证我们希望使用的库,即requestsurllib

C:\> pip install requests

Requirement already satisfied: requests in c:\python37\lib\site-packages (2.19.1)

如前面的代码所示,我们正在尝试安装requests,但命令返回Requirement already satisfied。在安装新库之前,pip命令检查系统上是否存在现有安装

在下面的代码块中,我们将使用 Python IDE 导入urllib。我们将使用 Python 的内置help()方法查看其详细信息。

代码中的>>>符号表示 Python IDE 的使用;它接受代码或指令,并在下一行显示输出:

>>> import urllib 
>>> help(urllib) #display documentation available for urllib

以下是输出:

Help on package urllib:
NAME
 urllib
PACKAGE CONTENTS
 error
 parse
 request
 response
 robotparser
FILE
 c:\python37\lib\urllib\__init__.py

与前面的代码类似,让我们使用 Python IDE 导入requests

>>> import requests 
>>> requests.__version__ #display requests version 

'2.21.0'

>>> help(requests)   #display documentation available for requests

Help on package requests:
NAME
 requests
DESCRIPTION
 Requests HTTP Library
 ~~~~~~~~~~~~~~~~~~
 Requests is an HTTP library, written in Python, for human beings.

如果我们导入urllib requests而这些库不存在,结果将抛出错误:

ModuleNotFoundError: No module named 'requests'

对于缺失的模块或之前的情况,请先安装模块;按以下方式使用pip进行安装或升级。您可以从命令行安装它,如下所示:

C:\> pip install requests

您还可以使用--upgrade参数升级模块版本:

C:\> pip install requests -upgrade

加载 URL

现在我们已经确认了所需的库和系统需求,我们将继续加载 URL。在从 URL 查找内容时,还需要确认和验证为所需内容选择的确切 URL。内容可以在单个网页上找到,也可以分散在多个网页上,并且可能并不总是我们要查找的 HTML 源。

我们将加载一些 URL,并使用几个任务探索内容。

Before loading URLs using Python script, it's also advisable to verify the URLs are working properly and contain the detail we are looking for, using web browsers. Developer tools can also be used for similar scenarios, as discussed in Chapter 1Web Scraping Fundamentals, in the Developer tools section.

任务 1:查看维基百科中最受欢迎网站的列表相关数据。我们将从页面源中的站点、域和类型列中识别数据。

我们将按照以下链接中的步骤完成我们的任务(将在第 3 章中使用 LXML、XPath 和 CSS 选择器完成与数据提取相关的活动):https://en.wikipedia.org/wiki/List_of_most_popular_websites

在维基百科上搜索我们正在寻找的信息。可以在 web 浏览器中轻松查看前面的链接。内容是表格格式(如下面的屏幕截图所示),因此可以通过重复使用选择、复制和粘贴操作,或者通过收集表中的所有文本来收集数据。

但是,此类操作不会导致我们感兴趣的内容采用理想的格式,或者需要对文本执行额外的编辑和格式化任务,以实现理想的结果。我们对从浏览器获取的页面源也不感兴趣:

Page from Wikipedia, that is, https://en.wikipedia.org/wiki/List_of_most_popular_websites

完成包含所需内容的链接后,让我们使用 Python 加载该链接。我们正在向链接发出请求,并希望看到两个库返回的响应,即,urllibrequests

  1. 让我们使用urllib
>>> import urllib.request as req #import module request from urllib
>>> link = "https://en.wikipedia.org/wiki/List_of_most_popular_websites"
>>> response = req.urlopen(link)  #load the link using method urlopen()

>>> print(type(response))   #print type of response object
 <class 'http.client.HTTPResponse'>

>>> print(response.read()) #read response content
b'<!DOCTYPE html>\n<html class="client-nojs" lang="en" dir="ltr">\n<head>\n<meta charset="UTF-8"/>\n<title>List of most popular websites - Wikipedia</title>\n<script>…..,"wgCanonicalSpecialPageName":false,"wgNamespaceNumber":0,"wgPageName":"List_of_most_popular_websites","wgTitle":"List of most popular websites",……

来自urllib.requesturlopen()函数已通过所选 URL 或已向 URL 发出的请求,并收到response,即HTTPResponse。可以使用read()方法读取为请求接收的response

2.现在,让我们使用requests

>>> import requests
>>> link = "https://en.wikipedia.org/wiki/List_of_most_popular_websites"
>>> response = requests.get(link)

>>> print(type(response))
 <class 'requests.models.Response'>

>>> content = response.content #response content received
>>> print(content[0:150])  #print(content) printing first 150 character from content

b'<!DOCTYPE html>\n<html class="client-nojs" lang="en" dir="ltr">\n<head>\n<meta charset="UTF-8"/>\n<title>List of most popular websites - Wikipedia</title>'

在这里,我们使用requests模块加载页面源代码,就像我们使用urllib一样。requests使用get()方法,该方法接受 URL 作为参数。还检查了两个示例的response类型。

The output that's displayed in the preceding code blocks has been shortened. You can find the code files for this at https://github.com/PacktPublishing/Hands-On-Web-Scraping-with-Python.

在前面的示例中,页面内容或response对象包含我们要查找的详细信息,即站点、域和类型列。

我们可以选择任意一个库来处理 HTTP 请求和响应。关于这两个 Python 库及其示例的详细信息将在下一节URL 处理和使用 urllib 和请求的操作中提供。

让我们看一下以下屏幕截图:

Wikipedia.com page content, viewed using Python libraries

进一步的活动,如处理和解析,可以应用于这样的内容,以便提取所需的数据。有关进一步处理工具/技术和解析的更多详细信息,请参见第 3 章使用 LXML、XP**ath 和 CSS 选择器、第 4 章使用 pyquery 进行抓取–Python 库第 5 章使用 Scrapy an**d 靓汤爬取

任务 2:从加载并保存页面内容 https://www.samsclub.com/robots.txthttps://www.samsclub.com/sitemap.xml 使用urllibrequests

一般来说,网站在其根路径中提供文件(有关这些文件的更多信息,请参阅第 1 章网站抓取基础网站数据查找技术部分):

  • robots.txt:包含爬虫、web 代理等的信息

  • sitemap.xml:包含最近修改的文件、发布的文件等的链接

任务 1开始,我们能够加载 URL 并检索其内容。本任务将使用库方法和文件处理概念将内容保存到本地文件。将内容保存到本地文件并使用解析和遍历等任务处理内容可以非常快速,甚至可以减少网络资源:

  1. 加载并保存内容 https://www.samsclub.com/robots.txt 使用urllib
>>> import urllib.request 

>>> urllib.request.urlretrieve('https://www.samsclub.com/robots.txt')
('C:\\Users\\*****\AppData\\Local\\Temp\\tmpjs_cktnc', <http.client.HTTPMessage object at 0x04029110>)

>>> urllib.request.urlretrieve(link,"testrobots.txt") #urlretrieve(url, filename=None)
('testrobots.txt', <http.client.HTTPMessage object at 0x04322DF0>)

来自urllib.requesturlretrieve()函数,即urlretrieve(url, filename=None, reporthook=None, data=None)返回一个包含文件名和 HTTP 头的元组。如果没有给出路径,您可以在C:\\Users..Temp目录中找到该文件;否则,将在当前工作目录中生成文件,并将提供给urlretrieve()方法的名称作为第二个参数。这是前面代码中的testrobots.txt

>>> import urllib.request
>>> import os
>>> content = urllib.request.urlopen('https://www.samsclub.com/robots.txt').read() #reads robots.txt content from provided URL

>>> file = open(os.getcwd()+os.sep+"contents"+os.sep+"robots.txt","wb") #Creating a file robots.txt inside directory 'contents' that exist under current working directory (os.getcwd()) 

>>> file.write(content) #writing content to file robots.txt opened in line above. If the file doesn't exist inside directory 'contents', Python will throw exception "File not Found"

>>> file.close() #closes the file handle

在前面的代码中,我们正在读取 URL 并编写使用文件处理概念找到的内容

  1. 加载并保存内容 https://www.samsclub.com/sitemap.xml 使用requests
>>> link="https://www.samsclub.com/sitemap.xml"
>>> import requests
>>> content = requests.get(link).content
>>> content 

b'<?xml version="1.0" encoding="UTF-8"?>\n<sitemapindex >\n<sitemap><loc>https://www.samsclub.com/sitemap_categories.xml</loc></sitemap>\n<sitemap><loc>https://www.samsclub.com/sitemap_products_1.xml</loc></sitemap>\n<sitemap><loc>https://www.samsclub.com/sitemap_products_2.xml</loc></sitemap>\n<sitemap><loc>https://www.samsclub.com/sitemap_locators.xml</loc></sitemap>\n</sitemapindex>'

>>> file = open(os.getcwd()+os.sep+"contents"+os.sep+"sitemap.xml","wb") #Creating a file robots.txt inside directory 'contents' that exist under current working directory (os.getcwd()) 

>>> file.write(content) #writing content to file robots.txt opened in line above. If the file doesn't exist inside directory 'contents', Python will throw exception "File not Found"

>>> file.close() #closes the file handle

在这两种情况下,我们都能够从各自的 URL 中找到内容,并将其保存到各个文件和位置。前面代码中的内容被发现为字节文本,例如,b'<!DOCTYPE …b'<?xml。页面内容也可以以文本格式检索,例如requests.get(link).text

我们可以使用decode()方法将字节转换为字符串,使用encode()方法将字符串转换为字节,如下代码所示:

>>> link="https://www.samsclub.com/sitemap.xml"
>>> import requests
>>> content = requests.get(link).text  #using 'text'
>>> content

'<?xml version="1.0" encoding="UTF-8"?>\n<sitemapindex >\n<sitemap><loc>https://www.samsclub.com/sitemap_categories.xml</loc></sitemap>\n<sitemap><loc>https://www.samsclub.com/sitemap_products_1.xml</loc></sitemap>\n<sitemap><loc>https://www.samsclub.com/sitemap_products_2.xml</loc></sitemap>\n<sitemap><loc>https://www.samsclub.com/sitemap_locators.xml</loc></sitemap>\n</sitemapindex>' >>> content = requests.get(link).content 
>>> content.decode() # decoding 'content' , decode('utf-8')

'<?xml version="1.0" encoding="UTF-8"?>\n<sitemapindex >\n<sitemap><loc>https://www.samsclub.com/sitemap_categories.xml</loc></sitemap>\n<sitemap><loc>https://www.samsclub.com/sitemap_products_1.xml</loc></sitemap>\n<sitemap><loc>https://www.samsclub.com/sitemap_products_2.xml</loc></sitemap>\n<sitemap><loc>https://www.samsclub.com/sitemap_locators.xml</loc></sitemap>\n</sitemapindex>'

在处理各种域和类型的文档时,识别适当的字符集或charset非常重要。为了确定正确的charset编码类型,我们可以使用content-typecharset向页面源寻求<meta>标记的帮助。

具有charset属性的<meta>标记,即<meta charset="utf-8"/>,是从页面源中识别出来的,如以下截图(或<meta http-equiv="content-type" content="text/html; charset=utf-8">所示:

Identifying charset from the document response or page source

此外, < meta   http-equiv ="content-type"  content="text/html; charset=utf-8">的内容可以从响应标题中获得,如以下屏幕截图中突出显示的:

I dentifying charset through the browser DevTools, Network panel, Headers tab, and response headers

使用 Python 代码,我们可以在 HTTP 头中找到charset

>>> import urllib.request
>>> someRequest = urllib.request.urlopen(URL) #load/Open the URL
>>> urllib.request.getheaders() #Lists all HTTP headers. 

>>> urllib.request.getheader("Content-Type") #return value of header 'Content-Type'

'text/html; charset=ISO-8859-1' or 'utf-8'

已识别的charset将用于编码和解码requests.get(link).content.decode('utf-8')

Python 3.0 uses the concepts of text and (binary) data instead of Unicode strings and 8-bit strings. All text is Unicode; however, encoded Unicode is represented as binary data. The type that's used to hold text is str (https://docs.python.org/3/library/stdtypes.html#str), and the type that's used to hold data is bytes (https://docs.python.org/3/library/stdtypes.html#bytes). For more information on Python 3.0, please visit https://docs.python.org/3/whatsnew/3.0.html.

在本节中,我们设置并验证了我们的技术需求,还探讨了 URL 加载和内容查看。在下一节中,我们将探索 Python 库,以找到一些有用的函数及其属性。

URL 处理和使用 urllib 和请求的操作

对于我们从网页中提取数据的主要动机,有必要使用 URL。在我们迄今为止看到的示例中,我们注意到 Python 使用了一些非常简单的 URL 来与其源代码或内容进行通信。web 抓取过程通常需要使用来自不同域的不同 URL,这些域不存在相同的格式或模式。

开发人员还可能面临许多情况,需要对 URL 进行操作(更改、清理),以快速方便地访问资源。URL 处理和操作用于设置、更改查询参数或清除不必要的参数。它还通过适当的值传递所需的请求头,并标识发出请求的适当 HTTP 方法。在许多情况下,您会发现使用浏览器 DevTools 或网络面板识别的 URL 相关操作。

我们将在本书中使用的urllib requestsPython 库处理 URL 和基于网络的客户机-服务器通信。这些库提供了各种易于使用的函数和属性,我们将探讨一些重要的函数和属性。

urllib

urllib库是一个标准的 Python 包,它收集了几个模块,用于处理与 HTTP 相关的通信模型。urllib 中的模块是专门设计的,包含处理各种类型的客户机-服务器通信的函数和类

Similarly named packages also exist, like urllib2, an extensible library, and urllib3, a powerful HTTP client that addresses missing features from Python standard libraries.

处理 URL 请求和响应的两个最重要的urllib模块如下所示。我们将在本章和后续章节中使用这些模块:

  • urllib.request:用于打开和读取 URL,请求或访问网络资源(cookie、身份验证等)
  • urllib.response:此模块用于对生成的请求提供响应

存在许多函数和公共属性来处理与 HTTP 请求相关的请求信息和处理响应数据,例如urlopen()urlretrieve()getcode()getheaders()getheader()geturl()read()readline()等等

我们可以使用 Python 内置的dir()函数来显示模块的内容,比如它的类、函数和一个属性,如下代码所示:

>>> import urllib.request
>>> dir(urllib.request) #list features available from urllib.request

['AbstractBasicAuthHandler', 'AbstractDigestAuthHandler', 'AbstractHTTPHandler', 'BaseHandler', 'CacheFTPHandler', 'ContentTooShortError', 'DataHandler', 'FTPHandler', 'FancyURLopener', 'FileHandler', 'HTTPBasicAuthHandler', 'HTTPCookieProcessor',....'Request', 'URLError', 'URLopener',......'pathname2url', 'posixpath', 'proxy_bypass', 'proxy_bypass_environment', 'proxy_bypass_registry', 'quote', 're', 'request_host', 'socket', 'splitattr', 'splithost', 'splitpasswd', 'splitport', 'splitquery', 'splittag', 'splittype', 'splituser', 'splitvalue', 'ssl', 'string', 'sys', 'tempfile', 'thishost', 'time', 'to_bytes', 'unquote', 'unquote_to_bytes', 'unwrap', 'url2pathname', 'urlcleanup', 'urljoin', 'urlopen', 'urlparse', 'urlretrieve', 'urlsplit', 'urlunparse', 'warnings']

urlopen()函数接受 URL 或urllib.request.Request对象,如requestObj,并通过urllib.response``read()函数返回响应,如下代码所示:

>>> import urllib.request
>>> link='https://www.google.com' [](https://www.google.com) 
>>> linkRequest = urllib.request.urlopen(link) #open link
>>> print(type(linkRequest)) #object type
 <class 'http.client.HTTPResponse'> [](https://www.google.com) 
>>> linkResponse = urllib.request.urlopen(link).read() #open link and read content
>>> print(type(linkResponse))
 <class 'bytes'>
 [](https://www.google.com) >>> requestObj = urllib.request.Request('https:/www.samsclub.com/robots.txt')
>>> print(type(requestObj)) #object type
 <class 'urllib.request.Request'>

>>> requestObjResponse = urllib.request.urlopen(requestObj).read()
>>> print(type(requestObjResponse))  #object type
 <class 'bytes'>

linkRequestrequestObj的情况下,返回的对象类型分别与urlopen()函数和类请求不同。还创建了linkResponserequestObjResponse对象,它们保存read()函数的urllib.response信息

Generally, urlopen() is used to read a response from the URL, while urllib.request.Request is used to send extra arguments like data or headers, and even to specify the HTTP method and retrieve a response. It can be used as follows:

urllib.request.Request(url, data=None, headers={}, origin_req_host=None, unverifiable=False, method=None)

urllib.response及其功能,如read()readline()urllib.request对象一起使用。

如果请求成功并收到来自正确 URL 的响应,我们可以检查 HTTP 状态代码、使用的 HTTP 方法以及返回的 URL 以查看描述:

  • getcode()返回 HTTP 状态码。使用codestatus公共属性也可以获得相同的结果,如下代码所示:
>>> linkRequest.getcode()  #can also be used as: linkRequest.code or linkRequest.status 

 200
  • geturl()返回当前 URL。有时,验证是否发生了任何重定向是很方便的。url属性可用于类似目的:
>>> linkRequest.geturl()   # can also be used as: linkRequest.url

 'https://www.google.com'
  • **_method**返回 HTTP 方法;GET是默认响应:
>>> linkRequest._method 
'GET'
  • getheaders()返回包含 HTTP 头的元组列表。从以下代码可以看出,我们可以从输出中确定有关 cookie、内容类型、日期等的值:
>>> linkRequest.getheaders()

[('Date','Sun, 30 Dec 2018 07:00:25 GMT'),('Expires', '-1'),('Cache-Control','private, max-age=0'),('Content-Type','text/html; charset=ISO-8859-1'),('P3P', 'CP="This is not a P3P policy! See g.co/p3phelp for more info."'),('Server', 'gws'),('X-XSS-Protection', '1; mode=block'),('X-Frame-Options','SAMEORIGIN'),('Set-Cookie', '1P_JAR=…..; expires=Tue, 29-Jan-2019 07:00:25 GMT; path=/; domain=.google.com'),('Set-Cookie 'NID=152=DANr9NtDzU_glKFRgVsOm2eJQpyLijpRav7OAAd97QXGX6WwYMC59dDPe.; expires=Mon, 01-Jul-2019 07:00:25 GMT; path=/; domain=.google.com; HttpOnly'),('Alt-Svc', 'quic=":443"; ma=2592000; v="44,43,39,35"'),('Accept-Ranges', 'none'),('Vary', 'Accept-Encoding'),('Connection', 'close')] 
  • getheader()与所需的头元素一起传递时,也可以检索单个基于请求的头,如下代码所示。在这里,我们可以看到我们可以获得 Content-Type 头的值。同样的结果也可以通过info()功能实现:
>>> linkRequest.getheader("Content-Type") 

 'text/html; charset=ISO-8859-1'

>>> linkRequest.info()["content-type"]

 'text/html; charset=ISO-8859-1'

我们使用了代码块,找到了与请求和响应相关的输出。Web 浏览器还允许我们使用浏览器开发工具(基于浏览器的开发工具)跟踪请求/响应相关信息。

以下屏幕截图显示网络面板和文档选项卡,其中包括标题选项。它包含各种部分,例如常规、响应头和请求头。与请求和响应相关的基本信息可在 Headers 选项中找到:

Network panel and Document tab with General and Request header information

urllib.error处理urllib.request提出的例外情况。可以针对请求引发异常,如URLErrorHTTPError,下面的代码演示了urllib.error的用法:

Exception handling deals with error handling and management in programming. Code that uses exception handling is also considered an effective technique and is often prescribed to adapt.

>>> import urllib.request as request
>>> import urllib.error as error

>>> try:  #attempting an error case
 request.urlopen("https://www.python.ogr") #wrong URL is passed to urlopen()
 except error.URLError as e:
 print("Error Occurred: ",e.reason)

Error Occurred: [Errno 11001] getaddrinfo failed #output

urllib.parse用于对请求(数据)或链接进行编码/解码,添加/更新标题,分析、解析和操作 URL。解析后的 URL 字符串或对象使用urllib.request进行处理。

此外,urlencode()urlparse()urljoin()urlsplit()quote_plus()urllib.parse中提供的几个重要功能,如下代码所示:

>>> import urllib.parse as urlparse
>>> print(dir(urlparse)) #listing features from urlparse

我们得到以下输出:

['DefragResult', 'DefragResultBytes', 'MAX_CACHE_SIZE', 'ParseResult', 'ParseResultBytes', 'Quoter', 'ResultBase', 'SplitResult', 'SplitResultBytes', .........'clear_cache', 'collections', 'namedtuple', 'non_hierarchical', 'parse_qs', 'parse_qsl', 'quote', 'quote_from_bytes', 'quote_plus', 're', 'scheme_chars', 'splitattr', 'splithost', 'splitnport', 'splitpasswd', 'splitport', 'splitquery', 'splittag', 'splittype', 'splituser', 'splitvalue', 'sys', 'to_bytes', 'unquote', 'unquote_plus', 'unquote_to_bytes', 'unwrap', 'urldefrag', 'urlencode', 'urljoin', 'urlparse', 'urlsplit', 'urlunparse', 'urlunsplit', 'uses_fragment', 'uses_netloc', 'uses_params', 'uses_query', 'uses_relative']

来自urllib.parseurlsplit()函数拆分传递到namedtuple对象的 URL。元组中的每个名称都标识 URL 的一部分。这些部分可以在其他变量中分离和检索,并根据需要使用。以下代码为amazonUrl实现urlsplit()

>>> amazonUrl ='https://www.amazon.com/s/ref=nb_sb_noss?url=search-alias%3Dstripbooks-intl-ship&field-keywords=Packt+Books'

>>> print(urlparse.urlsplit(amazonUrl)) #split amazonURL
SplitResult(scheme='https', netloc='www.amazon.com', path='/s/ref=nb_sb_noss', query='url=search-alias%3Dstripbooks-intl-ship&field-keywords=Packt+Books', fragment='')

>>> print(urlparse.urlsplit(amazonUrl).query) #query-string from amazonURL
'url=search-alias%3Dstripbooks-intl-ship&field-keywords=Packt+Books'

>>> print(urlparse.urlsplit(amazonUrl).scheme) #return URL scheme
'https'

使用urllib.parse中的urlparse()函数生成ParseResult对象。在 URL 中检索的参数(paramspathurlsplit()相比有所不同。以下代码打印来自urlparse()的对象:

>>> print(urlparse.urlparse(amazonUrl)) #parsing components of amazonUrl

 ParseResult(scheme='https', netloc='www.amazon.com', path='/s/ref=nb_sb_noss', params='', query='url=search-alias%3Dstripbooks-intl-ship&field-keywords=Packt+Books', fragment='')

让我们确认一下urlparse()urlsplit()之间的区别。创建的localUrl同时使用urlsplit()urlparse()进行解析。params仅适用于urlparse()

import urllib.parse as urlparse
>>> localUrl= 'http://localhost/programming/books;2018?browse=yes&sort=ASC#footer'

>>> print(urlparse.urlsplit(localUrl))
SplitResult(scheme='http', netloc='localhost', path='/programming/books;2018', query='browse=yes&sort=ASC', fragment='footer')

>>> parseLink = urlparse.urlparse(localUrl)
ParseResult(scheme='http', netloc='localhost', path='/programming/books', params='2018', query='browse=yes&sort=ASC', fragment='footer')

>>> print(parseLink.path) #path without domain information
 '/programming/books'

>>> print(parseLink.params) #parameters 
 '2018'

>>> print(parseLink.fragment) #fragment information from URL
 'footer'

基本上,urllib.request.Request接受数据和头相关信息,可以使用add_header()*将headers分配给对象;*例如object.add_header('host','hostname')object.add_header('referer','refererUrl')

为了请求data,需要将Query InformationURL arguments用作附加到所需 URL 的信息的键值对。这样的 URL 通常通过 HTTP GET 方法处理。传递给请求对象的查询信息应使用urlencode()编码

urlencode()确保参数符合 W3C 标准并被服务器接受。parse_qs()将百分比编码的查询字符串解析到 Python 字典。下面的代码演示了使用urlencode()的示例:

>>> import urllib.parse as urlparse
>>> data = {'param1': 'value1', 'param2': 'value2'}

>>> urlparse.urlencode(data)
 'param1=value1&param2=value2'

>>> urlparse.parse_qs(urlparse.urlencode(data))
 {'param1': ['value1'], 'param2': ['value2']}

>>> urlparse.urlencode(data).encode('utf-8')
 b'param1=value1&param2=value2'

在处理对服务器的请求之前,您可能还需要对 URL 中的特殊字符进行编码:

注意,urllib.parse包含quote()quote_plus()unquote()功能,允许无错误的服务器请求:

  • quote()通常应用于 URL 路径(以urlsplit()urlparse()列出),或在传递给urlencode()之前使用保留字符和特殊字符(由 RFC 3986 定义)进行查询,以确保服务器的可接受性。默认编码使用UTF-8完成
  • quote_plus()还编码特殊字符、空格和 URL 分隔符/
  • unquote()unquote_plus()用于还原使用quote()quote_plus()应用的编码。

以下代码演示了这些功能:

>>> import urllib.parse as urlparse
>>> url="http://localhost:8080/~cache/data file?id=1345322&display=yes&expiry=false"

>>> urlparse.quote(url) 
 'http%3A//localhost%3A8080/~cache/data%20file%3Fid%3D1345322%26display%3Dyes%26expiry%3Dfalse'

>>> urlparse.unquote(url)
 'http://localhost:8080/~cache/data file?id=1345322&display=yes&expiry=false'

>>> urlparse.quote_plus(url) 'http%3A%2F%2Flocalhost%3A8080%2F~cache%2Fdata+file%3Fid%3D1345322%26display%3Dyes%26expiry%3Dfalse' 

>>> urlparse.unquote_plus(url)
 'http://localhost:8080/~cache/data file?id=1345322&display=yes&expiry=false'

来自urllib.parseurljoin()函数有助于从提供的参数中获取 URL,如下代码所示:

>>> import urllib.parse as urlparse

>>> urlparse.urljoin('http://localhost:8080/~cache/','data file') #creating URL
 'http://localhost:8080/~cache/data file'

>>> urlparse.urljoin('http://localhost:8080/~cache/data file/','id=1345322&display=yes')
 'http://localhost:8080/~cache/data file/id=1345322&display=yes'

顾名思义,urllib.robotparser有助于解析robots.txt并识别基于代理的规则。有关robots.txt的更多详细信息,请参考第 1 章网页抓取基础网页数据查找技术部分。

我们可以在下面的代码中看到,parRobotFileParser的对象,可以通过set_url()函数设置 URL。还可以通过read()功能读取内容。诸如can_fetch()之类的函数可以为评估的条件返回布尔值:

>>> import urllib.robotparser as robot
>>> par = robot.RobotFileParser()
>>> par.set_url('https://www.samsclub.com/robots.txt') #setting robots URL
>>> par.read()  #reading URL content

>>> print(par)
User-agent: *
Allow: /sams/account/signin/createSession.jsp
Disallow: /cgi-bin/
Disallow: /sams/checkout/
Disallow: /sams/account/
Disallow: /sams/cart/
Disallow: /sams/eValues/clubInsiderOffers.jsp
Disallow: /friend
Allow: /sams/account/referal/

>>> par.can_fetch('*','https://www.samsclub.com/category') #verify if URL is 'Allow' to Crawlers 
True

>>> par.can_fetch('*','https://www.samsclub.com/friend')
False

我们可以看到,https://www.samsclub.com/friend在通过can_fetch()函数传递时返回False,从而满足robots.txt中的Disallow: /friend指令。类似地,https://www.samsclub.com/category返回True,因为没有列出限制类别 URL 的指令

但是,使用urllib.request有一些限制。使用urlopen()urlretrieve()等函数时,可能会出现基于连接的延迟。这些函数返回原始数据,需要将其转换为解析器所需的类型,然后才能在刮取过程中使用。

Deploying threads, or threading, is considered an effective technique when dealing with HTTP requests and responses.

请求

requestsHTTP Python 库于 2011 年发布,是近期开发人员最著名的 HTTP 库之一。

Requests 是一个优雅而简单的 Python HTTP 库,为人类构建。(来源:https://2.python-requests.org/en/master/ )。

有关requests的更多信息,请访问http://docs.python-requests.org/en/master/

与 Python 中的其他 HTTP 库相比,requests在使用 HTTP 的功能方面得到了高度评价。它的一些功能如下:

  • 简短、简单且可读的函数和属性

  • 访问各种 HTTP 方法(GET、POST 和 PUT 等)

  • 摆脱手动操作,如编码表单值

  • 处理查询字符串

  • 自定义标题

  • 会话和 cookie 处理

  • 处理 JSON 请求和内容

  • 代理设置

  • 部署编码和法规遵从性

  • 基于 API 的链接头

  • 原始套接字响应

  • 超时和更多

我们将使用requests库并访问它的一些属性。来自requestsget()函数用于向提供的 URL 发送 GET HTTP 请求。返回的对象为requests.model.Response类型,如下代码所示:

>>> import requests
>>> link="http://www.python-requests.org"
>>> r = requests.get(link)

>>> dir(r)
['__attrs__', '__bool__', '__class__'......'_content', '_content_consumed', '_next', 'apparent_encoding', 'close', 'connection', 'content', 'cookies', 'elapsed', 'encoding', 'headers', 'history', 'is_permanent_redirect', 'is_redirect', 'iter_content', 'iter_lines', 'json', 'links', 'next', 'ok', 'raise_for_status', 'raw', 'reason', 'request', 'status_code', 'text', 'url']

>>> print(type(r)) 
<class 'requests.models.Response'>

requests库还分别使用put()post()delete()head()options()方法支持PUTPOSTDELETEHEADOPTIONS等 HTTP 请求。

以下是一些requests属性,并对每个属性进行了简要说明:

  • url输出当前 URL
  • 使用status_code找到 HTTP 状态码
  • history用于跟踪重定向:
>>> r.url #URL of response object`
 'http://www.python-requests.org/en/master/'

>>> r.status_code #status code
 200

>>> r.history #status code of history event
 [<Response [302]>]

我们还可以获得使用开发人员工具时发现的一些详细信息,如 HTTP 头、编码等:

  • headers返回与响应相关的 HTTP 头
  • requests.header返回与请求相关的 HTTP 头
  • encoding显示从内容中获取的charset
>>> r.headers #response headers with information about server, date.. 
{'Transfer-Encoding': 'chunked', 'Content-Type': 'text/html', 'Content-Encoding': 'gzip', 'Last-Modified': '....'Vary': 'Accept-Encoding', 'Server': 'nginx/1.14.0 (Ubuntu)', 'X-Cname-TryFiles': 'True', 'X-Served': 'Nginx', 'X-Deity': 'web02', 'Date': 'Tue, 01 Jan 2019 12:07:28 GMT'}

>>> r.headers['Content-Type'] #specific header Content-Type
 'text/html'

>>> r.request.headers  #Request headers 
{'User-Agent': 'python-requests/2.21.0', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive'}

>>> r.encoding  #response encoding
 'ISO-8859-1'

页面或响应内容可以使用content以字节为单位检索,而text返回str字符串:

>>> r.content[0:400]  #400 bytes characters

b'\n<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"\n ....... <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\n <title>Requests: HTTP for Humans\xe2\x84\xa2 — Requests 2.21.0 documentation'

>>> r.text[0:400]  #sub string that is 400 string character from response

'\n<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"\n......\n <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\n <title>Requests: HTTP for Humansâ\x84¢ — Requests 2.21.0 documentation'

此外,requests还通过在get()请求中使用stream参数从服务器返回raw套接字响应。我们可以使用raw.read()函数读取原始响应:

>>> r = requests.get(link,stream=True) #raw response

>>> print(type(r.raw))   #type of raw response obtained
 <class 'urllib3.response.HTTPResponse'>

>>> r.raw.read(100)  #read first 100 character from raw response
 b"\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03\xed}[o\xdcH\x96\xe6{\xfe\x8a\xa8\xd4\xb4%O\x8bL2/JI\x96\xb2Z\x96e[U\xbe\xa8-\xb9\xaa\x1b\x85^!\x92\x8c\xcc\xa4\xc5$Y\xbc(\x95\xae)\xa0\x1e\x06\x18\xcc\xf3\xce\xcb\x00\xbbX`\x16\xd8\xc7\xc5>\xed\xeb\x02\xfb3f_\x16\xf5\x0b\xf6'\xec9'\x82\x97\xbc\xc9\xb2+#g"

A raw response that's received using the raw attribute is raw bytes of characters that haven't been transformed or automatically decoded.

requests通过内置解码器非常有效地处理 JSON 数据。如我们所见,包含 JSON 内容的 URL 可以通过requests解析并根据需要使用:

>>> import requests
>>> link = "https://feeds.citibikenyc.com/stations/stations.json"
>>> response = requests.get(link).json()

>>> for i in range(10): #read 10 stationName from JSON response.
 print('Station ',response['stationBeanList'][i]['stationName'])

Station W 52 St & 11 Ave
Station Franklin St & W Broadway
Station St James Pl & Pearl St
........
Station Clinton St & Joralemon St
Station Nassau St & Navy St
Station Hudson St & Reade St

注意,requestsurllib3用于会话和原始套接字响应。在撰写本文时,requests版本 2.21.0 可用。

对脚本进行爬网可能会使用任何提到的或可用的 HTTP 库来进行基于 web 的通信。大多数情况下,来自多个库的函数和属性将使此任务变得简单。在下一节中,我们将使用requests库来实现 HTTP(GET/POST方法。

实现 HTTP 方法

一般来说,网页与用户或读者之间基于网络的交互或交流是通过以下方式实现的:

  • 用户或读者可以访问网页,阅读或浏览呈现给他们的信息
  • 用户或读者还可以使用 HTML 表单向网页提交某些信息,例如通过搜索、登录、用户注册、密码恢复等

在本节中,我们将使用requestsPython 库来实现常见的 HTTP 方法(GETPOST,这些方法执行前面列出的基于 HTTP 的通信场景。

收到

请求信息的命令方式是使用安全方法,因为资源状态没有改变。GET参数,也称为查询字符串,在 URL 中可见。它们使用?附加到 URL,并以key=value对的形式提供。

通常,没有任何指定 HTTP 方法的已处理 URL 通常是 GET 请求。使用 GET 发出的请求可以缓存并添加书签。在发出GET请求时也有长度限制。以下是一些 URL 示例:

在前面的部分中,对正常的 URL 进行了请求,例如robots.txtsitemap.xml,这两种 URL 都使用 HTTPGET方法。来自requestsget()函数接受 URL、参数和标题:

import requests
link="http://localhost:8080/~cache"

queries= {'id':'123456','display':'yes'}

addedheaders={'    user-agent    ':''}

#request made with parameters and headers
r = requests.get(link, params=queries, headers=addedheaders) 
print(r.url)

这是前面代码的输出:

http://localhst:8080/~cache?id=123456+display=yes

邮递

这些被称为向源发出的安全请求。可以更改请求的资源状态。发布或发送到请求 URL 的数据在 URL 中不可见;相反,它被传输到请求主体。使用POST发出的请求不会被缓存或添加书签,并且在长度方面没有限制。

在下面的示例中,一个简单的 HTTP 请求和响应服务来源:http://httpbin.org/ 已用于发出POST请求。

pageUrl接受paramspostUrl中定义的待过账数据。自定义标题被指定为headers。来自requests库的post()函数接受 URL、数据和标题,并以 JSON 格式返回响应:

    import         requests
        pageUrl="http://httpbin.org/forms/post"
postUrl="http://httpbin.org/post"

params         =         {        'custname'        :        'Mr. ABC'        ,        'custtel'        :        ''        ,        'custemail'        :        'abc@somedomain.com'        ,        'size'        :        'small'        ,
        'topping'        :[        'cheese'        ,        'mushroom'        ],        'delivery'        :        '13:00'        ,        'comments'        :        'None'        }

        headers=        {         'Accept'        :        'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8'        ,        'Content-Type'        :        'application/x-www-form-urlencoded'        ,
        'Referer'        :        pageUrl
        }

#making POST request to postUrl with params and request headers, response will be read as JSON
        response         =         requests.post(postUrl        ,data        =        params,headers        =        headers).json()
print(response)    

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

 {
'args': {}, 
'data': '', 
'files': {}, 
'form': {
'comments': 'None', 
'custemail': 'abc@somedomain.com',
'custname': 'Mr. ABC', 
'custtel': '',
'delivery': '13:00', 
'size': 'small', 
'topping': ['cheese', 'mushroom']
}, 
'headers': {    'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'Accept-Encoding': 'gzip, deflate', 
'Connection': 'close', 
'Content-Length': '130', 
'Content-Type': 'application/x-www-form-urlencoded', 
'Host': 'httpbin.org', 
'Referer': 'http://httpbin.org/forms/post', 
'User-Agent': 'python-requests/2.21.0'
}, 
'json': None, 'origin': '202.51.76.90', 
'url': 'http://httpbin.org/post'
} 

对于我们尝试的POST请求,我们可以使用 DevTools 网络面板找到有关请求头、响应头、HTTP 状态和POST数据(参数)的详细信息,如以下屏幕截图所示:

POST data submitted and found as form data in the DevTools Network panel It's always beneficial to learn and detect the request and response sequences that are made with URLs through the browser and the available DevTools .

总结

在本章中,我们学习了如何使用 Python 库向 web 资源发出请求并收集返回的响应。本章的主要目的是演示通过urllibrequestsPython 库提供的核心功能,以及探索各种格式的页面内容。

在下一章中,我们将学习并使用一些技术从 web 内容中识别和提取数据。

进一步阅读