基础库urllib的使用
学习爬虫,其基本的操作便是模拟浏览器向服务器发出请求,那么我们需要从哪个地方做起呢?请求需要我们自己构造吗?我们需要关心请求这个数据结构怎么实现吗?需要了解 HTTP、TCP、IP层的网络传输通信吗?需要知道服务器如何响应以及响应的原理吗?
可能你无从下手,不过不用担心,Python的强大之处就是提供了功能齐全的类库来帮助我们实现这些需求。最基础的 HTTP库有 urllib、requests、httpx等。
拿 urllib 这个库来说,有了它,我们只需要关心请求的链接是什么,需要传递的参数是什么,以及如何设置可选的请求头,而无须深人到底层去了解到底是怎样传输和通信的。有了urllib 库,只用两行代码就可以完成一次请求和响应的处理过程,得到网页内容,是不是感觉方便极了?
本篇博客我们先理解urllib的使用。
urllib简介
首先介绍一个 Python 库,叫作 urllib,利用它就可以实现 HTTP 请求的发送,而且不需要关心 HTTP协议本身甚至更底层的实现,我们要做的是指定请求的 URL、请求头、请求体等信息。此外 urllib 还可以把服务器返回的响应转化为Python对象,我们通过该对象便可以方便地获取响应的相关信息,如响应状态码、响应头、响应体等。
在Python2中,有urllib 和urllib2 两个库来实现HTTP请求的发送。而在Python3中,urllib2库已经不存在了,统一为了 urllib。而且现在基本上没有人使用Python2了。
首先,我们了解一下 urllib 库的使用方法,它是 Python 内置的 HTTP 请求库,也就是说不需要额外安装,可直接使用。urllib库包含如下4个模块。
- request:这是最基本的 HTTP 请求模块,可以模拟请求的发送。就像在浏览器里输人网址然后按下回车一样,只需要给库方法传人 URL以及额外的参数,就可以模拟实现发送请求的过程了。
- error:异常处理模块。如果出现请求异常,那么我们可以捕获这些异常,然后进行重试或其他操作以保证程序运行不会意外终止。
- parse:一个工具模块。提供了许多 URL的处理方法,例如拆分、解析、合并等。
- robotparser:主要用来识别网站的 robots.txt 文件,然后判断哪些网站可以爬,哪些网站不可以,它其实用得比较少。
发送请求
使用 urllib 库的 request 模块,可以方便地发送请求并得到响应。我们先来看下它的具体用法。
- urlopen
ur1lib.request 模块提供了最基本的构造 HTTP请求的方法,利用这个模块可以模拟浏览器的请求发起过程,同时它还具有处理授权验证(Authentication)、重定向(Redirection)、浏览器 Cookie 以及其他一些功能。
下面我们体会-下 request 模块的强大之处。这里以百度官网为例,我们把这个网页抓取下来:
import urllib.requestresponse = urllib.request.urlopen('https://www.python.org')
print(response.read().decode('utf-8'))
一运行直接报错了:
Traceback (most recent call last):File "D:\projects\scrapy-demo\main.py", line 4, in <module>print(response.read().decode('utf-8'))
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x8b in position 1: invalid start byte
也就是我们请求回来的格式并不是utf-8,那么我们需要看看它源码中的编码格式究竟是什么:
import urllib.request
response = urllib.request.urlopen('https://www.python.org')
content_encoding = response.getheader('Content-Encoding')
print(content_encoding)
结果是:
gzip
很早之前这个网页返回的就是utf-8,现在格式变了,那么我们只要解压就可以
import urllib.request
import gzipresponse = urllib.request.urlopen('https://www.python.org')
data = gzip.decompress(response.read())
print(data.decode('utf-8'))
运行结果如下:
这里我们只用了几行代码,便完成了 Python 官网的抓取,输出了其网页的源代码。得到源代码之后,我们想要的链接、图片地址、文本信息不就都可以提取出来了吗?
接下来,看看返回的响应到底是什么。利用type 方法输出响应的类型:
import urllib.requestresponse = urllib.request.urlopen('https://www.python.org')
print(type(response))
输出结果如下:
<class 'http.client.HTTPResponse'>
可以看出,响应是一个 HTTPResposne 类型的对象,主要包含read、readinto、getheader、getheaders、fileno 等方法,以及msg、version、status、reason、debuglevel、closed 等属性。
得到响应之后,我们把它赋值给response 变量,然后就可以调用上述那些方法和属性,得到返回结果的一系列信息了。
例如,调用 read 方法可以得到响应的网页内容、调用 status 属性可以得到响应结果的状态码(200代表请求成功,404代表网页未找到等)。
利用最基本的 urlopen 方法,已经可以完成对简单网页的 GET请求抓取。如果想给链接传递一些参数,又该怎么实现呢?首先看一下urlopen 方法的 API:
urllib,request.urlopen(url, data=None, [timeout,]*, cafile=None, capath=None, cadefault=False, context=None)
可以发现,除了第一个参数用于传递 URL之外,我们还可以传递其他内容,例如 data(附加数据)、timeout(超时时间)等。
接下来就详细说明一下 urlopen 方法中几个参数的用法。
- data参数
data参数是可选的。在添加该参数时,需要使用 bytes 方法将参数转化为字节流编码格式的内容,即 bytes 类型。另外,如果传递了这个参数,那么它的请求方式就不再是 GET,而是 POST 了。
下面用实例来看一下:
import urllib.parse
import urllib.requestdata = bytes(urllib.parse.urlencode({'name': 'germey'}), encoding='utf-8')
response = urllib.request.urlopen('https://www.httpbin.org/post', data=data)
print(response.read().decode('utf-8'))
这里我们传递了一个参数 name,值是germey,需要将它转码成 bytes 类型。转码时采用了 bytes 方法该方法的第一个参数得是 str(字符串)类型,因此用 urllib.parse 模块里的 urlencode 方法将字典参数转化为字符串;第二个参数用于指定编码格式,这里指定为 utf-8。
此处我们请求的站点是 www.httpbin.org,它可以提供 HTTP 请求测试。本次我们请求的 URL 为https://www,.htpbin.org/post,这个链接可以用来测试 POST 请求,能够输出请求的一些信息,其中就包含我们传递的 data 参数。
上面实例运行的结果如下:
{"args": {}, "data": "", "files": {}, "form": {"name": "germey"}, "headers": {"Accept-Encoding": "identity", "Content-Length": "11", "Content-Type": "application/x-www-form-urlencoded", "Host": "www.httpbin.org", "User-Agent": "Python-urllib/3.9", "X-Amzn-Trace-Id": "Root=1-6759abf8-40e4809d78505a460c2a2298"}, "json": null, "origin": "36.163.167.171", "url": "https://www.httpbin.org/post"
}
可以发现我们传递的参数出现在了form字段中,这表明是模拟表单提交,以 POST方式传输数据。
- timeout参数
timeout 参数用于设置超时时间,单位为秒,意思是如果请求超出了设置的这个时间,还没有得到响应,就会抛出异常。如果不指定该参数,则会使用全局默认时间。这个参数支持 HTTP、HTTPS.FTP 请求。
下面用实例来看一下:
import urllib.requestresponse = urllib.request.urlopen('https://www.httpbin.org/get', timeout=0.1)
print(response.read())
运行结果可能如下:
...raise URLError(err)
urllib.error.URLError: <urlopen error timed out>
这里我们设置超时时间为0.1秒。程序运行了0.1秒后,服务器依然没有响应,于是抛出了 URLError异常。该异常属于 urllib.error 模块,错误原因是超时。
因此可以通过设置这个超时时间,实现当一个网页长时间未响应时,就跳过对它的抓取。此外,利用 try except 语句也可以实现,相关代码如下:
import socket
import urllib.request
import urllib.errortry:response = urllib.request.urlopen('https://www.httpbin.org/get', timeout=0.1)
except urllib.error.URLError as e:if isinstance(e.reason, socket.timeout):print('TIME OUT')
这里我们请求了 https://www.httpbin.org/get 这个测试链接,设置超时时间为 0.1 秒,然后捕获到URLError 这个异常,并判断异常类型是 socket.timeout,意思是超时异常,因此得出确实是因为超时而报错的结论,最后打印输出了TIME OUT。
运行结果如下:
TIME OUT
按照常理来说,0.1秒几乎不可能得到服务器响应,因此输出了TIME OUT的提示。
通过设置 timeout 参数实现超时处理,有时还是很有用的。
- 其他参数
除了 data参数和 timeout 参数,urlopen方法还有 context参数,该参数必须是 ss1.SSLContext 类型,用来指定 SSL的设置。
此外,cafile 和 capath 这两个参数分别用来指定 CA 证书和其路径,这两个在请求 HTTPS 链接时会有用。
cadefault 参数现在已经弃用了,其默认值为 False。
至此,我们讲解了 urlopen 方法的用法,通过这个最基本的方法,就可以完成简单的请求和网页抓取。
- Request
利用 urlopen 方法可以发起最基本的请求,但它那几个简单的参数并不足以构建一个完整的请求。
如果需要往请求中加入 Headers 等信息,就得利用更强大的 Request 类来构建请求了。
首先,我们用实例感受一下 Request 类的用法:
import urllib.requestrequest = urllib.request.Request('https://python.org')
response = urllib.request.urlopen(request)
print(response.read().decode('utf-8'))
可以发现,我们依然是用urlopen方法来发送请求,只不过这次该方法的参数不再是URL,而是一个 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-ur1lib,我们可以通过修改这个值来伪装浏览器。例如要伪装火狐浏览器,就可以把 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 等。
下面我们传人多个参数尝试构建 Request 类:
from urllib import request, parseurl = 'https://www.httpbin.org/post'
headers = {'User-Agent': 'Mozilla/4.0(compatible; MSIE 5.5; Windows NT)','Host': 'ww.httpbin.org'}
dict = {'name': 'germey'}
data = bytes(parse.urlencode(dict), encoding='utf-8')
req = request.Request(url=url, data=data, headers=headers, method='POST')
response = request.urlopen(req)
print(response.read().decode('utf-8'))
这里我们通过 4个参数构造了一个 Request 类,其中的 url即请求 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": "ww.httpbin.org", "User-Agent": "Mozilla/4.0(compatible; MSIE 5.5; Windows NT)", "X-Amzn-Trace-Id": "Root=1-675c441e-1f5aeb5b3eccd32e6a610f6f"}, "json": null, "origin": "36.163.154.16", "url": "https://ww.httpbin.org/post"
}
观察结果可以发现,我们成功设置了 data、headers 和 method。通过 add header 方法添加 headers 的方式如下:
req =request.Request(url=url,data=data, method='POST')
req.add header('User-Agent', Mozilla/4.0(compatible; MSIE 5.5; Windows NT)')
有了 Request类,我们就可以更加方便地构建请求,并实现请求的发送啦。
- 高级用法
我们已经可以构建请求了,那么对于一些更高级的操作(例如 Cookie 处理、代理设置等 ),又该怎么实现呢?
此时需要更强大的工具,于是 Handler 登场了。简而言之,Handler 可以理解为各种处理器,有专门处理登录验证的、处理 Cookie 的、处理代理设置的。利用这些Handler,我们几乎可以实现 HTTP请求中所有的功能。
首先介绍一下 urllib.request 模块里的 BaseHandler 类,这是其他所有 Handler 类的父类。它提供了最基本的方法,例如 default open、protocol request 等。
会有各种 Handler 子类继承 BaseHandler类,接下来举几个子类的例子如下。
(1) HTTPDefaultErrorHandler 用于处理 HTTP 响应错误,所有错误都会抛出 HTTPError 类型的异常。
(2) HTTPRedirectHandler 用于处理重定向。
(3) HTTPCookieProcessor 用于处理 Cookie。
(4) ProxyHandler 用于设置代理,代理默认为空。
(5) HTTPPasswordMgr用于管理密码,它维护着用户名密码的对照表。
(6) HTTPBasicAuthHandler 用于管理认证,如果一个链接在打开时需要认证,那么可以用这个类来解决认证问题。
关于这些类如何使用,现在先不急着了解,后面会用实例演示。
另一个比较重要的类是 0penerDirector,我们可以称之为0pener。我们之前用过的 urlopen 方法实际上就是 urllib 库为我们提供的一个 0pener。
那么,为什么要引人 0pener 呢?因为需要实现更高级的功能。之前使用的Request类和 urlopen 类相当于类库已经封装好的极其常用的请求方法,利用这两个类可以完成基本的请求,但是现在我们需要实现更高级的功能,就需要深人一层进行配置,使用更底层的实例来完成操作,所以这里就用到了 Opener。
0pener 类可以提供 open方法,该方法返回的响应类型和 urlopen方法如出一辙。那么,0pener 类和 Handler 类有什么关系呢?简而言之就是,利用 Handler 类来构建 0pener 类。
下面用几个实例来看看 Handler 类和 0pener 类的用法。
- 验证
在访问某些网站时,例如 https://ssr3.scrape.center,可能会弹出这样的认证窗口,如下图所示。
遇到这种情况,就表示这个网站启用了基本身份认证,英文叫作 HTTP Basic Access Authentication.这是一种登录验证方式,允许网页浏览器或其他客户端程序在请求网站时提供用户名和口令形式的身份凭证。
那么爬虫如何请求这样的页面呢?借助 HTTPBasicAuthHandler 模块就可以完成,相关代码如下:
from urllib.request import HTTPBasicAuthHandler, build_opener, HTTPPasswordMgrWithDefaultRealm
from urllib.error import URLErrorusername = 'admin'
password = 'admin'
url = 'https://ssr3.scrape.center/'
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)
这里首先实例化了一个 HTTPBasicAuthHandler 对象 auth_handler,其参数是 HTTPPasswordMgr-WithDefaultRealm对象,它利用add_password方法添加用户名和密码,这样就建立了一个用来处理验证的 Handler 类。
然后将刚建立的 auth_handler 类当作参数传人 build_opener 方法,构建一个0pener,这个0pener在发送请求时就相当于已经验证成功了。
最后利用 0pener 类中的 open 方法打开链接,即可完成验证。这里获取的结果就是验证成功后的页面源码内容。
- 代理
做爬虫的时候,免不了要使用代理,如果要添加代理,可以这样做:
from urllib.error import URLError
from urllib.request import ProxyHandler, build_openerproxy_handler = ProxyHandler({"http": 'http://127.0.0.1:8080', 'https': "https://127.0.0.1:8080"})
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)
这里需要我们事先在本地搭建一个 HTTP 代理,并让其运行在 8080端口上。
上面使用了 ProxyHandler,其参数是一个字典,键名是协议类型(例如 HTTP 或者 HTTPS 等)、键值是代理链接,可以添加多个代理。
然后利用这个 Handler 和 build opener 方法构建了一个 0pener,之后发送请求即可。
- Cookie
处理 Cookie 需要用到相关的 Handler。
我们先用实例来看看怎样获取网站的Cookie,相关代码如下:
import http.cookiejar, urllib.requestcookie = http.cookiejar.CookieJar()
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open('https://www.baidu.com')
for item in cookie:print(item.name + "=" + item.value)
首先,必须声明一个 Cookie]ar 对象。然后需要利用 HTTPCookieProcessor 构建一个 Handler,最后利用 build opener 方法构建 0pener,执行 open 函数即可。
运行结果如下:
BAIDUID=077CDC4722F11FC3E3B03E82251C67DD:FG=1
BAIDUID_BFESS=077CDC4722F11FC3E3B03E82251C67DD:FG=1
H_PS_PSSID=61027_60851_61331_61358_61366_61391_61388_61406
PSTM=1734101145
BD_NOT_HTTPS=1
可以看到,这里分别输出了每个Cookie 条目的名称和值。
既然能输出,那么可不可以输出文件格式的内容呢?我们知道 Cookie 实际上也是以文本形式保存的。因此答案当然是肯定的,这里通过下面的实例来看看:
import urllib.request, http.cookiejarfilename = 'cookie.txt'
cookie = http.cookiejar.MozillaCookieJar(filename)
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open('https://www.baidu.com')
cookie.save(ignore_discard=True)
这时需要将 Cookie]ar 换成 MozillaCookie]ar,它会在生成文件时用到,是 (ookie]ar 的子类,可以用来处理跟 Cookie 和文件相关的事件,例如读取和保存 Cookie,可以将 Cookie 保存成 Mozilla 型浏览器的 Cookie 格式。
运行上面的实例之后,会发现生成了一个 cookie.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 1765637336 BAIDUID 8B9571B632AF777394D83C0368E8AAE7:FG=1
.baidu.com TRUE / TRUE 1765637336 BAIDUID_BFESS 8B9571B632AF777394D83C0368E8AAE7:FG=1
.baidu.com TRUE / FALSE 1765637335 H_PS_PSSID 60277_61027_61099_61219_61245_60853_61367_61390_61388
.baidu.com TRUE / FALSE 3881584983 PSTM 1734101335
www.baidu.com FALSE / FALSE 1734101636 BD_NOT_HTTPS 1
另外,LWPCookie]ar同样可以读取和保存 Cookie,只是 Cookie 文件的保存格式和 MozillaCookie]ar不一样,它会保存成LWP(libwww-perl)格式。
要保存 LWP 格式的 Cookie 文件,可以在声明时就进行修改:
import urllib.request, http.cookiejarfilename = 'cookie.txt'
cookie =http.cookiejar.LWPCookieJar(filename)
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open('https://www.baidu.com')
cookie.save(ignore_discard=True)
此时生成的内容如下:
#LWP-Cookies-2.0
Set-Cookie3: BAIDUID="D8BAD7FD29F9BC81EB78AFCEA14DC816:FG=1"; path="/"; domain=".baidu.com"; path_spec; expires="2025-12-13 14:52:21Z"; version=0
Set-Cookie3: BAIDUID_BFESS="D8BAD7FD29F9BC81EB78AFCEA14DC816:FG=1"; path="/"; domain=".baidu.com"; path_spec; secure; expires="2025-12-13 14:52:21Z"; SameSite=None; version=0
Set-Cookie3: H_PS_PSSID=60275_61027_61098_61218_61243_60853_61325_61360_61372; path="/"; domain=".baidu.com"; path_spec; domain_dot; expires="2025-12-13 14:52:20Z"; version=0
Set-Cookie3: PSTM=1734101540; path="/"; domain=".baidu.com"; path_spec; domain_dot; expires="2092-12-31 18:06:28Z"; version=0
Set-Cookie3: BD_NOT_HTTPS=1; path="/"; domain="www.baidu.com"; path_spec; expires="2024-12-13 14:57:21Z"; version=0
由此看来,不同格式的 Cookie 文件差异还是比较大的。
那么,生成 Cookie 文件后,怎样从其中读取内容并加以利用呢?
下面我们以 LWPCookie]ar 格式为例来看一下:
import urllib.request, http.cookiejarcookie = http.cookiejar.LWPCookieJar()
cookie.load('cookie.txt', ignore_discard=True, ignore_expires=True)
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open('https://www.baidu.com')
print(response.read().decode('utf-8'))
可以看到,这里调用 1oad方法来读取本地的 Cookie 文件,获取了Cookie的内容。这样做的前提是我们首先生成了 LWPCookie]ar 格式的 Cookie,并保存成了文件。读取 Cookie 之后,使用同样的方法构建 Handler 类和 0pener 类即可完成操作。
运行结果正常的话,会输出百度网页的源代码。
通过上面的方法,我们就可以设置绝大多数请求的功能。
处理异常
我们已经了解了如何发送请求,但是在网络不好的情况下,如果出现了异常,该怎么办呢?这时要是不处理这些异常,程序很可能会因为报错而终止运行,所以异常处理还是十分有必要的。
urllib 库中的 error 模块定义了由 request 模块产生的异常。当出现问题时,request 模块便会抛出 error 模块中定义的异常。
- URLError
URLError 类来自 urllib库的error 模块,继承自 0SError类,是error 异常模块的基类,由 request模块产生的异常都可以通过捕获这个类来处理。
它具有一个属性 reason,即返回错误的原因。
下面用一个实例来看一下:
from urllib import request, errortry:response = request.urlopen('https://cuiqingcai.com/404')
except error.URLError as e:print(e.reason)
我们打开了一个不存在的页面,照理来说应该会报错,但是我们捕获了URLError 这个异常,运行结果如下:
Not Found
程序没有直接报错,而是输出了错误原因,这样可以避免程序异常终止,同时异常得到了有效处理。
- HTTPError
HTTPError 是URLError 的子类,专门用来处理HTTP请求错误,例如认证请求失败等。它有如下3个属性。
(1) code:返回 HTTP 状态码,例如 404表示网页不存在,500 表示服务器内部错误等。
(2) reason:同父类一样,用于返回错误的原因。
(3) headers:返回请求头。
下面我们用几个实例来看看:
from urllib import request, errortry:response = request.urlopen('https://cuiqingcai.com/404')
except error.HTTPError as e:print(e.reason, e.code, e.headers, sep='\n')
运行结果如下:
Not Found
404
Connection: close
Content-Length: 9379
Server: GitHub.com
Content-Type: text/html; charset=utf-8
Access-Control-Allow-Origin: *
ETag: "64d39a40-24a3"
Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline'; img-src data:; connect-src 'self'
x-proxy-cache: MISS
X-GitHub-Request-Id: A70E:3CA0C7:3F7B8D:449189:675C4EB2
Accept-Ranges: bytes
Age: 0
Date: Fri, 13 Dec 2024 15:11:54 GMT
Via: 1.1 varnish
X-Served-By: cache-lon420137-LON
X-Cache: MISS
X-Cache-Hits: 0
X-Timer: S1734102715.581747,VS0,VE85
Vary: Accept-Encoding
X-Fastly-Request-ID: aef6c63cad118dac55be2f1269f01bbfaf098acb
依然是打开同样的网址,这里捕获了 HTTPError 异常,输出了reason、code 和 headers 属性。因为URLError 是HTTPError 的父类,所以可以先选择捕获子类的错误,再捕获父类的错误,于是上述代码的更好写法如下:
from urllib import request, errortry:response = request.urlopen('https://cuiqingcai.com/404')
except error.HTTPError as e:print(e.reason, e.code, e.headers, sep='\n')
except error.URLError as e:print(e.reason)
else:print('Request Successfully')
这样就可以做到先捕获 HTTPError,获取它的错误原因、状态码、请求头等信息。如果不是HTTPError 异常,就会捕获URLError 异常,输出错误原因。最后,用else语句来处理正常的逻辑。这是一个较好的异常处理写法。
有时候,reason属性返回的不一定是字符串,也可能是一个对象。再看下面的实例:
import socket
import urllib.request
import urllib.errortry:response = urllib.request.urlopen('https://www.baidu.com', timeout=0.01)
except urllib.error.URLError as e:print(type(e.reason))if isinstance(e.reason, socket.timeout):print('TIME OUT')
这里我们直接设置超时时间来强制抛出 timeout 异常。
运行结果如下:
<class 'socket.timeout'>
TIME OUT
可以发现,reason 属性的结果是 socket.timeout 类。所以这里可以用 isinstance 方法来判断它的类型,做出更详细的异常判断。
本节我们讲述了 error 模块的相关用法,通过合理地捕获异常可以做出更准确的异常判断,使程序更加稳健。
解析链接
前面说过,urllib库里还提供了 parse模块,这个模块定义了处理URL的标准接口,例如实现 URL各部分的抽取、合并以及链接转换。它支持如下协议的URL处理:file、fp、gopher、hdl、htp、https、imap、 mailto、 mms、 news、 nntp、 prospero、 rsync、rtsp、rtspu、sftp、 sip、 sips、 snews、svn、svntssh.telnet 和 wais。
下面我们将介绍 parse 模块中的常用方法,看一下它的便捷之处。
- urlparse
该方法可以实现 URL的识别和分段,这里先用一个实例来看一下:
from urllib.parse import urlparseresult = urlparse('https://www.baidu.com/index.html;user?id=5#comment')
print(type(result))
print(result)
这里我们利用 urlparse 方法对一个URL进行了解析,然后输出了解析结果的类型以及结果本身。运行结果如下:
<class 'urllib.parse.ParseResult'>
ParseResult(scheme='https', netloc='www.baidu.com', path='/index.html', params='user', query='id=5', fragment='comment')
可以看到,解析结果是一个ParseResult类型的对象,包含6部分,分别是scheme、netloc、path、params、query和fragment。
再观察一下上述实例中的 URL:
https://www.baidu.com/index.html;user?id=5#comment
可以发现,urlparse 方法在解析 URL 时有特定的分隔符。例如://前面的内容就是 scheme,代表协议。第一个/符号前面便是netloc,即域名;后面是path,即访问路径。分号;后面是params,代表参数。问号?后面是査询条件 query,一般用作 GET类型的 URL。井号#后面是锚点 fragment,用于直接定位页面内部的下拉位置。
于是可以得出一个标准的链接格式,具体如下:
scheme://netloc/path;params?query#fragment
一个标准的 URL都会符合这个规则,利用 urlparse 方法就可以将它拆分开来。
除了这种最基本的解析方式外,urlparse 方法还有其他配置吗?接下来,看一下它的 API用法:
urllib.parse.urlparse(urlstring,scheme='', allow fragments=True)
可以看到,urlparse 方法有3个参数。
(1) urlstring:这是必填项,即待解析的 URL。
(2) scheme:这是默认的协议(例如 http 或 https 等 )。如果待解析的 URL 没有带协议信息,就会将这个作为默认协议。我们用实例来看一下:
from urllib.parse import urlparseresult = urlparse('www.baidu.com/index.html;user?id=5#comment', scheme='https')
print(result)
运行结果如下:
ParseResult(scheme='https', netloc='', path='www.baidu.com/index.html', params='user', query='id=5', fragment='comment')
可以发现,这里提供的 URL不包含最前面的协议信息,但是通过默认的scheme 参数,返回了结果 https。
假设带上协议信息:
from urllib.parse import urlparseresult = urlparse('http://www.baidu.com/index.html;user?id=5#comment', scheme='https')
print(result)
运行结果如下:
ParseResult(scheme='http', netloc='www.baidu.com', path='/index.html', params='user', query='id=5', fragment='comment')
可见,scheme 参数只有在 URL,中不包含协议信息的时候才生效。如果 URL中有,就会返回解析出的 scheme。
(3)allow fragments:是否忽略 fragment。如果此项被设置为 False,那么 fragment 部分就会被忽略,它会被解析为 path、params 或者 query 的一部分,而 fragment 部分为空。
下面我们用实例来看一下:
from urllib.parse import urlparseresult = urlparse('https://www.baidu.com/index.html;user?id=5#comment', allow_fragments=False)
print(result)
运行结果如下:
ParseResult(scheme='https', netloc='www.baidu.com', path='/index.html', params='user', query='id=5#comment', fragment='')
假设 URL中不包含 params 和 query,我们再通过实例看一下:
from urllib.parse import urlparseresult = urlparse('https://www.baidu.com/index.html#comment', allow_fragments=False)
print(result)
运行结果如下:
ParseResult(scheme='https', netloc='www.baidu.com', path='/index.html#comment', params='', query='', fragment='')
可以发现,此时 fragment 会被解析为 path 的一部分。
返回结果 ParseResult 实际上是一个元组,既可以用属性名获取其内容,也可以用索引来顺序获实例如下:
from urllib.parse import urlparseresult = urlparse('https://ww.baidu.com/index.html#comment', allow_fragments=False)
print(result.scheme, result[0], result.netloc, result[1], sep='\n')
这里我们分别用属性名和索引获取了scheme 和 netloc,运行结果如下:
https
https
ww.baidu.com
ww.baidu.com
可以发现,两种获取方式都可以成功获取,且结果是一致的。
- urlunparse
有了 urlparse 方法,相应就会有它的对立方法 urlunparse,用于构造 URL。这个方法接收的参数是一个可迭代对象,其长度必须是6,否则会抛出参数数量不足或者过多的问题。先用一个实例看一下:
from urllib.parse import urlunparsedata = ['https', 'www.baidu.com', 'index.html', 'user', 'a=6', 'comment']
print(urlunparse(data))
这里参数 data用了列表类型。当然,也可以用其他类型,例如元组或者特定的数据结构。
运行结果如下:
https://www.baidu.com/index.html;user?a=6#comment
这样我们就成功实现了 URL 的构造。
- urlsplit
这个方法和 urlparse 方法非常相似,只不过它不再单独解析 params 这一部分(params 会合并到path中),只返回5个结果。实例如下:
from urllib.parse import urlsplitresult = urlsplit('https://www.baidu.com/index.html;user?id-5#comment')
print(result)
运行结果如下:
SplitResult(scheme='https', netloc='www.baidu.com', path='/index.html;user', query='id-5', fragment='comment')
可以发现,返回结果是SplitResult,这其实也是一个元组,既可以用属性名获取其值,也可以用索引获取。实例如下:
from urllib.parse import urlsplitresult = urlsplit('https://www.baidu.com/index.html;user?id-5#comment')
print(result.scheme, result[0])
运行结果如下:
https https
- urlunsplit
与urlunparse方法类似,这也是将链接各个部分组合成完整链接的方法,传人的参数也是一个可迭代对象,例如列表、元组等,唯一区别是这里参数的长度必须为5。实例如下:
from urllib.parse import urlunsplitdata = ['https', 'www.baidu.com', 'index.html', 'a=6', 'comment']
print(urlunsplit(data))
运行结果如下:
https://www.baidu.com/index.html?a=6#comment
- urljoin
urlunparse 和 urlunsplit方法都可以完成链接的合并,不过前提都是必须有特定长度的对象,链接的每一部分都要清晰分开。
除了这两种方法,还有一种生成链接的方法,是urljoin。我们可以提供一个 base url(基础链接)作为该方法的第一个参数,将新的链接作为第二个参数。urljoin方法会分析 base_url的 scheme.netloc和 path 这3个内容,并对新链接缺失的部分进行补充,最后返回结果。
下面通过几个实例看一下:
from urllib.parse import urljoinprint(urljoin('https://www.baidu.com', 'FAQ.html'))
print(urljoin('https://www.baidu.com', 'https://cuigingcai.com/FA0.html'))
print(urljoin('https://ww,baidu.com/about.html', 'https://cuigingcai.com/FAQ.html'))
print(urljoin('https://ww.baidu.com/about.html', 'https://cuigingcai.com/FAQ.html?question=2'))
print(urljoin('https://www.baidu.com?wd=abc', 'https://cuiqingcai.com/index.php'))
print(urljoin('https://www.baidu.com', '?category=2#comment'))
print(urljoin('www.baidu.com', '?category=2#comment'))
print(urljoin('www.baidu.com#comment', '?category=2'))
可以发现,base_url提供了三项内容:scheme、netloc和 path。如果新的链接里不存在这三项就予以补充;如果存在,就使用新的链接里面的,base_url中的是不起作用的。
通过 urljoin 方法,我们可以轻松实现链接的解析、拼合与生成。
- urlencode
这里我们再介绍一个常用的方法–urlencode,它在构造 GET 请求参数的时候非常有用,实例如下:
from urllib.parse import urlencodeparams = {'name': 'germey', 'age': 25}
base_url = 'https://www.baidu.com?'
url = base_url + urlencode(params)
print(url)
这里首先声明了一个字典 params,用于将参数表示出来,然后调用urlencode方法将 params 序列化为 GET请求的参数。
运行结果如下:
https://www.baidu.com?name=germey&age=25
可以看到,参数已经成功地由字典类型转化为 GET请求参数:
urlencode 方法非常常用。有时为了更加方便地构造参数,我们会事先用字典将参数表示出来然后将字典转化为 URL的参数时,只需要调用该方法即可。
- parse_qs
有了序列化,必然会有反序列化。利用parse_qs 方法,可以将一串 GET请求参数转回字典,实例如下:
from urllib.parse import parse_qsquery = 'name=germey&age=25'
print(parse_qs(query))
运行结果如下:
{'name': ['germey'], 'age': ['25']}
可以看到,URL的参数成功转回为字典类型。
- parse_qsl
parse qsl方法用于将参数转化为由元组组成的列表,实例如下:
from urllib.parse import parse_qslquery = 'name=germey&age=25'
print(parse_qsl(query))
运行结果如下:
[('name', 'germey'), ('age', '25')]
可以看到,运行结果是一个列表,该列表中的每一个元素都是一个元组,元组的第一个内容是参数名,第二个内容是参数值。
- quote
该方法可以将内容转化为 URL编码的格式。当 URL 中带有中文参数时,有可能导致乱码问题此时用 quote 方法可以将中文字符转化为 URL编码,实例如下:
from urllib.parse import quotekeyword = '壁纸'
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
- unquote
有了 quote 方法,当然就有 unquote 方法,它可以进行 URL解码,实例如下:
from urllib.parse import unquoteurl = 'https://www.baidu.com/s?wd=%E5%A3%81%E7%BA%B8'
print(unquote(url))
这里的 ur1 是上面得到的 URL编码结果,利用 unquote 方法将其还原,结果如下:
https://www.baidu.com/s?wd=壁纸
可以看到,利用 unquote 方法可以方便地实现解码。
本节我们介绍了 parse 模块的一些常用 URL处理方法。有了这些方法,我们可以方便地实现 URL的解析和构造,建议熟练掌握。
分析Robots协议
利用 urllib 库的 robotparser 模块,可以分析网站的 Robots 协议。我们再来简单了解一下这个模块的用法。
- Robots协议
Robots协议也称作爬虫协议、机器人协议,全名为网络爬虫排除标准( Robots Exclusion Protocol),用来告诉爬虫和搜索引擎哪些页面可以抓取、哪些不可以。它通常是一个叫作 robots.txt的文本文件,一般放在网站的根目录下。
搜索爬虫在访问一个站点时,首先会检查这个站点根目录下是否存在 robots.txt 文件,如果存在就会根据其中定义的爬取范围来爬取。如果没有找到这个文件,搜索爬虫便会访问所有可直接访问的页面。
下面我们看一个robots.txt 的样例:
User-agent:*
Disallow:/
Allow: /public/
这限定了所有搜索爬虫只能爬取 public 目录。将上述内容保存成robots.txt文件,放在网站的根目录下,和网站的入口文件(例如index.php、index.html和 index.jsp 等)放在一起。
上面样例中的 User-agent 描述了搜索爬虫的名称,这里将其设置为*,代表 Robots 协议对所有爬取爬虫都有效。例如,我们可以这样设置:
User-agent: Baiduspider
这代表设置的规则对百度爬虫是有效的。如果有多条user-agent记录,则意味着有多个爬虫会受到爬取限制,但至少需要指定一条。
Disallow 指定了不允许爬虫爬取的目录,上例设置为/,代表不允许爬取所有页面。
A11ow一般不会单独使用,会和 Disallow一起用,用来排除某些限制。上例中我们设置为 /public/,结合 Disallow 的设置,表示所有页面都不允许爬取,但可以爬取 public 目录。
下面再来看几个例子。禁止所有爬虫访问所有目录的代码如下:
User-agent:*
Disallow:/
允许所有爬虫访问所有目录的代码如下:
User-agent:*
Disallow:
另外,直接把 robots.txt 文件留空也是可以的。
禁止所有爬虫访问网站某些目录的代码如下:
User-agent:*
Disallow: /private/
Disallow:/tmp/
只允许某一个爬虫访问所有目录的代码如下:
User-agent: WebCrawler
Disallow:
User-agent:*
Disallow:/
以上是 robots.txt 的一些常见写法。
- 爬虫名称
大家可能会疑惑,爬虫名是从哪儿来的?为什么叫这个名?其实爬虫是有固定名字的,例如百度的爬虫就叫作 BaiduSpider。下表列出了一些常见搜索爬虫的名称及对应的网站。
- robotparser
了解 Robots 协议之后,就可以使用 robotparser 模块来解析robots.txt文件了。该模块提供了一个类 RobotfileParser,它可以根据某网站的 robots.txt文件判断一个爬取爬虫是否有权限爬取这个网页。
该类用起来非常简单,只需要在构造方法里传人robots.txt文件的链接即可。首先看一下它的声明:
urllib.robotparser.RobotFileParser(url='")
当然,也可以不在声明时传人robots.txt文件的链接,就让其默认为空,最后再使用set ur1()方法设置一下也可以。
下面列出了 RobotFileParser 类的几个常用方法。
- set url:用来设置 robots.txt 文件的链接。如果在创建 RobotFileParser 对象时传人了链接就不需要使用这个方法设置了。
- read:读取 robots.txt 文件并进行分析。注意,这个方法执行读取和分析操作,如果不调用这个方法,接下来的判断都会为False,所以一定记得调用这个方法。这个方法虽不会返回任何内容,但是执行了读取操作。
- parse:用来解析 robots.txt文件,传人其中的参数是robots.txt文件中某些行的内容,它会按照robots.txt 的语法规则来分析这些内容。
- can fetch:该方法有两个参数,第一个是User-Agent,第二个是要抓取的 URL。返回结果是 True或 False,表示 User-Agent 指示的搜索引擎是否可以抓取这个 URL。
- mtime:返回上次抓取和分析 robots.xt文件的时间,这对于长时间分析和抓取robots.txt文件的搜索爬虫很有必要,你可能需要定期检查以抓取最新的 robots.txt 文件。
- modified:它同样对长时间分析和抓取的搜索爬虫很有帮助,可以将当前时间设置为上次抓取和分析 robots.txt 文件的时间。
下面我们用实例来看一下:
from urllib.robotparser import RobotFileParserrp = RobotFileParser()
rp.set_url('https://www.baidu.com/robots.txt')
rp.read()
print(rp.can_fetch('Baiduspider', 'https://www.baidu.com'))
print(rp.can_fetch('Baiduspider', 'https://www.baidu.com/homepage/'))
print(rp.can_fetch('Googlebot', 'https://www.baidu.com/homepage/'))
这里以百度为例,首先创建了一个 RobotFileParser 对象 p,然后通过 set url 方法设置了robots.txt 文件的链接。当然,要是不用 set ur1方法,可以在声明对象时直接用如下方法设置:
rp= RobotFileParser('https://www.baidu.com/robots.txt')
接着利用 can fetch 方法判断了网页是否可以被抓取。
运行结果如下:
True
True
False
可以看到,这里我们利用 Baiduspider 可以抓取百度的首页以及 homepage 页面,但是 Googlebot就不能抓取 homepage 页面。
打开百度的 robots.txt 文件,可以看到如下信息:
User-agent: Baiduspider
Disallow: /baidu
Disallow: /s?
Disallow: /ulink?
Disallow: /link?
Disallow: /home/news/data/
Disallow: /bhUser-agent: Googlebot
Disallow: /baidu
Disallow: /s?
Disallow: /shifen/
Disallow: /homepage/
Disallow: /cpro
Disallow: /ulink?
Disallow: /link?
Disallow: /home/news/data/
Disallow: /bhUser-agent: MSNBot
Disallow: /baidu
Disallow: /s?
Disallow: /shifen/
Disallow: /homepage/
Disallow: /cpro
Disallow: /ulink?
Disallow: /link?
Disallow: /home/news/data/
Disallow: /bhUser-agent: Baiduspider-image
Disallow: /baidu
Disallow: /s?
Disallow: /shifen/
Disallow: /homepage/
Disallow: /cpro
Disallow: /ulink?
Disallow: /link?
Disallow: /home/news/data/
Disallow: /bhUser-agent: YoudaoBot
Disallow: /baidu
Disallow: /s?
Disallow: /shifen/
Disallow: /homepage/
Disallow: /cpro
Disallow: /ulink?
Disallow: /link?
Disallow: /home/news/data/
Disallow: /bhUser-agent: Sogou web spider
Disallow: /baidu
Disallow: /s?
Disallow: /shifen/
Disallow: /homepage/
Disallow: /cpro
Disallow: /ulink?
Disallow: /link?
Disallow: /home/news/data/
Disallow: /bhUser-agent: Sogou inst spider
Disallow: /baidu
Disallow: /s?
Disallow: /shifen/
Disallow: /homepage/
Disallow: /cpro
Disallow: /ulink?
Disallow: /link?
Disallow: /home/news/data/
Disallow: /bhUser-agent: Sogou spider2
Disallow: /baidu
Disallow: /s?
Disallow: /shifen/
Disallow: /homepage/
Disallow: /cpro
Disallow: /ulink?
Disallow: /link?
Disallow: /home/news/data/
Disallow: /bhUser-agent: Sogou blog
Disallow: /baidu
Disallow: /s?
Disallow: /shifen/
Disallow: /homepage/
Disallow: /cpro
Disallow: /ulink?
Disallow: /link?
Disallow: /home/news/data/
Disallow: /bhUser-agent: Sogou News Spider
Disallow: /baidu
Disallow: /s?
Disallow: /shifen/
Disallow: /homepage/
Disallow: /cpro
Disallow: /ulink?
Disallow: /link?
Disallow: /home/news/data/
Disallow: /bhUser-agent: Sogou Orion spider
Disallow: /baidu
Disallow: /s?
Disallow: /shifen/
Disallow: /homepage/
Disallow: /cpro
Disallow: /ulink?
Disallow: /link?
Disallow: /home/news/data/
Disallow: /bhUser-agent: ChinasoSpider
Disallow: /baidu
Disallow: /s?
Disallow: /shifen/
Disallow: /homepage/
Disallow: /cpro
Disallow: /ulink?
Disallow: /link?
Disallow: /home/news/data/
Disallow: /bhUser-agent: Sosospider
Disallow: /baidu
Disallow: /s?
Disallow: /shifen/
Disallow: /homepage/
Disallow: /cpro
Disallow: /ulink?
Disallow: /link?
Disallow: /home/news/data/
Disallow: /bhUser-agent: yisouspider
Disallow: /baidu
Disallow: /s?
Disallow: /shifen/
Disallow: /homepage/
Disallow: /cpro
Disallow: /ulink?
Disallow: /link?
Disallow: /home/news/data/
Disallow: /bhUser-agent: EasouSpider
Disallow: /baidu
Disallow: /s?
Disallow: /shifen/
Disallow: /homepage/
Disallow: /cpro
Disallow: /ulink?
Disallow: /link?
Disallow: /home/news/data/
Disallow: /bhUser-agent: *
Disallow: /
不难看出,百度的 robots.txt 文件没有限制 Baiduspider 对百度 homepage 页面的抓取,限制了Googlebot 对 homepage 页面的抓取。
这里同样可以使用 parse 方法执行对 robots.txt 文件的读取和分析,实例如下:
from urllib.request import urlopen
from urllib.robotparser import RobotFileParserrp = RobotFileParser()
rp.parse(urlopen('https://www.baidu.com/robots.txt').read().decode('utf-8').split('\n'))
print(rp.can_fetch('Baiduspider', 'https://www.baidu.com'))
print(rp.can_fetch('Baiduspider', 'https://www.baidu.com/homepage/'))
print(rp.can_fetch('Googlebot', 'https://www.baidu.com/homepage/'))
运行结果是一样的:
True
True
False
本节介绍了 robotparser 模块的基本用法和实例,利用此模块,我们可以方便地判断哪些页面能抓取、哪些页面不能。
总结
本篇博客内容比较多,我们介绍了 urllib库的request、error、parse、robotparser 模块的基本用法这些是一些基础模块,有一些模块的实用性还是很强的,例如我们可以利用 parse 模块来进行 URL,的各种处理,还是很方便的。
相关文章:
基础库urllib的使用
学习爬虫,其基本的操作便是模拟浏览器向服务器发出请求,那么我们需要从哪个地方做起呢?请求需要我们自己构造吗?我们需要关心请求这个数据结构怎么实现吗?需要了解 HTTP、TCP、IP层的网络传输通信吗?需要知道服务器如何响应以及响应的原理吗? 可能…...
SSM 电脑配件销售系统设计要点与 JSP 实现难点攻克
摘 要 随着科学技术的飞速发展,各行各业都在努力与现代先进技术接轨,通过科技手段提高自身的优势;对于电脑配件销售系统当然也不能排除在外,随着网络技术的不断成熟,带动了电脑配件销售系统,它彻底改变了过…...
AI前沿分析:ChatGPT搜索上线,Google搜索地位能否守住?
名人说:莫听穿林打叶声,何妨吟啸且徐行。—— 苏轼 Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 目录 引言:AI与搜索领域的激烈博弈一、ChatGPT搜索的优势是什么?1. 实时信息获取:超越传统搜索2. 对话式搜索:重构用户体验3. 个性化推荐:深度挖掘用户需求二、G…...
单片机:实现utf-8转gb2312(附带源码)
单片机实现UTF-8转GB2312 在嵌入式系统中,字符编码是常见的问题之一,尤其是在显示中文字符时。UTF-8和GB2312都是常见的字符编码标准,UTF-8广泛用于Web和现代操作系统中,而GB2312是中国大陆常用的中文字符集。在一些嵌入式系统中…...
VMProtect:软件保护与安全的全面解决方案
在当今数字化时代,软件的安全性和保密性愈发重要。VMProtect 作为一款备受瞩目的软件保护工具,因其强大的功能和广泛的应用而成为开发者保护软件的首选方案。 VMProtect 是一款新一代的软件保护实用程序,支持多个编译器平台,包括…...
linux下观察进程捕获信号的情况
linux观察进程收到信号的情况,信号可以来自外部进程、进程自身、内核定时器等。 观察捕获信号方法一:strace strace所有信号: strace -e signal -f -p $pid 过滤出某个信号: #grep 信号名,取自kill -l strace -e …...
VSCode编辑+GCC for ARM交叉编译工具链+CMake构建+OpenOCD调试(基于STM32的标准库/HAL库)
一、CMake安装 进入CMake官网的下载地址Get the Software,根据系统安装对应的Binary distributions。 或者在CMake——国内镜像获取二进制镜像安装包。 或者访问GitHub的xPack项目xPack CMake v3.28.6-1,下载即可。 记得添加用户/系统的环境变量&#…...
概率论得学习和整理26:EXCEL 关于plot 折线图--频度折线图的一些细节
目录 0 折线图有很多 1 频度折线图 1.1 直接用原始数据做的频度折线图 2 将原始数据生成数据透视表 3 这样可以做出了,频度plot 4 做按某字段汇总,成为累计plot分布 5 修改上面显示效果,做成百分比累计plot频度分布 0 折线图有很多 这…...
数据结构之栈和队列算法题
一:有效括号数 学了栈之后这一题就比较简单了。 思路:1、左括号进栈 2、右括号出栈匹配。 完整代码: 因为使用C语言写的,所以里面包含了栈的实现 #include<stdio.h> #include<stdlib.h> #include<assert.h>…...
离散数学---随机漫步
本文根据 MIT 计算机科学离散数学课程整理(Lecture 25)。 赌徒破产问题(Gamblers Ruin) 问题描述 初始为 n 元,对于每一次独立的赌注,都有 p 的概率赢得 1 元,(1-p) 的概率输掉 1 元。当输完…...
HCIA-Access V2.5_2_2网络通信基础_TCP/IP协议栈报文封装
TCP/IP协议栈的封装过程 用户从应用层发出数据先会交给传输层,传输层会添加TCP或者UDP头部,然后交给网络层,网络层会添加IP头部,然后交给数据链路层,数据链路层会添加以太网头部和以太网尾部,最后变成01这样…...
java开发入门学习一 -基本概念入门
目录 Java的开发环境搭建 开发环境简单认识 开发环境下载与安装 初始JAVA之helloWorld java基本运行原理 helloWord实例 JAVA文档注释和API 文档 注释 API文档地址 Java的优缺点 优点 缺点 JVM的简单介绍 功能 1. 实现JAVA程序的跨平台性 2. 自动内存管理(管理…...
Coturn 实战指南:WebRTC 中的 NAT 穿透利器
1. 什么是 Coturn? Coturn 是一种开源的 TURN(Traversal Using Relays around NAT)服务器,用于解决 NAT 穿透问题。它帮助客户端在受限网络环境(例如防火墙或 NAT 后面)中实现双向通信,常用于 WebRTC 应用、VoIP、在线游戏等场景。 2. Cotur…...
【每日一练 基础题】[蓝桥杯 2022 省 A] 求和
[蓝桥杯 2022 省 A] 求和 暴力破解会超时,用因式分解的平方差公式 a2 2abb2(a)2 a-2abb2(a-b)2 输出整数((a1a2a3…an)-a1-a2-a3-…-an)/2 import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner scanner new Scanner(System.in);l…...
华为ensp--BGP路径选择-Preferred Value
学习新思想,争做新青年。今天学习的是BGP路径选择-Preferred Value 实验目的 理解BGP路由信息首选值(Preferred Value)的作用 掌握修改Preferred Value属性的方法 掌握通过修改Preferred Value属性来实现流量分担的方法 实验拓扑 实验要求…...
Flink是什么?Flink技术介绍
官方参考资料:Apache Flink — Stateful Computations over Data Streams | Apache Flink Flink是一个分布式流处理和批处理计算框架,具有高性能、容错性和灵活性。以下是关于Flink技术的详细介绍: 一、Flink概述 定义:Fli…...
Java中的重要集合
目录 List 1.vector 2.ArrayList 3.CopyonWriteArraylist 4.ArrayList变成线程安全的方式 5.LinkList Map 1.HashTable 2.HashMap 3.ConcurrentHashMap Set 1.LinkedHashSe List 1.vector Vector 是线程安全的动态数组,其内部方法基本都经过synchroni…...
实景视频与模型叠加融合?
[视频GIS系列]无人机视频与与实景模型进行实时融合_无人机视频融合-CSDN博客文章浏览阅读1.5k次,点赞28次,收藏14次。将无人机视频与实景模型进行实时融合是一个涉及多个技术领域的复杂过程,主要包括无人机视频采集、实景模型构建、视频与模型…...
【一本通】分数约分
【一本通】分数约分 C语言CJavaPython 💐The Begin💐点点关注,收藏不迷路💐 编写一个给一个分数约分的程序。 输入 输出整数a,b,分别表示分子与分母 输出 约分后的分子与分母,用“/“分隔 …...
ArkTs的容器布局
组件按照布局的要求依次排列,构成应用的页面。在声明式UI中,所有的页面都是由自定义组件构成,开发中可以根据自己的需求,选择合适的布局进行页面开发。 一. 如何选择布局 声明式UI提供了以下10种常见布局,开发者可根…...
jenkins 出现 Jenkins: 403 No valid crumb was included in the request
文章目录 前言解决方式:1.跨站请求为找保护勾选"代理兼容"2.全局变量或者节点上添加环境变量3.(可选)下载插件 the strict Crumb Issuer plugin4.重启 前言 jenkins运行时间长了,经常出现点了好几次才能构建,然后报了Je…...
SpringDataJpa-字段加解密存储
SpringDataJpa-字段加解密存储 背景场景实现类型转换器实体类修改 Crypto 注解AOP 目前可使用场景注意 背景 遇到一个需求,对数据库中的某些字段进行加密存储,但是在各个服务流转中,需要是解密状态的。框架使用的是JPA。 Spring 提供了 Attri…...
python03-保留字、标识符;变量、常量;数据类型、数据类型之间的转化
一、保留字 VS 标识符 1-1、35个保留字 保留字,严格区分大小写! 查询保留字: 1-2、标识符 常量:python中没有明确的定义常量的关键字,常量的值在程序运行过程中不允许修改! 二、变量 VS 常量 2-1、变量 变…...
投影互动装置入驻,科普基地学习体验再上新台阶!
数字科技的迅猛进步极大地充盈了人们的日常生活,这一变革在主题乐园与科普教育基地等场所尤为显著。在这些地方,投影互动装置引领我们穿梭于光影构建的奇妙世界。特别是互动投影墙,它赋予观众前所未有的交互体验。那么,这种技术究…...
socket编程UDP-实现滑动窗口机制与累积确认GBN
在下面博客中,我介绍了利用UDP模拟TCP连接、按数据包发送文件的过程,并附上完整源码。 socket编程UDP-文件传输&模拟TCP建立连接脱离连接(进阶篇)_udp socket发送-CSDN博客 下面博客实现了停等机制。 socket编程UDP-实现停…...
乐观锁与悲观锁的概念
在多线程或多进程并发访问同一资源的情况下,为了防止数据的不一致性和竞态条件,常常需要使用锁机制来控制并发访问。锁机制大致可以分为 乐观锁(Optimistic Locking)和 悲观锁(Pessimistic Locking)。这两种…...
考研数学【线性代数基础box(数二)】
本文是对数学二线性代数基础进行总结,一些及极其简单的被省略了,代数的概念稀碎,不如高数关联性高,所以本文仅供参考,做题请从中筛选! 本文为初稿,后面会根据刷题和自己的理解继续更新 高数&a…...
游戏引擎学习第45天
仓库: https://gitee.com/mrxiao_com/2d_game 回顾 我们刚刚开始研究运动方程,展示了如何处理当人物遇到障碍物时的情况。有一种版本是角色会从障碍物上反弹,而另一版本是角色会完全停下来。这种方式感觉不太自然,因为在游戏中,…...
基于 mzt-biz-log 实现接口调用日志记录
🎯导读:mzt-biz-log 是一个用于记录操作日志的通用组件,旨在追踪系统中“谁”在“何时”对“何事”执行了“何种操作”。该组件通过简单的注解配置,如 LogRecord,即可实现接口调用的日志记录,支持成功与失败…...
无人设备之RTK地面基站篇
一、定义与功能 RTK地面基站是一种通过差分定位技术来实现GPS等全球导航卫星系统信号精确定位的设备。它通过与无人设备上的流动站进行实时数据通信,利用载波相位差分原理,消除卫星定位过程中的大部分公共误差,如卫星轨道误差、电离层延迟、对…...
TMS320C55x DSP芯片结构和CPU外围电路
第2章 DSP芯片结构和CPU外围电路 文章目录 第2章 DSP芯片结构和CPU外围电路TMS320C55x处理器的特点TMS320c55x CPU单元指令缓冲(Instruction Buffer Unit) I单元程序流程(Program Flow Unit) P单元地址数据(Address-data Flow Unit) A单元数据计算(Data Computation Unit) D单元…...
【CC2530开发基础篇】继电器模块使用
一、前言 1.1 开发背景 本实验通过使用CC2530单片机控制继电器的吸合与断开,深入了解单片机GPIO的配置与应用。继电器作为一种常见的电气控制元件,广泛用于自动化系统中,用于控制大功率负载的开关操作。在本实验中,将通过GPIO口…...
3D 生成重建035-DiffRF直接生成nerf
3D 生成重建035-DiffRF直接生成nerf 文章目录 0 论文工作1 论文方法2 实验结果 0 论文工作 本文提出了一种基于渲染引导的三维辐射场扩散新方法DiffRF,用于高质量的三维辐射场合成。现有的方法通常难以生成具有细致纹理和几何细节的三维模型,并且容易出…...
安宝特分享 | AR技术助力医院总院与分院间的远程面诊
随着科技的迅猛发展,增强现实(AR)技术在各行各业的应用愈发广泛,特别是在医疗领域,其潜力和价值正在被不断挖掘。在现代医疗环境中,患者常常面临“看病难、看病远、看病急”等诸多挑战,而安宝特…...
【功能安全】硬件常用安全机制
目录 安全机制分类: ECC内存保护: 看门狗定时器WDT 看门狗的诊断覆盖率 程序流监控 软件自检<...
linux上qt打包(二)
sudo apt install git 新建一个文件夹 名为xiazai, chmod -R 777 xiazai cd xiazai 并进入这个文件夹,然后clone git clone https://github.com/probonopd/linuxdeployqt.git 此处可能要fanQiang才能下 cd linuxdeployqt文件夹 下载平台需要的…...
Vue3 左右2栏的宽度 比例resize
Vue3, 页面左右2栏布局,用vue-resizer.页面效果如下图。 安装 npm i vue-resizer引入 import { DragCol, DragRow, ResizeCol, ResizeRow, Resize } from vue-resizer<DragCol height"100%" width"100%" :leftPercent"15">…...
企业车辆管理系统(源码+数据库+报告)
一、项目介绍 352.基于SpringBoot的企业车辆管理系统,系统包含两种角色:管理员、用户,系统分为前台和后台两大模块 二、项目技术 编程语言:Java 数据库:MySQL 项目管理工具:Maven 前端技术:Vue 后端技术&a…...
LeetCode 1847.最近的房间:有序集合
【LetMeFly】1847.最近的房间:有序集合 力扣题目链接:https://leetcode.cn/problems/closest-room/ 一个酒店里有 n 个房间,这些房间用二维整数数组 rooms 表示,其中 rooms[i] [roomIdi, sizei] 表示有一个房间号为 roomIdi 的…...
C05S07-Tomcat服务架设
一、Tomcat 1. Tomcat概述 Tomcat也是一个Web应用程序,具有三大核心功能。 Java Servlet:Tomcat是一个Servlet容器,负责管理和执行Java Servlet、服务端的Java程序,处理客户端的HTTP请求和响应。Java Server:服务端…...
Vscode搭建C语言多文件开发环境
一、文章内容简介 本文介绍了 “Vscode搭建C语言多文件开发环境”需要用到的软件,以及vscode必备插件,最后多文件编译时tasks.json文件和launch.json文件的配置。即目录顺序。由于内容较多,建议大家在阅读时使用电脑阅读,按照目录…...
mac电脑可以使用的模拟器
BlueStacks Air 推荐-》亲测可用 BlueStacks Air https://www.bluestacks.com 支持macOS/Windows,刚新增了对Apple Silicon系列M芯片的支持 GameLoop https://www.gameloop.com/ 支持 macOS/Windows Genymotion https://www.genymotion.com/ 支持Android/macO…...
vertx idea快速使用
目录 1.官网下载项目 2.修改代码 2.1拷贝代码方式 为了能够快速使用,我另外创建一个新的maven项目,将下载项目的src文件和pom文件拷贝到新建的maven项目。 2.2删除.mvn方式 3.更新配置 4.配置application 5.idea启动项目 1.官网下载项目 从vert…...
selenium 在已打开浏览器上继续调试
关闭浏览器,终端执行如下指令,--user-data-dir换成自己的User Data路径 chrome.exe --remote-debugging-port9222 --user-data-dir"C:\Users\xxx\AppData\Local\Google\Chrome\User Data" 会打开浏览器,打开百度,如下状…...
RequestContextHolder 与 HttpServletRequest 的联系
1. 什么是 RequestContextHolder? RequestContextHolder 是 Spring 框架 提供的一个工具类,用于在当前线程中存储和获取与请求相关的上下文信息。它是基于 ThreadLocal 实现的,能够保证每个线程独立存储和访问请求信息。 与 HttpServletReq…...
力扣hot100——普通数组
53. 最大子数组和 class Solution { public:int maxSubArray(vector<int>& nums) {int n nums.size();int ans nums[0];int sum 0;for (int i 0; i < n; i) {sum nums[i];ans max(ans, sum);if (sum < 0) sum 0;}return ans;} }; 最大子数组和…...
本地部署大模型QPS推理测试
目录 1、测试环境1.1、显卡1.2、模型1.3、部署环境1.3.1、docker1.3.2、执行命令 2、测试问题2.1、20字左右问题2.2、50字左右问题2.3、100字左右问题 3、测试代码3.1、通用测试代码3.2、通用测试代码(仅供参考) 4、测试结果4.1、通用测试结果4.2、RAG测…...
内存、硬盘、DRAM、FLASH
内存 内存是计算机系统中用于临时存储和快速存取当前正在处理的数据和指令的组件,属于临时存储设备,它在计算机运行时用于存储活跃的程序和数据。内存的性能直接影响计算机的处理速度和响应能力。 内存的类型主要分为: DRAM(动态随…...
全景图转6面体图 全景图与6面体图互转
目录 图片转360度全景中心图 依赖项: 库的使用方法: 源代码: 全景图拆成6面体图: 全景图,转6面体,再转全景图: 图片转360度全景中心图 原图: 效果图: 依赖项: pip install py360convert pip install numpy==2.2 库的使用方法: https://g...
【psutil模块02】Python运维模块之系统进程管理方法
系统进程管理方法 获取当前系统的进程信息,包括进程的启动时间、查看或设置CPU亲和度、内存使用率、IO信息、socket连接、线程数等。 1.进程信息 psutil.pids()获取所有PID,使用psutil.Process()接收单个进程的PID,获取进程名、路径、状态、系统资源等…...