爬虫中网络请求的那些事之urllib库

爬虫之网络请求中的那些事

image

urllib库

urllib库是python自带的内置库,不需要安装

urllib库是Python中一个最基本的网络请求库。可以模拟浏览器的行为,向指定的服务器发送一个请求,并可以保存服务器返回的数据

在Python3的urllib库中,所有和网络请求相关的方法,都被集到urllib.request模块中

request中常用的方法

urlopen函数

urlopen函数用来创建一个表示远程url的类文件对象,然后像本地文件一样操作这个类文件对象来获取远程数据

部分源码

def urlopen(url, data=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
            *, cafile=None, capath=None, cadefault=False, context=None):
    '''Open the URL url, which can be either a string or a Request object.

    *data* must be an object specifying additional data to be sent to
    the server, or None if no such data is needed.  See Request for
    details.
    '''

image

参数详解

  • url:请求的url
  • data:请求的data,如果设置了这个值,将变成post请求
  • 返回值:返回值是一个http.client.HTTPResponse对象,这个对象是一个类文件句柄对象。有read(size)、readline、readlines以及getcode等方法

句柄如何理解

数值上,是一个32位无符号整型值(32位系统下);逻辑上,相当于指针的指针;形象理解上,是Windows中各个对象的一个唯一的、固定不变的ID;作用上,Windows使用句柄来标识诸如窗口、位图、画笔等对象,并通过句柄找到这些对象

from urllib import request

'''
Open the URL url, which can be either a string or a Request object
'''
resp = request.urlopen('//www.sogou.com/')
print(resp)  # 返回一个HTTPresponse对象,<http.client.HTTPResponse object at 0x000002E0CADA0C50>
print(resp.read()) # 返回网页源代码
print(resp.read(1024)) # 返回1024字节大小的数据
print(resp.readline())  # 返回一行数据
print(resp.readlines())  # 返回多行数据
print(resp.getcode())   # 返回状态码:200

urlretrieve函数

这个函数可以方便的将网页上的一个文件保存到本地。以下代码可以非常方便的将搜狗的首页下载到本地:

from urllib import request
request.urlretrieve('//www.sogou.com/','sogou.html')

image

部分源码

def urlretrieve(url, filename=None, reporthook=None, data=None):
    """
    Retrieve a URL into a temporary location on disk.

    Requires a URL argument. If a filename is passed, it is used as
    the temporary file location. The reporthook argument should be
    a callable that accepts a block number, a read size, and the
    total file size of the URL target. The data argument should be
    valid URL encoded data.

    If a filename is passed and the URL points to a local resource,
    the result is a copy from local file to new file.

    Returns a tuple containing the path to the newly created
    data file as well as the resulting HTTPMessage object.
    """

image
image

格式

request.urlretrieve(url,文件名)

示例

from urllib import request

request.urlretrieve('//th.bing.com/th/id/OIP.NX9KpjV4Mk_e72YV_6nPNgDhEs?w=182&h=243&c=7&r=0&o=5&dpr=1.25&pid=1.7','haizei.jpg')

image

urlencode、parse_qs函数

urlencode可以把字典数据转换为URL编码的数据,如果我们使用浏览器发送一个请求的时候,url中携带汉字,那么浏览器就会自动进行了url编码

如何解码?使用parse_qs函数就可以解码了

部分源码

def urlencode(query, doseq=False, safe='', encoding=None, errors=None,
              quote_via=quote_plus):
'''Encode a dict or sequence of two-element tuples into a URL query string.'''
def parse_qs(qs, keep_blank_values=False, strict_parsing=False,
             encoding='utf-8', errors='replace', max_num_fields=None):
'''Parse a query given as a string argument.'''

示例1

from urllib import parse

data = {'name': 'Hammer', 'age': 18}
# 编码
res = parse.urlencode(data)
print(res)  # name=Hammer&age=18

# 解码
ret = parse.parse_qs(res)
print(ret) # {'name': ['Hammer'], 'age': ['18']}

示例2

