解锁豆瓣高清海报(一) 深度爬虫与requests进阶之路
前瞻
PosterBandit
这个脚本能够根据用户指定的日期,爬取你看过的影视最高清的海报,然后使用 PixelWeaver.py
自动拼接成指定大小的长图。
你是否发现直接从豆瓣爬取下来的海报清晰度很低? 使用 .pic .nbg img
CSS 选择器,在 我看过的影视
界面找到图片元素并直接爬取(爬取深度为 0),甚至不用太重视 time.sleep()
。好处是速度超快,坏处是这样爬取的海报都是被压缩过的,画质很差。任何一个脚本小子都不会屈服于这样的质量。
想要爬取最高清的海报,答案一定是增加爬取深度!
脚本地址:
项目地址: Gazer
PosterBandit.py
使用方法
- 克隆或下载项目代码。
- 安装依赖:
pip install requests
,或者克隆项目代码后pip install -r requirements.txt
- 修改脚本内部的常量
DEFAULT_POSTER_PATH
,设置默认保存路径。 - 修改主函数处的
poster_save_path
保存路径。 - 修改主函数处的起始日期
target_date_1
和截止日期target_date_2
。 - 填写你的
cookies
。 - 运行脚本
PosterBandit
。
注意
- 起止日期不要写错, 否则判断逻辑会出错。
- 可能依然无法避免 418 错误, 日志中会输出保存失败的图片链接。为了后续拼接海报数据完整性, 建议自行补充保存失败的图片并规范命名(如 2024-12-31)。
示例:
target_date_1 = "2024-12-1" # TODO 填写起始日期target_date_2 = "2024-12-31" # TODO 填写截止日期
准备工作 - JavaScript 动态加载?
首先测试豆瓣海报相关页面是否是通过 JavaScript 动态加载的。在浏览器上设置“不允许网站使用 JavaScript”,刷新豆瓣界面,页面几乎全部正常加载。这很奇怪,和我之前做的脚本使用 requests 打印 raw HTML 得出用户相关信息以及影视相关信息都是使用 JS 动态加载的结论相悖。
先不管了,总之经过测试,完全可以只用 requests 爬取影视海报。😿
脚本构思
- 默认从第 1 页开始爬取,根据输入的起止日期参数,依次检查每页的所有条目是否在指定日期之间,如果是,爬取该条目的海报图片,如果不是,停止爬取;(
requests
) - 根据输入的长宽(长x张, 宽x张)参数,将海报拼接为长图(
pillow
/open CV
) - 自动计算爬取耗时,包括每条爬取耗时和总耗时,并在完成时输出。(
time
)
开始纸上谈兵
先以影视为例,书籍后面再核对元素选择器是否需要修改(做到书籍爬取的时候需要在开头增加选择书/影函数)。
构造请求来翻页,可以绕过使用选择器寻找 “下一页” 的元素。
首先访问 https://movie.douban.com/mine?status=collect
,观察不同页的载荷。
第 1 页
载荷 / payload
start: 0
sort: time
rating: all
mode: grid
type: all
filter: all
请求网址 (GET)
https://movie.douban.com/people/665544778/collect?start=0&sort=time&rating=all&mode=grid&type=all&filter=all
第 2 页
载荷 / payload
start: 15
sort: time
rating: all
mode: grid
type: all
filter: all
请求网址 (GET)
https://movie.douban.com/people/665544778/collect?start=15&sort=time&rating=all&mode=grid&type=all&filter=all
第 3 页
载荷 / payload
start: 30
sort: time
rating: all
mode: grid
type: all
filter: all
请求网址 (GET)
https://movie.douban.com/people/665544778/collect?start=30&sort=time&rating=all&mode=grid&type=all&filter=all
结论:
要获取不同页数的 URL,只需要改变 URL 中的 start=0
参数,第 x 页 URL 的 start
参数是 (x - 1) * 15
。
代码实现方案
使用广度优先搜索 (Breadth-First Search, BFS):一种常用的爬虫策略,先访问同一层级的所有页面,然后再访问下一层级的页面。最大爬取深度为 3,下面我在括号中标记了爬取深度。
-
构造不同页数的 URL,默认从第 1 页开始爬取。
-
以默认第一页或指定的页数作为爬取的起始页 (Level 0),找到所有包含电影条目的 div 元素,最大为 15 个。 ▶️
get_movie_elements
电影条目 CSS 选择器:
#content > div.grid-16-8.clearfix > div.article .item.comment-item
-
在电影条目的 div 元素内找到对应的日期元素和具体条目链接 ▶️
get_movie_info
日期 CSS 选择器:
#content div.info span.date
具体条目 CSS 选择器:
#content div.article div.pic > a
检查是否在指定的起止日期参数之间 ▶️
compare_date
-
进入具体条目链接 (Level 1),找到清晰的海报列表链接 ▶️
get_poster_url
crawl_link
海报列表链接 CSS 选择器:
#mainpic > a
-
进入海报列表页 (Level 2),找到默认的第一张海报 ▶️
crawl_link
第一张海报 CSS 选择器:
#content > div > div.article > ul > li:nth-child(1) > div.cover > a > img
-
进入未压缩的最终海报的链接 (Level 3) ▶️
get_poster_url
最终海报 CSS 选择器:
#content div.article div.cover > a
-
下载图片保存到指定路径,创建文件夹名称,根据日期定义,如
2024_1_1_2024_12_31
▶️create_folder
save_poster
文件结构
Gazer/
├── DoubanGaze/
│ ├── data/
│ │ └── poster/
│ │ └── 2024_1_1_2025_1_31/
│ └── src/
│ └── PosterBandit.py
└── ...
代码实践
find
, find_all
, select
, 和 select_one
几个方法的辨析
先来说说 find
和 find_all
,它们是一对,都是基于标签名和属性来查找元素:
-
find(name, attrs, recursive, string, **kwargs)
- 用途: 查找 第一个 匹配条件的 标签。
- 参数:
name
: 标签名,比如'div'
,'img'
,'a'
。attrs
: 一个字典,包含属性的键值对,比如{'class': 'title', 'id': 'myImage'}
。recursive
: 一个布尔值,表示是否递归查找所有子孙标签,默认为True
。string
: 查找包含特定文本的标签。**kwargs
: 可以直接写属性名作为参数,比如class_='title'
,id='myImage'
。
- 返回值: 如果找到,返回一个
Tag
对象;如果没找到,返回None
。
-
find_all(name, attrs, recursive, string, limit, **kwargs)
- 用途: 查找 所有 匹配条件的 标签。
- 参数:
name
,attrs
,recursive
,string
,**kwargs
: 和find
相同。limit
: 一个整数,限制返回的结果数量。
- 返回值: 返回一个
ResultSet
对象,它是一个包含所有匹配标签的列表。
举个例子:
<html>
<body><div class="movie"><img src="poster1.jpg" class="poster" id="poster1"><p class="title">电影1</p></div><div class="movie"><img src="poster2.jpg" class="poster" id="poster2"><p class="title">电影2</p></div>
</body>
</html>
from bs4 import BeautifulSoupsoup = BeautifulSoup(html_doc, 'html.parser')# 查找第一个 class 为 "movie" 的 div 标签
first_movie_div = soup.find('div', class_='movie')# 查找所有 class 为 "movie" 的 div 标签
all_movie_divs = soup.find_all('div', class_='movie')# 查找第一个 src 属性为 "poster1.jpg" 的 img 标签
first_poster = soup.find('img', src='poster1.jpg')# 查找所有 class 为 "poster" 的 img 标签
all_posters = soup.find_all('img', class_='poster')# 查找所有包含文本 "电影" 的 p 标签
movie_titles = soup.find_all('p', string='电影') #注意这个用法, 和class_='title'是不一样的, 一个是找文本内容, 一个是找属性内容
再来说说 select
和 select_one
,它们是另一对,都是基于 CSS 选择器来查找元素:
-
select_one(selector)
- 用途: 使用 CSS 选择器查找 第一个 匹配的 标签。
- 参数:
selector
: 一个字符串,表示 CSS 选择器。
- 返回值: 如果找到,返回一个
Tag
对象;如果没找到,返回None
。
-
select(selector)
- 用途: 使用 CSS 选择器查找 所有 匹配的 标签。
- 参数:
selector
: 一个字符串,表示 CSS 选择器。
- 返回值: 返回一个列表,包含所有匹配的标签。
CSS 选择器的优点:
- 简洁灵活: CSS 选择器语法非常强大,可以非常灵活地定位元素。
- 与前端开发衔接: 如果你熟悉前端开发,使用 CSS 选择器会非常顺手。
举个例子 (继续用上面的 HTML):
# 查找第一个 class 为 "movie" 的 div 标签
first_movie_div = soup.select_one('div.movie')# 查找所有 class 为 "movie" 的 div 标签
all_movie_divs = soup.select('div.movie')# 查找第一个 id 为 "poster1" 的 img 标签
first_poster = soup.select_one('img#poster1')# 查找所有 class 为 "poster" 的 img 标签
all_posters = soup.select('img.poster')# 查找所有 class 为 "movie" 的 div 标签下的 p 标签
movie_titles = soup.select('div.movie p')
总结一下:
方法 | 用途 | 基于 | 返回值 |
---|---|---|---|
find | 查找第一个匹配的标签 | 标签名和属性 | Tag 对象 或 None |
find_all | 查找所有匹配的标签 | 标签名和属性 | ResultSet 对象 (标签列表) |
select_one | 查找第一个匹配的标签 | CSS 选择器 | Tag 对象 或 None |
select | 查找所有匹配的标签 | CSS 选择器 | 列表 (包含所有匹配的标签) |
什么时候用哪个?
- 简单情况: 如果只是根据简单的标签名和属性查找,
find
和find_all
就足够了。 - 复杂情况: 如果需要根据复杂的条件查找,或者你更熟悉 CSS 选择器,那么
select
和select_one
更合适。 - 只要一个结果: 如果你确定只需要一个结果,或者只关心第一个结果,就用
find
或select_one
。 - 需要所有结果: 如果你需要所有匹配的结果,就用
find_all
或select
。
为什么 headers
中的 "cookies": cookies
要改成 "Cookie": cookies
?
这是因为在 HTTP 请求的头部信息中,用于传递 Cookie 的字段名是 Cookie
(注意首字母大写),而不是 cookies
。
- 服务器端识别的是
Cookie
这个字段名。 当服务器收到你的请求时,它会去解析Cookie
字段,获取你发送的 Cookie 信息。如果你写成cookies
,服务器就无法正确识别和处理你的 Cookie 了。 - 这是 HTTP 协议的规定。 就像你写信要按照固定的格式写地址一样,HTTP 协议也规定了请求头和响应头中各个字段的名称和格式,
Cookie
字段就是其中之一。
所以,为了让服务器能够正确识别你发送的 Cookie,我们必须使用 "Cookie": cookies
。
关于在 div
元素内部继续查找的选择器,有两种选择:
1. 只针对 div
内部编写选择器 (相对路径):
- 这种方式更简洁,也更符合当前的代码逻辑。
- 选择器直接从当前
div
元素的子元素开始写。 - 例如,如果当前
div
元素是movie_element
,那么movie_element.select_one("div.info > ul > li:nth-child(3)")
就表示选择当前div
元素内部div.info > ul
下的第三个li
元素。
2. 从 #content
开始编写选择器 (绝对路径):
- 这种方式也是可以的,但是没有必要,也更繁琐。
- 选择器需要从
#content
开始,写出完整的路径。 - 例如,
movie_element.select_one("#content > div.grid-16-8.clearfix > div.article .item.comment-item")
也能选择到相同的日期元素,但是这种写法很冗长,而且容易出错。而且我们已经通过movie_elements
缩小了范围, 没有必要继续从#content
开始了
Python 的函数参数传递规则 - 关于 page_id=1
参数位置:
把 page_id=1
这个参数放到任意参数前面会导致 IDE 提示错误,而放到最后就不会报错,这是因为 Python 的函数参数传递规则:
- 位置参数: 按照定义顺序传递的参数,必须按照顺序传入。
- 关键字参数: 通过参数名传递的参数,可以不按照顺序传入。
- 默认参数: 在函数定义时设置了默认值的参数,如果调用时没有传入该参数,则使用默认值。
规则:
- 位置参数必须在关键字参数前面。
- 默认参数必须在位置参数后面。
page_id=1
是一个默认参数,所以它必须放在位置参数cookies
,target_date_1
,target_date_2
,poster_save_path
后面,否则 IDE 会报错。
所以,只能把 page_id=1
放到最后。
关于 BeautifulSoup 解析:
是否总是使用 soup = BeautifulSoup(response.content.decode('utf-8'), 'html.parser')
? 是否可以只用 soup = BeautifulSoup(response, 'html.parser')
?
答案是:不建议。
response
对象默认是字节串,需要先解码成字符串,再交给BeautifulSoup
解析。- 如果你的 HTML 编码不是
utf-8
,需要使用正确的编码方式来解码(例如gbk
,iso-8859-1
等)。
建议:
- 始终使用
response.content.decode('utf-8', errors='ignore')
来解码,errors='ignore'
是为了忽略解码错误, 如果遇到无法解码的字符, 会忽略它, 不会报错。 - 最好在请求的时候设置正确的编码:
-
response = requests.get(target_link) response.encoding = response.apparent_encoding soup = BeautifulSoup(response.text, 'html.parser')
response.apparent_encoding
会根据响应内容尝试识别正确的编码方式,并设置到response.encoding
中,这样response.text
会使用正确的编码来解码.
-
关于 while 循环中的日期比较:
- 在 while 循环中,已经有
if not compare_date(target_date_1, target_date_2, viewed_date_text): break
跳出循环,为什么还要在最后加上if not compare_date(target_date_1, target_date_2, viewed_date_text): break
? - 理解
break
的作用域- 第一个
if not compare_date(...) : break
是在for movie_element in viewed_movie_elements:
循环内部,它只能跳出当前的for
循环。 - 为了在所有页面都爬取完毕后跳出
while True
的循环,还需要在 while 循环的末尾加上if not compare_date(...) : break
。 - 但是需要注意的是:
viewed_date_text
有可能为空, 这会导致错误, 你需要设置一个默认值viewed_date_text = ""
- 第一个
418 I’m a teapot?? Bring 'em on!!
418 错误
在哪里开始碰到 418 错误?
在 get_poster_url
函数内部,当访问海报页面的时候,被豆瓣服务器拒绝了,并返回 418
错误。
之前所有的步骤,包括访问列表页和详情页,都是成功的 (200)。
错误信息:
418 Client Error: for url: https://movie.douban.com/subject/3586996/
418 Client Error: for url: https://movie.douban.com/subject/2373195/
418 Client Error: for url: https://movie.douban.com/subject/10449575/
418 错误解决方案
1. 理解 418 错误
- 状态码含义: 418 是一个 HTTP 状态码,全称是
I'm a teapot
,本意表示服务器是一个茶壶,而不是咖啡机,无法提供请求的服务。 - 反爬虫应用: 网站会故意返回 418 状态码,来识别和阻止爬虫的访问。
- 选择 418 的原因: 这是一个不常见的 HTTP 状态码,可以更好地迷惑和阻止爬虫程序。
2. 418 错误产生的原因
- 请求头不完整: 网站会检查 HTTP 请求头来判断是否是爬虫。
- User-Agent: 缺失或使用默认爬虫
User-Agent
,容易被识别为爬虫。 - 其他请求头:
Referer
等信息不完整或不正确,也可能被识别为爬虫。 - 访问频率过快: 短时间内大量访问同一页面,也会被认为是爬虫行为。
3. 解决 418 错误的思路:伪装成浏览器
-
核心思路: 伪装成正常的浏览器访问行为,绕过网站的反爬虫机制。
-
解决方案:
-
3.1. 使用
requests.Session()
管理 Cookies 和连接池requests.Session
是requests
库中用于发送 HTTP 请求的类,它可以自动管理 Cookies 和连接池。- Cookies 管理:
requests.Session
可以自动保存和发送 Cookies,确保你的每次请求都携带了正确的 Cookies,从而避免被豆瓣服务器识别为爬虫。- 当你的爬虫第一次访问豆瓣时,豆瓣服务器会返回一些 Cookies,这些 Cookies 可以用来标识你的身份。使用
requests.Session
,你可以确保你的每次请求都携带了正确的 Cookies。
- 连接池:
requests.Session
还可以管理连接池,从而提高你的爬虫的性能。- 当你使用
requests.Session
发送多个请求时,requests.Session
会自动重用连接,从而避免每次请求都建立新的连接,从而提高效率。
- HTTP Keep-Alive (Persistent Connections):
- HTTP Keep-Alive,也称为持久连接,是一种在 HTTP/1.1 中引入的机制,用于提高 HTTP 请求的性能。
- 传统 HTTP 请求: 每次请求都会建立新的 TCP 连接,请求完成后会断开连接。
- Keep-Alive: 使用 Keep-Alive,可以在一个 TCP 连接上发送多个 HTTP 请求和响应,从而避免每次请求都建立新的 TCP 连接。
- 正确使用
session
- 如果在
get_poster_url
函数内创建了新的session
, 每次调用get_poster_url
都会创建一个新的session
。 - 这会导致
session
的Keep-Alive
特性无法被利用,每次请求都会建立新的 TCP 连接。 - 最佳实践:你需要在
download_poster_images
中创建session
,并将session
作为参数传递给get_poster_url
函数。
- 如果在
-
3.2 使用重试机制
- 使用
requests.Session()
和Retry
,以确保每个请求都有重试机制。 - 代码:
retry = Retry(connect=3, backoff_factor=0.5)adapter = HTTPAdapter(max_retries=retry)session.mount('http://', adapter)session.mount('https://', adapter)
- 作用:
Retry(connect=3, backoff_factor=0.5)
创建一个Retry
对象,用于定义重试策略。connect=3
表示连接错误最多重试 3 次,backoff_factor=0.5
表示重试的间隔时间会以 0.5 为系数逐渐增加。HTTPAdapter(max_retries=retry)
创建一个HTTPAdapter
对象,用于将Retry
对象应用到requests.Session
对象中。session.mount('http://', adapter)
和session.mount('https://', adapter)
将HTTPAdapter
对象应用到http
和https
协议的请求中。- 目的: 当你的请求因为网络错误或者服务器错误而失败时,
requests
会自动重试,从而提高你的代码的健壮性。
- 捕获 HTTPError - 非 200 状态码的重试
- ** 捕获 418 错误的关键是捕获
requests
库抛出的HTTPError
异常,然后根据错误码进行重试。在对关键的图片下载的 418 异常中, 加入重试的机制. - 为什么只捕获
HTTPError
?requests
库在遇到非 200 状态码(例如 418)时会抛出requests.exceptions.HTTPError
异常, 这种异常是requests.exceptions.RequestException
的子类.- 我们只捕获
HTTPError
,是因为requests.exceptions.RequestException
可能会捕捉到其他类型的异常。我们希望只在遇到 HTTP 状态码错误时进行重试,而不是其他类型的异常。
2. 之前的重试机制针对连接错误
- 之前的重试机制 (通过
urllib3.util.retry.Retry
和requests.adapters.HTTPAdapter
实现) 确实是针对连接错误的:urllib3.util.retry.Retry
中的connect=3
参数表示针对连接错误(例如requests.exceptions.ConnectionError
或者 socket 错误)最多重试 3 次。- 连接错误通常是指无法建立网络连接的情况,例如 DNS 解析失败、服务器无响应、网络中断等。
requests.adapters.HTTPAdapter
将重试策略应用到 HTTP 请求中,仅当发生连接错误时才会触发重试。
3.
requests.exceptions.RequestException
的作用requests.exceptions.RequestException
是一个基类: 它包含了所有requests
库可能抛出的异常,例如:requests.exceptions.ConnectionError
: 连接错误(例如 DNS 解析失败、服务器无响应)。requests.exceptions.HTTPError
: HTTP 状态码错误(例如 404 Not Found、500 Internal Server Error, 也包括 418)。requests.exceptions.Timeout
: 请求超时错误。requests.exceptions.TooManyRedirects
: 重定向次数过多错误。- 其他
requests
库的异常.
2.
raise_for_status()
的作用response.raise_for_status()
的作用是: 检查 HTTP 响应的状态码。- 如果状态码是 200 (OK): 表示请求成功,代码会继续往下执行。
- 如果状态码不是 200 (例如 404, 418, 500 等): 表示请求失败,会抛出一个
requests.exceptions.HTTPError
异常。
- 之前的代码已经有
raise_for_status()
:- 所以原本就可以检测 HTTP 状态码错误,并在遇到错误的时候抛出
requests.exceptions.HTTPError
异常. - 但是,没有处理这个异常,所以之前的代码会因为异常而直接终止, 而不会重试
- 所以原本就可以检测 HTTP 状态码错误,并在遇到错误的时候抛出
- 增加
try...except HTTPError
的作用:- 增加
try...except HTTPError
结构是为了捕获raise_for_status()
抛出的HTTPError
异常。 - 捕获到这个异常后,可以执行一些错误处理逻辑,例如打印错误信息,然后重新发送请求 (即进入下一循环)。
- 所以,增加
try...except HTTPError
的核心作用就是让你的代码能够处理 HTTP 状态码错误,并进行重试。
- 增加
总结
- 捕获 418 错误: 需要使用
try...except HTTPError
来捕获 HTTP 状态码错误。 - 之前的重试机制: 针对连接错误,而非 HTTP 状态码错误。
requests.exceptions.RequestException
:- 是一个基类,捕获所有
requests
库可能抛出的异常,但是不包括HTTPError
, 因为raise_for_status()
会单独抛出HTTPError
。 - 现在主要用于捕捉下载图片时发生的异常。
- 是一个基类,捕获所有
- 清晰的错误处理:
- 使用
response.raise_for_status()
并且try...except HTTPError
来进行重试, 这能确保我们只在 HTTP 状态码错误的时候重试。
- 使用
- 使用
-
3.3 减慢请求速度
- 添加
time.sleep()
: 在每次请求之前,设置随机的time.sleep()
,可以降低爬虫的访问频率,从而减少被网站识别为爬虫的风险。time.sleep(random.randint(2, 5))
- 添加
-
3.4 使用更真实的
User-Agent
- 使用真实的浏览器
User-Agent
,让网站误认为我们是浏览器在访问。 - 代码示例:
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",}
- 添加其他头: 添加常见的 HTTP 头,例如
Accept
,Accept-Language
,Referer
等, 添加浏览器常用的 header, 比如Upgrade-Insecure-Requests
,Sec-Fetch-User
,Sec-Fetch-Mode
,Sec-Fetch-Dest
,Sec-Fetch-Site
等.
- 使用真实的浏览器
-
-
3.5 动态获取
headers
- 定义一个返回
headers
的函数, 在while
循环里面调用函数来动态获取headers
。
- 定义一个返回
-
3.6 传递
headers
参数- 将
headers
作为参数传递到get_poster_url
函数中,让get_poster_url
函数内部的每一个请求都能够携带正确的headers
信息,包括User-Agent
、Cookie
、Referer
等。
- 将
-
3.7 细粒度控制请求
- 使用
session.get
访问海报列表页面,使得我们能够更细粒度地控制请求,从而成功避开了豆瓣的反爬虫机制。
- 使用
4. requests.get
和 session.get
的对比
-
requests.get
:requests.get
是requests
库中用于发送 HTTP GET 请求的函数。- 每次调用
requests.get
,都会建立一个新的 TCP 连接。 - 不会自动保存 Cookies,每次请求都需要手动传递 Cookies。
- 不会自动管理连接池。
-
session.get
:session.get
是requests.Session
对象中用于发送 HTTP GET 请求的方法。- 使用同一个
requests.Session
对象发送多个请求,可以重用同一个 TCP 连接,从而提高效率。 - 可以自动保存和发送 Cookies,从而保持登录状态。
- 会自动管理连接池。
-
何时使用
session.get
:- 需要保持登录状态的爬虫: 如果你的爬虫需要访问需要登录才能访问的页面,那么你需要使用
session.get
来管理 Cookies,保持登录状态。 - 需要发送多个请求的爬虫: 如果你的爬虫需要访问多个页面,那么你可以使用
session.get
来重用 TCP 连接,从而提高效率。 - 需要使用重试机制的爬虫: 如果你的爬虫需要使用重试机制来处理请求失败的情况,你可以在
requests.Session
对象中配置重试策略。 - 总之,在很多时候
session.get
都是更合适的选择。
- 需要保持登录状态的爬虫: 如果你的爬虫需要访问需要登录才能访问的页面,那么你需要使用
-
session.get
取决于爬虫深度吗?- **并不是完全取决于爬虫深度,**虽然深层次爬取需要更多请求,使用
session.get
效率会更高,但是并不是说,浅层次的爬取就不需要session.get
。 - 选择
session.get
还是requests.get
, 主要取决于你的爬虫的复杂度和具体需求。- 如果你只需要发送一次请求,那么使用
requests.get
就可以。 - 但是,如果你需要发送多个请求,或者需要管理 Cookies 或者重试机制,那么使用
session.get
是更合适的选择。
- 如果你只需要发送一次请求,那么使用
- **并不是完全取决于爬虫深度,**虽然深层次爬取需要更多请求,使用
5. 经验总结
- 遇到反爬机制强的网站,可以尝试在每一次更深层次爬取的时候,都带上构造好的
headers
。 - 反爬虫策略通常会对深层次的爬取进行更严格的限制,因为深层次的爬取通常会消耗更多的服务器资源。
优化写入速度
-
为什么写入图片会很慢?
- 原因:
- 同步 I/O: 现在的代码使用同步 I/O 来写入图片,这意味着程序会阻塞在写入操作上,直到写入完成才会继续执行。
- 磁盘写入速度: 磁盘写入速度通常比内存读写速度慢很多。
chunk_size
: 在代码中设置了chunk_size=8192
,每次读取 8KB 的数据进行写入。- **CPU 负载:**虽然CPU性能足够,但是如果频繁读取和写入大量小块数据,会增加CPU的负载。
- 结论:
- 同步 I/O 加上磁盘写入速度限制导致了写入图片的速度较慢。
- 原因:
-
如何优化写入速度?
- 使用更大的
chunk_size
:- 增加
chunk_size
可以减少读取和写入的次数,从而提高写入速度。 - 你可以尝试将
chunk_size
设置为 65536 (64KB) 或者更大。 - 但是:
chunk_size
过大也可能导致内存使用过高,你需要根据实际情况进行调整。
- 增加
- 使用异步 I/O:
- 使用异步 I/O 可以让程序在写入图片的同时,执行其他操作,从而提高程序的效率。
- 需要使用异步 I/O库,例如
asyncio
和aiohttp
,这将大大增加代码的复杂度。 - 需要异步处理,也需要修改
save_poster
的调用方式。
- 使用多线程或多进程:
- 使用多线程或多进程可以并发地进行多个写入操作,从而提高整体的写入速度。
- 但是: 多线程可能受到 GIL 的限制,多进程可能会增加系统开销。
- 使用
shutil.copyfileobj
:shutil.copyfileobj
可以更高效地将文件对象复制到磁盘,减少代码量。
- 使用更大的
iter_content(chunk_size=65536)
和 shutil.copyfileobj
在不同情况下的性能问题
在爬取少量图片时,iter_content(chunk_size=65536)
和 shutil.copyfileobj
的性能差异不大,甚至 shutil.copyfileobj
还可能略慢。
爬取更多图片时,应该选择哪个?
shutil.copyfileobj
的优势:- 更高效:
shutil.copyfileobj
使用了更高效的底层实现,可以减少 Python 代码的开销,避免频繁读写操作,从而在大量数据传输时表现更好。 - 更简洁:
shutil.copyfileobj
的代码更简洁,易于维护。 - 更稳定: 由于
shutil.copyfileobj
由 Python 官方维护, 可以保证其稳定性。
- 更高效:
iter_content(chunk_size=65536)
的局限:- Python 代码开销: 每次循环读取
chunk_size
大小的数据都需要进行 Python 代码的执行,这会增加 Python 代码的开销。 - 需要手动处理: 需要自己编写代码来处理读取到的数据,容易出错。
- Python 代码开销: 每次循环读取
- 建议:
- 在爬取更多图片时,
shutil.copyfileobj
是更稳定和更好的选择。 - 不需要自己处理分块的数据,从而简化你的代码,让代码更易于维护。
- 如果你不希望使用
shutil.copyfileobj
, 你可以尝试使用更大的chunk_size
,但是不建议这样操作。
- 在爬取更多图片时,
代码计时
增加计时器来计算每次爬取耗时
-
在哪里增加计时器?
- 核心问题: 需要决定计时器应该放在代码的哪个位置,才能准确地计算每次爬取的耗时。
- 方案:
- 在
download_poster_images
函数开始时启动计时器: 这样可以计算整个爬取过程的耗时。 - 在
download_poster_images
函数结束时停止计时器: 这样可以获取整个爬取过程的耗时。 - 在
while
循环开始时记录时间,在while
循环结束时记录时间: 了解每次循环的耗时。 - 在
for
循环开始时记录时间,在for
循环结束时记录时间: 了解每次处理电影条目的耗时。
- 在
- 选择:
- 将计时器放在
download_poster_images
函数的开始和结束处,这样可以计算整个爬取过程的耗时。 - 同时,将计时器放在
for
循环的开始和结束处, 从而得到单个条目的爬取时间。
- 将计时器放在
-
如何使用 Python 实现计时器?
- 使用
time
模块: Python 的time
模块提供了time()
函数,可以获取当前时间的时间戳(以秒为单位)。 - 代码示例:
import timestart_time = time.time() # 启动计时器# 执行一些代码end_time = time.time() # 停止计时器 elapsed_time = end_time - start_time # 计算耗时 print(f"耗时:{elapsed_time:.2f} 秒")
time.perf_counter()
:time.perf_counter()
返回性能计数器的值(以秒为单位),该计数器提供尽可能高的可用分辨率测量时间。- 这个方法通常用来测量时间间隔, 非常适合我们的情景。
- 它的原理是基于CPU的硬件计时器,因此具有非常高的精度,可以达到纳秒级别。
- 使用
time.perf_counter()
和 time.time()
的区别
-
time.time()
的特点:- 返回时间戳:
time.time()
返回的是当前时间的时间戳,即从 Unix 纪元(1970年1月1日00:00:00 UTC)到现在的秒数,是一个浮点数。 - 系统时间:
time.time()
获取的是系统的实时时间,可能会受到系统时间调整的影响,例如:时钟同步、手动调整时间等。 - 精度较低:
time.time()
的精度通常较低,可能只能达到毫秒级别,甚至秒级别,具体取决于操作系统的实现。
- 返回时间戳:
-
time.perf_counter()
的特点:- 返回性能计数器值:
time.perf_counter()
返回的是性能计数器的值,这是一个单调递增的计时器,不会受到系统时间调整的影响。 - 高精度:
time.perf_counter()
的精度通常比time.time()
高很多,可以达到纳秒级别,具体取决于 CPU 的硬件实现。 - 适用于测量时间间隔:
time.perf_counter()
主要用于测量代码执行的时间间隔,而不是测量绝对时间。 - 不受系统时间影响:
time.perf_counter()
不受系统时间调整的影响,可以提供更准确的计时结果。
- 返回性能计数器值:
-
time.perf_counter()
为什么比time.time()
好?- 核心问题: 为什么在测量代码执行时间时,
time.perf_counter()
通常比time.time()
更好? - 原因:
- 高精度:
time.perf_counter()
的精度比time.time()
高,可以提供更准确的计时结果。这对于测量执行时间较短的代码片段,尤其重要。 - 不受系统时间影响:
time.perf_counter()
不受系统时间调整的影响,可以提供更稳定的计时结果。这对于长时间运行的代码或者在不同环境下运行的代码,尤其重要。 - 单调递增:
time.perf_counter()
返回的值是单调递增的,这意味着它可以确保时间测量的顺序性,避免出现时间回溯的问题。
- 高精度:
- 结论:
- 在测量代码执行时间时,
time.perf_counter()
是更合适的选择,因为它提供了更高的精度、更稳定的结果,并且不受系统时间调整的影响。
- 在测量代码执行时间时,
- 核心问题: 为什么在测量代码执行时间时,
-
什么时候使用
time.time()
?- 获取当前时间: 如果你需要获取当前时间,例如:记录日志的时间、设置定时任务等,那么可以使用
time.time()
。 - 需要系统时间: 如果你的程序需要使用系统时间,并且对精度要求不高,那么可以使用
time.time()
。 - 例如: 需要获得当前的日期, 你可能需要
time.time()
结合datetime
来实现。
- 获取当前时间: 如果你需要获取当前时间,例如:记录日志的时间、设置定时任务等,那么可以使用
-
总结:
time.time()
: 用于获取当前时间,精度较低,可能会受到系统时间调整的影响。time.perf_counter()
: 用于测量时间间隔,精度高,不受系统时间调整的影响。- 在测量代码执行时间时,通常使用
time.perf_counter()
,因为它可以提供更高的精度和更稳定的结果。
下一篇
解锁豆瓣高清海报(二) 使用 OpenCV 拼接和压缩 😽
相关文章:
解锁豆瓣高清海报(一) 深度爬虫与requests进阶之路
前瞻 PosterBandit 这个脚本能够根据用户指定的日期,爬取你看过的影视最高清的海报,然后使用 PixelWeaver.py 自动拼接成指定大小的长图。 你是否发现直接从豆瓣爬取下来的海报清晰度很低? 使用 .pic .nbg img CSS 选择器,在 我…...
【机器学习与数据挖掘实战】案例11:基于灰色预测和SVR的企业所得税预测分析
【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈机器学习与数据挖掘实战 ⌋ ⌋ ⌋ 机器学习是人工智能的一个分支,专注于让计算机系统通过数据学习和改进。它利用统计和计算方法,使模型能够从数据中自动提取特征并做出预测或决策。数据挖掘则是从大型数据集中发现模式、关联…...
聚簇索引、哈希索引、覆盖索引、索引分类、最左前缀原则、判断索引使用情况、索引失效条件、优化查询性能
聚簇索引 聚簇索引像一本按目录排版的书,用空间换时间,适合读多写少的场景。设计数据库时,主键的选择(如自增ID vs 随机UUID)会直接影响聚簇索引的性能。 什么是聚簇索引? 数据即索引:聚簇索引…...
克隆OpenAI(基于openai API和streamlit)
utils.py: from langchain_openai import ChatOpenAI from langchain.memory import ConversationBufferMemory from langchain.chains import ConversationChain import osdef get_chat_response(api_key,prompt,memory): # memory不能是函数的内部局部变量&…...
DeepSeek技术深度解析:从不同技术角度的全面探讨
DeepSeek技术深度解析:从不同技术角度的全面探讨 引言 DeepSeek是一个集成了多种先进技术的平台,旨在通过深度学习和其他前沿技术来解决复杂的问题。本文将从算法、架构、数据处理以及应用等不同技术角度对DeepSeek进行详细分析。 一、算法层面 深度学…...
完全卸载mysql server步骤
1. 在控制面板中卸载mysql 2. 打开注册表,运行regedit, 删除mysql信息 HKEY_LOCAL_MACHINE-> SYSTEM->CurrentContolSet->Services->EventLog->Application->Mysql HKEY_LOCAL_MACHINE-> SYSTEM->CurrentContolSet->Services->Mysql …...
2025年大年初一篇,C#调用GPU并行计算推荐
C#调用GPU库的主要目的是利用GPU的并行计算能力,加速计算密集型任务,提高程序性能,支持大规模数据处理,优化资源利用,满足特定应用场景的需求,并提升用户体验。在需要处理大量并行数据或进行复杂计算的场景…...
机器学习优化算法:从梯度下降到Adam及其实验改进
机器学习优化算法:从梯度下降到Adam及其实验改进 在机器学习和深度学习领域,模型的训练过程本质上是一个优化问题。优化算法的作用是通过调整模型参数,使得模型在给定的数据 集上实现最优性能。而优化算法的效率和效果直接决定了模型的收敛速…...
在 Ubuntu 中使用 Conda 创建和管理虚拟环境
Conda 是一个广泛使用的包管理和环境管理系统,尤其适用于数据科学和 Python 开发。本文将指导你如何在 Ubuntu 系统中安装 Conda 并创建基于 python3.11 的虚拟环境。 1. 安装 Miniconda 或 Anaconda 方法 1:下载并安装 Miniconda Miniconda 是一个轻量…...
【深度学习】搭建卷积神经网络并进行参数解读
第一步 导包 import torch import torch.nn as nn import torch.optim as optim import torch.nn.functional as F from torchvision import datasets,transforms import matplotlib.pyplot as plt import numpy as np %matplotlib inline transforms 模块是 torchvision 库的…...
稀疏进化训练:机器学习优化算法中的高效解决方案
稀疏进化训练:机器学习优化算法中的高效解决方案 稀疏进化训练:机器学习优化算法中的高效解决方案引言第一部分:背景与动机1.1 传统优化算法的局限性1.2 进化策略的优势1.3 稀疏性的重要性 第二部分:稀疏进化训练的核心思想2.1 稀…...
Vue - Suspense的使用
在 Vue 3 中,Suspense 是一个用于处理异步组件的 API。它允许在加载异步组件时提供一个后备内容(例如加载指示器),从而改善用户体验。在加载期间,可以在页面上显示一个占位符,而不是让用户看到一个空白或错…...
在K8S中,pending状态一般由什么原因导致的?
在Kubernetes中,资源或Pod处于Pending状态可能有多种原因引起。以下是一些常见的原因和详细解释: 资源不足 概述:当集群中的资源不足以满足Pod或服务的需求时,它们可能会被至于Pending状态。这通常涉及到CPU、内存、存储或其他资…...
【算法】回溯算法专题② ——组合型回溯 + 剪枝 python
目录 前置知识进入正题小试牛刀实战演练总结 前置知识 【算法】回溯算法专题① ——子集型回溯 python 进入正题 组合https://leetcode.cn/problems/combinations/submissions/596357179/ 给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。 你可以…...
理解红黑树
简介:红黑树是一种自平衡二叉查找树,由鲁道夫贝尔(Rudolf Bayer)在1972年发明,最初称为“对称二叉B树”。它的设计旨在解决普通二叉查找树在频繁插入和删除操作时可能退化为链表的问题,从而保持高效的查找、…...
从0开始使用面对对象C语言搭建一个基于OLED的图形显示框架(OLED设备层封装)
目录 OLED设备层驱动开发 如何抽象一个OLED 完成OLED的功能 初始化OLED 清空屏幕 刷新屏幕与光标设置1 刷新屏幕与光标设置2 刷新屏幕与光标设置3 绘制一个点 反色 区域化操作 区域置位 区域反色 区域更新 区域清空 测试我们的抽象 整理一下,我们应…...
大模型能力评估数据集都有哪些?
大模型能力的评估数据集种类繁多,涵盖了语言理解、推理、生成、代码能力、安全性和鲁棒性等多个方面。以下是一些主要的评估数据集及其特点: 通用能力评估数据集: MMLU:多模态大规模多语言任务理解数据集,覆盖从基础教育到高级专业水平的57个科目,用于评估模型的知识储备…...
论文阅读(二):理解概率图模型的两个要点:关于推理和学习的知识
1.论文链接:Essentials to Understand Probabilistic Graphical Models: A Tutorial about Inference and Learning 摘要: 本章的目的是为没有概率图形模型背景或没有深入背景的科学家提供一个高级教程。对于更熟悉这些模型的读者,本章将作为…...
《OpenCV》——图像透视转换
图像透视转换简介 在 OpenCV 里,图像透视转换属于重要的几何变换,也被叫做投影变换。下面从原理、实现步骤、相关函数和应用场景几个方面为你详细介绍。 原理 实现步骤 选取对应点:要在源图像和目标图像上分别找出至少四个对应的点。这些对…...
【16届蓝桥杯寒假刷题营】第2期DAY4
【16届蓝桥杯寒假刷题营】第2期DAY4 - 蓝桥云课 问题描述 幼儿园小班的浩楠同学有一个序列 a。 他想知道有多少个整数三元组 (i,j,k) 满足 1≤i,j,k≤n 且 aiajak。 输入格式 共2行,第一行一个整数 n,表示序列的长度。 第二行 n 个整数&#x…...
用 HTML、CSS 和 JavaScript 实现抽奖转盘效果
顺序抽奖 前言 这段代码实现了一个简单的抽奖转盘效果。页面上有一个九宫格布局的抽奖区域,周围八个格子分别放置了不同的奖品名称,中间是一个 “开始抽奖” 的按钮。点击按钮后,抽奖区域的格子会快速滚动,颜色不断变化…...
【人工智能学习笔记 一】 AI分层架构、基本概念分类与产品技术架构
新的一年2025要对AI以及LLM有个强化的学习,所以第一篇先对整体有个大概的认知,一直分不清LLM和AI的关系,在整个体系里的位置,以及AIGC是什么东西,AI AGENT类似豆包等和大语言模型的具体关系是什么,整个AI的…...
windows10 配置使用json server作为图片服务器
步骤1:在vs code中安装json server, npm i -g json-server 注意:需要安装对应版本的json server,不然可能会报错,比如: npm i -g json-server 0.16.3 步骤2:出现如下报错: json-server 不是…...
【Elasticsearch 基础入门】Centos7下Elasticsearch 7.x安装与配置(单机)
Elasticsearch系列文章目录 【Elasticsearch 基础入门】一文带你了解Elasticsearch!!!【Elasticsearch 基础入门】Centos7下Elasticsearch 7.x安装与配置(单机) 目录 Elasticsearch系列文章目录前言单机模式1. 安装 J…...
【MySQL】语言连接
语言连接 一、下载二、mysql_get_client_info1、函数2、介绍3、示例 三、其他函数1、mysql_init2、mysql_real_connect3、mysql_query4、mysql_store_result5、mysql_free_result6、mysql_num_fields7、mysql_num_rows8、mysql_fetch_fields9、mysql_fetch_row10、mysql_close …...
【零拷贝】
目录 一:了解IO基础概念 二:数据流动的层次结构 三:零拷贝 1.传统IO文件读写 2.mmap 零拷贝技术 3.sendFile 零拷贝技术 一:了解IO基础概念 理解CPU拷贝和DMA拷贝 我们知道,操作系统对于内存空间&…...
四、GPIO中断实现按键功能
4.1 GPIO简介 输入输出(I/O)是一个非常重要的概念。I/O泛指所有类型的输入输出端口,包括单向的端口如逻辑门电路的输入输出管脚和双向的GPIO端口。而GPIO(General-Purpose Input/Output)则是一个常见的术语,…...
qt-Quick3D笔记之官方例程Runtimeloader Example运行笔记
qt-Quick3D笔记之官方例程Runtimeloader Example运行笔记 文章目录 qt-Quick3D笔记之官方例程Runtimeloader Example运行笔记1.例程运行效果2.例程缩略图3.项目文件列表4.main.qml5.main.cpp6.CMakeLists.txt 1.例程运行效果 运行该项目需要自己准备一个模型文件 2.例程缩略图…...
IM 即时通讯系统-01-概览
前言 有时候希望有一个 IM 工具,比如日常聊天,或者接受报警信息。 其实主要是工作使用,如果是接收报警等场景,其实DD这种比较符合场景。 那么有没有必要再创造一个DD呢? 答案是如果处于个人的私有化使用࿰…...
二叉树——429,515,116
今天继续做关于二叉树层序遍历的相关题目,一共有三道题,思路都借鉴于最基础的二叉树的层序遍历。 LeetCode429.N叉树的层序遍历 这道题不再是二叉树了,变成了N叉树,也就是该树每一个节点的子节点数量不确定,可能为2&a…...
Baklib构建高效协同的基于云的内容中台解决方案
内容概要 随着云计算技术的飞速发展,内容管理的方式也在不断演变。企业面临着如何在数字化转型过程中高效管理和协同处理内容的新挑战。为应对这些挑战,引入基于云的内容中台解决方案显得尤为重要。 Baklib作为创新型解决方案提供商,致力于…...
MP4基础
一、什么是MP4? MP4是一套用于音频、视频信息的压缩编码标准,由国际标准化组织(ISO)和国际电工委员会(IEC)下属的“动态图像专家组”(Moving Picture Experts Group,即MPEGÿ…...
年化18%-39.3%的策略集 | backtrader通过xtquant连接qmt实战
原创内容第785篇,专注量化投资、个人成长与财富自由。 大年初五,年很快就过完了。 其实就是本身也只是休假一周,但是我们赋予了它太多意义。 周五咱们发布发aitrader v4.1,带了backtraderctp期货的实盘接口: aitra…...
通过Redisson构建延时队列并实现注解式消费
目录 一、序言二、延迟队列实现1、Redisson延时消息监听注解和消息体2、Redisson延时消息发布器3、Redisson延时消息监听处理器 三、测试用例四、结语 一、序言 两个月前接了一个4万的私活,做一个线上商城小程序,在交易过程中不可避免的一个问题就是用户…...
RAG是否被取代(缓存增强生成-CAG)吗?
引言: 本文深入研究一种名为缓存增强生成(CAG)的新技术如何工作并减少/消除检索增强生成(RAG)弱点和瓶颈。 LLMs 可以根据输入给他的信息给出对应的输出,但是这样的工作方式很快就不能满足应用的需要: 因…...
MiniMax:人工智能领域的创新先锋
MiniMax:人工智能领域的创新先锋 在人工智能领域,MiniMax正以其强大的技术实力和创新的模型架构,成为全球关注的焦点。作为一家成立于2021年12月的通用人工智能科技公司,MiniMax专注于开发多模态、万亿参数的MoE(Mixt…...
pytorch基于GloVe实现的词嵌入
PyTorch 实现 GloVe(Global Vectors for Word Representation) 的完整代码,使用 中文语料 进行训练,包括 共现矩阵构建、模型定义、训练和测试。 1. GloVe 介绍 基于词的共现信息(不像 Word2Vec 使用滑动窗口预测&…...
Unity实现按键设置功能代码
一、前言 最近在学习unity2D,想做一个横版过关游戏,需要按键设置功能,让用户可以自定义方向键与攻击键等。 自己写了一个,总结如下。 二、界面效果图 这个是一个csv文件,准备第一列是中文按键说明,第二列…...
C++ 入门速通-第3章【黑马】
内容来源于:黑马 集成开发环境:CLion 先前学习完了C第1章的内容: C 入门速通-第1章【黑马】-CSDN博客 C 入门速通-第2章【黑马】-CSDN博客 下面继续学习第3章: 数组: 字符数组: 多维数组: …...
JavaScript 中的 CSS 与页面响应式设计
JavaScript 中的 CSS 与页面响应式设计 JavaScript 中的 CSS 与页面响应式设计1. 引言2. JavaScript 与 CSS 的基本概念2.1 CSS 的作用2.2 JavaScript 的作用3. 动态控制样式:JavaScript 修改 CSS 的方法3.1 使用 `document.styleSheets` API3.2 使用 `classList` 修改类3.3 使…...
100.3 AI量化面试题:解释配对交易(Pairs Trading)的原理,并说明如何选择配对股票以及设计交易信号
目录 0. 承前1. 配对交易基本原理1.1 什么是配对交易1.2 基本假设 2. 配对选择方法2.1 相关性分析2.2 协整性检验 3. 价差计算方法3.1 简单价格比率3.2 回归系数法 4. 交易信号设计4.1 标准差方法4.2 动态阈值方法 5. 风险管理5.1 止损设计5.2 仓位管理 6. 策略评估6.1 回测框架…...
[SAP ABAP] Debug Skill
SAP ABAP Debug相关资料 [SAP ABAP] DEBUG ABAP程序中的循环语句 [SAP ABAP] 静态断点的使用 [SAP ABAP] 在ABAP Debugger调试器中设置断点 [SAP ABAP] SE11 / SE16N 修改标准表(慎用)...
WSL2中安装的ubuntu开启与关闭探讨
1. PC开机后,查询wsl状态 在cmd或者powersell中输入 wsl -l -vNAME STATE VERSION * Ubuntu Stopped 22. 从windows访问WSL2 wsl -l -vNAME STATE VERSION * Ubuntu Stopped 23. 在ubuntu中打开一个工作区后…...
走向基于大语言模型的新一代推荐系统:综述与展望
HightLight 论文题目:Towards Next-Generation LLM-based Recommender Systems: A Survey and Beyond作者机构:吉林大学、香港理工大学、悉尼科技大学、Meta AI论文地址: https://arxiv.org/abs/2410.1974 基于大语言模型的下一代推荐系统&…...
【深度分析】DeepSeek 遭暴力破解,攻击 IP 均来自美国,造成影响有多大?有哪些好的防御措施?
技术铁幕下的暗战:当算力博弈演变为代码战争 一场针对中国AI独角兽的全球首例国家级密码爆破,揭开了数字时代技术博弈的残酷真相。DeepSeek服务器日志中持续跳动的美国IP地址,不仅是网络攻击的地理坐标,更是技术霸权对新兴挑战者的…...
双指针算法思想——OJ例题扩展算法解析思路
大家好!上一期我发布了关于双指针的OJ平台上的典型例题思路解析,基于上一期的内容,我们这一期从其中内容扩展出来相似例题进行剖析和运用,一起来试一下吧! 目录 一、 基于移动零的举一反三 题一:27. 移除…...
初始Linux(7):认识进程(下)
1. 进程优先级 cpu 资源分配的先后顺序,就是指进程的优先权( priority )。 优先权高的进程有优先执行权利。配置进程优先权对多任务环境的 linux 很有用,可以改善系统性能。 还可以把进程运行到指定的CPU 上,这样一来…...
人工智能第2章-知识点与学习笔记
结合教材2.1节,阐述什么是知识、知识的特性,以及知识的表示。人工智能最早应用的两种逻辑是什么?阐述你对这两种逻辑表示的内涵理解。什么谓词,什么是谓词逻辑,什么是谓词公式。谈谈你对谓词逻辑中的量词的理解。阐述谓词公式的解…...
Kotlin 协程 与 Java 虚拟线程对比测试(娱乐性质,请勿严谨看待本次测试)
起因 昨天在群里聊到虚拟线程的执行效率问题的时候虽然最后的结论是虚拟线程在针对IO密集型任务时具有很大的优势。但是讨论到虚拟线程和Kotlin 的协程的优势对比的话,这时候所有人都沉默了。所以有了本次的测试 提前声明:本次测试是不严谨的࿰…...
C++中的拷贝构造器(Copy Constructor)
在C中,拷贝构造器(Copy Constructor)是一种特殊的构造函数,用于创建一个新对象,该对象是另一个同类型对象的副本。当使用一个已存在的对象来初始化一个新对象时,拷贝构造器会被调用。 拷贝构造器的定义 拷…...