Python爬虫学习笔记(二)—— urllib库的使用

178 阅读22分钟

urllib,是一个Python库,利用它就可以实现HTTP请求的发送,而且不需要关心 HTTP 协议本身甚至更底层的实现,我们只需指定请求的URL、请求头、请求体等信息。

此外urllib还 可以把服务器返回的响应转化为Python对象,我们通过该对象便可以方便地获取响应的相关信息,如响应状态码、响应头、响应体等。 urllib库是Python内置的HTTP请求库,也就是说不需要额外安装,可直接使用。urllib 库包含如下 4个模块:

  • request:这是最基本的 HTTP请求模块,可以模拟请求的发送。就像在浏览器里输入网址然 后按下回车一样,只需要给库方法传入URL以及额外的参数,就可以模拟实现发送请求的过程了。
  • error:异常处理模块。如果出现请求异常,我们可以捕获这些异常,然后进行重试或其他操作以保证程序运行不会意外终止。
  • parse:一个工具模块。提供了许多URL的处理方法,例如拆分、解析、合并等。
  • robotparser;主要用来识别网站的 robots.txt 文件,然后判断哪些网站可以爬,哪些网站不可以(用得比较少)。

发送请求

使用urllib库的 request 模块,可以方便地发送请求并得到响应。

urlopen

urllib.request 模块提供了最基本的构造 HTTP 请求的方法,利用这个模块可以模拟浏览器的请 求发起过程,同时它还具有处理授权验证(Authentication)、重定向(Redirection)、浏览器 Cookie 以及其他一些功能。

示例:抓取Python官网:

import urllib.request

response = urllib.request.urlopen("https://www.python.org")
print(response.read().decode('utf-8'))

运行此代码会出现一个 UnicodeDecodeError 错误(运行环境:VSCODE): (可是用pycharm就能正常运行?)

UnicodeDecodeError: 'utf-8' codec can't decode byte 0x8b in position 1: invalid start byte

这段代码会检查网页的 Content-Encoding 头信息,如果是gzip压缩的话,就使用gzip库来解压缩,然后再解码为UTF-8编码的字符串。这样就可以避免 UnicodeDecodeError 错误。

import urllib.request
import gzip
from io import BytesIO

response = urllib.request.urlopen("https://www.python.org")
if response.info().get('Content-Encoding') == 'gzip':
    buf = BytesIO(response.read())
    f = gzip.GzipFile(fileobj=buf)
    html = f.read().decode('utf-8')
else:
    html = response.read().decode('utf-8')

print(html)

最终会打印出Python官网的HTML文件

接下来,看看返回的响应到底是什么。利用 type 方法输出响应的类型:

import urllib.request

response = urllib.request.urlopen("https://www.python.org")
print(type(response))

输出结果:

<class 'http.client.HTTPResponse'>

可以看出,响应是一个 HTTPResposne类型的对象,主要包含 read、readinto、getheader、getheaders、fileno 等方法,以及msg、version,status、reason、debuglevel、closed 等属性。

得到响应之后,我们把它赋值给 response变量,然后就可以调用上述那些方法和属性,得到返回 结果的一系列信息了。 例如,调用 read方法可以得到响应的网页内容、调用 status 属性可以得到响应结果的状态码 (200 代表请求成功,404 代表网页未找到等)。

import urllib.request

response = urllib.request.urlopen("https://www.python.org")
print(response.status)
print(response.getheaders())
print(response.getheader('Server'))

输出结果:

200
[('Connection', 'close'), ('Content-Length', '50566'), ('Content-Type', 'text/html; charset=utf-8'), ('X-Frame-Options', 'SAMEORIGIN'), ('Via', '1.1 varnish, 1.1 varnish'), ('Accept-Ranges', 'bytes'), ('Date', 'Thu, 11 Apr 2024 14:55:05 GMT'), ('Age', '2905'), ('X-Served-By', 'cache-iad-kiad7000025-IAD, cache-nrt-rjtf7700021-NRT'), ('X-Cache', 'HIT, HIT'), ('X-Cache-Hits', '6, 502'), ('X-Timer', 'S1712847305.294895,VS0,VE0'), ('Vary', 'Cookie'), ('Strict-Transport-Security', 'max-age=63072000; includeSubDomains; preload')]
None