浏览器会帮助我们进行url编码,那么我们使用pycharm进行爬取数据的时候,中文需要我们自己处理

from urllib import parse,request
data1 = {'q':'古力娜扎'}
res1 = parse.urlencode(data1)
print(res1,type(res1)) # q=%E5%8F%A4%E5%8A%9B%E5%A8%9C%E6%89%8E  <class 'str'>

res = request.urlopen('//www.bing.com/search?'+res1)
print(res.read())

示例3

这里需要注意的是,url字符串编码和encode方法编码的结果是不一样的

data = {'Name':'古力娜扎'}
res2 = parse.urlencode(data)
print(res2) # Name=%E5%8F%A4%E5%8A%9B%E5%A8%9C%E6%89%8E
qs = parse.quote('古力娜扎')
print(qs) # %E5%8F%A4%E5%8A%9B%E5%A8%9C%E6%89%8E

print('古力娜扎'.encode('utf8')) # b'\xe5\x8f\xa4\xe5\x8a\x9b\xe5\xa8\x9c\xe6\x89\x8e'

urlparse、urlsplit函数:

有时候拿到一个url,想要对这个url的各个组成部分进行分割,那么这个时候使用urlparse或者urlsplit来进行分割;

URL组成:

  • 协议部分:http/https
  • 域名部分:www.baidu.com / ip地址
  • 端口部分:跟在域名后面,以:作为分隔符,不写的时候使用端口,比如127.0.0.1:8000
  • 虚拟目录部分:从域名后的第一个“/”开始到最后一个“/”为止,是虚拟目录部分。虚拟目录也不是一个URL必须的部分。比如:127.0.0.1:8000/backend/3.html,backend就是虚拟目录
  • 文件名部分:从域名后的最后一个“/”开始到“?”为止,是文件名部分,如果没有“?”,则是从域名后的最后一个“/”开始到“#”为止,是文件部分,如果没有“?”“#”,那么从域名后的最后一个“/”开始到结束,都是文件名部分。文件名部分也不是一个URL必须的部分,如果省略该部分,则使用默认的文件名
  • 锚部分:从“#”开始到最后,都是锚部分
  • 参数部分:从?开始到#为止之间的部分为有参部分,又称为搜索部分或者查询部分,参数允许有多个,一般用&做分隔符

部分源码

# urlsplit函数
def urlsplit(url, scheme='', allow_fragments=True):
'''
    Parse a URL into 5 components:
    <scheme>://<netloc>/<path>?<query>#<fragment>
    Return a 5-tuple: (scheme, netloc, path, query, fragment).
'''
# urlparse函数
def urlparse(url, scheme='', allow_fragments=True):
    """Parse a URL into 6 components:
    <scheme>://<netloc>/<path>;<params>?<query>#<fragment>
    Return a 6-tuple: (scheme, netloc, path, params, query, fragment).
    Note that we don't break the components up in smaller bits
    (e.g. netloc is a single string) and we don't expand % escapes."""

注意

通过源码注释的对比,我们发现urlparse返回的参数比urlsplit返回的参数多一个params,params是url中的可选参数

示例

from urllib import parse
res = parse.urlparse('//zzk.cnblogs.com/my/s/blogpost-p?Keywords=python')
ret = parse.urlsplit('//zzk.cnblogs.com/my/s/blogpost-p?Keywords=python')
print(res,type(res))
print(ret,type(ret))
'''
结果:
ParseResult(scheme='https', netloc='zzk.cnblogs.com', path='/my/s/blogpost-p', params='', query='Keywords=python', fragment='') <class 'urllib.parse.ParseResult'>
SplitResult(scheme='https', netloc='zzk.cnblogs.com', path='/my/s/blogpost-p', query='Keywords=python', fragment='') <class 'urllib.parse.SplitResult'>
'''
# 返回的是一个对象,那么可以通过点的方法获取到该对象的属性值
print(res.scheme,res.params)

request.Request类

Request类也是用于处理网络请求,如果想要在请求的时候增加一些请求头,那么就必须使用request.Request类来实现,比如要增加一个User-Agent

部分源码:

