Python爬虫第4节-请求库urllib的request模块使用

发布于:2025-04-04 ⋅ 阅读:(8) ⋅ 点赞:(0)

目录

前言:基本库urllib的使用

一、urlopen方法

二、Request类

三、高级用法


前言:基本库urllib的使用

        开始学习爬虫时,第一步就是要模拟浏览器给服务器发送请求。这个时候,你可能会有很多问题:该从哪里开始做呢?需不需要自己来构造请求?要不要去关心请求数据结构是怎么实现的?需不需要了解HTTP、TCP、IP层的网络传输通信原理?要不要知道服务器是怎么响应和应答的?

        你可能会不知道该怎么做,但别担心。Python很厉害,它有功能齐全的类库,能帮我们完成这些请求。最基础的HTTP库有urllib、httplib2、requests、treg等。

        就拿urllib库来说,用它的时候,我们只要关注请求的链接、要传的参数,还有怎么设置可选的请求头就行,不用去深究它底层的传输和通信机制。用这个库,只需要两行代码,就能完成请求和响应的处理,拿到网页内容,是不是很方便?接下来,我们从最基础的部分开始,学习这些库的使用方法。

        在Python2里,有urllib和urllib2这两个库可以用来发送请求。不过到了Python3,urllib2库没了,统一成了urllib。它的官方文档链接是:https://docs.python.org/3/library/urllib.html 。urllib是Python自带的HTTP请求库,不用额外安装就能用。它包含下面4个模块:

        - request:这是最基本的HTTP请求模块,能模拟发送请求。就像在浏览器地址栏输入网址然后回车一样,只要给库方法传入URL和额外的参数,就能模拟这个操作。
        - error:这是异常处理模块。要是请求的时候出了错误,可以捕获这些异常,然后重试或者做其他操作,保证程序不会突然停止。
        - parse:这是个工具模块,有很多处理URL的方法,像拆分、解析、合并URL等。
        - robotparser:主要是用来识别网站的robots.txt文件,判断哪些网站能爬,哪些不能爬。在实际用的时候,这个模块用得比较少。

        本节先讲讲 request 模块。使用urllib的request模块,能便捷地实现请求的发送,并获取响应。下面来看其具体用法。

一、urlopen方法

        urllib.request模块有构造HTTP请求最基础的方法,用它能模拟浏览器发起请求的过程。而且,它还可以处理授权验证(authenticaton)、重定向(redirection)、浏览器Cookies这些内容。 

        下面以抓取Python官网为例,展示其功能,不过要迅速访问Python官网需要使用外网:

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

        运行上述代码,仅用两行代码,便完成了Python官网的抓取,输出了网页的源代码。获取源代码后,就可以从中提取所需的链接、图片地址、文本信息等。

接下来,查看urlopen()返回的内容类型。利用type()方法输出响应类型:

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

        输出结果是`<class 'http.client.HTTPResponse'>`,这说明返回的是HTTPResponse类型的对象。这个对象有read()、readinto()、getheader(name)、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
[('Server', 'nginx'), ('Content-Type', 'text/html; charset=utf-8'), ('X-Frame-Options', 'SAMEORIGIN'), ('X-Clacks-Overhead', 'GNu Terry Pratchett'), ('Content-Length', '47397'), ('Accept-Ranges', 'bytes'), ('Date', 'Mon, 01 Aug 2016 09:57:31 GMT'), ('Via', '1.1 varnish'), ('Age', '2473'), ('Connection', 'close'), ('X-Served-By', 'cache-lcy1125-LCY'), ('X-Cache', 'HIT'), ('X-Cache-Hits', '23'), ('Vary', 'Cookie'), ('Strict-Transport-Security','max-age=63072000;includeSubDomains')]
nginx

        从输出结果能看到,前两个输出分别是响应状态码和响应头信息。最后一个输出,是调用`getheader()`方法,并把`Server`作为参数传进去,获取到了响应头里`Server`的值,结果是`nginx`,这就说明服务器是用Nginx搭建的。

        通过最基本的`urlopen()`方法,能完成简单网页最基础的GET请求抓取。

        要是想给链接传递参数,该怎么做呢?我们先看看`urlopen()`函数的API:
`urllib.request.urlopen(url, data=None, [timeout, ]*, cafile=None, capath=None, cadefault=False, context=None)`

        可以知道,除了第一个参数用来传递URL,还能传递其他信息,比如`data`(附加数据)、`timeout`(超时时间)等。下面,我来详细讲讲这几个参数的用法。

- data参数:`data`参数不是必须的。要是添加这个参数,而且数据是字节流编码格式(也就是`bytes`类型),就得用`bytes()`方法进行转换。另外,一旦传递了这个参数,请求方式就从GET变成POST了 。

下面通过实例说明:

        这次我们传递了一个参数,参数名为word,值是hello。由于程序要求参数是bytes(字节流)类型,所以得进行转码。转码时用的是bytes()方法,这个方法的第一个参数得是str(字符串)类型。因此,我们要借助urllib.parse模块里的urlencode()方法,把参数字典转换成字符串。bytes()方法的第二个参数用来指定编码格式,这里我们设置为utf8。 

        这次请求的网站是httpbin.org,这个网站专门提供HTTP请求测试服务。请求的URL是http://httpbin.org/post,这个链接可以用来测试POST请求。使用该链接测试时,网站会输出请求的相关信息,其中就包含我们传递的data参数。 

运行结果如下:

{
    "args": {},
    "data": "",
    "files": {},
    "form": {
        "word": "hello"
    },
    "headers": {
        "Accept-Encoding": "identity",
        "Content-Length": "10",
        "Content-Type": "application/x-www-form-urlencoded",
        "Host": "httpbin.org",
        "User-Agent": "Python-urllib/3.5"
    },
    "json": null,
    "origin": "123.124.23.253",
    "url": "http://httpbin.org/post"
}

        我们传递的参数出现在了form字段里,这说明我们模拟了表单提交操作,数据是以POST方式进行传输的。

- timeout参数:timeout参数的作用是设置超时时间,单位为秒。也就是说,当请求发出后,如果超过了设定的时间,还没有收到服务器的响应,程序就会抛出异常。要是不设置这个参数,程序就会采用全局默认的超时时间。这个参数在HTTP、HTTPS、FTP请求中都能使用。

下面通过实例展示:

import urllib.request
response = urllib.request.urlopen('http://httpbin.org/get', timeout=1)
print(response.read())

运行结果如下:

During handling of the above exception, another exception occurred:
Traceback (most recent call last):
  file "/var/py/python/urllibtest.py", line 4, in <module>
    response = urllib.request.urlopen('http://httpbin.org/get', timeout=1)
...
urllib.error.URLError: <urlopen error timed out>

        这里设置超时时间为1秒。1秒过后,服务器仍未响应,于是抛出了URLError异常。该异常属于urllib.error模块,错误原因是超时。
        因此,可通过设置超时时间,控制若网页长时间未响应,就跳过其抓取。这可利用try - except语句实现,相关代码如下:

import socket
import urllib.request
import urllib.error

try:
    response = urllib.request.urlopen('http://httpbin.org/get', timeout=0.1)
except urllib.error.URLError as e:
    if isinstance(e.reason, socket.timeout):
        print('TIME OUT')

        这次我们请求了http://httpbin.org/get这个测试链接,还把超时时间设成了0.1秒。程序运行时,会捕获可能出现的URLError异常。捕获到异常后,再判断这个异常是不是socket.timeout类型,要是属于这种类型,那就说明是因为超时而报错,然后程序就会打印输出“TIME OUT”。

        实际运行的结果就是输出了“TIME OUT”。一般来说,0.1秒的时间太短,服务器很难在这么短时间内给出响应,所以就出现了这个提示。由此可见,设置timeout参数来处理超时情况,有时候真的很有用。

- 其他参数:除了data参数和timeout参数,urlopen()函数还有个context参数。这个参数得是ssl.SSLContext类型,主要用来指定SSL的设置。另外,cafile和capath这两个参数,分别是用来指定CA证书和证书的路径,在请求HTTPS链接的时候会用到它们。不过cadefault这个参数现在已经不用了,它原来的默认值是False。

        上面讲了urlopen()方法的用法,用这个最基础的方法,能够完成一些简单的请求,也能抓取网页内容。要是你还想知道更详细的内容,可以去看官方文档,链接是:https://docs.python.org/3/library/urllib.request.html。

