【Python】爬虫基础

爬虫是一种模拟浏览器实现,用以抓取网站信息的程序或者脚本。常见的爬虫有三大类:

通用式爬虫:通用式爬虫用以爬取一整个网页的信息。

聚焦式爬虫:聚焦式爬虫可以在通用式爬虫爬取到的一整个网页的信息基础上只选取一部分所需的信息。

增量式爬虫:增量式爬虫每次只爬取网站中更新的信息。

传输协议

我们知道,当我们点进某个页面时,一般我们的客户端会向服务器发送HTTP请求报文。其中报文里面很重要的一个内容就是报文的头信息(包括请求行、首部行等),它们包含了我们请求的一些基本信息。所以我们有必要了解如何找到客户端和服务器端的相互传输的HTTP报文的头信息:先打开任意一个网址,按下键盘的F12

在打开的窗口中点击network栏,再在Name一栏随意找到该网页中的某个对象,点击后选择右端的headers即可看到我们发送的报文一些头信息。

然而为了防止传输的报文被第三方拦截泄露信息,一般情况下我们的传输过程会加上一些保密协议:例如HTTPS就比HTTP更加安全,因为它采用了证书密钥的加密方式。

requests模块

报文请求过程

因为爬虫是模拟浏览器发送报文,所以我们需要先知道怎么模拟浏览器实现向服务器发送请求功能。正好Python中就为我们提供了相应的功能模块--“requests”。

我们需要明确浏览器发送时它共经历了三个步骤:1、获取URL(点击网址);2、向服务器发送请求报文(等待加载);3、得到、处理响应报文(页面展示),所以我们需要利用requests模块也需要模拟出上面三个过程。(下面以获取百度首页文字为例)

1、获取URL:

导入该模块,并且开辟一个变量来存储目标URL。

import requestsurl = 'https://www.baidu.com'

2、发送请求:

这里我们模拟的是使用GET方法的报文,当然我们也可以模拟使用POST、DELETE等方法的报文。具体发送请求就调用requests模块里面的get方法,它常用有三个参数:url、params和headers。我们这里先只传入url。因为我们使用的是get方法,所以它会返回一个响应报文。

import requestsurl = 'https://www.baidu.com/?tn=44004473_52_oem_dg'
response = requests.get(url)

3、处理响应报文

我们将得到的响应报文存到变量response里面,并且将其字符编码设置为“utf-8”(防止乱码)。然后我们的目标是获取返回的报文里面的文字信息,所以我们将其文章的文字信息以字符串的形式存储到一个文件里面。

import requestsurl = 'https://www.baidu.com/?tn=44004473_52_oem_dg'
response = requests.get(url)
response.encoding = 'utf-8'
file = open("./myget.html", "w", encoding='utf-8')
file.write(response.text)
file.close()

这样我们运行生成的html就可以访问到百度的主页文字了

 

静态网页采集

之前只是获取一个特定的网页的信息,下面我们以实现采集利用关键词搜索出来的网页内容为例子看如何获取我们想要的网页(搜索):

以百度搜索为例,我们利用百度搜索某个关键词时使用的URL如下:

所以一般搜索类的URL结构如下(百度)

域名+s?wd=“我们的问题”

所以我们在爬取网页内容之前需要模仿关键词搜索,实现URL中包含我们需要搜索的内容。为了实现在URL中插入参数变量,get方法提供了一个params参数,我们只需要向其传入一个需要搜索的关键字即可,这个关键字一般存储于一个字典中。以百度搜索为例,我们的问题对应的键就是’wd‘ 

import requestsurl = 'https://www.baidu.com/s'
rec = input()
dic = {'wd': rec
}
response = requests.get(url= url, params= dic)
response.encoding = 'utf8'
text = response.text
file = open("./output.html", 'w', encoding='utf8')
file.write(text)
file.close()

但是这还不够,我们还需要注意的一点是,我们使用这个方法还不能获取当前的网页,有一网页服务器发现我们不是浏览器发送的请求,会拒绝响应。所以我们需要加上一些伪装信息,绕过一些服务器有反爬机制。所以需要利用get方法的第三个常用参数headers(头信息),假装我们是浏览器发送的报文,而非爬虫程序,一般加入Cookie和Accept即可了。要想知道自己这些信息打开网页按下F12即可看到:

加上我们自己的伪装后的程序可以直接不需要验证就获取网页:

import requestsurl = 'https://www.baidu.com/s'
rec = input()
dic = {'wd': rec
}
headers = {'Accept': ...'User-Agent':...'Cookie':...
}
response = requests.get(url= url, params= dic,  headers=headers)
response.encoding = 'utf8'
text = response.text
file = open("./output.html", 'w', encoding='utf8')
file.write(text)
file.close()

动态网页采集

但是并不是所有访问网页上端显示的URL都能获取到对应的完整界面,有时候网页上有一些信息是通过AJAX请求后才返回给我们的,下面是判断信息是否为AJAX请求返回的:

假如我们打开bilibili,找到电影索引的网页,我们目标是想获取所有电影的名字和评分,并将从第一页到第四页的电影名和对应评分都获取下来存到对应的txt文件中,所以需要知道怎么获取其信息。按下F12,我们在All里面找到对应该页面的URL的对象,但是它的response里面没有找到我们需要的数据,那么就证明这些数据不是直接通过访问URL获取的,而是由AJAX请求后再传输给我们的。

我们可以再次定位到XHR一栏,逐个寻找,找到Response里面包含电影名的那个Name,查看头信息获取其URL和返回的文件类型(json类型)以此来模拟浏览器请求过程。

import json
import requestsurl ='https://api.bilibili.com/pgc/season/index/result'
headers = {'User-Agent':...}
file = open('./output.txt', 'w', encoding='utf8')
for i in range(1,5):params={'st': '2','order': '2','area': '-1','style_id': '-1','release_date': '-1','season_status': '-1','sort': '0','page': i,'season_type': '2','pagesize': '20','type': '1'}response = requests.get(url=url, headers=headers, params=params).json()for p in response['data']['list']:file.write('电影名:'+p['title']+' '+'评分:'+p['score']+'\n')
file.close()

数据解析

上面使用到的requests模块主要还是用于获取一整张网页,但是我们很多情况下只需要某部分对应的信息,这时候我们就需要对采集的数据进行解析了,解析位于标签之间的文本内容或文本属性。

所以总结我们数据解析的步骤,可以大致分为两步:1、定位到该信息的标签;2、从该标签中提取该信息。

针对上面的数据解析,我们主要利用bs4和xpath的解析方法。

bs4

bs4的数据解析方法共分为两步:

1、将请求到的页面源码赋予给实例化的对象Beautifulsoup

2、调用Beautifulsoup的属性或者方法来从标签中获取我们所需的内容

在我们使用bs4的方法前,需要先导两个包:bs4包和解释源码用到lxml包:

pip install bs4

pip install lxml

第一步实现:

然后我们既可以实例化出一个BeautifulSoup对象bs了,实例化需要向BeautifulSoup里传入两个参数:1、我们获取的网页源码;2、固定是'lxml'的解析方式。其中所需的源码就是我们曾经使用get方法中的text方式获取到的对应网页的数据。

第二步实现:

1、定位

再者就是如何调用实例化出来的对象bs里面的方法和属性来获取对应标签里面的所需信息了。常见的使用方法或者属性有bs.find()、bs.find_all、bs.tagname。

bs.tagname是获取bs对象里面标签名为tagname的第一个标签的内容,相似地,bs.find('tagname')也是找出第一个标签名为tagname的标签里面的内容,如果想找到更深入,还可以加上属性:bs.find('tagname', class_=‘属性’);要想找到全部标签名为tagmane的标签内容,需要使用find_all,传入标签名即可。