class Request:
    def __init__(self, url, data=None, headers={},
                 origin_req_host=None, unverifiable=False,
                 method=None):

通过源码我们可以看到,必传的参数有url和headers(传字典)

from urllib import request
header = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36 Edg/99.0.1150.39'
}
res = request.Request('//www.cnblogs.com/',headers=header)
print(res) # <urllib.request.Request object at 0x000001959F347CF8>

ret = request.urlopen(res)
print(ret.read()) # 返回网页源代码

我们可以通过添加UA来获取更多的网页源代码··

实战:爬取猫眼票房数据

from urllib import request
import json

url = '//piaofang.maoyan.com/dashboard-ajax?orderType=0&uuid=7cba1a83-0143-4e91-b431-6e48cf2c4f57&timeStamp=1647662149259&User-Agent=TW96aWxsYS81LjAgKFdpbmRvd3MgTlQgMTAuMDsgV2luNjQ7IHg2NCkgQXBwbGVXZWJLaXQvNTM3LjM2IChLSFRNTCwgbGlrZSBHZWNrbykgQ2hyb21lLzk5LjAuNDg0NC41MSBTYWZhcmkvNTM3LjM2IEVkZy85OS4wLjExNTAuMzk%3D&index=442&channelId=40009&sVersion=2&signKey=fb7aa416ff3c852bfe69b01ee7952272'
header = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36 Edg/99.0.1150.39'
}


ret = request.Request(url,headers=header)
resp = request.urlopen(ret)
# print(resp.get('movieList').get('data').get('list').decode('utf8'))
data = resp.read().decode('utf8')
'''注意这里获取的是json格式数据'''
data = json.loads(data)
data_list = data.get('movieList').get('data').get('list')
for line in data_list:
    print(line.get('movieInfo'))

'''结果'''
{'movieId': 341955, 'movieName': '新蝙蝠侠', 'releaseInfo': '上映2天'}
{'movieId': 338380, 'movieName': '神秘海域', 'releaseInfo': '上映6天'}
{'movieId': 1446115, 'movieName': '长津湖之水门桥', 'releaseInfo': '上映47天'}
{'movieId': 1405863, 'movieName': '熊出没·重返地球', 'releaseInfo': '上映47天'}
{'movieId': 1433696, 'movieName': '这个杀手不太冷静', 'releaseInfo': '上映47天'}
{'movieId': 1300821, 'movieName': '花束般的恋爱', 'releaseInfo': '上映26天'}
{'movieId': 1383307, 'movieName': '奇迹·笨小孩', 'releaseInfo': '上映47天'}
{'movieId': 1435147, 'movieName': '我们的冬奥', 'releaseInfo': '上映29天'}
{'movieId': 1309268, 'movieName': '可不可以你也刚好喜欢我', 'releaseInfo': '上映9天'}
{'movieId': 1265626, 'movieName': '印度女孩', 'releaseInfo': '上映2天'}
{'movieId': 1367251, 'movieName': '狙击手', 'releaseInfo': '上映47天'}
{'movieId': 1445469, 'movieName': '喜羊羊与灰太狼之筐出未来', 'releaseInfo': '上映47天'}
{'movieId': 1303796, 'movieName': '“炼”爱', 'releaseInfo': '上映2天'}
{'movieId': 1405621, 'movieName': '玛纳斯人之失落的秘境', 'releaseInfo': '上映首日'}
{'movieId': 1214749, 'movieName': '纽约的一个雨天', 'releaseInfo': '上映23天'}
{'movieId': 1302341, 'movieName': '*小道', 'releaseInfo': ''}
{'movieId': 1247341, 'movieName': '妖怪手表:永远的朋友', 'releaseInfo': '上映8天'}
{'movieId': 1396517, 'movieName': '年少有你', 'releaseInfo': '上映2天'}
{'movieId': 1338396, 'movieName': '如果有一天我将会离开你', 'releaseInfo': '上映9天'}

实战:爬取别逗了网站信息

from urllib import request,parse