其中前两个输出分别是响应的状态码和响应的头信息;最后一个输出是调用getheader 方法,并传入参数 Server,获取了响应头中 Server 的值,但结果是None,可能是header被隐藏了。

urlopen 方法的API:

urllib.request.urlopen(url, data=None,[timeout,]*, cafile=None, capath-None, cadefault=False, context-None)

data参数

data参数是可选的。在添加该参数时,需要使用bytes方法将参数转化为字节流编码格式的内容,即 bytes类型。另外,如果传递了这个参数,就意味着它的请求方式不再是GET,而是POST了。

import urllib.request
import urllib.parse

data = bytes(urllib.parse.urlencode({'name':'germey'}),encoding='utf-8')
response = urllib.request.urlopen("https://www.httpbin.org/post",data=data)
print(response.read().decode('utf-8'))

这里我们传递了一个参数name,值是germey,需要将它转码成bytes类型。转码时采用了bytes方法,该方法的第一个参数得是str(字符串)类型,因此用urllib.parse 模块里的urlencode 方法将字典参数转化为字符串;第二个参数用于指定编码格式,这里指定为utf-8。

此处我们请求的站点是 <www.httpbin.org>,它可以提供HTTP请求测试。本次我们请求的 URL为 www.httpbin.org/post ,这个链接可以用来测试 POST 请求,能够输出请求的一些信息,其中就包含我们传递的 data 参数。

输出结果:

{
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {
    "name": "germey"
  }, 
  "headers": {
    "Accept-Encoding": "identity", 
    "Content-Length": "11", 
    "Content-Type": "application/x-www-form-urlencoded", 
    "Host": "www.httpbin.org", 
    "User-Agent": "Python-urllib/3.10", 
    "X-Amzn-Trace-Id": "Root=1-66180102-6c01f7d156c1ee460f755eba"
  }, 
  "json": null, 
  "origin": "222.85.167.205", 
  "url": "https://www.httpbin.org/post"
}

可以发现我们传递的参数出现在了form字段中,这表明是模拟表单提交,以POST方式传输数据。

timeout参数

timeout 参数用于设置超时时间,单位为秒,意思是如果请求超出了设置的这个时间,还没有得到响应,就会抛出异常。如果不指定该参数,则会使用全局默认时间。这个参数支持HTTP、HTTPS、FTP请求。

示例:

import urllib.request

response = urllib.request.urlopen("https://www.httpbin.org/get",timeout=0.1)
print(response.read())

运行出现的异常: urllib.error.URLError: <urlopen error timed out>

正常来说,0.1秒是无法得到服务器的响应的。这里可以用try except来捕捉异常:

import socket
import urllib.request
import urllib.error

try :
    response = urllib.request.urlopen("https://www.httpbin.org/get",timeout=0.1)

except urllib.error.URLError as e:
    if isinstance(e.reason,socket.timeout):
        print("TIME OUT")

这里我们请求了 www.httpbin.org/get 这个测试链接,设置超时时间为0.1秒,然后捕获到URLError 这个异常,并判断异常类型是 socket.timeout,意思是超时异常,因此得出确实是因为超时而报错的结论,最后打印输出了TIME OUT

TIME OUT

其他参数

  • context参数,该参数必须是ss1.SStContext类型,用来指定 SSL的设置。
  • cafile和capath, 这两个参数分别用来指定CA证书和其路径,这两个在请求 HTTPS链接时会有用。
  • cadefault 参数现在已经弃用了,其默认值为False。

Request

urlopen方法可以发起最基本的请求,但无法构建一个完整的请求。如需要在请求中加入Headers等信息,就得使用Request了。

import urllib.request

request = urllib.request.Request('https://www.python.org')
response = urllib.request.urlopen(request)
print(response.read().decode('utf-8'))

这里依然是用urlopen方法来发送请求,不过这里的参数是一个 Request 类型的对象。通过构造这个数据结构,一方面可以将请求独立成一个对象,另一方面可更加丰富和灵活地配置参数。

Request类的构造方法:

class urllib.request.Request(url,data=None, headers={}, origin_req_host=None, unverifiable-False, method-None)
  • url:: 用于请求 URL,这是必传参数,其他的都是可选参数。
  • data:如果要传数据,必须传bytes类型的。如果数据是字典,可以先用 urllib.parse模块里的urlencode方法进行编码。
  • headers:是一个字典,这就是请求头,我们在构造请求时,既可以通过 headers 参数直接构造此项,也可以通过调用请求实例的 add_header 方法添加。

添加请求头最常见的方法就是通过修改 User-Agent 来伪装浏览器。默认的 User-AgentPython-urllib,我们可以通过修改这个值来伪装浏览器。例如要伪装火狐浏览器,就可以把 User-Agent 设置为:

Mozilla/5.0(X11;U; Linux i686)Gecko/20071127 Firefox/2.0,0.11
  • origin_req_host: 指的是请求方的 host 名称或者 IP地址。
  • unverifiable: 表示请求是否是无法验证的,默认取值是False,意思是用户没有足够的权限来接收这个请求的结果。例如,请求一个HTML文档中的图片,但是没有自动抓取图像的权限,这时 unverifiable 的值就是True
  • method: 是一个字符串,用来指示请求使用的方法,例如GET、POST和PUT等。

尝试构建一个Request类:

from urllib import request, parse

url = 'https://www.httpbin.org/post'
headers = {
    'User-Agent': 'Mozilla/4.0(compatible; MSIE 5.5;Windows NT)',
    'Host': 'www.httpbin.org'
}
dicts = {'name': 'germey'}
data = bytes(parse.urlencode(dicts),encoding='utf-8')
req = request.Request(url=url, data=data, headers=headers, method='POST')
response = request.urlopen(req)
print(response.read().decode('utf-8'))

这里我们通过4个参数构造了一个Request 类,其中的 url即请求 URL,headers 中指定了 User-Agent 和 Host,data用 urlencode 方法和 bytes方法把字典数据转成字节流格式。另外、指定了请求方式为POST。

输出结果:

{
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {
    "name": "germey"
  }, 
  "headers": {
    "Accept-Encoding": "identity", 
    "Content-Length": "11", 
    "Content-Type": "application/x-www-form-urlencoded", 
    "Host": "www.httpbin.org", 
    "User-Agent": "Mozilla/4.0(compatible; MSIE 5.5;Windows NT)", 
    "X-Amzn-Trace-Id": "Root=1-66191a34-3a7fc209527dc43c49ea3dae"
  }, 
  "json": null, 
  "origin": "222.85.167.205", 
  "url": "https://www.httpbin.org/post"
}

通过add_header 方法添加 headers :

req = request.Request(url=url, data=data, method='POST')
req.add_header('User-Agent', 'Mozilla/4.0(compatible; MSIE 5.5;Windows NT)')

高级用法

Handler可以实现更高级的操作,例如Cookie处理、代理设置等。

简而言之,Handler 可以理解为各种处理器,有专门处理登录验证的、处理Cookie的.处理代理设置的。利用这些Handler,我们几乎可以实现HTTP请求中所有的功能。

urllib.request 模块里的 BaseHandler类,是其他所有 Handler 类的父类。它提供了最基本的方法,例如default_open.protocol_request 等。

有各种 Handler 子类继承 BaseHandler类,接下来举几个子类的例子如下:

  • HTTPDefaultErrorHandler:用于处理HTTP响应错误,所有错误都会抛出HTTPError类型的异常。
  • HTTPRedirectHandler:用于处理重定向。
  • HTTPCookieProcessor:用于处理 Cookie。
  • ProxyHandler:用于设置代理,代理默认为空。
  • HTTPPasswordMgr:用于管理密码,它维护着用户名密码的对照表。
  • HTTPBasicAuthHandler:用于管理认证,如果一个链接在打开时需要认证,那么可以用这个类来解决认证问题。

另一个比较重要的类是OpenerDirector,我们可以称之为Opener。我们之前用过的urlopen方法,实际上就是urllib库为我们提供的一个Opener

opener类可以提供open方法,该方法返回的响应类型和urlopen方法如出一辙。那么,Opener类和Handler类有什么关系呢?

简而言之就是,利用Handler 类来构建 Opener类。下面用几个实例来看看 Handler 类和 Opener 类的用法:

验证

在访问某些网站时,可能会弹出类似的验证窗口:

转存失败,建议直接上传图片文件

表示这个网站启用了基本身份认证,英文叫作 HTTP Basic Access Authentication, 这是一种登录验证方式,允许网页浏览器或其他客户端程序在请求网站时提供用户名和口令形式的身份凭证。

对于这样的页面,我们可以使用HTTPBasicAuthHandler来完成页面的爬取

from urllib.request import HTTPPasswordMgrWithDefaultRealm, HTTPBasicAuthHandler, build_opener
from urllib.error import URLError

username = 'admin'
password = 'admin'
url = 'https://ssr3.scrape.center'

p = HTTPPasswordMgrWithDefaultRealm()               # 创建一个密码管理器对象。
p.add_password(None, url, username, password)       # 向密码管理器中添加用户名和密码,关联到指定的URL。
auth_handler = HTTPBasicAuthHandler(p)              # 创建一个HTTP基本身份验证处理程序,用于处理身份验证请求。
opener = build_opener(auth_handler)                 # 据身份验证处理程序创建一个自定义的URL打开器。

try:
    result = opener.open(url)
    html = result.read().decode('utf-8')
    print(html)
except URLError as e:
    print(e.reason)

这里获取的结果就是验证成功后的页面源码内容。

代理

在爬虫时,我们可以这样添加代理:

from urllib.error import URLError
from urllib.request import ProxyHandler, build_opener

proxy_handler = ProxyHandler({
    'http': 'http://127.0.0.1:8080',
    'https': 'https://127.0.0.1:8080'
})
opener = build_opener(proxy_handler)

try:
    response = opener.open('https://www.baidu.com')
    print(response.read().decode('utf-8'))
except URLError as e:
    print(e.reason)

这里需要我们事先在本地搭建一个HTTP代理,并让其运行在8080端口上。

上面使用了ProxyHandler,其参数是一个字典,键名是协议类型(例如 HTTP或者HTTPS等),键值是代理链接,可以添加多个代理。 然后利用这个Handler 和 build_opener 方法构建了一个 Opener,之后发送请求即可。

Cookie 

处理 Cookie需要用到相关的 Handler。

获取网站的Cookie:

import http.cookiejar, urllib.request

cookie = http.cookiejar.CookieJar()
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open('http://www.baidu.com')
for item in cookie:
    print(item.name + "=" + item.value)

首先,必须声明一个 CookieJar 对象。然后需要利用 HTTPCookieProcessor 构建一个Handler,最后利用 build_opener 方法构建 Opener,执行 open 函数即可。

输出结果:

BAIDUID=BA363C3893B97F225520C934C0E19B0F:FG=1  
BIDUPSID=BA363C3893B97F2223358135A4671892  
PSTM=1712933328

Cookie实际上是以文本形式存储的,我们可以输出文本格式的内容:

import http.cookiejar, urllib.request

filename = 'cookie.txt'
cookie = http.cookiejar.MozillaCookieJar(filename)
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open('http://www.baidu.com')
cookie.save(ignore_discard=True, ignore_expires=True)

这时需要将 CookieJar 换成 MozillaCookieJar,它会在生成文件时用到,是 CookieJar 的子类,可以用来处理跟 Cookie和文件相关的事件,例如读取和保存Cookie,可以将 Cookie 保存成 Mozilla 型浏览器的 Cookie 格式。

输出结果:生成一个 cookie.txt 文件

# Netscape HTTP Cookie File 
# http://curl.haxx.se/rfc/cookie_spec.html 
# This is a generated file! Do not edit. 
.baidu.com TRUE / FALSE 1744471955 BAIDUID D046FA122BB7FBFA429B545F5E2DE864:FG=1 
.baidu.com TRUE / FALSE 3860419602 BIDUPSID D046FA122BB7FBFADBE1DA053561ECD5 
.baidu.com TRUE / FALSE 3860419602 PSTM 1712935955

还可以用LWPCookiejar来保存Cookie文件为LWP(libwww-perl)格式

cookie - http.cookiejar.LWPCookieJar(filename)

此时的文件内容:

#LWP-Cookies-2.0
Set-Cookie3: BAIDUID="ABFB3EF2B0085A4826968BB79BAC199D:FG=1"; path="/"; domain=".baidu.com"; path_spec; domain_dot; expires="2025-04-12 15:34:18Z"; comment=bd; version=0
Set-Cookie3: BIDUPSID=ABFB3EF2B0085A4894F1B82A4C70DA46; path="/"; domain=".baidu.com"; path_spec; domain_dot; expires="2092-04-30 18:48:25Z"; version=0
Set-Cookie3: PSTM=1712936058; path="/"; domain=".baidu.com"; path_spec; domain_dot; expires="2092-04-30 18:48:25Z"; version=0

读取生成的Cookie文件:

import http.cookiejar, urllib.request
 
filename = 'cookie.txt'
cookie = http.cookiejar.MozillaCookieJar(filename)
cookie.load('cookie.txt', ignore_discard=True, ignore_expires=True)
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open('http://www.baidu.com')
print(response.read().decode('utf-8'))

这里调用load方法来读取本地的Cookie文件,获取了Cookie的内容。这样做的前提是我们首先生成了 LWPCookieJar 格式的 Cookie,并保存成了文件。读取 Cookie 之后,使用同样的方法构建 Handler 类和 Opener 类即可完成操作。

程序正常执行后会输出百度网页的源代码。

处理异常

urllib库中的error模块定义了由 request模块产生的异常。当出现问题时,request 模块便会抛出error 模块中定义的异常。

URLError

URLError类来自 urllib 库的 error 模块,继承自 OSError 类,是 error异常模块的基类,由 request 模块产生的异常都可以通过捕获这个类来处理。

from urllib import request, error

try:
    response = request.urlopen('https://cuiqingcai.com/404')
except error.URLError as e:
    print(e.reason)

该类的属性 reason 可以返回异常的原因:

Not Found

这里打开了不存在的页面,理应报错,但这里捕捉了该异常,避免了程序异常终止。

HTTPError

HTTPError 是 URLError 的子类,专门用来处理HTTP请求错误,例如认证请求失败等。

它有如下3个属性:

  • code: 返回HTTP状态码,例如404表示网页不存在,500表示服务器内部错误等;
  • reason: 同父类一样,用于返回错误的原因。
  • headers: 返回请求头。
from urllib import request, error

try:
    response = request.urlopen('https://cuiqingcai.com/404')
except error.HTTPError as e:
    print(e.reason, e.code, e.headers, sep='\n')

输出结果:

Not Found
404
Server: GitHub.com
Content-Type: text/html; charset=utf-8
Access-Control-Allow-Origin: *
ETag: "64d39a40-24a3"
Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline'; img-src data:; connect-src 'self'
x-proxy-cache: MISS
X-GitHub-Request-Id: F2AE:10B59F:1E3D74:289C9D:6619F859
Accept-Ranges: bytes
Age: 1138
Date: Sat, 13 Apr 2024 03:32:29 GMT
Via: 1.1 varnish
X-Served-By: cache-icn1450083-ICN
X-Cache: HIT
X-Cache-Hits: 0
X-Timer: S1712979149.360555,VS0,VE1
Vary: Accept-Encoding
X-Fastly-Request-ID: 0436358ce9c090c14806852653887af7b992c460
X-Cache-Lookup: Cache Miss
Content-Length: 9379
X-NWS-LOG-UUID: 16613917973374880822
Connection: close
X-Cache-Lookup: Cache Miss

这里打开了相同的网址,捕获了 HTTPError 异常,输出了 reasoncodeheaders 属性。因为 URLErrorHTTPError 的父类,所以可以先选择捕获子类的错误,再捕获父类的错误,

代码优化:

from urllib import request, error

try:
    response = request.urlopen('https://cuiqingcai.com/404')
except error.HTTPError as e:
    print(e.reason, e.code, e.headers, sep='\n')
except error.URLError as e:
    print(e.reason)
else:
    print('Request Successfully')

有时候,reason 属性返回的不一定是字符串,也可能是一个对象。

import socket
import urllib.request
import urllib.error

try:
    response = urllib.request.urlopen('https://www.baidu.com',timeout=0.01)
