urllib库

urllib是python内置的网络库

一.urllib的主要功能

urllib分为4个模块
1.request:最基本的HTTP请求模块,可以用来发送HTTP请求,并接收服务端的响应数据。这个过程就像在浏览器地址栏输入URL,然后按下Enter键一样
2.error:异常处理模块,如果出现请求错误,可以捕获这些异常,然后根据实际情况,进行重试或者直接忽略,或进行其他操作。
3.parse:工具模块,提供了很多处理URL的API,如拆分、解析、合并
4.robotparser:主要用来识别网站的robots.txt文件,然后判断哪些文件可以抓取,哪些文件不可以抓取

二:发送请求与获得响应

1.get请求

import urllib.request

# urlopen可以用http也可以用https
response=urllib.request.urlopen('https://www.jd.com')
# 输出响应的数据类型
print('response的类型:',type(response))
# 输出响应状态码、响应消息、和HTTP版本
print('status:',response.status,' msg:',response.msg,' version:', response.version)
# 输出所有的响应头信息
print('headers:',response.getheaders())
# 输出Content-Type信息
print('headers.Content-Type',response.getheader('Content-Type'))
# 输出url链接返回响应的HTML代码
print(response.read().decode('utf-8'))

2.post请求

import urllib.request
# urlencode方法将字典类型的表单转换为字符串形式的表单
data=bytes(urllib.parse.urlencode({'name':'Bill','age':30}),encoding='utf-8') # data参数是字节类型byte()来处理
response=urllib.request.urlopen('http://httpbin.org/post',data=data) # 发送POST请求用data参数命名
print(response.read().decode('utf-8')) # 这里并不需要显示指出是POST请求,缘由是这个url是测试HTTP POST请求的网址

3.请求超时

import urllib.request
import socket
import urllib.error
try:
    response=urllib.request.urlopen('http://httpbin.org/get',timeout=0.1) # 由于绝大网站不太可能在0.1秒内
    # 响应客户端的请求,所以超时
except urllib.error.URLError as e:
    if isinstance(e.reason,socket.timeout):
        print('超时')
print("继续爬虫其他的工作")

4.加入User-Agent和Host请求头和自定义请求头who

from urllib import request,parse
# 定义要提交HTTP请求的URL
url = 'http://httpbin.org/post'

# 定义HTTP请求头,其中who是自定义的请求字段
headers = {
    'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36',
    'Host':'httpbin.org',


    'who':'Python Scrapy'
}
# 定义表单数据
dict = {
    'name':'Bill',
    'age':30
}
# 将表单数据转化为bytes形式
data = bytes(parse.urlencode(dict),encoding='utf-8')
# 创建request对象,通过request类的构造方法指定了表单数据和HTTP请求头
req = request.Request(url = url,data=data,headers=headers,method="POST")
# urlopen函数通过request对象向服务端发送HTTP POST
response=request.urlopen(req)
print(response.read().decode('utf-8'))

5.设置中文请求头

from urllib import request
from urllib.parse import unquote,urlencode
import base64

# base64.b64encode编码 ;base64.b64decode解码

url = 'http://httpbin.org/post'
headers = {
    'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36',
    'Host':'httpbin.org',
    'Chinese1':urlencode({'name':'李宁'}),
    # 设置中文HTTP请求头,用base64编码格式
    'MyChinese':base64.b64encode(bytes('这是中文HTTP请求头',encoding='utf-8')),
    'who':'Python Scrapy'
}
dict = {
    'name':'Bill',
    'age':30
}
data = bytes(urlencode(dict),encoding='utf-8')
req = request.Request(url = url,data=data,headers=headers,method="POST")
# 通过add_header方法添加中文HTTP请求头,url编码格式
req.add_header('Chinese2',urlencode({"国籍":"中国"}))
response=request.urlopen(req)
# 获得服务端的响应信息
value = response.read().decode('utf-8')
print(value)
import json
# 将返回值转化为json对象
responseObj = json.loads(value)
# 解码url编码的HTTP请求头
print(unquote(responseObj['headers']['Chinese1']))
print(unquote(responseObj['headers']['Chinese2']))
print(str(base64.b64decode(responseObj['headers']['Mychinese']),'utf-8'))

6.请求基础验证页面

6.1编写个支持验证页面的Web服务器

这个web服务器可以生成一个表单验证,只有通过这个验证才能访问下一个页面。表单验证也是基础验证,是HTTP验证的一种

from flask import Flask  # 
from flask import request
import base64
app = Flask(__name__)
# 判断客户端是否提交了用户名和密码,如果未提交,设置状态码为401,并设置WWW-Authenticate响应头
# auth:Authorization请求头字段的值,response:响应对象

def hasAuth(auth,response):
    if auth == None or auth.strip() == "":
        # 设置响应状态码为401
        response.status_code = 401
        # 设置响应头的WWW-Authenticate字段,其中localhost是需要验证的范围
        response.headers["WWW-Authenticate"] = 'Basic realm="localhost"'
        # 返回False,表示客户端未提交用户名和密码
        return False
    return True

# 根路由
@app.route("/") # 这个函数是把传输的密码可以用编码的方式写入headers头中,这里是Authorization中
def index():
    # 创建响应对象,并指定未输入用户名和密码(单击"取消”按钮)或输入错误后的返回内容
    response = app.make_response('username or password error')
    # 输出所有的HTTP请求头
    print(request.headers)
    # 得到Authorization请求头的值
    auth = request.headers.get('Authorization') 
    # 得到Authorization请求头的值
    print('得到Authorization请求头的值:',auth) 
    if hasAuth(auth, response):   # 把请求头的值和response放进这个函数中
        # 将用户名和密码按Base64编码格式解码,这里按空格拆分成两个值,第一个是Basic,第二个是Base64编码后的用户名和密码

        auth = str(base64.b64decode(auth.split(' ')[1]),'utf-8')
        # 用户名和密码之间用冒号(:)分隔,所以需要将他们拆开
        values = auth.split(':')
        # 获取用户名
        username = values[0]
        #获取密码
        password = values[1]
        print('username:',username) # 他们又是从哪获取密码的呢
        print('password:',password)
        # 判断用户名和密码是否正确,如果正确,返回success
        if username == 'bill' and password == '1234':
            return "success"
    return response


if __name__ == '__main__':
    app.run()

6.2用户名和密码通过HTTP请求头的Authorization字段发送给服务器

from urllib import request
import base64


url = 'http://localhost:5000'
headers = {
    'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36',
    'Host':'localhost:5000',
    'Authorization': 'Basic ' + str(base64.b64encode(bytes('bill:1234','utf-8')),'utf-8'),

}
req = request.Request(url = url,headers=headers,method="GET")
response=request.urlopen(req)
print(response.read().decode('utf-8'))

6.3采用HTTPPasswordMgrWithDefaultRealm对象封装请求字段数据

from urllib.request import HTTPPasswordMgrWithDefaultRealm,HTTPBasicAuthHandler,build_opener
from urllib.error import URLError
username = 'bill'
password = '1234'
url = 'http://localhost:5000'

p = HTTPPasswordMgrWithDefaultRealm() # HTTPPasswordMgrWithDefaultRealm对象封装请求字段数据
# 封装realm,url、用户名和密码
p.add_password('localhost',url,username,password)
auth_handler = HTTPBasicAuthHandler(p) # HTTPBasicAuthHandler用于处理管理认证
# 发送http请求
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)

7.搭建代理与使用代理(创建ProxyHandler对象,并指定HTTP和HTTPS代理的IP和端口号)

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


# 创建ProxyHandler对象,并指定HTTP和HTTPS代理的IP和端口号
proxy_handler = ProxyHandler({
    #'http':'http://103.100.96.174:53281',
    'http':'http://182.86.191.16:24695',
    'https':'https://182.86.191.16:24695'
})

opener = build_opener(proxy_handler)
try:
    #response = opener.open('http://blogjava.net')
    response = opener.open('https://www.jd.com')
    print(response.read().decode('utf-8'))
except URLError as e:
    print(e.reason)

8.Cookie的读写过程

8.1用Falsk编写一个Web服务器

该Web服务器读取客户端发送过来的Cookie,以及将Cookie写入客户端

from flask import Flask # flask果然是小巧可扩展
from flask import request
app = Flask(__name__)
@app.route("/readCookie")
def readCookie():
    print(request.cookies) # 这个是读取刚刚写入的Cookie,或者还可以从用户的请求中读取Cookie
    print(request.cookies.get('MyCookie'))
    return "hello world"

@app.route("/writeCookie") # route后跟着的因该是url路径
def writeCookie():
    response = app.make_response('write cookie')
    response.set_cookie("id", value="12345678") # 这里竟然可以直接写入cookie
    return response
if __name__ == '__main__':
    app.run()

8.2获取Cookie

urllib获取Cookie真的很麻烦
首先读取Cookie需要创建http.cookiejar.CookieJar类的实例a,然后再创建urllib.request.HTTPCookieProcessor类的实例b,并将a作为实例b的参数传进。而build_opener的参数就是实例b,所以,当build_open从服务端响应某个数据时就会读取服务端发送过来的Cookie然后保存在实例a中,返回的是个字典

import http.cookiejar
import  urllib.request
# 创建CookieJar对象
cookie = http.cookiejar.CookieJar()
# 创建HTTPCookieProcessor对象
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler) # build_opener从服务器端获取响应数据时就会读取服务端发送过来的Cookie
# 给http://www.baidu.com发送请求,并获得响应数据
response = opener.open('http://www.baidu.com') # open()中传入url就可以获取响应了
print('------http://www.baidu.com--------')
# 输出服务器端发送的所有Cookie
for item in cookie:
    print(item.name + '=' + item.value)
# 下面的代码用同样的方式访问CookieServer,并输出返回的Cookie
print('------http://127.0.0.1:5000/writeCookie--------')
cookie = http.cookiejar.CookieJar()
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open('http://127.0.0.1:5000/writeCookie')
for item in cookie:
    print(item.name + '=' + item.value)

8.3读取Cookie并把Cookie保存起来

可以用MozillaCookieJar类和LWPCookieJar类再获得Cookie的同时讲Cookie分别保存成Mozilla浏览器格式和libwww-perl(LWP)格式,再创建MozillaCookieJar类和LWPCookieJar类的实例过程中需要传入Cookie文件名。其他的使用方法与.CookieJar类基本一样

import http.cookiejar
import urllib.request
filename1 = 'cookies5.txt' 
filename2 = 'cookies4.txt'
# 创建MozillaCookieJar对象
cookie1 = http.cookiejar.MozillaCookieJar(filename1) # 这个可以自动创建文件
# 创建LWPCookieJar对象
cookie2 = http.cookiejar.LWPCookieJar(filename2)


handler1 = urllib.request.HTTPCookieProcessor(cookie1)
handler2 = urllib.request.HTTPCookieProcessor(cookie2)
opener1 = urllib.request.build_opener(handler1)
opener2 = urllib.request.build_opener(handler2)
opener1.open('http://www.baidu.com')
opener2.open('http://www.baidu.com')
# 将Cookie保存成MozillaCookieJar格式
cookie1.save(ignore_discard=True,ignore_expires=True)
# 将Cookie保存成LWPCookieJar格式
cookie2.save(ignore_discard=True,ignore_expires=True)

8.4创建一个Cookie.txt文件,并自定义2个Cookie,然后通过load方法装在Cookie.txt文件中的Cookie

import http.cookiejar
import urllib.request
filename = 'cookies.txt'
cookie = http.cookiejar.LWPCookieJar()
# 装载cookies.txt文件,由于使用了LWPCookieJar读取Cookie,所以Cookie文件必须是LWP格式
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://127.0.0.1:5000/readCookie')
print(response.read().decode('utf-8'))

三.异常处理

1.URLError

from urllib import request,error # 异常类都在urllib的error模块中定义

try:
    response = request.urlopen('http://www.jd123.com/test.html')
except error.URLError as e:
    print(e.reason) # Not Found


try:
    response = request.urlopen('https://geekori.com/abc.html')
except error.URLError as e:
    print(e.reason) # Not Found


try:
    response = request.urlopen('https://geekori123.com/abc.html')
except error.URLError as e:
    print(e.reason) # [Errno 11001] getaddrinfo failed

    
try:
    response = request.urlopen('https://bbbccc.com',timeout=2)
except error.URLError as e:
    print(e.reason) # [Errno 11001] getaddrinfo failed

2.HTTPError

from urllib import request,error
import socket
try:
    response = request.urlopen('http://www.jd123.com/test.html')
except error.HTTPError as e:
    print(type(e.reason))
    print(e.reason,e.code,e.headers)
try:
    response = request.urlopen('https://www.baidu.com',timeout=0.01)
except error.HTTPError as e:
    print('=====')
    print('error.HTTPError:', e.reason) # 这个语句没有抛出
except error.URLError as e:
    # <class 'socket.gaierror'> gaierror后端抛出的异常这个报错直接是域名错了,url不存在
    print(type(e.reason)) 
    print('error.URLError:', e.reason) # error.URLError: timed out
    if isinstance(e.reason,socket.timeout):
        print('超时错误')

四。解析链接

1.拆分与合并URL

urlparse函数用于拆分URL,也就是将URL分解成不同的部分
urlunparse合并函数

from urllib.parse import urlparse,urlunparse

# urlparse用于拆分URL
result = urlparse('https://search.jd.com/Searchprint;hello?keyword=Python从菜鸟到高手&enc=utf-8#comment')

print(type(result),result)
print('--------分割线----------')
# 将url拆成6部分
print('scheme:',result.scheme) # scheme: https
print('netloc:',result.netloc) # netloc: search.jd.com
print('path:',result.path)  #path: /Searchprint
print('params:',result.params) # params: hello
print('query:',result.query) # query: keyword=Python从菜鸟到高手&enc=utf-8
print('fragment:',result.fragment) # fragment: comment
print('-----------------')
result = urlparse('search.jd.com/Searchprint;hello?keyword=Python从菜鸟到高手&enc=utf-8#comment',scheme='ftp',allow_fragments=False)

print('scheme:',result.scheme) # scheme: ftp
print('fragment:',result.fragment) # 表示false则表示忽略fragment部分,默认参数值是True

print('----------------')
data = ['https','search.jd.com','Searchprint','world','keyword=Python从菜鸟到高手&enc=utf-8','comment']
# urlunparse函数合并url不同的部分
print(urlunparse(data)) # https://search.jd.com/Searchprint;world?keyword=Python从菜鸟到高手&enc=utf-8#comment

2.另一种拆分与合并URL

urlsplit函数与urlparse函数类似,只是将path与params看作一个整体,也就是urlsplit函数会将url拆分为5部分。
urlunsplit函数与urlunparse函数类似,只不过需要指定一个包含5个元素的可迭代对象,而urlunparse函数需要包含6个元素的可迭代对象。

from urllib.parse import urlsplit,urlunsplit

# 将Url拆成5部分
result = urlsplit('https://search.jd.com/Searchprint;hello?keyword=Python从菜鸟到高手&enc=utf-8#comment')

print('scheme:',result.scheme)
print('netloc:',result.netloc)
print('path:',result.path)# 少了params 与parse()函数相比
print('query:',result.query)
print('fragment:',result.fragment)
print('-----------------')
# 将Url拆成5部分,并指定默认的scheme,以及不考虑fragment部分
result = urlsplit('search.jd.com/Searchprint;hello?keyword=Python从菜鸟到高手&enc=utf-8#comment',scheme='ftp',allow_fragments=False)

print('scheme:',result.scheme) # scheme可选参数,如果url没有带协议(https、httpftp等),那么scheme参数的值就会作为默认协议。该参数默认值为空字符串
print('fragment:',result.fragment)

print('----------------')
data = ['https','search.jd.com','Searchprint;world','keyword=Python从菜鸟到高手&enc=utf-8','comment']

print(urlunsplit(data)) # 合并url可以把

3.连接URL

from urllib.parse import urljoin

"""
urljoin函数的第一个参数是base_url,是一个基URL,
只能设置scheme、netloc和path;第二个参数是url,
如果第二个参数不是一个完整的url,则会将第二个参数的值加到第一个参数后面,自动添加/
如果第二个参数是完整的url就直接返回他

"""
# 输出https://www.jd.com/index.html
print(urljoin('https://www.jd.com','index.html'))
# 输出https://www.taobao.com
print(urljoin('https://www.jd.com','https://www.taobao.com'))
# 输出https://www.taobao.com/index.html
print(urljoin('https://www.jd.com/index.html','https://www.taobao.com/index.html'))
# 输出https://www.jd.com/index.php?name=Bill&age=30
print(urljoin('https://www.jd.com/index.php','?name=Bill&age=30'))
# 输出https://www.jd.com/index.php?name=Bill
print(urljoin('https://www.jd.com/index.php?value=123','?name=Bill'))

4.url编码(urlencode)

urljoin函数连接来组成完整的URL

from urllib.parse import urlencode,urljoin
# urlencode参数对URL进行编码,对中文的url非常有用
# 中文转码后的格式是%xx 其中xx表示的是2位16进制数
params = {
    'name':'王军',
    'country':'China',
    'age':30
}

base_url = 'https://www.google.com?'
#url = base_url + urlencode(params)
url = urljoin(base_url,'?' + urlencode(params))
print(url)

5.解码与编码(quote和unquote)

from urllib.parse import quote,unquote
keyword = '李宁'
url = 'https://www.baidu.com/s?wd=' + quote(keyword)
print(url)
url = unquote(url)
print(url)

五。Robots协议

待补(具体详情)

1.RobotFileParser解析robots.txt文件

from urllib.robotparser import RobotFileParser # 解析robots.txt文件
from urllib import request
robot = RobotFileParser()


url = 'https://www.jianshu.com/robots.txt'
headers = {
    'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36',
    'Host': 'www.jianshu.com',
}
req = request.Request(url=url, headers=headers)

# 抓取robots.txt文件的内容,并提交给parse方法进行分析
robot.parse( request.urlopen(req).read().decode('utf-8').split('
'))
# 输出True
print(robot.can_fetch('*','https://www.jd.com')) # can_fetch方法判断网站中的某一个url更具Robots协议是否有权抓取
# 输出True
print(robot.can_fetch('*','https://www.jianshu.com/p/92f6ac2c350f'))
# 输出False
print(robot.can_fetch('*','https://www.jianshu.com/search?q=Python&page=1&type=note'))

努力拼搏吧,不要害怕,不要去规划,不要迷茫。但你一定要在路上一直的走下去,尽管可能停滞不前,但也要走。
原文地址:https://www.cnblogs.com/wkhzwmr/p/15224775.html