Python3标准库urllib

Python3标准库urllib

前言

做web开发的,和http请求打交道最多了,不得不熟悉的就是urllib。当然爬虫也经常用。并且有好的第三方库requests。
本文就介绍这些东东。
note:是在python3.5下测试运行。

urllib

urllib有4个库。分别是:

  • urllib.request 打开和读url
  • urllib.error 包含由urllib.request抛出的异常。
  • urllib.parse 用来解析url
  • urllib.robotparser 用来解析robots.txt文件。

玩爬虫的同学请关注一下urllib.robotparser,做一个好程序员。-^

request

两行代码拿到页面内容。

get请求

from urllib.request import urlopen
print(urlopen("http://www.baidu.com").read())

urllib.request.urlopen(url, data=None, [timeout, ]*, cafile=None, capath=None, cadefault=False, context=None)
支持http、https、ftp协议。
urlopen返回 一个类文件对象,他提供了如下方法:

  • read() , readline() , readlines() , fileno() , close() :这些方法的使用方式与文件对象完全一样;
  • info():返回一个httplib.HTTPMessage 对象,表示远程服务器返回的头信息;
  • getcode():返回Http状态码。如果是http请求,200表示请求成功完成;404表示网址未找到;
  • geturl():返回请求的url;

参数说明:

  • url 可以是字符串,也可以是Request对象.
  • data 用于post请求时的数据。
  • timeout 超时时间
  • cafile & capath & cadefault 用于https的ca证书
  • context 同样是用于https请法庭。

需要注意的是这个方法使用的HTTP/1.1协议,并且在HTTP header里自动加入了Connection:close。

post请求

同样可以用urllib.request.urlopen实现。传入data即可。
第二种方式是传入的url是个Request对象。Request的构造函数中传入data即可。
如果没有数据传输,只能用第二第方式,只是Request对象的函数函数中需要明确指定method=’POST’。

完整的Request类定义如下:
urllib.request.Request(url, data=None, headers={}, origin_req_host=None, unverifiable=False, method=None)

参数说明:

  • url 字符串的网址
  • data 用于post的数据。如果有,则method自动为’POST’
  • headers http头。
  • origin_req_host 原始请求host。
  • unverifiable 用于指示请求是否是不可证实的。
  • method http方法

示例如下:

from urllib import request
from urllib import parse
#从这可以看出,前端的校验是给小白用的.码农是可以直接发起http请求,绕过前端js校验的.
data = {'name':'test1','email':'test@qq.com','passwd':'123456'}
response  = request.urlopen('http://awesome.go2live.cn/api/users',parse.urlencode(data).encode('utf-8'))
print(response.getcode())
print(response.read())

自定义header

前文已经说了。在构造Request对象时,传入headers参数即可。也可以之后调用Request的add_header方法。
这个主要用来模拟User-Agent和HTTP_REFERER。因为这两个经常用来在后端屏蔽掉请求。

譬如我的博客站点就是有屏蔽功能的。

直接请求会失败。

from urllib.request import urlopen
print(urlopen("http://www.go2live.cn").read())

输出结果:

Traceback (most recent call last):

urllib.error.HTTPError: HTTP Error 403: Forbidden

通过构造合适的user-agent就可以正常访问了。

from urllib import request
req = request.Request('http://www.go2live.cn',headers={'User-Agent':'Mozilla/5.0 (X11; U; Linux i686) Gecko/20071127 Firefox/2.0.0.11'})
response  = request.urlopen(req)
print(response.getcode())

输出结果:

200

还有Referer是用来防盗链的。请求某些资源时,Referer必须是来自指定的网站才可以,否则403拒绝访问。
另一个是Content-Type。
在post请求,自动成了application/x-www-form-urlencoded。
但是在api请求时,现在好多都改成了json的。这个时候需要用application/json。
另外上传文件时也不一样:multipart/form-data。

增加cookie