而挑选某个符合条件的信息需要使用select方法,我们向它传递的参数是一个选择器,我们可以选择传递类选择器(标志为 . 号)、id选择器(标志位#号)或者标签名选择器等等...(或者我们还可以传递层选择器“上一级标签 > 次级标签 >最低级标签”、如果有非隶属则>换为空格)。

2、获取

当我们利用定位获取到当前信息存储到标签位置时,我么们就需要考虑如何去获取这些信息了,在BeautifulSoup模块中,其为每一个实例出来的对象都提供了两个属性和一个方法来获取这些信息:我们以bs.tagname为定位标签,则获取信息的方式是bs.tagname.text 、 bs.tagname.get_text() 、 bs.tagname.string。但是这三者中前两者可以获取当前标签下的所有内容,而后者只能获取当前标签下的直隶内容。

import json
import requests
import lxml
from bs4 import BeautifulSoupurl = 'https://blog.csdn.net/m0_61151031'
headers ={'cookie':....、'user-agent': ...
}
param = {'spm': '1011.2415.3001.5343'
}
response = requests.get(url=url, params=param, headers= headers)
response.encoding='utf8'bs = BeautifulSoup(response.text, 'lxml')
title_save = []
URL_save = []
for j in bs.find_all('article' ,class_='blog-list-box'):URL_save.append(j.find('a')['href'])
for i in bs.find_all('h4'):title_save.append(i.text)file= open('./output.txt', 'w', encoding='utf-8')
print(len(title_save))
print(len(URL_save))
for i in range(0,len(title_save)-1):file.write(title_save[i]+'  :  '+URL_save[i]+'\n')
file.close()

Xpath

在我们使用Xpath之前需要先安装Xpath所需的解释器 lxml 包:

pip install lxml

安装完后,下面我们就可以使用Xpath来捕获网页中某个标签对应的具体信息。整个获取过程总结起来为两步:1、定位到具体存储的标签2、获取该标签下的信息。实际使用Xpath来操作这两个步骤的过程如下:

1、实例化出一个etree对象,并且将网页源码赋予该对象

2、调用etree对象里面的xpath方法,利用xpath表达式定位到我们所需的标签,捕获其内容信息。

下面是作为例子的html文件,我们可以看到信息都是夹杂在一些尖括号括起来的标签里边(类似这样:<XX>信息</XX>):

<html lang="en">
<head><meta charset="UTF-8" /><title>测试bs4</title>
</head>
<body><div><p>百里守约</p></div><div class="song"><p>李清照</p><p>王安石</p><p>苏轼</p><p>柳宗元</p><a href="http://www.song.com/" title="赵匡胤" target="_self"><span>this is span</span>宋朝是最强大的王朝,不是军队的强大,而是经济很强大,国民都很有钱</a><a href="" class="du">总为浮云能蔽日,长安不见使人愁</a><img src="http://www.baidu.com/meinv.jpg" alt="" /></div><div class="tang"><ul><li><a href="http://www.baidu.com" title="qing">清明时节雨纷纷,路上行人欲断魂,借问酒家何处有,牧童遥指杏花村</a></li><li><a href="http://www.163.com" title="qin">秦时明月汉时关,万里长征人未还,但使龙城飞将在,不教胡马度阴山</a></li><li><a href="http://www.126.com" alt="qi">岐王宅里寻常见,崔九堂前几度闻,正是江南好风景,落花时节又逢君</a></li><li><a href="http://www.sina.com" class="du">杜甫</a></li><li><a href="http://www.dudu.com" class="du">杜牧</a></li><li><b>杜小月</b></li><li><i>度蜜月</i></li><li><a href="http://www.haha.com" id="feng">凤凰台上凤凰游,凤去台空江自流,吴宫花草埋幽径,晋代衣冠成古丘</a></li></ul></div>
</body>
</html>

第一步实现:

要想定位到具体的标签,首先我们需要有一个etree对象。调用etree的parse方法将本地的html文件传入该方法中(如果是网上的html文件,需要调用etree的HTML方法),该函数会返回一个已解释了html对象:

import requests
from lxml import etreerec_etree = etree.parse('./test.html')

然后我们就可以 调用该对象的方法xpath来定位标签,其中xpath方法有多种使用方式,不过具体还是向其传入一个标签位置。

一个标签位置的格式为 :/ 标签名 / 标签名 / 标签名。由 / 符号表示层级关;但是我们还可以利用 // 表示多层级关系,/ 标签名 // 标签名,省略中间的标签名。如果我们以及获取了定位在中间层级的某个对象,那么接下来的位置也可以表示为 ./标签名/标签名,这里的 ./ 就表示从当前位置开始,和Linux系统中用法类似。如果某一层的下一次标签名都相同,那么我们需要在相同的标签后面加上[n](n为数字)来表示是哪一个标签。

例如上图中P[1]是李清照、P[2]是王安石...这里要注意下标从1开始

如果定位时要加上标签的属性,我们则需要在标签后面加上[@class="属性"],例如上面的例子要定位到具有属性song的标签地址就需要写为:

//div[@class="song"]

下面以定位信息苏轼的标签p为例可以写出多种定位写法:

print(rec_etree.xpath('/html/body/div/p[3]'))
print(rec_etree.xpath('/html//p[3]'))
print(rec_etree.xpath('//div/p[3]'))
print(rec_etree.xpath('//p[3]'))

从上到下我们依次来看:首先第一个是直接完全每一个层级都标清楚的定位;第二个是省略了中间层级的定位;第三个是省略局部前面层级的定位;第四个是省略全部前面层级的定位。

(或者有时候可以直接用右键点击该信息复制...)

第二步实现:

当我们定位到某个标签后,我们得到的还只是给elements对象,我们要想获取里面对应的信息,最简单的方式就是在标签后再加一层/text(),但是这样得出来的还只是一个字典:

为此我们还需要获取其首元素信息

print(rec_etree.xpath('/html/body/div/p[3]/text()')[0])
print(rec_etree.xpath('/html//p[3]/text()')[0])
print(rec_etree.xpath('//div/p[3]/text()')[0])
print(rec_etree.xpath('//p[3]/text()')[0])

但是我们仔细看可以看到有时候有些我们需要的信息没有存在标签之间,而是存在了标签里边,作为标签的属性,这时候我们就需要在定位到标签后加上..标签名/@属性名来获取这个属性。

print(rec_etree.xpath('//div[@class="song"]/a[@title="赵匡胤"]/@href')[0])

验证码

验证码是服务器指定的一种反爬机制,当我们登录某些网址时,在输入了账号和密码后系统可能需要我们输入一个随机的验证码,这样可以有效地防止我们的爬虫程序模拟登录,因为程序预先不知道系统会给哪些个验证码,以此来保障反爬。

为此,我们要想爬取需要验证码的网址,需要借助一些第三方验证码识别工具进行验证码破解或者人工识别图片或者用专门的AI图片识别算法。

多线程

现在我们需要考虑一种情况,如果我们一次性需要请求很多的URL,那么系统会如何去执行我们的代码呢?下面是一次模拟请求情况的代码:

import requestsurl =['url1''url2''url3'...
]
headers = {...
}def get_status(each_url):response = requests.get(url=url, headers=headers)return response.status_codefor each_url in url:ret = get_status(each_url)print(ret)

我们将输出每一个请求的URL的返回状态码。系统会将我们的URL通过GET函数一次一次地串行发送过去,这样也就表面,如果GET函数发送请求一个报文并获得响应需要1s,那么100个URL请求将要花费100s,更多情况下花费地时间将更长!

其实我们回过头来看,问题主要还是出在了每一次GET只发送一个URL的请求,下面为串行通信的示意:

 为了解决这个问题,人们提出的方案为开辟多个进程进行发送,也就是使用并行的通信方式,这种方式的示意图如下,虽然多线程的通信可以大大减少发送的时间,但是每次调用就开辟一个进程,用完就关掉的往复操作却增大了CPU的工作量!所以每一个方法都会有副作用! 

 

 除了上面的两种方法,聪明的人们还想出了另外一种方法:线程池,它在多线程的基础上进行修改,基本原理为一次开辟多个进程,然后用这些有限的线程池来进行数据传输,这样可以有效地避免了多线程情况下线程使用的开关开关操作导致的消耗。下面我们来做两个例子对比一下:

首先是模拟单线程,模拟的例子的思路是用一个函数operation,这个函数将期待我们发送报文的函数,其中每次get函数所导致的阻塞时间就由sleep函数来模拟,每次的url就由一个elements列表来表示。

from time import *start = time()
def operation(element):sleep(2)elements = [1, 2, 3, 4]
for i in elements:operation(i)
end = time()
print('您一共花费了:'+'%lf'%(end-start)+'秒')

通过测试上面的代码我们可以得到共花费的时间:

然后我们来使用多线程传输。要想实现多线程传输我们首先需要导入一个包,这个包里面的Pool类将帮助我们创建一个多线程对象,其中初始化对象时传进去的参数就是未来我们将开辟的线程池的总线程数:

from time import *
from multiprocessing.dummy import Poolstart = time()
def operation(element):sleep(2)elements = [1, 2, 3, 4]
pool = Pool(4)
pool.map(operation, elements)end = time()
print('您一共花费了:'+'%lf'%(end-start)+'秒')

然后调用实例化出来的多线程对象的map方法,向其传入产生通信阻塞的操作(这里是operation函数)和需要向该操作传递的参数。它就会帮助我们使用多线程的动作来实现这个操作,并且如果该操作本身有返回值那么它也会向我们返回某些数值。 所以使用了多线程那么理论上消耗时间将变为原来的1/4:

当然,如果使用3个线程,那么类似木桶理论,传输时间取最大的值,也就是先用三进程传输3个,再用三进程中其中一个传输第四个数,所以共消耗4s左右:

协程

协程的定义及其实现过程

在我们学习协程之前首先需要理解协程的定义,下面的解释来自百度百科:

协程不是进程或线程,其执行过程更类似于子例程,或者说不带返回值的函数调用

总的来说,协程是运行在线程之上的函数调用操作,之所以选择这么做,是因为有时候即使我们开启了线程池,同时阻塞的情况也是可能存在,线程之间由于阻塞而等待切换的过程会导致时间以及空间上的浪费。所以发明一种协程可以单独运行在堵塞的线程之上,当一个协程完成后另一个可以补上,并且它还不会增加线程的数量。

下面我们来看看如何用Python实现协程:

协程对象及事件循环

因为协程本质是一个函数调用,所以我们首先需要定义一个函数,并且为其加上关键字async,这就表明这个函数不再是一个简单的函数,它会会返回一个协程对象,并且当我们再次调用这个函数时,函数里面的语句不会被执行!

当我们利用async修饰一个函数后,调用该函数会返回一个协程对象,并且如果我们想去使用这个协程对象,需要先创建一个事件循环,我们只有将协程对象注册进事件循环里面其才能被执行,这个过程就像先烧一锅开水(事件循环),然后才能放方便面(协程对象)下去焖(执行)。而创建一个事件循环就是调用asyncio里面的方法,并且还需要启动它和放入协程对象。

import asyncioasync def func1():print("函数已调用!")ret = func1()
loop = asyncio.get_event_loop()
loop.run_until_complete(ret)

基于事件循环创建任务对象

关于函数func1返回的协程对象ret,我们还可以将其封装成一个任务对象,它在原来协程对象的基础上会增加一些对象的状态,而封装的方法就是调用loop里面的方法,方法调用完后会返回一个新的任务对象,它具有大部分协程对象的性质:

import asyncioasync def func1():print("函数已调用!")ret = func1()
loop = asyncio.get_event_loop()
task = loop.create_task(ret)
loop.run_until_complete(task)
print(task)

 下面就是task对象所附带的状态信息的输出

基于asyncio创建任务对象

除了上面利用loop创建任务对象的方法,我们还可以利用一种别的方式来创建任务对象,具体是直接调用asyncio里面的ensure_future方法向其传入协程对象即可创建出一个任务对象:

import asyncioasync def func1():print("函数已调用!")ret = func1()
loop = asyncio.get_event_loop()
task = asyncio.ensure_future(ret)
loop.run_until_complete(task)
print(task)

 回调函数

如果我们想在函数执行完后绑定一个回调函数,只需要调用下面的方法将task和该回调函数绑定即可,这样在事件循环中凡是执行了task任务对象后它会调用这个回调函数!

import asyncioasync def func1():print("函数已调用!")return 'a'def show_ret(task):print(task.result())
ret = func1()
loop = asyncio.get_event_loop()
task = loop.create_task(ret)
task.add_done_callback(show_ret)
loop.run_until_complete(task)

协程的优势测试

下面我们就一个具体的例子来看看协程的优势:

下面是一组测试代码,具体的意思是我们先创建一个协程对象,然后在这个协程对象里面写一个睡眠函数,让函数执行停止2秒模仿等待过程(注意这里要用asyncio里面的sleep函数,因为time包里面的sleep函数是基于同步(线程如果堵塞会等待当前任务响应才继续处理下一个任务)实现的,而我们的多任务协程的实现方式是异步(线程如果堵塞将任务挂起直到收到回应才重新处理,不阻碍线程),这里如果调用基于同步实现的函数会失效。同时注意在前面加上一个await的挂起操作)然后使用一个for循环来模仿多任务的情况,每一次循环都会创建一个协程对象并且基于其来创建一个任务对象。最后将存储任务的任务列表注册到事件循环中,这里注意要调用因为是任务列表所以要使用wait。

import asyncio
from time import *
start = time()async def func():print("调用该函数!")await asyncio.sleep(2)task_list=[]
loop = asyncio.get_event_loop()
for i in range(0,3):ret = func()task = loop.create_task(ret)task_list.append(task)loop.run_until_complete(asyncio.wait(task_list))
end = time()
print(end-start)

 

协程的具体实现

接下来我们就可以直接来看看如何使用协程进行多任务异步实现,我们一次性爬取百度搜索的搜狗、360和猎豹页面。按照上面的使用方法,我们先创建一个URL的元组,然后定义一个请求该URL内容的函数,并且将加上async的定义关键字,此时这个函数将会返回一个协程对象。然后创建一个事件循环并且每一次都将协程对象创建出来的任务对象放进事件循环中执行。

import requests
import asyncio
import time
start = time.time()
urls = ['https://www.baidu.com/s?wd=%E6%90%9C%E7%8B%97','https://www.baidu.com/s?wd=360','https://www.baidu.com/s?wd=%E7%8C%8E%E8%B1%B9']
headers = {'Accept': ...'User-Agent':'...'Cookie': ...
}async def get_content(url):ret = requests.get(url=url, headers=headers)print("success!")loop = asyncio.get_event_loop()
tasks=[]
for url in urls:ret_ = get_content(url)task = loop.create_task(ret_)tasks.append(task)
loop.run_until_complete(asyncio.wait(tasks))
end = time.time()
print(end-start)

虽然这样看起来好像实现了异步多任务处理,但是实际上requests里面的get函数是一个基于同步处理的函数,也就是说我们根本没有利用协程实现异步多任务的处理!为了解决这个问题我们需要利用基于异步而写的请求函数,为此我们需要使用到aiohttp模块。我们首先需要获取一个请求对象session,这个对象将会帮助我们发送请求报文,我们会调用aiohttp里面的CilentSession方法来创建一个对象,然后类似于requests一样调用对象里面的get方法来获取响应(注意这里获取字符串需要使用到text是一个方法而非属性),而且请求函数和获取响应报文内容等操作是都需要进行挂起(使用await)。

async def get_content(url):ret = requests.get(url=url, headers=headers)print("success!")||V
async def get_content(url):async with aiohttp.ClientSession() as session:async with await session.get(url=url, headers=headers) as  response:print('success!')//ret = await response.text()

使用协程:

不使用协程:

我们可以明显看出使用了协程的速度比不使用要快! 

Selenium

因为Selenium的内容有很多,所以我另外写了一篇博客来记录我的Selenium学习过程👉

代理

代理就是我们在客户端和服务器之间构建一个中间桥梁,我们可以通过代理服务器来向目标服务器发送请求。这样可以在我们IP被目标服务器封的时候还能够请求到目标服务器的内容,或者可以让我们不想直接使用自己的以IP的身份直接请求某个服务器而隐藏起自己的IP。

其中代理IP有三种类型:透明、匿名和高匿。

使用透明的代理IP服务器会知道你在使用代理IP并且也知道你的真实IP;

使用匿名的代理IP服务器会知道你在使用代理IP但不知道你的真实IP;

使用高匿的代理IP服务器不知道你在使用代理IP更不会知道你的真实IP。


参考资料:

什么是协程? - 知乎

Day1 - 1.爬虫简介-爬虫的概念和价值_哔哩哔哩_bilibili

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/77018.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用生成式 AI 增强亚马逊云科技智能文档处理

数据分类、提取和分析对于处理大量文档的组织来说可能具有挑战性。传统的文档处理解决方案是手动的、昂贵的、容易出错的,并且难以扩展。利用 Amazon Textract 等 AI 服务,亚马逊云科技智能文档处理(IDP)允许您利用业界领先的机器学习(ML)技术来快速准确地处理任何扫描文档或图…

Blender之锁定摄像机到视图方位

文章目录 当你在blender 中时&#xff0c;想要让你的摄像机跟随你的视图方位&#xff0c;以方便你的后期的制作&#xff0c;那应该怎么半&#xff1f; 先点击摄像机的图标&#xff0c;进入摄像机视图 然后按一下键盘的N 键&#xff0c;进入编辑模式&#xff0c;选择视图 最后…

【业务功能107】微服务-springcloud-springboot-Sentinel容器安装-熔断降级限流

Sentinel 在微服务架构下&#xff0c;会涉及到 微服务A需要远程调用其他的微服务B,C,D等的接口&#xff0c;比如商品服务接口&#xff0c;需要调用库存服务数据&#xff0c;秒杀服务数据等&#xff0c;这里就会衍生一个长链路的调用过程&#xff0c;那么一旦下游需要被调用的数…

800V威迈斯车载充电机

800V威迈斯车载充电机VMAX二合一车载充电系统高压平台&#xff0c;将传统的6.6KW双向OBC、2.5KW DC\DC进行系统级的高功率密度集成&#xff1b;采用磁集成技术方案&#xff0c;创新性地将产品的原边绕组&#xff0c;高压绕组及低压Busbar进行系统级的集成&#xff1b;OBC功能转…

力扣(LeetCode)算法_C++——稀疏矩阵的乘法

给定两个 稀疏矩阵 &#xff1a;大小为 m x k 的稀疏矩阵 mat1 和大小为 k x n 的稀疏矩阵 mat2 &#xff0c;返回 mat1 x mat2 的结果。你可以假设乘法总是可能的。 示例 1&#xff1a; 输入&#xff1a;mat1 [[1,0,0],[-1,0,3]], mat2 [[7,0,0],[0,0,0],[0,0,1]] 输出&am…

【STL】模拟实现map和set {带头结点的红黑树;红黑树的核心结构;红黑树的迭代器;红黑树的插入和查找;map和set的封装}

模拟实现map和set map和set是红黑树的两种不同封装形式&#xff0c;底层使用同一颗泛型结构的红黑树。set是红黑树的K模型&#xff1b;map是红黑树的KV模型。 下面的代码和讲解着重体现红黑树的底层实现和map\set上层封装的衔接。关于二叉搜索树性质&#xff0c;map和set的介…

UMA 2 - Unity Multipurpose Avatar☀️四.UMA人物部位的默认颜色和自定义(共享)颜色

文章目录 🟥 人物颜色介绍1️⃣ 使用默认颜色2️⃣ 使用自定义颜色🟧 UMA自定义颜色的作用🟨 自定义颜色还可作为共享颜色🟥 人物颜色介绍 UMA不同部位的颜色分为默认的内置颜色和我们新定义的颜色. 1️⃣ 使用默认颜色 比如不勾选UseSharedColor时,使用的眼睛的默认…

品牌策划经理工作内容|工作职责|品牌策划经理做什么?

一位美国作家曾说过“品牌是一系列期望、记忆、故事和关系&#xff0c;他们共同构成了消费者最终原则一个产品或者服务的原因。” 所以&#xff0c;品牌经理这个岗位主要是创造感知价值主张&#xff0c;激发消费者购买这个品牌后带来的感知价值&#xff0c;这种回报的本质相对…

Git 基本操作【本地仓库与远程仓库的推送、克隆和拉取】

文章目录 一、Git简介二、Git的下载安装三、Git常规命令四、新建本地仓库五、本地分支操作六、Git远程仓库七、远程仓库克隆、抓取和拉取八、总结九、学习交流 一、Git简介 Git是分布式版本控制系统&#xff08;Distributed Version Control System&#xff0c;简称 DVCS&…

使用rpm重新安装包

#查询 rpm -qa | grep cloudstack #卸载 rpm -e cloudstack-agent-4.18.0.0-1.x86_64 #安装 rpm -ivh cloudstack-agent-4.18.0.0-1.x86_64.rpm

【PowerQuery】PowerQuery学习路径

PowerQuery这么好,怎么去学习呢?相信很多初读本书的朋友迫切的希望了解整个PowerQuery全景知识和它提供的相应的功能。但是对于PowerQuery来说,一开始就会进行自定义函数的构建当然也是不可能的,这里有相应的学习路径来进行由浅入深的学习,帮助读者更好的理解PowerQuery的…

leetcode 649. Dota2 参议院

2023.9.11 先简化一下题意&#xff1a;本题的意思就是每次投票中&#xff0c;前面的议员可以干掉后面的议员(当然是干掉敌对方的)&#xff0c;然后他将参与下一次的投票&#xff0c;而被干掉的议员则不能参与投票了。 如&#xff1a;[R D D] -> [R D] ->[R D]->[D] 。…

【算法基础】时间复杂度和空间复杂度

目录 1 算法的评价 2 算法复杂度 2.1 时间复杂度&#xff08;Time Complexity&#xff09; 2.1.1 如何计算时间复杂度&#xff1a; 2.1.2 常见的时间复杂度类别与示例 2.2 空间复杂度 2.2.1 如何计算空间复杂度 2.2.2 常见的空间复杂度与示例 3 时间复杂度和空间复杂度…

【附安装包】2023最新版Python安装详细教程!一键安装,永久使用

一、python官网 Python官网主要有python的About (简介)、Downloads (下载)、Documentation(文档)、Community (团体)、Success Stories (成功案例)、News (新闻)、Events (事件动态)等栏目。 Python官网地址&#xff1a;https://www.python.org/ 【领取方式见文末】 二、在…

免费的低代码助力售后工单管理:快速搭建,高效定制

编者按&#xff1a;本文旨在阐述免费且高效的低代码平台在实现售后工单管理系统方面的优势、功能及其作用。这些优势和功能对于提高企业的服务质量和效率具有重要的意义。 关键词&#xff1a;低代码平台、售后工单系统、私有化部署 1.售后工单系统有什么作用&#xff1f; 售后工…

day55:C++ day5,运算符重载剩余部分、静态成员、继承

#include <iostream> #include <cstring> #define pi 3.14 using namespace std;class Shape { protected:double round;double area; public://无参构造Shape():round(40),area(100){cout<<"Shape::无参构造函数&#xff0c;默认周长为40&#xff0c;面…

Python的命令行参数

Python的命令行参数&#xff0c;提供了很多有用的功能&#xff0c;可以方便调试和运行&#xff0c;通过man python就能查看&#xff0c;以下是一些常用参数使用实例和场景: 1. -B参数 在import时候&#xff0c;不产生pyc或者pyo文件: 比如有程序main.py如下: from Hello im…

软件测试/测试开发丨使用ChatGPT自动进行需求分析

简介 在实际工作过程中&#xff0c;常常需要拿到产品的PRD文档或者原型图进行需求分析&#xff0c;为产品的功能设计和优化提供建议。 而使用ChatGPT可以很好地帮助分析和整理用户需求。 实践演练 接下来&#xff0c;需要使用ChatGPT 辅助我们完成需求分析的任务 注意&…

数据链路层相关知识

数据链路层的作用 两个设备(同一种数据链路节点)之间进行传递数据 认识以太网 "以太网"是一种技术标准;既包含了数据链路层的内容,也包含了一些物理层的内容.例如:规定了网络拓扑结构,访问控制方式,传输速率等;以太网中的网线必须使用双绞线;传输速率有10M,100M,1…

Linux设备驱动——自动创建设备节点udev机制的实现过程

创建设备文件的机制有以下下列几种&#xff1a; mknod命令&#xff1a;手动创建设备节点的命令devfs:可以用于创建设备节点&#xff0c;创建设备节点的逻辑在内核空间&#xff08;内核2.4版本之前使用&#xff09;udev:自动创建设备节点的机制&#xff0c;创建设备节点的逻辑在…