二、Request类

        用urlopen()方法,能发起最基本的请求。不过,要是只靠它那几个简单参数,没办法构建出完整的请求。要是请求里需要添加Headers这些信息,就得用功能更强大的Request类来构建请求了。

        首先,通过实例感受Request的用法:

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

        我们会发现,发送请求还是用urlopen()方法。但这次,urlopen()方法的参数不再是网址URL,而是一个Request类型的对象。创建这样一个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 - Agent是Python-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等 。

下面传入多个参数构建请求:

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

        这次我们用4个参数构建了一个请求。其中,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": "httpbin.org",
        "User-Agent": "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)"
    },
    "json": null,
    "origin": "219.224.169.11",
    "url": "http://httpbin.org/post"
}

        观察结果可知,成功设置了data、headers和method。另外,headers也可用add_header()方法添加:

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

如此,就能更便捷地构造请求,实现请求的发送。

三、高级用法

        在前面的操作里,我们已经能构造请求了。不过,要是遇到更高级的操作,像处理Cookies、设置代理这些,该怎么弄呢?这时候,更厉害的工具Handler就派上用场了。简单讲,Handler就像是各种不同的处理器,有的专门处理登录验证,有的处理Cookies,还有的处理代理设置。有了它们,HTTP请求里的大部分操作我们都能完成。

        先说说urllib.request模块里的BaseHandler类,它是其他所有Handler的父类,提供了一些最基础的方法,像default_open()、protocol_request()这些。

        然后,有很多Handler子类继承了BaseHandler类,下面举几个例子:
- HTTPDefaultErrorHandler:专门处理HTTP响应出错的情况,一旦出错就会抛出HTTPError类型的异常。
- HTTPRedirectHandler:用来处理重定向的问题。
- HTTPCookieProcessor:负责处理Cookies。
- ProxyHandler:可以设置代理,默认是没有代理的。
- HTTPPasswordMgr:用来管理密码,它会记录用户名和密码。
- HTTPBasicAuthHandler:处理认证相关的事情,如果打开一个链接需要认证,就可以用它来解决。

        除了上面这些,还有其他的Handler类,这里就不一个一个说了,详细信息可以看官方文档:https://docs.python.org/3/library/urllib.request.html#urllib.request.BaseHandler。

        后面会通过具体例子来讲怎么用这些Handler。

        还有一个比较重要的类叫OpenerDirector,我们可以简称它为Opener。之前用过的urlopen()方法,其实就是urllib给我们提供的一个Opener。

        那为什么要引入Opener呢?是为了实现更高级的功能。之前用的Request和urlopen(),就像是类库给我们封装好的常用请求方法,用它们能完成一些基本的请求。但现在我们要做更高级的功能,就得再深入一些进行配置,用更底层的实例来操作,这就需要用到Opener了。

        Opener可以用open()方法,它返回的类型和urlopen()一样。那Opener和Handler有啥关系呢?简单说,就是用Handler来创建Opener。

        下面通过几个例子看看它们怎么用。

- 验证:有些网站打开的时候会弹出一个框,让你输入用户名和密码,输对了才能看页面,就像下面这个图显示的一样。

        若要请求这样的页面,借助HTTPBasicAuthHandler就能完成,相关代码如下:

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

username = 'username'
password = 'password'
url = 'http://localhost:5000/'
p = HTTPPasswordMgrWithDefaultRealm()
p.add_password(None, url, username, password)
auth_handler = HTTPBasicAuthHandler(p)
opener = build_opener(auth_handler)
try:
    result = opener.open(url)
    html = result.read().decode('utf-8')
    print(html)
except URLError as e:
    print(e.reason)

        这里第一步,我们创建一个HTTPPasswordMgrWithDefaultRealm对象,将它作为参数,去实例化HTTPBasicAuthHandler对象。紧接着,通过对象的add_password()方法,把用户名和密码添加进去,如此一来,一个专门处理验证的Handler就搭建好了。 

        第二步,借助刚才创建的Handler,调用build_opener()方法,就能构建出一个Opener。这个Opener在发送请求时,等同于已经通过验证。最后,使用Opener的open()方法打开目标链接,验证操作便顺利完成。这时获取到的,就是通过验证后网页的源代码。 

- 代理:做爬虫时,免不了要使用代理。若要添加代理,可按如下方式操作:

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

proxy_handler = ProxyHandler({
    'http': 'http://127.0.0.1:9743',

    'https': 'https://127.0.0.1:9743'
})
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)

        这里我们在本地搭建了一个代理,它运行在9743端口上。这里使用了ProxyHandler,其参数是一个字典,键名是协议类型(比如HTTP或者HTTPS等),键值是代理链接,可以添加多个代理。

        然后,利用这个Handler及build_opener()方法构造一个Opener,之后发送请求即可。