except urllib.error.URLError as e:
    print(type(e.reason))
    if isinstance(e.reason, socket.timeout):
        print('TIME OUT!')

输出结果:

<class 'TimeoutError'>
TIME OUT!

解析链接

urllib库里还提供了parse模块,这个模块定义了处理URL的标准接口,例如实现URL各部分的抽取、合并以及链接转换。它支持如下协议的URL处理:file、ftp、gopher、hdl、http、https、imap、mailto、mms、news、nntp、prospero、rsync、rtsp、rtspu、sftp、sip、sips、snews、svn、svn+ssh、telnet 和 wais

urlparse

该方法可以实现URL的识别和分段。

from urllib.parse import urlparse

result = urlparse('https://www.baidu.com/index.html;user?id=5#comment')
print(type(result))
print(result)

输出结果:

<class 'urllib.parse.ParseResult'>
ParseResult(scheme='https', netloc='www.baidu.com', path='/index.html', params='user', query='id=5', fragment='comment')

可以看到,解析结果是一个ParseResult类型的对象,包含6部分,分别是 schemenetlocpathparamsqueryfragment

可以发现,urlparse 方法在解析 URL时有特定的分隔符。 例如://前面的内容就是 scheme,代表协议。 第一个/符号前面便是 netloc,即域名; 后面是path,即访问路径。分号;后面是params, 代表参数。 问号?后面是查询条件query,一般用作GET类型的URL。 井号#后面是锚点fragment, 用于直接定位页面内部的下拉位置。

由此可以得到一个标准的链接格式:

scheme://netloc/path;params?query#fragment

urlparse方法的API:

urllib.parse.urlparse(urlstring, schemes'', allow_fragments=True)

urlparse方法的三个参数:

  • urlstring:这是必填项,即待解析的 URL。
  • scheme:这是默认的协议(例如 http或 https 等)。如果待解析的 URL 没有带协议信息,就会将该参数作为默认协议。
  • allow_fragments: 是否忽略 fragment。如果此项被设置为 False,那么 fragment 部分就会被忽略,它会被解析为pathparams 或者 query的一部分,而 fragment 部分为空。
from urllib.parse import urlparse

result = urlparse('www.baidu.com/index.html;user?id=5#comment', scheme='https')
print(result)

输出结果:

ParseResult(scheme='https', netloc='', path='www.baidu.com/index.html', params='user', query='id=5', fragment='comment')

这里返回了默认的协议信息,并且netloc被解析为path。

带上协议信息:

from urllib.parse import urlparse

result = urlparse('https://www.baidu.com/index.html;user?id=5#comment', scheme='http')
print(result)

输出结果:

ParseResult(scheme='https', netloc='www.baidu.com', path='/index.html', params='user', query='id=5', fragment='comment')

可见,scheme参数只有在URL中不包含协议信息的时候才生效。

allow_fragments 参数:

from urllib.parse import urlparse

result = urlparse('https://www.baidu.com/index.html;user?id=5#comment', allow_fragments=False)
print(result)

输出结果:

ParseResult(scheme='https', netloc='www.baidu.com', path='/index.html', params='user', query='id=5#comment', fragment='')

假设URL中不包含 params 和 query:

from urllib.parse import urlparse

result = urlparse('https://www.baidu.com/index.html#comment', allow_fragments=False)
print(result)

输出结果:

ParseResult(scheme='https', netloc='www.baidu.com', path='/index.html#comment', params='', query='', fragment='')

此时 fragment 会被解析为 path 的一部分。

返回结果 ParseResult 实际上是一个元组,既可以用属性名获取其内容,也可以用索引来顺序获取。

from urllib.parse import urlparse

result = urlparse('https://www.baidu.com/index.html#comment', allow_fragments=False)
print(result.scheme, result[0], result.netloc, result[1], sep='\n')

输出结果:

https
https
www.baidu.com
www.baidu.com

两种获取方式都可以成功获取,且结果是一致的。

urlunparse

有了urlparse方法,相应就会有它的对立方法 urlunparse,用于构造 URL。这个方法接收的参数是一个可迭代对象,其长度必须是6,否则会抛出参数数量不足或者过多的问题。

from urllib.parse import urlunparse