for page  in range(9270,9273):
    header = {
        'user - agent': 'Mozilla / 5.0(Windows NT 10.0;Win64; x64) AppleWebKit /537.36KHTML, likeGecko) Chrome/99.0.4844.51 Safari / 537.36 Edg / 99.0.1150.39'
    }
    url = f'//www.biedoul.com/index/{page}/'
    request.Request(url=url,headers=header)
    res = request.urlopen(url)
    request.urlretrieve(url,f'{page}.html')
    page_data = res.read().decode('utf8')
    print(page_data)

ProxyHandler处理器(代理设置)

很多时候我们玩游戏开外挂,飞天遁地透视····最后被封号~我们爬虫也类似,老是不断的去请求,人家也会烦,封你的ip;

在我们爬取数据的时候如果对服务器请求的次数过多的时候,服务器也不傻会识别出来是非正常请求,一般采取我措施是封IP措施,这时候我们的IP就不能正常访问,可以通过“换小号继续玩”;

👉查看http请求的一些参数://httpbin.org

查看没有使用代理的ip地址://httpbin.org/ip

image

from urllib import request
# No Proxy
url = '//httpbin.org/ip'
resp = request.urlopen(url)
print(resp.read()) # b'{\n  "origin": "112.64.68.252"\n}\n'

设置代理ip

通过设置代理ip解决封ip问题,原理是通过请求代理服务器,然后通过代理服务器请求目标服务器,然后返回数据

image

常用的代理有:

from urllib import request

# Use Proxy
url = '//httpbin.org/ip'
# 使用ProxyHandler,传入代理构造一个handler
handler = request.ProxyHandler({'http':'106.15.197.250:8001'})
# 使用上面的handler构造opener
opener = request.build_opener(handler)
# 使用opener发送请求
resp = opener.open(url)
print(resp.read()) # b'{\n  "origin": "106.15.197.250"\n}\n'
'''我们可以看到ip并不是以前的ip了,这样就设置成功了,但是缺点就是免费的不稳定'''

什么是cookie?

指某些网站为了辨别用户身份、进行 session 跟踪而储存在用户本地终端上的数据,cookie存储的数据量有限,不同的浏览器有不同的存储大小,但一般不超过4KB。因此使用cookie只能存储一些小量的数据;

👉Cookie、Session

我们知道HTTP的四大特征:

  • 基于请求响应:服务端永远不会主动给客户端发消息 必须是客户端先发请求,如果想让服务端主动给客户端发送消息可以采用其他网络协议;
  • 基于TCP/UDP、IP作用于应用层之上的协议
  • 无状态:不保存客户端的状态信息,不知道是属于哪一个用户发过来的请求,每次过来都仿佛第一次看见(待你如初恋)
  • 无连接/短连接:两者请求响应之后立刻断绝关系

image

image

  • Cookie的出现就是为了解决无状态,用来辨别身份登录一次,下一次来携带Cookie就知道是谁了;

cookie的格式

Set-Cookie: NAME=VALUE;Expires/Max-age=DATE;Path=PATH; Domain=DOMAIN_NAME;SECURE

参数意义

  • NAME:cookie的名字。
  • VALUE:cookie的值。
  • Expires:cookie的过期时间。
  • Path:cookie作用的路径。
  • Domain:cookie作用的域名。
  • SECURE:是否只在https协议下起作用。

image

实战:爬虫使用Cookie实现模拟登录

from urllib import request