- Cookies
        Cookies的处理就需要相关的Handler了。

        我们先用实例来看看怎样将网站的Cookies获取下来,相关代码如下

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=2E65A683F8A8BA3DF521469DF8EFF1E1:FG=1
BIDUPSID=2E65A683F8A8BA3DF521469DF8EFF1E1
H_PS_PSSID=20987 14211 8282 17949 21122 17001 21227 21189 21161 20927
PSTM=1474900615
BDSVRTM=0
BD_HOME=0

        从输出结果能看到,每条Cookie的名称和值都被打印出来了。既然能实现输出,那能不能把它们输出保存成文件呢?毕竟Cookies本质上是以文本形式存储的。答案是可以的,下面通过实例来演示一下:

filename = 'cookies.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。MozillaCookieJar是CookieJar的子类,生成Cookies文件时会用到它。它专门用来处理Cookies和文件相关操作,像读取Cookies文件,以及把Cookies保存为Mozilla浏览器使用的Cookies格式文件。 

        运行之后,可以发现生成了一个cookies.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   3622386254  BAIDUID 05AE39B5F56C1DEC474325CDA522D44F:FG=1
.baidu.com       TRUE    /       FALSE   3622386254  BIDUPSID        05AE39B5F56C1DEC474325CDA522D44F
.baidu.com       TRUE    /       FALSE   3622386254  H_PS_PSSID      19638 1453 17710 18240 21091 118560 17001 21191 21161
.baidu.com       TRUE    /       FALSE   1474902606  PSTM    1474902606
www.baidu.com    FALSE   /       FALSE   0       BDSVRTM       0
www.baidu.com    FALSE   /       FALSE   0       BD_HOME       0

        另外,LWPCookieJar同样可以读取和保存Cookies,但是保存的格式和MozillaCookieJar不一样,它会保存成libwww-perl(LWP)格式的Cookies文件。要保存成LWP格式的Cookies文件,可以在声明时就改为:

cookie = http.cookiejar.LWPCookieJar(filename)

此时生成的内容如下:

#LWP-Cookies-2.0
Set-Cookie3:BAIDUID="0CE9C56F598E69DB375B7C294AE5C591:FG=1"; path="/"; domain=".baidu.com"; path_spec;domain_dot;expires="2084-10-14 18:25:19Z";version=0
Set-Cookie3: BIDUPSID=0CE9C56F598E69DB375B7C294AE5C591; path="/"; domain=".baidu.com"; path_spec; domain dot;expires="2084-10-14 18:25:19Z":version=0
Set-Cookie3:H_PS_PSSID=20048 1448 18240 17944 21089 21192 21161 20929; path="/"; domain=".baidu.com";path_spec;domain dot;discard;version=0
Set-Cookie3: PSTM=1474902671; path="/"; domain=".baidu.com"; path_spec; domain dot; expires="2084-10-1418:25:19Z";version=0
Set-Cookie3: BDSVRTM=0; path="/"; domain="www.baidu.com"; path_spec; discard; version=0
Set-Cookie3: BD_HOME=0; path="/";domain="www.baidu.com"; path_spec; discard; version=0

        从上面可以看出,两种格式生成的Cookies文件差别挺大。既然已经生成了Cookies文件,那怎么从文件里读取Cookies,并在程序里使用它们呢?下面,我们以LWPCookieJar格式的Cookies文件为例,来介绍具体做法。 

cookie = http.cookiejar.LWPCookieJar()
cookie.load('cookies.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()方法读取本地的Cookies文件,这样就获取到了Cookies内容。但有个前提,得先生成LWPCookieJar格式的Cookies,并保存成文件。获取Cookies后,按照之前构建Handler和Opener的方法操作,就能完成后续流程。

        正常情况下,运行程序会输出百度网页的源代码。通过上述方法,大部分请求功能都能进行设置。

        这些就是urllib库中request模块的基本使用方法。要是你想实现更多功能,可查看官方文档:https://docs.python.org/3/library/urllib.request.html#basehandler-objects 。

参考学习书籍:Python 3网络爬虫开发实战


网站公告

今日签到

点亮在社区的每一天
去签到