data = ['https', 'www.baidu.com', 'index.html', 'user', 'a=6', 'comment']
print(urlunparse(data))

输出结果:

https://www.baidu.com/index.html;user?a=6#comment

urlsplit

这个方法和 urlparse方法非常相似,只不过它不再单独解析 params 这一部分(params 会合并到path中),只返回5个结果。

from urllib.parse import urlsplit

result = urlsplit('https://www.baidu.com/index/html;user?id=5#comment')
print(result)

输出结果:

SplitResult(scheme='https', netloc='www.baidu.com', path='/index/html;user', query='id=5', fragment='comment')

返回结果是SplitResult,这其实也是一个元组,既可以用属性名获取其值,也可以用索引获取。

from urllib.parse import urlsplit

result = urlsplit('https://www.baidu.com/index/html;user?id=5#comment')
print(result.scheme, result[0])

输出结果:

https https

urlunsplit

urlunparse方法类似,这也是将链接各个部分组合成完整链接的方法,传入的参数也是一个可迭代对象,例如列表、元组等,唯一区别是这里参数的长度必须为5。

from urllib.parse import urlunsplit

data = ['https', 'www.baidu.com', 'index.html', 'a=6', 'comment']
print(urlunsplit(data))
https://www.baidu.com/index.html?a=6#comment

urljoin

除了urlunparse方法 和 urlunsplit方法,还有一种生成链接的方法,是urljoin

我们可以提供一个 base_url(基础链接)作为该方法的第一个参数,将新的链接作为第二个参数。urljoin方法会分析 base_url的 scheme、 netloc 和path这3个内容,并对新链接缺失的部分进行补充,最后返回结果。

from urllib.parse import urljoin
print(urljoin('https://www.baidu.com', 'FAQ.html'))
print(urljoin('https://www.baidu.com', 'https://cuiqingcai.com/FAQ.html'))
print(urljoin('https://www.baidu.com/about.html', 'https://cuiqingcai.com/FAQ.html'))
print(urljoin('https://www.baidu.com/about.html', 'https://cuiqingcai.com/FA0.html?question=2'))
print(urljoin('https://www.baidu.com?wd=abc', 'https://cuiqingcai,cam/index.php'))
print(urljoin('https://www.baidu.com', '?category=2#comment'))
print(urljoin('www.baidu,com', '?category=2#comment'))
print(urljoin('www.baidu.com?comment', '?category=2'))

输出结果:

https://www.baidu.com/FAQ.html  
https://cuiqingcai.com/FAQ.html  
https://cuiqingcai.com/FAQ.html  
https://cuiqingcai.com/FA0.html?question=2  
https://cuiqingcai,cam/index.php  
https://www.baidu.com?category=2#comment  
www.baidu,com?category=2#comment  
www.baidu.com?category=2

base_url提供了三项内容:scheme、netloc 和 path。如果新的链接里不存在这三项,就予以补充;如果存在,就使用新的链接里面的,base_url中的是不起作用的。

urlencode

urlencode,可以用来构造GET请求参数

from urllib.parse import urlencode

params = {
    'name': 'Alan',
    'age': '25'
}
base_url = 'https://www.baidu.com?'
url = base_url + urlencode(params)
print(url)

输出结果:

https://www.baidu.com?name=Alan&age=25

这里首先声明了一个字典 params,用于将参数表示出来,然后调用 urlencode 方法将 params 序列化为 GET请求的参数。

parse_qs

利用 parse_qs 方法,可以将一串 GET 请求参数转回字典,实现反序列化。

from urllib.parse import parse_qs

query = 'name=Alan&age=25'
print(parse_qs(query))

输出结果:

{'name': ['Alan'], 'age': ['25']}

parse_qsl

parse_gsl 方法用于将参数转化为由元组组成的列表

from urllib.parse import parse_qsl

query = 'name=Alan&age=25'
print(parse_qsl(query))

输出结果:

[('name', 'Alan'), ('age', '25')]

运行结果是一个列表,该列表中的每一个元素都是一个元组,元组的第一个内容是参数名,第二个内容是参数值。

quote

该方法可以将内容转化为URL编码的格式。当URL中带有中文参数时,有可能导致乱码问题,此时用 quote 方法可以将中文字符转化为 URL编码。