url = '//www.zhihu.com/hot'
headers = {
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36',
    'cookie': '_zap=8812b5e0-0b83-4ce1-9ac5-c913debd77c5; d_c0="AIDel4RQJxSPTp9hk0DqVuFjlrklYMgxsHM=|1639015736"; _9755xjdesxxd_=32; YD00517437729195%3AWM_TID=cfvcktVDh9REFEQRAVJ%2B97fYipUcBdd5; Hm_lvt_98beee57fd2ef70ccdd5ca52b9740c49=1642496751,1642507682,1642515429,1642954789; _xsrf=3S5Nqf0sEQkI3Cln9pmAI6lgYnk6Wxoh; __snaker__id=IKbmra7CqY74Sn2D; YD00517437729195%3AWM_NI=utbfMp3td5HSlvkH%2FYeFIlzsYQABzUsxftqGf3FISbVKXYoqU0oCNKXIYIGU4%2Bb1V3MvuX1jFJyMVSE8dQ2rCDro8DGNd1ZNcs%2BOCpPc8OSf67cfOoqOnWaAR%2B3MfelJdkU%3D; YD00517437729195%3AWM_NIKE=9ca17ae2e6ffcda170e2e6ee9aae429789fa90e44083b08ab7c55e828b9aaaf87efba899a9ef658ca681d2fb2af0fea7c3b92ab7acae87d07cf1acacd6cb59b0aaa5a9ae53a79a878df9799b8f8fa3f95fb1a8aa95f44da6ed8aaad26a969598baee7b97f0a1a4cd419c9797aac65ff8f5aed2d5398faabbb9b4628696b7aed55987e8acb2b17a819cbe90bb508592acd2d946a1ade5b0c27c94bdf8b0d26ef6e9ad87ce65b391a2b8e44b92f5fdaee56fb096ac8cd837e2a3; gdxidpyhxdE=V9Pnc%5CLTb2HTGVfNr3WCQW%2FW%5CCPZOK0XnGxAObaD1RC8G7lwZLG5UoGtylAs8UsXb6no7b1wQyxAg%2FPw1xBoaf%2BUulfG9%2BxkiQ0JwMdYZodeVJ6V1lpw1YfGScmJnPoMVyVrarJ%5CzZja6%2FMUjsb4IjY1wB2gyVrmaOoSKxvHeJfUiKGZ%3A1647680377803; captcha_session_v2=2|1:0|10:1647679931|18:captcha_session_v2|88:RlMvRkdSRUU4dCtjMnlPRmVnN2JjQ2EzeCtJT3p1YkViREM2L3R1TlByMXdna0RRRWpiSWVwMUd6cWFxa2dKdQ==|b723dce9ca2e90df4737d71cbe58ceb6006083307013e7b44c5557d89ec4ff2f; captcha_ticket_v2=2|1:0|10:1647679942|17:captcha_ticket_v2|704:eyJ2YWxpZGF0ZSI6IkNOMzFfUHZ1dGhpeHdVa3VCcGM3S3Z4MDBwcGVDd2ZLV0dLVWtCQVRpZlFuUGNQZi15U3hZMnJQMkQxYlE0ZTlYSzlrTTdzTnVuaXZySjZyb3RYcnlwa0hUODhVbFFzU1poT0NIR0FVYU56YlJPc3lEYzlGbldQbFp2VGRVTXBMX3J4djVVbUhtd1VWRGI4MFB1QUhyVUxWUzhDVGpYN1lHaHBYRDBJQndwcVZUZDFsUWVqTXFWUHE0MnFicGhGSEFQZFVsb0dXd1FYd3g3X21VekdUd0ZUWllhNUtoam9vT1NwMHQxUUxzWEZXZmN2d0N0Y3NLamxvZ244LTRxd2t2Zk41WVZpc3dlOVJKUkwyZ21vNWFBcy03N0ZqbXFBZXNiRlpiRklIeC5ndnRkSUJlUi5CR2hQR05VQnMxWGRHUFh3aG11TGlrdEVhbnNoR1dzN2RLZXVzWks1eG1zMXVSeXdqaGR4VW1GeEVIc1VNZ2xQMXU1NnNyVmxCWUdTSmFESlJHNU9zUnlDVG1mTHE3OW92MFdiV1lYQzRmdXFXOVVaMnlHLjRKWEFNQTZVeHh6cXBGcExjLWtWd1FKMG9UbG03OFVyYmlRMmFJeVg4emhJVkd4WU5uRnRmNmo4NFdjZG54SS5WQ1F6LXhjRWNuS21hNi1CVVJTcHVULXNwMyJ9|deb333726cc56b3900502d1c8ecd3dd1d91e56ba4090ee683a0af86c18986274; z_c0=2|1:0|10:1647679962|4:z_c0|92:Mi4xaGlpOEJBQUFBQUFBZ042WGhGQW5GQ1lBQUFCZ0FsVk4ydWNpWXdCUVpFSXBYM2JBWTdMSnFEUHZ6VnhIT01HZXd3|63b889aa3a6ee8c58b1e84d9a7de39bfa84a8b8c74f14775399c623f0d73f11a; q_c1=87cdf895ba3444b8bdf4fc2bf51766c2|1647679962000|1647679962000; NOT_UNREGISTER_WAITING=1; tst=h; '
}
hdr = request.Request(url, headers=headers)
resp = request.urlopen(hdr)
print(resp.read().decode('utf8'))

http.cookiejar模块

介于每次处理cookie比较麻烦还得从浏览器上copy过来使用,现在使用cookiejar模块来进行优化;

该模块主要的类有CookieJar、FileCookieJar、MozillaCookieJar、LWPCookieJar。这四个类的作用分别如下:

  • CookieJar:管理HTTP cookie值、存储HTTP请求生成的cookie、向传出的HTTP请求添加cookie的对象。整个cookie都存储在内存中,对CookieJar实例进行垃圾回收后cookie也将丢失;
  • FileCookieJar (filename,delayload=None,policy=None):从CookieJar派生而来,用来创建FileCookieJar实例,检索cookie信息并将cookie存储到文件中。filename是存储cookie的文件名。delayload为True时支持延迟访问访问文件,即只有在需要时才读取文件或在文件中存储数据;
  • MozillaCookieJar (filename,delayload=None,policy=None):从FileCookieJar派生而来,创建与Mozilla浏览器 cookies.txt兼容的FileCookieJar实例;
  • LWPCookieJar (filename,delayload=None,policy=None):从FileCookieJar派生而来,创建与libwww-perl标准的 Set-Cookie3 文件格式兼容的FileCookieJar实例;
from urllib import request
from urllib import parse
from http.cookiejar import  CookieJar

# 登录://i.meishi.cc/login.php?redirect=https%3A%2F%2Fwww.meishij.net%2F
#个人网页//i.meishi.cc/cook.php?id=13686422

headers={
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36'
}

#1.登录
#1.1 创建cookiejar对象
cookiejar = CookieJar()
#1.2 使用cookiejar创建一个HTTPCookieProcess对象
handler = request.HTTPCookieProcessor(cookiejar)
#1.3 使用上一步的创建的handler创建一个opener
opener = request.build_opener(handler)
#1.4 使用opener发送登录请求  (账号和密码)

post_url = '//i.meishi.cc/login.php?redirect=https%3A%2F%2Fwww.meishij.net%2F'
post_data = parse.urlencode({
    'username':'[email protected]',
        'password':'wq15290884759.'
})
'''如果登录后发送的是GET请求,那么就不需要携带登录信息直接获取'''
req = request.Request(post_url,data=post_data.encode('utf-8'))
opener.open(req)


#2.访问个人网页
url = '//i.meishi.cc/cook.php?id=13686422'
rq = request.Request(url,headers=headers)
resp = opener.open(rq)
print(resp.read().decode('utf-8'))

Cookie加载与保存

from urllib import request
from http.cookiejar import MozillaCookieJar

# 保存
cookiejar = MozillaCookieJar('cookie.txt')
handler = request.HTTPCookieProcessor(cookiejar)
opener = request.build_opener(handler)
resp = opener.open('//www.baidu.com')
# resp = opener.open('//www.httpbin.org/cookies/set/course/abc')

cookiejar.save(ignore_discard=True,ignore_expires=True)

# ignore_discard=True  即使cookies即将被丢弃也要保存下来
# ignore_expires=True  如果cookies已经过期也将它保存并且文件已存在时将覆盖
from urllib import request
from http.cookiejar import MozillaCookieJar

#加载
cookiejar = MozillaCookieJar('cookie.txt')
cookiejar.load()

for cookie in cookiejar:
    print(cookie)