这个主要用来处理需要登录才能访问的页面。
这个稍为麻烦点。修复修改下opener才能处理。
http.cookiejiar包是来保存cookie的。
request.HTTPCookieProcessor是用来处理cookie的。
完整的代码示例如下:

from urllib import request
from urllib import parse
import http.cookiejar
import hashlib

URL_ROOT = 'http://awesome.go2live.cn'

cookie = http.cookiejar.CookieJar()# 用来保存cookie的对象
handler = request.HTTPCookieProcessor(cookie) #处理cookie的工具
opener = request.build_opener((handler))
response = opener.open(URL_ROOT)
print("before login")
for item in cookie:
    print('Name={0}, Value={1}'.format(item.name, item.value))
    
data = {'email':'test@qq.com','passwd':'123456'}
data['passwd'] = data['email']+":"+data['passwd']
data['passwd'] = hashlib.sha1(data['passwd'].encode()).hexdigest()
req = request.Request(URL_ROOT+'/api/authenticate', parse.urlencode(data).encode())
response = opener.open(req)
print(response.read())
print("after login")
for item in cookie:
    print('Name={0}, Value={1}'.format(item.name, item.value))

输出结果:

before login
b'{“admin”: 0, “created_at”: 1486789465.7326, “id”: “001486789465697a2db999d99f84506a24a457bee0eab76000”, “name”: “test”, “email”: “test@qq.com“, “passwd”: “******”, “image”: “http://www.gravatar.com/avatar/bf58432148b643a8b4c41c3901b81d1b?d=mm&s=120″}
after login
Name=awesession, Value=001486789465697a2db999d99f84506a24a457bee0eab76000-1486887360-220a1b13969736bb0f868cfef9076023a7ea3b02

上传文件

用还是urlopen方法。只是data的构造真的好蛋疼。。

#!/usr/bin/env python
urllib.request
import urllib.parse
import random, string
import mimetypes

def random_string (length):
    return ''.join (random.choice (string.ascii_letters) for ii in range (length + 1))

def encode_multipart_data (data, files):

    def get_content_type (filename):
        return mimetypes.guess_type (filename)[0] or 'application/octet-stream'

    def encode_field (field_name):
        return ('-----------------------------96951961826872/r/n',
                'Content-Disposition: form-data; name="%s"' % field_name, '/r/n'
                '', str (data [field_name]))

    def encode_file (field_name):
        filename = files [field_name]
        return ('-----------------------------96951961826872/r/n',
                'Content-Disposition: form-data; name="%s"; filename="%s"' % (field_name, filename), '/r/n'
                'Content-Type: %s' % get_content_type(filename), '/r/n/r/n'
                '', open (filename, 'rb').read ())

    lines = []
    for name in data:
        lines.extend (encode_field (name))
    for name in files:
        lines.extend (encode_file (name))
    lines.extend ('/r/n-----------------------------96951961826872--/r/n')
    body = b''
    for x in lines:
        if(type(x) == str):
            body += x.encode('ascii')
        else:
            body += x
    headers = {'Content-Type': 'multipart/form-data; boundary=---------------------------96951961826872',
               'Content-Length': str (len (body))}

    return body, headers

def main():
    url = 'http://awesome.go2live.cn'
    data = {}
    files = {'notePhoto': '01.jpg'}
    req = urllib.request.Request (url, *encode_multipart_data (data, files))
    response = urllib.request.urlopen(req)
if __name__ == '__main__':
    main()

下载文件

这个其实没啥。

from urllib import request
response = request.urlopen('http://stock.gtimg.cn/data/get_hs_xls.php?id=ranka&type=1&metric=chr')
with open('test1.xls', 'wb') as fd:
    fd.write(response.read())

Debug调试

这个有时候要看看具体的http请求是啥样的。
使用下面的方式会把收发包的内容在屏幕上打印出来。

from urllib import request