from urllib.parse import quote

keyword = "图片"
url = "https://www.baidu.com/s?wd=" + quote(keyword)
print(url)

输出结果:

https://www.baidu.com/s?wd=%E5%9B%BE%E7%89%87

unquote

unquote可以对URL进行解码

from urllib.parse import unquote

url = 'https://www.baidu.com/s?wd=%E5%9B%BE%E7%89%87'
print(unquote(url))

输出结果:

https://www.baidu.com/s?wd=图片

Robots协议

Robots协议也称作爬虫协议、机器人协议,全名为网络爬虫排除标准(Robots Exclusion Protocol ),用来告诉爬虫和搜索引擎哪些页面可以抓取、哪些不可以。它通常是一个叫作 robots.txt 的文本文件, 一般放在网站的根目录下。

robots.txt样例:

  • User-agent: * 表示适用于所有搜索引擎爬虫。
  • Disallow: / 表示不允许爬取所有页面
  • Disallow: /private/ 表示不允许爬虫访问 /private/ 目录及其下的所有内容。
  • Disallow: /admin/ 表示不允许爬虫访问 /admin/ 目录及其下的所有内容。
  • Disallow: /secret/ 表示不允许爬虫访问 /secret/ 目录及其下的所有内容。
  • Allow: /public/ 表示允许爬虫访问 /public/ 目录及其下的所有内容。

Allow一般和Disallow结合起来使用,一些常见写法:

禁止所有爬虫访问所有目录:

  • User-agent: *

  • Disallow:/

允许所有爬虫访问所有目录:

  • User-agent:*

  • Disallow:

另外,直接把 robots.txt 文件留空也是可以的。

禁止所有爬虫访问网站某些目录:

  • User-agent:*

  • Disallow:/private/

  • Disallow:/tmp/

只允许某一个爬虫访问所有目录:

  • User-agent:WebCrawler

  • Disallow:

  • User-agent:·

  • Disallow;/

爬虫名称

爬虫名称网站名称
BaiduSpider百度
Googlebot谷歌
360Spider360 搜索
YodaoBot有道
ia archiverAlexa
Scooteraltavista
Bingbot必应

robotparser

可以使用robotparser模块来解析 robots.txt文件了。该模块提供了一个类RobotFileParser,它可以根据某网站的 robots.txt文件判断一个爬取爬虫是否有权限爬取这个网页。

该类用起来非常简单,只需要在构造方法里传入robotstxt文件的链接即可。

urllib.robotparser.RobotFileParser(url='')

RobotFileParser 类的几个常用方法:

  • seturl: 用来设置 robots.txt文件的链接。如果在创建 RobotFileParser 对象时传入了链接, 就不需要使用这个方法设置了。
  • read: 读取 robots.txt 文件并进行分析。注意,这个方法执行读取和分析操作,如果不调用这 个方法,接下来的判断都会为False,所以一定记得调用这个方法。这个方法虽不会返回任何内容,但是执行了读取操作。
  • parse: 用来解析 robots.txt文件,传人其中的参数是robots.txt文件中某些行的内容,它会按照 robots.txt的语法规则来分析这些内容。
  • can_fetch: 该方法有两个参数,第一个是User-Agent,第二个是要抓取的URL。返回结果是True 或 False,表示 User-Agent 指示的搜索引擎是否可以抓取这个URL。
  • mtime: 返回上次抓取和分析robots.txt文件的时间,这对于长时间分析和抓取robots.txt文件的搜索爬虫很有必要,你可能需要定期检查以抓取最新的 robots.txt 文件。
  • modified: 它同样对长时间分析和抓取的搜索爬虫很有帮助,可以将当前时间设置为上次抓取 和分析 robots.txt 文件的时间。

示例:

from urllib.robotparser import RobotFileParser

rp = RobotFileParser()
rp.set_url('https://www.baidu.com/robot.txt')
rp.read()
print(rp.can_fetch('BaiduSpider', 'https://www.baidu.com'))
print(rp.can_fetch('BaiduSpider', 'https://www.baidu.com/homepage/'))
print(rp.can_fetch('Googlebot', 'https://www.baidu.com/homepage/'))

输出结果:

True  
True  
True