紧接上一篇:Python3 模块2之 Urllib之 urllib.error
urllib.parse
urllib.parse 模块定义了一个标准接口,在组件(addressing scheme、网址以及路径等)中打破了统一资源定位器(URL)字符串,并将相对 URL(relative URL)转换为给定的 基 URL(base URL) 的绝对 URL(absolute URL)。
urllib.parse 被设计成在相对统一资源定位器(Relative Uniform Resource Locators)上与互联网 RFC 相匹配。它支持如下的 URL schemes (URL 协议): file、 ftp、gopher、hdl、http、 https、imap、 mailto、 mms、news、nntp、 prospero、rsync、rtsp、 rtspu、 sftp、 shttp、 sip、 sips、 snews、svn、svn+ssh、 telnet、 wais、 ws、wss。
urllib.parse 分为 URL parsing (网址解析)和URL quoting(地址引用) 。
一. 网址解析(URL Parsing)
URL 解析函数专注于将 URL 字符串拆分为其组件,或将 URL 组件组合到 URL 字符串中。
下面简要分析使用对应解析函数
1.urlparse
定义:urllib.parse.urlparse(urlstring, scheme=”, allow_fragments=True)
作用特点:将 URL 拆分成 6 大组件
通常一个基本点 URL 应该为:scheme://netloc/path;parameters?query#fragment ,每个元素组都为 String 字符串类型,或者为空。例如,http://www.cwi.nl:80/%7Eguido/Python.html
除这六大组件外,该类具有以下附加的只读便利属性(可看下表):
属性 索引 值 值为 None
scheme 0 URL 协议 scheme 参数
netloc 1 网络端口 空字符串
path 2 分层路径 空字符串
params 3 最后一个路径元素参数 空字符串
query 4 Query 组件 空字符串
fragment 5 片段标志符 空字符串
username 用户名 None
password Password None
hostname 主机名 (小写) None
port 如果存在,端口值为整数 None
下面是一个实例:
#! /usr/bin/evn python3
#"测试urlparse"
#导入parse模块
from urllib import parse
urp = parse.urlparse('https://docs.python.org/3/search.html?q=parse&check_keywords=yes&area=default')
print(urp)
#result:ParseResult(scheme='http', netloc='www.baidu.com:80', path='/doc', params='', query='age=5', fragment='ff')
print(urp.scheme)
#result:http
print(urp.netloc)
#result:www.baidu.com:80
1
2
3
4
5
6
7
8
9
10
11
12
13
14
输出:
ParseResult(scheme='https', netloc='docs.python.org', path='/3/search.html', params='', query='q=parse&check_keywords=yes&area=default', fragment='')
https
docs.python.org
1
2
3
4
urllib.parse.parse_qsl(qs, keep_blank_values=False, strict_parsing=False, encoding=’utf-8’, errors=’replace’)
2.urlunparse
定义:urllib.parse.urlunparse(parts)
从urlparse() 返回的元组元素构造一个URL 。该部分参数可以是任何六个组件的迭代。如果最初解析的 URL 有不必要的分隔符(例如 ?;带有空查询; RFC 声明它们是等同的),则这可能会导致稍微不同但等效的URL 。
下面是一个实例:
#! /usr/bin/evn python
#测试urlunparse
#导入parse模块
from urllib import parse
parsed=parse.urlparse('http://user:pass@NetLoc:80/path;parameters?query=argument#fragment')
print(parsed)
url=parse.urlunparse(parsed)
print(url)
1
2
3
4
5
6
7
8
9
输出:
ParseResult(scheme='http', netloc='user:pass@NetLoc:80', path='/path', params='parameters', query='query=argument', fragment='fragment')
http://user:pass@NetLoc:80/path;parameters?query=argument#fragment
1
2
3
三.parse_qs
定义:urllib.parse.parse_qs(qs, keep_blank_values=False, strict_parsing=False, encoding=’utf-8’, errors=’replace’)
解析一个作为字符串参数给定的查询字符串(类型application/x-www-form-urlencoded 类型的数据)。数据作为字典返回。字典键是唯一的查询变量名且值是每个名称的值列表。
可选参数 keep_blank_values 是指示分空值编码的查询应处理为空字符串标志。一个真值表示空值应保留为空字符串。参数 keep_blank_values 的默认值为 false 表示空值将被忽略,并被视为不包括在内。
可选参数 strict_parsing 是一个标志,表示如何处理解析错误。如果值为 FALSE(默认),错误将被忽略。如果是 `TRUE 1的,误差使 ValueError 异常增加。
四.parse_qsl
定义:urllib.parse.parse_qsl(qs, keep_blank_values=False, strict_parsing=False, encoding=’utf-8’, errors=’replace’)
基本用法与 parse_qs 一致,只是urllib.parse.parse_qs 返回字典,urllib.parse.parse_qsl 返回列表。
下面是一个针对 三、四的实例:
from urllib import parse
url = r'https://docs.python.org/3.5/search.html?q=parse&check_keywords=yes&area=default'
parseResult = parse.urlparse(url)
#print(parseResult)
# parseResult 数据格式满足 parse.parse_qs、parse.parse_qsl 传入的数据格式要求
param_dict = parse.parse_qs(parseResult.query)
param_list = parse.parse_qsl(parseResult.query)
print("返回字典:",param_dict)
print("返回列表:",param_list)
#注意:加号会被解码,可能有时并不是我们想要的
pps = parse.parse_qs('proxy=183.222.102.178:8080&task=XXXXX|5-3+2')
print(pps)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
输出:
返回字典: {'q': ['parse'], 'check_keywords': ['yes'], 'area': ['default']}
返回列表: [('q', 'parse'), ('check_keywords', 'yes'), ('area', 'default')]
{'proxy': ['183.222.102.178:8080'], 'task': ['XXXXX|5-3 2']}
1
2
3
4
五.urlsplit
定义:urllib.parse.urlsplit(urlstring, scheme=”, allow_fragments=True)
返回:(scheme, netloc, path, query, fragment) 比 urlparse 少个params 参数
这与 urlparse()URL 相似,但不会将参数分开。通常应该使用这种方法,而不是使用 urlparse() 允许将参数应用到 URL 的路径部分的每个段的更新的 URL(请参阅RFC2396 )。分段函数分隔路径段和参数。这个函数返回一个5元组:(寻址方案(addressing scheme),网络地址(network location),路径(path),查询( query),片段标识符(fragment identifier))。
返回值实际上是一个子类的实例tuple。该类具有以下附加的只读便利属性:
属性 索引 值 值为空
scheme 0 URL 协议 scheme 参数
netloc 1 网络端口 空字符串
path 2 分层路径 空字符串
params 3 最后一个路径元素参数 空字符串
query 4 Query 组件 空字符串
fragment 5 片段标志符 空字符串
username 用户名 None
password Password None
hostname 主机名 (小写) None
port 如果存在,端口值为整数 None
下面是一个实例:
#! /usr/bin/evn python
#测试 urlsplit
#导入 parse 模块
from urllib import parse
print (parse.urlsplit('http://www.jb51.net:80/faq.cgi?src=fie'))
#result:SplitResult(scheme='http', netloc='www.jb51.net:80', path='/faq.cgi', query='src=fie', fragment='')
1
2
3
4
5
6
7
输出:
SplitResult(scheme='http', netloc='www.jb51.net:80', path='/faq.cgi', query='src=fie', fragment='')
1
六.urlunsplit
urllib.parse.urlunsplit(parts)
结合一个 urlsplit() 返回的元组元素形成一个完整的 URL 字符串。参数的部分参数可以是可迭代的 five-item。如果被解析的 URL 含有本不必要的分隔符(比如 ? 、查询为空、RFC 明这些是等价的),有这可能会导致一个稍有不同但等效的 URL。
下面是一个实例:
#! /usr/bin/evn python
#测试urlunparse
#导入parse模块
from urllib import parse
sr = parse.SplitResult(scheme='http', netloc='www.baidu.com:80', path='/doc', query='age=5', fragment='ff')
print(parse.urlunsplit(sr))
#result:http://www.baidu.com:80/doc?age=5#ff
1
2
3
4
5
6
7
8
输出:
http://www.baidu.com:80/doc?age=5#ff
1
七.urljoin
urllib.parse.urljoin(base, url, allow_fragments=True)
通过将基URL(base )与另一个 URL(url) 组合起来构建完整的(绝对)的URL。
下面是一个例子:
#!/usr/bin/evn python3
#测试urljoin
#导入parse模块
from urllib import parse
uj1 = parse.urljoin("http://www.asite.com/folder1/currentpage.html","anotherpage.html")
uj2 = parse.urljoin("http://www.asite.com/folder1/currentpage.html","folder2/anotherpage.html")
uj3 = parse.urljoin("http://www.asite.com/folder1/currentpage.html","/folder3/anotnerpage.html")
uj4 = parse.urljoin("http://www.asite.com/folder1/currentpage.html","../finalpage.html")
print(uj1)
print(uj2)
print(uj3)
print(uj4)
1
2
3
4
5
6
7
8
9
10
11
12
13
输出:
http://www.asite.com/folder1/anotherpage.html
http://www.asite.com/folder1/folder2/anotherpage.html
http://www.asite.com/folder3/anotnerpage.html
http://www.asite.com/finalpage.html
1
2
3
4
5
Note:如果 url 是一个绝对 URL(即以// 或 scheme:// 开头的 url ),url 的主机名或 scheme 将会替代 base 。
下面是一个实例:
#!/usr/bin/evn python3
#测试urljoin
#导入parse模块
from urllib import parse
uj1 = parse.urljoin("http://www.asite.com/folder/currentpage.html","https://www.python.org/folder2")
uj2 = parse.urljoin("http://www.asite.com/folder/currentpage.html","//www.python.org/folder1")
uj3 = parse.urljoin("http://www.asite.com/folder/currentpage.html","www.python.org/folder2")
print(uj1)
print(uj2)
print(uj3)
1
2
3
4
5
6
7
8
9
10
11
12
输出:
https://www.python.org/folder2
http://www.python.org/folder1
http://www.asite.com/folder/www.python.org/folder2
1
2
3
4
八.urldefrag
urllib.parse.urldefrag(url)
如果 url 包含片段标志符(即 url 尾部的 #+锚点标签 内容),则返回一个不含片段标志符的 url 且片段标志符分成独立的字符串序列。如何 url 不包含片段标志符则返回未修改的 url 和一个空字符串。
返回值实际上是元组( tuple) 的一个子类的实例。这个类具有以下附加的只读的,便利的属性:
属性 索引 值 值为空
url 0 不具有标志符的 url 空字符串
fragment 1 片段标志符 空字符串
下面是一个实例:
#! /usr/bin/evn python
#测试 urlunparse
#导入parse模块
from urllib import parse
ud = parse.urldefrag('http://music.163.com/#/my/')
print(ud)
#result:DefragResult(url='http://music.163.com/', fragment='/my/')
1
2
3
4
5
6
7
8
输出:
DefragResult(url='http://music.163.com/', fragment='/my/')
1
二.地址引用(URL Quoting)
URL引用函数侧重于获取程序数据,并通过引用特殊字符和适当地编码非ASCII文本来使其作为URL组件安全使用。它们还支持逆转这些操作,以使URL组件的内容重新创建原始数据,如果上述URL解析函数未覆盖该任务的话。
一.quote |quote_from_bytes
定义:urllib.parse.quote(string, safe=’/’, encoding=None, errors=None)
定义:urllib.parse.quote_from_bytes(bytes, safe=’/’)
功能:对字符进行转码,特殊字符(保留字符),如“;” | “/” | “?” | “:” | “@” | “&” | “=” | “+” |”$” | “,” 不转码。
下面是一个实例:
#! /usr/bin/evn python
#测试 parse.quote
#导入parse模块
from urllib import parse
quoted = parse.quote('https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&tn=baidu&wd=%E6%95%B0%E5%AD%A6&oq=%25E9%25AB%2598%25E7%25AD%2589%25E6%2595%25B0%25E5%25AD%25A6&rsv_pq=bc3192bd00006199&rsv_t=9874L5kHfiTTvwbjdnArv85fD%2B4yAJXywKFWw1HfLoGCNsctPGieUGbvTcY')
print(quoted)
1
2
3
4
5
6
7
输出:
https%3A//www.baidu.com/s%3Fie%3Dutf-8%26f%3D8%26rsv_bp%3D1%26tn%3Dbaidu%26wd%3D%25E6%2595%25B0%25E5%25AD%25A6%26oq%3D%2525E9%2525AB%252598%2525E7%2525AD%252589%2525E6%252595%2525B0%2525E5%2525AD%2525A6%26rsv_pq%3Dbc3192bd00006199%26rsv_t%3D9874L5kHfiTTvwbjdnArv85fD%252B4yAJXywKFWw1HfLoGCNsctPGieUGbvTcY
1
2
二.quote_plus
定义:urllib.parse.quote_plus(string, safe=”, encoding=None, errors=None)
与 quote 相似,由 quote_plus 编码 /, quote 不编码 /
下面是个实例:
#! /usr/bin/evn python
#测试 parse.quote 、parse.quote_plus
#导入parse模块
from urllib import parse
p=parse.quote('a&b/c') #未编码斜线
print('quote:',p)
plus=parse.quote_plus('a&b/c') #编码了斜线
print('plus:',plus)
1
2
3
4
5
6
7
8
输出
quote: a%26b/c
plus: a%26b%2Fc
1
2
3
三.unquote|unquote_to_bytes
定义:urllib.parse.unquote(string, encoding=’utf-8’, errors=’replace’)
定义:urllib.parse.unquote_to_bytes(string)
功能:quote 的逆过程
下面是一个实例:
#!/usr/bin/evn python
#测试 unquote、unquote_to_bytes
#导入parse模块
from urllib import parse
print(parse.unquote('http%3A//www.baidu.com/doc/sub.html%3Fname%3Dhan%20jian%26age%3D45%40%3B+$'))
print(parse.unquote_to_bytes('http%3A//www.baidu.com/doc/sub.html%3Fname%3Dhan%20jian%26age%3D45%40%3B+$'))
1
2
3
4
5
6
7
输出:
http://www.baidu.com/doc/sub.html?name=han jian&age=45@;+$
b'http://www.baidu.com/doc/sub.html?name=han jian&age=45@;+$'
1
2
四.unquote_plus
定义:urllib.parse.unquote_plus(string, encoding=’utf-8’, errors=’replace’)
功能:quote_plus的逆过程
下面是个实例:
#! /usr/bin/evn python
#测试 parse.unquote 、parse.unquote_plus
#导入parse模块
from urllib import parse
uq=parse.unquote('1+2') #不解码加号
print('unquote:',uq)
uqp=parse.unquote_plus('1+2') #把加号解码为空格
print('unquote_plus:',uqp)
1
2
3
4
5
6
7
8
输出:
unquote: 1+2
unquote_plus: 1 2
1
2
五.urlencode
定义:urllib.parse.urlencode(query, doseq=False, safe=”, encoding=None, errors=None, quote_via=quote_plus)
功能:将字典形式的数据转化成查询字符串
参数的含义:
query:需要转化的字典数据
doseq:如果字典的某个值是序列的话是否解析,deseq值为False不解析doseq的值为True的时候解析,稍后在例子中给出
safe:那些字符串不需要编码
encoding:要转化成的字符串的编码
quote_via:使用quote编码还是qutoe_plus编码,默认quote_plus也就是空格被转化成+号
下面是一个实例:
#!/usr/bin/env python3
#urlencode 测试
from urllib import parse
#定义要转化的字典数据
qdict = {'age':34,'grils':('lili','tingting'),'name':'han p$'}
print(parse.urlencode(qdict))
#result:
#age=34&grils=%28%27lili%27%2C+%27tingting%27%29&name=han+p%24
#怎么让两个女朋友分开呢
print(parse.urlencode(qdict,True))
#result
#age=34&grils=lili&grils=tingting&name=han+p%24
#怎么让name里边的$不要编码呢
print(parse.urlencode(qdict,True,'$'))
#result
#age=34&grils=lili&grils=tingting&name=han+p$
#怎么让空格不编码成+而编译成%20呢
print(parse.urlencode(qdict,True,'$',quote_via=parse.quote))
#由于前面还有两个位置参数所以使用关键字参数
#result age=34&grils=lili&grils=tingting&name=han%20p$
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
输出:
age=34&grils=%28%27lili%27%2C+%27tingting%27%29&name=han+p%24
age=34&grils=lili&grils=tingting&name=han+p%24
age=34&grils=lili&grils=tingting&name=han+p$
age=34&grils=lili&grils=tingting&name=han%20p$
1
2
3
4
5
三.urllib.robotparse
一. 了解网站文件 robots.txt
每个网站都会定义 robots.txt 文件,这个文件可以告诉网络爬虫爬取该网站时存在哪些限制。作为良好网民以及其他人利益,一般上遵从这些限制。
如何查看这个文件?可以通过在目标网站站点或域名后面加上 robots.txt 进行访问。
例如 目标网站站点 https://www.douban.com 的 robots.txt 文件就是 https://www.douban.com/robots.txt。
下面即为这个文件内容:
User-agent: *
Disallow: /subject_search
Disallow: /amazon_search
Disallow: /search
Disallow: /group/search
Disallow: /event/search
Disallow: /celebrities/search
Disallow: /location/drama/search
Disallow: /forum/
Disallow: /new_subject
Disallow: /service/iframe
Disallow: /j/
Disallow: /link2/
Disallow: /recommend/
Disallow: /trailer/
Disallow: /doubanapp/card
# section 1
Sitemap: https://www.douban.com/sitemap_index.xml
Sitemap: https://www.douban.com/sitemap_updated_index.xml
# section 2
# Crawl-delay: 5
# section 3
User-agent: Wandoujia Spider
Disallow: /
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
关于这个 robots.txt 文件内容:
section 1:
定义了 ` Sitemap` 文件,即所谓的网站地图。
1
section 2:
这被注释掉的部分,如果没有被注释且指明了跳转链接,那么,表明每个用户两次爬虫之间的时间间隔不能小于 5s 否则所访问的网站网页会自动跳转到指定的链接页面。此时,相当于网站服务器禁封了 IP ,禁封时间依据各网站的情况。
1
section 3:
这部分表示,`robots.txt` 文件禁止那些代理为 ` Wandoujia Spider` 的爬虫访问网站。理解过来,就是禁止豌豆荚爬虫代理访问网站。
1
二. 检查网站地图(Sitemap)
打开 robots.txt 文件里面的 Sitemap 地址,例如上面的 Sitemap 有, https://www.douban.com/sitemap_index.xml 和 Sitemap: https://www.douban.com/sitemap_updated_index.xml 。
网站提供的 Sitemap 文件(即 网站地图)提供了该网站站点里面所有页面的链接,这些链接组成了这个 Sitemap 文件,所以叫做地图并不过分。 这样,便无须爬取某个网站站点里面的每一个网页因为网站提供的 Sitemap 文件 帮助了网络爬虫定为网站最新的内容(如下图)。
Note:虽然 Sitemap 文件提供了一种爬取网站的有效方式,但是我们仍需要对其谨慎处理,因为该文件经常存在缺失、过期或者不完整的问题。(观点以及学习视角引用自:Python 网络爬虫 010 (高级功能) 解析 robots.txt 文件
三. robots.txt 文件的使用测试
还是那个豆瓣的 robots.txt 文件 https://www.douban.com/robots.txt。
如下为文件内容:
User-agent: *
Disallow: /subject_search
Disallow: /amazon_search
Disallow: /search
Disallow: /group/search
Disallow: /event/search
Disallow: /celebrities/search
Disallow: /location/drama/search
Disallow: /forum/
Disallow: /new_subject
Disallow: /service/iframe
Disallow: /j/
Disallow: /link2/
Disallow: /recommend/
Disallow: /trailer/
Disallow: /doubanapp/card
Sitemap: https://www.douban.com/sitemap_index.xml
Sitemap: https://www.douban.com/sitemap_updated_index.xml
# Crawl-delay: 5
User-agent: Wandoujia Spider
Disallow: /
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
对于这个 robots.txt 文件来说, Wandoujia Spider 的代理用户是禁止预览该站点的。
可以使用Python 自带的 robotparser 模块测试一下:
import urllib.robotparser
rp = robotparser.RobotFileParser()
rp.set_url('https://www.douban.com/robots.txt')
rp.read()
url = 'https://www.douban.com'
user_agent = 'Wandoujia Spider'
wsp_info = rp.can_fetch(user_agent, url)
print("Wandoujia Spider 代理用户访问情况:",wsp_info)
user_agent = 'Other Spider'
osp_info = rp.can_fetch(user_agent, url)
print("Other Spider 代理用户访问情况:",osp_info)
1
2
3
4
5
6
7
8
9
10
11
12
输出:
Wandoujia Spider 代理用户访问情况: False
Other Spider 代理用户访问情况: True
1
2
3
学习资料:python doc 、Python 网络爬虫 010 (高级功能) 解析 robots.txt 文件 、Python 网络爬虫 010 (高级功能) 解析 robots.txt 文件、urllib的parse模块。
技术链接