http_handler = request.HTTPHandler(debuglevel=1)
https_handler = request.HTTPSHandler(debuglevel=1)
opener = request.build_opener(http_handler,https_handler)
request.install_opener(opener)
response = request.urlopen('http://m.baidu.com')
print(response.read())

输出结果:

send: b’GET http://m.baidu.com HTTP/1.1\r\nAccept-Encoding…
reply: ‘HTTP/1.1 200 OK\r\n’
header: Cache-Control header: Content-Length header: Content-Type…

内容太长。打…省略。

requests库

相当强大的一个第三方库。支持以下特性:

  • International Domains and URLs
  • Keep-Alive & Connection Pooling
  • Sessions with Cookie Persistence
  • Browser-style SSL Verification
  • Basic/Digest Authentication
  • Elegant Key/Value Cookies
  • Automatic Decompression
  • Automatic Content Decoding
  • Unicode Response Bodies
  • Multipart File Uploads
  • HTTP(S) Proxy Support
  • Connection Timeouts
  • Streaming Downloads
  • .netrc Support
  • Chunked Requests
  • Thread-safety

官方文档

get请求

同样简单的很,两行代码即可。

import requests
print(requests.get('http://blog.go2live.cn').content)

post请求

requests的api好简单。get请求就调用get方法。post请求就调用post方法。

import requests

print(requests.post('http://awesome.go2live.cn/api/users',data={'name':'test1','email':'test@qq.com','passwd':'123456'}).content)

两行代码搞定。对比下urllib用了4行代码简单多了。。

而要传入json数据。再加个json参数就ok了。

自定义header

这个好简单,直接添加一个参数就可以了。

>>> url = 'https://api.github.com/some/endpoint'
>>> headers = {'user-agent': 'my-app/0.0.1'}

>>> r = requests.get(url, headers=headers)

处理cookie

cookie可以直接用响应对象的cookies变量拿到。

>>> url = 'http://example.com/some/cookie/setting/url'
>>> r = requests.get(url)

>>> r.cookies['example_cookie_name']
'example_cookie_value'

发送cookie可以加个cookies字典。

>>> url = 'http://httpbin.org/cookies'
>>> cookies = dict(cookies_are='working')

>>> r = requests.get(url, cookies=cookies)
>>> r.text
'{"cookies": {"cookies_are": "working"}}'

RequestsCookieJar可以设置得更复杂。

>>> jar = requests.cookies.RequestsCookieJar()
>>> jar.set('tasty_cookie', 'yum', domain='httpbin.org', path='/cookies')
>>> jar.set('gross_cookie', 'blech', domain='httpbin.org', path='/elsewhere')
>>> url = 'http://httpbin.org/cookies'
>>> r = requests.get(url, cookies=jar)
>>> r.text
'{"cookies": {"tasty_cookie": "yum"}}'

上传文件

上传文件也简单。用post方法,传个files参数。

>>> url = 'http://httpbin.org/post'
>>> files = {'file': ('report.xls', open('report.xls', 'rb'), 'application/vnd.ms-excel', {'Expires': '0'})}

>>> r = requests.post(url, files=files)
>>> r.text
{
  ...
    "files": {
        "file": "<censored...binary...data>"
    },
    ...
}

下载文件

感觉好简单。见下面。

import requests
url = 'http://stock.gtimg.cn/data/get_hs_xls.php?id=ranka&type=1&metric=chr'
r = requests.get(url) 
with open('test.xls', "wb") as code:
    code.write(r.content)

考虑到一次下载,可能文件过大,会消耗过多的内存。
requests推荐的下载方式如下:

import requests
r = requests.get('http://stock.gtimg.cn/data/get_hs_xls.php?id=ranka&type=1&metric=chr',stream=True)
with open('test.xls', 'wb') as fd:
    for chunk in r.iter_content(chunk_size=128):
            fd.write(chunk)

写在后面

对比了urllib和requests库。我决定以后都用requests库了。太方便了。
给个git地址,去fork下吧。

Tags: