2.1 Python使用Urllib库爬取数据

Urllib是Python自带的库,麻雀虽小五脏俱全,可以利用这个库完成网站的简单爬虫应用。本文为学习崔庆才的gitbookUrllib官网总结输出。

其中最主要的三个模块如下:

  • urllib.request:第一个模块 request,它是最基本的 HTTP 请求模块,我们可以用它来模拟发送一请求,就像在浏览器里输入网址然后敲击回车一样,只需要给库方法传入 URL 还有额外的参数,就可以模拟实现这个过程了。

  • urllib.error:第二个 error 模块即异常处理模块,如果出现请求错误,我们可以捕获这些异常,然后进行重试或其他操作保证程序不会意外终止。

  • urllib.parse:第三个 parse 模块是一个工具模块,提供了许多 URL 处理方法,比如拆分、解析、合并等等的方法。

1. 使用urllib.request发送请求

urllib.request 模块定义了函数与类来打开URL,获取HTTP/HTTPS报文,进行基本的授权,重定向,获取Cookie等等。主要通过urlopen()Request()函数来构造HTTP/HTTPS请求。

1.1 urlopen()函数的API

urllib.request.urlopen(url, data=None, [timeout, ]*, cafile=None, capath=None, cadefault=False, context=None)
  • url: 可以是一个string或者是一个Request对象。

  • data:一般使用POST方法向服务器提交数据时,可以指定该参数,一般是一个对象。

  • timeout: 指定等待服务器响应时间。

  • cafile:使用SSL时指定CA文件。

  • capath: CA文件路径。

  • cadefault: 一般忽略该参数。

函数返回一个对象,可作为文本管理器,同时这个对象有一些诸如geturl(),info(),read(),getcode()之类的函数。

测试一下:

import urllib.request

response = urllib.request.urlopen('https://www.python.org')
print('Status is',response.status,sep=':')
print('Headers are:',response.getheaders(), sep='')
print('Server is:',response.getheader('Server'), sep='')


$ python test1.py
Status is:200
Headers are:[('Server', 'nginx'), ('Content-Type', 'text/html; charset=utf-8'), ('X-Frame-Options', 'SAMEORIGIN'), ('x-xss-protection', '1; mode=block'), ('X-Clacks-Overhead', 'GNU Terry Pratchett'), ('Via', '1.1 varnish'), ('Content-Length', '48810'), ('Accept-Ranges', 'bytes'), ('Date', 'Mon, 23 Jul 2018 16:23:13 GMT'), ('Via', '1.1 varnish'), ('Age', '2732'), ('Connection', 'close'), ('X-Served-By', 'cache-iad2127-IAD, cache-sin18029-SIN'), ('X-Cache', 'HIT, HIT'), ('X-Cache-Hits', '1, 8'), ('X-Timer', 'S1532362994.863954,VS0,VE0'), ('Vary', 'Cookie'), ('Strict-Transport-Security', 'max-age=63072000; includeSubDomains')]
Server is:nginx

1.2 Request()函数

其实,Request是定义的一个类,Request()是该类的初始化定义对象的函数。

class Request:
     def __init__(self, url, data=None, headers={},
                 origin_req_host=None, unverifiable=False,
                 method=None):
            ...
            ...
  • 第一个 url 参数是请求 URL,这个是必传参数,其他的都是可选参数。

  • 第二个 data 参数如果要传必须传 bytes(字节流)类型的,如果是一个字典,可以先用 urllib.parse 模块里的 urlencode() 编码。

  • 第三个 headers 参数是一个字典,就是 Request Headers ,可以在构造 Request 时通过 headers 参数直接构造,也可以通过调用 Request 实例的 add_header() 方法来添加。一般通过修改 User-Agent 来伪装浏览器,默认的 User-Agent 是 Python-urllib,我们可以通过修改它来伪装浏览器,比如要伪装火狐浏览器或者Chrome浏览器。

  • 第四个 origin_req_host 参数指的是请求方的 host 名称或者 IP 地址。

  • 第五个 unverifiable 参数指的是这个请求是否是无法验证的,默认是False。意思就是说用户没有足够权限来选择接收这个请求的结果。例如我们请求一个 HTML 文档中的图片,但是我们没有自动抓取图像的权限,这时 unverifiable 的值就是 True。

  • 第六个 method 参数是一个字符串,它用来指示请求使用的方法,比如GET,POST,PUT等等。

测试一下,即可正常打印出网页源代码:

import urllib.request

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

1.3 urllib使用代理

需要先构建一个ProxyHandler的类, 随后将该类用于构建网页打开的opener的类,再在request中安装该opener.

代理格式是"http://112.25.41.136:80",如果要账号密码是"http://user:password@112.25.41.136:80".

proxy="http://112.25.41.136:80"
# Build ProxyHandler object by given proxy
proxy_support=urllib.request.ProxyHandler({'http':proxy,'https':proxy})
# Build opener with ProxyHandler object
opener = urllib.request.build_opener(proxy_support)
# open the website with opener
r=opener.open('https://www.baidu.com')

1.4 urllib获取网站cookies

需要使用http.cookiejar模块构建CookieJar对象,然后用HTTPCookieProcessor 来构建一个 Handler.

import urllib.request
import http.cookiejar

# Build ProxyHandler object by given proxy
proxy="http://112.25.41.136:80"
proxy_handler=urllib.request.ProxyHandler({'http':proxy,'https':proxy})

#Build cookie handler
cookie = http.cookiejar.CookieJar()
cookie_handler = urllib.request.HTTPCookieProcessor(cookie)

# Build opener with ProxyHandler object and cookie handler object
opener = urllib.request.build_opener(proxy_handler,cookie_handler)

# open the website with opener
r=opener.open('http://www.baidu.com')
print(r)

#print cookie content
for item in cookie:
    print(item.name+"="+item.value)

------------------------------------------------------------------------
------------------------------------------------------------------------
------------------------------------------------------------------------
$ python test1.py
<http.client.HTTPResponse object at 0x000001B695F51D68>
BAIDUID=B0DACE3F5ACA00E86E28F23E5F126F2E:FG=1
BIDUPSID=B0DACE3F5ACA00E86E28F23E5F126F2E
H_PS_PSSID=26524_1464_26909_21120_26350_22158
PSTM=1532397579
BDSVRTM=0
BD_HOME=0

2. 使用urllib处理异常

Urllib 的 error 模块定义了由 request 模块产生的异常。如果出现了问题,request 模块便会抛出 error 模块中定义的异常。主要包含URLErrorHTTPError

2.1 URLError

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

它具有一个属性reason,即返回错误的原因。例:

import urllib.request
import http.cookiejar

from urllib import request, error

# Build ProxyHandler object by given proxy
proxy="http://112.25.41.136:80"
proxy_handler=urllib.request.ProxyHandler({'http':proxy,'https':proxy})

#Build cookie handler
cookie = http.cookiejar.CookieJar()
cookie_handler = urllib.request.HTTPCookieProcessor(cookie)

# Build opener with ProxyHandler object and cookie handler object
opener = urllib.request.build_opener(proxy_handler,cookie_handler)

# open the website with opener
try:
    r=opener.open('http://www.baidu.com')
    print(r)
except urllib.error.URLError as err:
    print(err.reason)
else:
    print('ok.')

2.2 HTTPError

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

它有三个属性。

  • code,返回 HTTP Status Code,即状态码,比如 404 网页不存在,500 服务器内部错误等等。

  • reason,同父类一样,返回错误的原因。

  • headers,返回 Request Headers。

因为 URLError 是 HTTPError 的父类,所以我们可以先选择捕获子类的错误,可以输出 reason、code、headers 属性,然后再去捕获父类的错误,所以上述代码更好的写法如下:

import urllib.request
import http.cookiejar

from urllib import request, error

# Build ProxyHandler object by given proxy
proxy="http://112.25.41.136:80"
proxy_handler=urllib.request.ProxyHandler({'http':proxy,'https':proxy})

#Build cookie handler
cookie = http.cookiejar.CookieJar()
cookie_handler = urllib.request.HTTPCookieProcessor(cookie)

# Build opener with ProxyHandler object and cookie handler object
opener = urllib.request.build_opener(proxy_handler,cookie_handler)

# open the website with opener
try:
    r=opener.open('http://www.baidu.com')
    print(r)
except error.HTTPError as err:
    print(err.reason, err.code, err.headers, sep='\n')
except urllib.error.URLError as err:
    print(err.reason)
else:
    print('ok.')

这样我们就可以做到先捕获 HTTPError,获取它的错误状态码、原因、Headers 等详细信息。如果非 HTTPError,再捕获 URLError 异常,输出错误原因。最后用 else 来处理正常的逻辑,这是一个较好的异常处理写法。

3. 使用urllib解析链接

Urllib 库里还提供了 parse 这个模块,它定义了处理 URL 的标准接口,例如实现 URL 各部分的抽取,合并以及链接转换。它支持如下协议的 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。

3.1 urlparse()

urlparse() 方法可以实现 URL 的识别和分段,我们先用一个实例来感受一下:

from urllib.parse import urlparse

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

在这里我们利用了 urlparse() 方法进行了一个 URL 的解析,首先输出了解析结果的类型,然后将结果也输出出来。

运行结果:

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

观察可以看到,返回结果是一个 ParseResult 类型的对象,它包含了六个部分,分别是 scheme、netloc、path、params、query、fragment。

观察一下实例的URL:

http://www.baidu.com/index.html;user?id=5#comment

urlparse() 方法将其拆分成了六部分,大体观察可以发现,解析时有特定的分隔符,比如 :// 前面的就是 scheme,代表协议,第一个 / 前面便是 netloc,即域名,分号 ; 前面是 params,代表参数。

3.2 urlunparse()

有了 urlparse() 那相应地就有了它的对立方法 urlunparse()。

它接受的参数是一个可迭代对象,但是它的长度必须是 6,否则会抛出参数数量不足或者过多的问题。

先用一个实例感受一下:

from urllib.parse import urlunparse

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

参数 data 用了列表类型,当然你也可以用其他的类型如元组或者特定的数据结构。

运行结果如下:

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

这样我们就成功实现了 URL 的构造。

3.3 quote()

quote() 方法可以将内容转化为 URL 编码的格式,有时候 URL 中带有中文参数的时候可能导致乱码的问题,所以我们可以用这个方法将中文字符转化为 URL 编码,实例如下:

from urllib.parse import quote

keyword = '壁纸'
url = 'https://www.baidu.com/s?wd=' + quote(keyword)
print(url)

在这里我们声明了一个中文的搜索文字,然后用 quote() 方法对其进行 URL 编码,最后得到的结果如下:

https://www.baidu.com/s?wd=%E5%A3%81%E7%BA%B8

这样我们就可以成功实现URL编码的转换。

3.4 unquote()

有了 quote() 方法当然还有 unquote() 方法,它可以进行 URL 解码,实例如下:

from urllib.parse import unquote

url = 'https://www.baidu.com/s?wd=%E5%A3%81%E7%BA%B8'
print(unquote(url))

这是上面得到的 URL 编码后的结果,我们在这里利用 unquote() 方法进行还原,结果如下:

https://www.baidu.com/s?wd=壁纸

可以看到利用 unquote() 方法可以方便地实现解码。

Last updated