Selenium 隐藏浏览器指纹特征的几种方式

对一些做了反爬的网站,做了特征检测,用来阻止一些恶意爬虫

https://bot.sannysoft.com这个网站可以检测指纹特征

本篇文章将介绍几种常用的隐藏浏览器指纹特征的方式

1. 直接爬取

目标对象:

aHR0cHM6Ly9xaWthbi5jcXZpcC5jb20vUWlrYW4vU2VhcmNoL0FkdmFuY2U=

我们使用 Selenium 直接爬取目标页面

# selenium 直接爬取

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
import time

chrome_options = Options()

s = Service(r"chromedriver.exe路径")

driver = webdriver.Chrome(service=s, options=chrome_options)

driver.get(url='URL')

driver.save_screenshot('result.png')

# 保存
source = driver.page_source
with open('result.html', 'w') as f:
    f.write(source)

time.sleep(200)

页面明显做了反爬,网页返回直接返回空白内容

图片

2. CDP

CDP 全称为 Chrome Devtools-Protocol

https://chromedevtools.github.io/devtools-protocol

通过执行 CDP 命令,可以在网页加载前运行一段代码,进而改变浏览器的指纹特征

比如,window.navigator.webdriver 在 Selenium 直接打开网页时返回结果为 true;而手动打开网页时,该对象值为 undefined

因此,我们可以利用 CDP 命令修改该对象的值,达到隐藏指纹特征的目的

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
import time

chrome_options = Options()

s = Service(r"chromedriver.exe路径")

driver = webdriver.Chrome(service=s, options=chrome_options)

# 执行cdp命令,修改(window.navigator.webdriver )对象的值
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
    "source": """
            Object.defineProperty(navigator, 'webdriver', {
              get: () => undefined
            })
            """
})

driver.get(url='URL')

driver.save_screenshot('result.png')

# 保存
source = driver.page_source
with open('result.html', 'w', encoding='utf-8') as f:
    f.write(source)

time.sleep(200)

需要指出的是,浏览器的指纹特征很多,使用该方法存在一些局限性

3. stealth.min.js

该文件包含了常用的浏览器特征,我们只需要读取该文件,然后执行 CDP 命令即可

下载地址:

https://github.com/berstend/puppeteer-extra/tree/stealth-js
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
import time

chrome_options = Options()

# 无头模式
# chrome_options.add_argument("--headless")

# 添加请求头
chrome_options.add_argument(
    'user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36')

s = Service(r"chromedriver.exe路径")

driver = webdriver.Chrome(service=s, options=chrome_options)

# 利用stealth.min.js隐藏浏览器指纹特征,先下载,并与文件放同一目录下
# stealth.min.js下载地址:https://github.com/berstend/puppeteer-extra/tree/stealth-js
with open('./stealth.min.js') as f:
    driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
        "source": f.read()
    })

driver.get(url='URL')
# driver.get(url='https://bot.sannysoft.com/')

# 保存图片
driver.save_screenshot('result.png')

time.sleep(200)

4. undetected_chromedriver

这是一个防止浏览器指纹特征被识别的依赖库,可以自动下载驱动配置再运行

项目地址:

https://github.com/ultrafunkamsterdam/undetected-chromedriver

使用步骤也很方便

首先,我们安装依赖库

# 安装依赖
pip3 install undetected-chromedriver

然后,通过下面几行代码就能完美隐藏浏览器的指纹特征

from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
import time
import undetected_chromedriver as uc

chrome_options = Options()
# chrome_options.add_argument("--headless")

s = Service(r"chromedriver.exe")

driver = uc.Chrome(service=s, options=chrome_options)

driver.get(url='URL')
# driver.get(url='https://bot.sannysoft.com/')

driver.save_screenshot('result.png')
time.sleep(100)

5. 操作已开启的浏览器

最后一种方式上篇文章已经介绍过

如何利用 Selenium 对已打开的浏览器进行爬虫!

我们只需要通过命令行启动一个浏览器

import subprocess

# 1、打开浏览器
# 指定端口号为:1234
# 配置用户数据路径:--user-data-dir
cmd = 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe --remote-debugging-port=1234 --user-data-dir="C:\\selenum\\user_data"'

subprocess.run(cmd)

然后,利用 Selenium 直接操作上面的浏览器即可模拟正常操作浏览器的行为

import time
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service

# 操作上面已经打开的浏览器,进行百度搜索
chrome_options = Options()

# 指定已经打开浏览器的地址及端口号
chrome_options.add_experimental_option("debuggerAddress", "127.0.0.1:1234")

# 注意:chrome版本与chromedirver驱动要保持一致
# 下载地址:http://chromedriver.storage.googleapis.com/index.html
s = Service(r"chromedriver.exe")

driver = webdriver.Chrome(service=s, options=chrome_options)

# 打开目标网站
driver.get(url="URL")

time.sleep(200)

实操:自动化秒杀抢购

一文读懂QUIC 协议:更快、更稳、更高效的网络通信

原文

你是否也有这样的困扰:打开APP巨耗时、刷剧一直在缓冲、追热搜打不开页面、信号稍微差点就直接加载失败……

如果有一个协议能让你的上网速度,在不需要任何修改的情况下就能提升20%,特别是网络差的环境下能够提升30%以上;如果有一个协议可以让你在WiFi和蜂窝数据切换时,网络完全不断开、直播不卡顿、视频不缓冲;你愿意去了解一下它吗?它就是QUIC协议。本文将从QUIC的背景、原理、实践部署等方面来详细介绍。

一:网络协议栈  

1.1 什么叫网络协议?  

类似于我们生活中签署的合同一样,比如买卖合同是为了约束买卖双方的行为按照合同的要求履行,网络协议是为了约束网络通信过程中各方(客户端、服务端及中间设备)必须按照协议的规定进行通信,它制定了数据包的格式、数据交互的过程等等,网络中的所有设备都必须严格遵守才可以全网互联。

在网络协议栈中,是有分层的,每一层负责不同的事务。我们讨论最多的有三个:应用层、传输层、网络层。应用层主要是针对应用软件进行约束,比如你访问网站需要按照HTTP协议格式和要求进行,你发送电子邮件需要遵守SMTP等邮件协议的格式和要求;传输层主要负责数据包在网络中的传输问题,比如如何保证数据传输的时候的安全性和可靠性、数据包丢了怎么处理;网络层,也叫路由转发层,主要负责数据包从出发地到目的地,应该怎样选择路径才能更快的到达。合理的网络协议能够让用户上网更快!    

1.2 HTTP/3协议  

HTTP/3是第三个主要版本的HTTP协议。与其前任HTTP/1.1和HTTP/2不同,在HTTP/3中,弃用TCP协议,改为使用基于UDP协议的QUIC协议实现。所以,HTTP/3的核心在于QUIC协议。显然,HTTP/3属于应用层协议,而它使用的QUIC协议属于传输层协议。

1.3 我们需要HTTP/3协议吗  

很多人可能都会有这样一个疑问,为什么在 2015 年才标准化了 HTTP/2 ,这么快就需要 HTTP/3?

我们知道,HTTP/2通过引入“流”的概念,实现了多路复用。简单来说,假设你访问某个网站需要请求10个资源,你使用HTTP1.1协议只能串行地发请求,资源1请求成功之后才能发送资源2的请求,以此类推,这个过程是非常耗时的。如果想10个请求并发,不需要串行等待的话,在HTTP1.1中,应用就需要为一个域名同时建立10个TCP连接才行(一般浏览器不允许建立这么多),这无疑是对资源的极大的浪费。HTTP/2的多路复用解决了这一问题,能使多条请求并发。

但现实很残酷,为什么很多业务用了HTTP/2,反倒不如HTTP1.1呢?

第一:多流并发带来了请求优先级的问题,因为有的请求客户端(比如浏览器)希望它能尽快返回,有的请求可以晚点返回;又或者有的请求需要依赖别的请求的资源来展示。流的优先级表示了这个请求被处理的优先级,比如客户端请求的关键的CSS和JS资源是必须高优先级返回的,图片视频等资源可以晚一点响应。         
流的优先级的设置是一个难以平衡或者难以做到公平合理的事情,如果设置稍微不恰当,就会导致有些请求很慢,这在用户看来,就是用了HTTP/2之后,怎么有的请求变慢了。    

第二:HTTP/2解决了HTTP协议层面的队头阻塞,但是TCP的队头阻塞仍然没有解决,所有的流都在一条TCP连接上,如果万一序号小的某个包丢了,那么TCP为了保证到达的有序性,必须等这个包到达后才能滑动窗口,即使后面的序号大的包已经到达了也不能被应用程序读取。这就导致了在多条流并发的时候,某条流的某个包丢了,序号在该包后面的其他流的数据都不能被应用程序读取。这种情况下如果换做HTTP1.1,由于HTTP1.1是多条连接,某个连接上的请求丢包了,并不影响其他连接。所以在丢包比较严重的情况下,HTTP/2整体效果大概率不如HTTP1.1

事实上,我们并不是真的需要新的 HTTP 版本,而是需要对底层传输控制协议(TCP) 进行升级。

1.4 QUIC协议栈  

图片

图0-QUIC协议栈    

QUIC协议实现在用户态,建立在内核态的UDP的基础之上,集成了TCP的可靠传输特性,集成了TLS1.3协议,保证了用户数据传输的安全。

二:QUIC协议的优秀特性  

2.1 建连快  

数据的发送和接收,要想保证安全和可靠,一定是需要连接的。TCP需要,QUIC也同样需要。连接到底是什么?连接是一个通道,是在一个客户端和一个服务端之间的唯一一条可信的通道,主要是为了安全考虑,建立了连接,也就是建立了可信通道,服务器对这个客户端“很放心”,对于服务器来说:你想跟我进行通信,得先让我认识一下你,我得先确认一下你是好人,是有资格跟我通信的。那么这个确认对方身份的过程,就是建立连接的过程。

传统基于TCP的HTTPS的建连过程为什么如此慢?它需要TCP和TLS两个建连过程。如图1所示(传统HTTPS请求流程图):    

图片

图1-传统HTTPS请求流程图

对于一个小请求(用户数据量较小)而言,传输数据只需要1个RTT,但是光建连就花掉了3个RTT,这是非常不划算的,这里建连包括两个过程:TCP建连需要1个RTT,TLS建连需要2个RTT。RTT:Round Trip Time,数据包在网络上一个来回的时间。

为什么需要两个过程?可恶就可恶在这个地方,TCP和TLS没办法合并,因为TCP是在内核里完成的,TLS是在用户态。也许有人会说把干掉内核里的TCP,把TCP挪出来放到用户态,然后就可以和TLS一起处理了。首先,你干不掉内核里的TCP,TCP太古老了,全世界的服务器的TCP都固化在内核里了。所以,既然干不掉TCP,那我不用它了,我再自创一个传输层协议,放到用户态,然后再结合TLS,这样不就可以把两个建连过程合二为一了吗?是的,这就是QUIC。

2.1.1 QUIC的1-RTT建连    

如图2所示,是QUIC的连接建立过程:初次建连只需要1个RTT即可完成建连。后续再次建连就可以使用0-RTT特性

图片

图2-QUIC建连过程图

QUIC的1-RTT建连:客户端与服务端初次建连(之前从未进行通信过),或者长时间没有通信过(0-RTT过期了),只能进行1-RTT建连。只有先进行一次完整的1-RTT建连,后续一段时间内的通信才可以进行0-RTT建连。

如图3所示:QUIC的1-RTT建连可以分成两个部分。QUIC连接信息部分和TLS1.3握手部分。    

图片

图3-QUIC建连抓包

QUIC连接:协商QUIC版本号、协商quic传输参数、生成连接ID、确定Packet Number等信息,类似于TCP的SYN报文;保证通信的两端确认过彼此,是对的人。

TLS1.3握手:标准协议,非对称加密,目的是为了协商出 对称密钥,然后后续传输的数据使用这个对称密钥进行加密和解密,保护数据不被窃取。

我们重点看QUIC的TLS1.3握手过程。    

图片

图4-QUIC的1-RTT握手流程

我们通过图4可以看到,整个握手过程需要 2次握手(第三次握手是带了数据的),所以整个握手过程只需要1-RTT(RTT是指数据包在网络上的一个来回)的时间。

1-RTT的握手主要包含两个过程:

1.客户端发送Client Hello给服务端;

2.服务端回复Server Hello给客户端;

我们通过下图中图5和图6来看Client Hello和Server Hello具体都做了啥:

第一次握手(Client Hello报文)    

图片

图5-Client Hello报文

首先,Client Hello在扩展字段里标明了支持的TLS版本(Supported Version:TLS1.3)。值得注意的是Version字段必须要是TLS1.2,这是因为TLS1.2已经在互联网上存在了10年。网络中大量的网络中间设备都十分老旧,这些网络设备会识别中间的TLS握手头部,所以TLS1.3的出现如果引入了未知的TLS Version 必然会存在大量的握手失败。    

图片

图6-Client Hello报文

其次,ClientHello中包含了非常重要的key_share扩展:客户端在发送之前,会自己根据DHE算法生成一个公私钥对。发送Client Hello报文的时候会把这个公钥发过去,那么这个公钥就存在于key_share中,key_share还包含了客户端所选择的曲线X25519。总之,key_share是客户端提前生成好的公钥信息。

最后,Client Hello里还包括了:客户端支持的算法套、客户端所支持的椭圆曲线以及签名算法、psk的模式等等,一起发给服务端。    

图片

图7-Client Hello报文

第二次握手:(Server Hello报文)    

图片

图8-Server Hello报文

服务端自己根据DHE算法也生成了一个公私钥对,同样的,Key_share扩展信息中也包含了 服务端的公钥信息。服务端通过ServerHello报文将这些信息发送给客户端。

至此为止,双方(客户端服务端)都拿到了对方的公钥信息,然后结合自己的私钥信息,生成pre-master key,在这里官方的叫法是(client_handshake_traffic_secret和server_handshake_traffic_secret),然后根据以下算法进行算出key和iv,使用key和iv对Server Hello之后所有的握手消息进行加密。

注意:在握手完成之后,服务端会发送一个New Session Ticket报文给客户端,这个包非常重要,这是0-RTT实现的基础。    

图片

图9-New Session Ticket报文

2.1.2 QUIC的0-RTT握手  

这个功能类似于TLS1.2的会话复用,或者说0-RTT是基于会话复用功能的。

图片

图10- QUIC的0-RTT流程图

通过上面图10我们可以看到,client和server在建连时,仍然需要两次握手,仍然需要1个rtt,但是为什么我们说这是0-rtt呢,是因为client在发送第一个包client hello时,就带上了数据(HTTP 请求),从什么时候开始发送数据这个角度上来看,的确是0-RTT。

我们通过抓包来看0-RTT的过程:

图片

图11- QUIC的0-RTT抓包

所以真正在实现0-RTT的时候,请求的数据并不会跟Initial报文(内含Client Hello)一起发送,而是单独一个数据包(0-RTT包)进行发送,只不过是跟Initial包同时进行发送而已。

图片

图12- QUIC的0-RTT包    

我们单独看Initial报文发现,除了pre_share_key、early-data标识等信息与1-RTT时不同,其他并无区别。

2.1.3 QUIC建连需要注意的问题  

第一,QUIC实现的时候,必须缓存收到的乱序加密帧,这个缓存至少要大于4096字节。当然可以选择缓存更多的数据,更大的缓存上限意味着可以交换更大的密钥或证书。终端的缓存区大小不必在整个连接生命周期内保持不变。这里记住:乱序帧一定要缓存下来。如果不缓存,会导致连接失败。如果终端的缓存区不够用了,则其可以通过暂时扩大缓存空间确保握手完成。如果终端不扩大其缓存,则其必须以错误码CRYPTO_BUFFER_EXCEEDED关闭连接。

第二,0-RTT存在前向安全问题,请慎用!

2.2连接迁移  

QUIC通过连接ID实现了连接迁移。

我们经常需要在WiFi和4G之间进行切换,比如我们在家里时使用WiFi,出门在路上,切换到4G或5G,到了商场,又连上了商场的WiFi,到了餐厅,又切换到了餐厅的WiFi,所以我们的日常生活中需要经常性的切换网络,那每一次的切换网络,都将导致我们的IP地址发生变化。

传统的TCP协议是以四元组(源IP地址、源端口号、目的ID地址、目的端口号)来标识一条连接,那么一旦四元组的任何一个元素发生了改变,这条连接就会断掉,那么这条连接中正在传输的数据就会断掉,切换到新的网络后可能需要重新去建立连接,然后重新发送数据。这将会导致用户的网络会“卡”一下。    

但是,QUIC不再以四元组作为唯一标识,QUIC使用连接ID来标识一条连接,无论你的网络如何切换,只要连接ID不变,那么这条连接就不会断,这就叫连接迁移!

图片

图13-QUIC连接迁移介绍

2.2.1连接ID  

每条连接拥有一组连接标识符,也就是连接ID,每个连接ID都能标识这条连接。连接ID是由一端独立选择的,每个端(客户端和服务端统称为端)选择连接ID供对端使用。也就是说,客户端生成的连接ID供服务端使用(服务端发送数据时使用客户端生成的连接ID作为目的连接ID),反过来一样的。

连接ID的主要功能是确保底层协议(UDP、IP及更底层的协议栈)发生地址变更(比如IP地址变了,或者端口号变了)时不会导致一个QUIC连接的数据包被传输到错误的QUIC终端(客户端和服务端统称为终端)上。

2.2.2 QUIC的连接迁移过程    

QUIC限制连接迁移为仅客户端可以发起,客户端负责发起所有迁移。如果客户端接收到了一个未知的服务器发来的数据包,那么客户端必须丢弃这些数据包。

如图14所示,连接迁移过程总共需要四个步骤。

1.连接迁移之前,客户端使用IP1和服务端进行通信;

2.客户端IP变成IP2,并且使用IP2发送非探测帧给服务端;

3.启动路径验证(双方都需要互相验证),通过PATH_CHANLLENGE帧和PATH_RESPONSE帧进行验证。

4.验证通过后,使用IP2进行通信。

图片

图14- 连接迁移流程图   

2.3 解决TCP队头阻塞问题  

在HTTP/2中引入了流的概念。目的是实现 多个请求在同一个连接上并发,从而提升网页加载的效率。

图片

图15-QUIC解决TCP队头阻塞问题

由图15来看,假设有两个请求同时发送,红色的是请求1,蓝色的是请求2,这两个请求在两条不同的流中进行传输。假设在传输过程中,请求1的某个数据包丢了,如果是TCP,即使请求2的所有数据包都收到了,但是也只能阻塞在内核缓冲区中,无法交给应用层。但是QUIC就不一样了,请求1的数据包丢了只会阻塞请求1,请求2不会受到阻塞。

有些人不禁发问,不是说HTTP2也有流的概念吗,为什么只有QUIC才能解决呢,这个根本原因就在于,HTTP2的传输层用的TCP,TCP的实现是在内核态的,而流是实现在用户态度,TCP是看不到“流”的,所以在TCP中,它不知道这个数据包是请求1还是请求2的,只会根据seq number来判断包的先后顺序。

2.4 更优的拥塞控制算法  

拥塞控制算法中最重要的一个参数是 RTT,RTT的准确性决定了拥塞控制算法的准确性;然而,TCP的RTT测量往往不准确,QUIC的RTT测量是准确的。    

图片

图16-TCP计算RTT

如图16所示:由于网络中经常出现丢包,需要重传,在TCP协议中,初始包和重传包的序号是一样的,拥塞控制算法进行计算RTT的时候,无法区别是初始包还是重传包,这将导致RTT的计算值要么偏大,要么偏小。

图片

图17-QUIC计算RTT    

如图17所示:QUIC通过Packet Number来标识包的序号,而且规定Packet Number只能单调递增,这也就解决了初始包和重传包的二义性。从而保证RTT的值是准确的。

另外,不同于TCP,QUIC的拥塞控制算法是可插拔的,由于其实现在用户态,服务可以根据不同的业务,甚至不同的连接灵活选择使用不同的拥塞控制算法。(Reno、New Reno、Cubic、BBR等算法都有自己适合的场景)

2.5 QUIC的两级流量控制  

很多人搞不清楚流量控制与拥塞控制的区别。二者有本质上的区别。

流量控制要解决的问题是:接收方控制发送方的数据发送的速度,就是我的接收能力就那么大点,你别发太快了,你发太快了我承受不住,会给你丢掉 你还得重新发。         
拥塞控制要解决的问题是:数据在网络的传输过程中,是否网络有拥塞,是否有丢包,是否有乱序等问题。如果中间传输的时候网络特别卡,数据包丢在中间了,发送方就需要重传,那么怎么判断是否拥塞了,重传要怎么重传法,按照什么算法进行发送数据才能尽可能避免数据包在中间路径丢掉,这是拥塞控制的核心。

所以,流量控制解决的是接收方的接收能力问题,一般采用滑动窗口算法;拥塞控制要解决的是中间传输的时候网络是否拥堵的问题,一般采用慢启动、拥塞避免、拥塞恢复、快速重传等算法。                       

图片

图18-QUIC流量控制

QUIC是双级流控,不仅有连接这一个级别的流控,还有流这个级别的流控。如下图所示,每个流都有自己的可用窗口,可用窗口的大小取决于最大窗口数减去发送出去的最大偏移数,跟中间已经发送出去的数据包,是否按顺序收到了对端的ACK 无关。

3.QUIC协议如何优化  

QUIC协议定义了很多优秀的功能,但是在实现的过程中,我们会遇到很多问题导致无法达到预期的性能,比如0-RTT率很低,连接迁移失败率很高等等。

3.1 QUIC的0-RTT成功率不高  

导致0-RTT成功率不高的原因一般有如下几个:

1.服务端一般都是集群,对于客户端来说,处理请求的服务端是不固定的,新的请求到来时,如果当前client没有请求过该服务器,则服务器上没有相关会话信息,会把该请求当做一个新的连接来处理,重新走1-RTT。    

针对此种情况,我们可以考虑集群中所有的服务器使用相同的ticket文件。

2.客户端IP不是固定的,在发生连接迁移时,服务端下发的token融合了客户端的IP,这个IP变化了的话,携带token服务端校验不过,0-RTT会失败。

针对这个问题,我们可以考虑采用如图19所示的方法,使用设备信息或者APP信息来生成token,唯一标识一个客户端。

图片

图19- 使用设备信息提高0-RTT的成功率

3.Session Ticket过期时间默认是2天,超过2天后就会导致0-RTT失败,然后降级走1-RTT。可以考虑增长过期时间。

3.2 实现连接迁移并不容易  

连接迁移的实现,不可避开的两个问题:一个是四层负载均衡器对连接迁移的影响,一个是七层负载均衡器对连接迁移的影响。

四层负载均衡器的影响:LVS、DPVS等四层负载均衡工具基于四元组进行转发,当连接迁移发生时,四元组会发生变化,该组件就会把同一个请求的数据包发送到不同的后端服务器上,导致连接迁移失败;    

七层负载均衡器的影响(QUIC服务器多核的影响):由于多核的影响,一般服务器会有多个QUIC服务端进程,每个进程负载处理不同的连接。内核收到数据包后,会根据二元组(源IP、源port)选择已经存在的连接,并把数据包交给对应的socket。在连接迁移发生时,源地址发生改变,可能会让接下来的数据包去到不同的进程,影响socket数据的接收。

如何解决以上两个问题?DPVS要想支持QUIC的连接迁移,就不能再以四元组进行转发,需要以连接ID进行转发,需要建立 连接ID与对应的后端服务器的对应关系;

QUIC服务器也是一样的,内核就不能用四元组来进行查找socket,四元组查找不到时,就必须使用连接ID进行查找socket。但是内核代码又不能去修改(不可能去更新所有服务器的内核版本),那么我们可以使用eBPF的方法进行解决。如下图20所示:

图片

图20-多核QUIC服务器解决连接迁移问题    

3.3 UDP被限速或禁闭  

业内统计数据全球有7%地区的运营商对UDP有限速或者禁闭,除了运营商还有很多企业、公共场合也会限制UDP流量甚至禁用UDP。这对使用UDP来承载QUIC协议的场景会带来致命的伤害。对此,我们可以采用多路竞速的方式使用TCP和QUIC同时建连。除了在建连进行竞速以外,还可以对网络QUIC和TCP的传输延时进行实时监控和对比,如果有链路对UDP进行了限速,可以动态从QUIC切换到TCP。

图片

图21-QUIC和TCP协议竞速       

3.4 QUIC对CPU消耗大  

相对于TCP,为什么QUIC更消耗资源?

1.QUIC在用户态实现,需要更多的内核空间与用户空间的数据拷贝和上下文切换;

2.QUIC的ACK报文也是加密的,TCP是明文的。

3.内核知道TCP连接的状态,不用为每一个数据包去做诸如查找目的路由、防火墙规则等操作,只需要在tcp连接建立的时候做一次即可,然而QUIC不行;

总的来说,QUIC服务端消耗CPU的地方主要有三个:密码算法的开销;udp收发包的开销;协议栈的开销;    

针对这些,我们可以适当采取优化措施来:

1.使用Intel硬件加速卡卸载TLS握手

2.开启GSO功能。

3.数据在传输过程中,可以将一轮中所有的ACK解析后再同时进行处理,避免处理大量的ACK。

4.适当将QUIC的包长限制调高(比如从默认的1200调到1400个字节)

5.减少协议栈的内存拷贝

4.QUIC的性能  

从公开的数据来看,国内各个厂(腾讯、阿里、字节、华为、OPPO、网易等等)使用了QUIC协议后,都有很大的提升,比如网易上了QUIC后,响应速度提升45%,请求错误率降低50%;比如字节火山引擎使用QUIC后,建连耗时降低20%~30%;比如腾讯使用QUIC后,在腾讯会议、直播、游戏等场景耗时也降低30%;

总结  

QUIC协议的出现,为HTTP/3奠定了基础。这是近些年在web协议上最大的变革,也是最优秀的一次实践。面对新的协议,我们总是有着各种各样的担忧,诚然,QUIC协议在稳定性上在成熟度上,的确还不如TCP协议,但是经过近几年的发展,成熟度已经相当不错了,Nginx近期也发布了1.25.0版本,支持了QUIC协议。所以面对这样优秀的协议,我们希望更多的公司,更多的业务参与进来使用QUIC,推动QUIC更好的发展,推动用户上网速度更快!         

战略分析逻辑哲理模板

示例:【牧之野:一切都不一样了 – 今日头条】https://m.toutiao.com/is/iSdbM6C4/

能讲道理的前提是,双方实力均衡或历史有章可循。历史到了关键节点,不会以谁的意志为转移,一切都在加速。 我们如何定性上一周中国的变化,仅仅是一种救市的理解,或是一次巨大的跨越,现在去做评价还太早,或许某一天它会写在历史书里,以我们不太熟悉的表达方式。 但,有的时候,我们需要做决策。 林彪要塔山不要伤亡数字,是因为他知道塔山如果在那个时候拿不下来,可能导致整个战局的崩溃。 当年平津战役中新保安一战,我们一支部队因贪图攻打某城没有及时执行中央围剿傅作义最精锐的郭景云35师,差一点就让和平解放北京天津的战略功亏一篑。 这些,打塔山的部队、犯错的部队是领悟不到的,因为他们看不到全局。 在条件并不完全对等的情况下,战略上有些出其不意的招数,既有可能成为奇袭的决定性战役,也有可能犯下巨大失误。 同样,这些,我们也只能从事后去马后炮,形成理论上闭环,但当时,你怎么办? 因为在这些时刻,原来的所有的道理,可能都会瞬间失效,没有先例可遵循。 我已经充分发挥了自己研究和想象力,去尝试理解局势的变化。 其实远期的局势并不难研判,越是近的,反而越不容易。

40/70原则是指在决策过程中,当掌握了40%的信息时不要做出决策,而应该在掌握70%的信息时做出决定。‌ 这个原则是由美国人类心理学研究者皮特-霍林斯提出的,美国国务卿科林-鲍威尔曾经提到过这一原则‌。40/70原则的核心在于平衡决策的时机和信息的掌握程度。当你已经掌握了70%的信息时,应该做出决定,因为继续等待可能会错过更好的机会或者导致决策延误。这一原则强调了在决策过程中信息的重要性,并提醒人们在决策时要避免过度思考或者拖延‌。

压迫感是逐步给的,现在时机并不成熟。我们要给菲律宾一点压力,防范它的铤而走险,要尽量分化菲律宾和越南。所以,我们从工具箱子里拿出来的是绳子,而不是刀剑。

这一年多证明,的确他们要干的事情就是剧本本身。

从长中短期去看当下,决策结果都不同

我们每个人只能赚自己认知之内的钱

钱老曾说过一个观点:脱离系统的元素是毫无意义的。

输赢并不是就简单判定说一定要大涨就是赢,大跌就是输,而是,我们有没有在朝着一个正确的方向前进,去剜烂肉,去祛除长期存在于中国资本市场的顽疾,去规范上市公司,去吸引国外资本回流。

可怕的不是耍手段,而是即便打明牌,你也没办法。

西方文明解决问题,有点类似西医的风格,哪里坏了就切哪里,头疼医头脚疼医脚。以色列便是个例子,到处杀哈马斯杀真主党的头,可是,它一直搞不清楚,为什么这次就行不通了,为什么仗打了一年加沙一直还没弄干净。它们不懂,如何系统地、全局地解决问题,完全不理解。

对于这个世界的操控与反操控,对于国际斗争,说实话,我们还是小学生。我们是很正经的人,正经到即便再底线思维,或许也无法理解没有底线人做的事情。

对于结局,没有人知道,但有个道理是肯定的:谁能坚持到最后,谁就是胜利者。

getattr详解

一、基础用法
getattr() 是 Python 内置的一个函数,可以用来获取一个对象的属性值或方法。其基本语法为:

getattr(object, name[, default])
1
其中,object 是要获取属性值或方法的对象;name 是要获取的属性名或方法名;default 是可选参数,当指定的属性或方法不存在时,会返回 default 的值。

getattr() 可以通过对象实例或类名来获取属性值或方法,也可以获取内置函数、内置类型和标准库中的属性和方法。

下面是一些常见的使用 getattr() 的案例:

获取对象的属性值
class MyClass:
def init(self):
self.x = 1
self.y = 2

obj = MyClass()

print(getattr(obj, ‘x’)) # 输出 1
print(getattr(obj, ‘y’)) # 输出 2

运行运行
获取对象的方法
class MyClass:
def my_method(self):
print(‘Hello, world!’)

obj = MyClass()

method = getattr(obj, ‘my_method’)
method() # 输出 “Hello, world!”

运行运行
获取内置函数和类型
func = getattr(builtins, ‘abs’)
print(func(-1)) # 输出 1

type_name = ‘str’
type_obj = getattr(builtins, type_name)
print(type_obj(‘Hello, world!’)) # 输出 “Hello, world!”

运行运行
获取标准库中的属性和方法
import datetime

now = datetime.datetime.now()

attr_name = ‘year’
attr_value = getattr(now, attr_name)
print(attr_value) # 输出当前年份

method_name = ‘strftime’
method_args = [‘%Y-%m-%d %H:%M:%S’]
method = getattr(now, method_name)
formatted = method(*method_args)
print(formatted) # 输出格式化后的时间字符串,如 “2023-05-06 10:30:00”

运行运行
在实际开发中,getattr() 还可以用于实现动态调用函数或方法的功能,以及在需要处理大量类似属性或方法的代码时,简化代码的编写。

二、复杂案例
getattr() 还可以结合 import_module() 函数,实现动态执行某个文件中某个类的方法的功能。

下面是一个稍微复杂一些的例子。

假设我们有一个 data.py 文件,其中定义了一个 Data 类和一些数据操作方法,代码如下:

data.py

class Data:
def init(self, data):
self.data = data

def get(self):
    return self.data

def add(self, value):
    self.data.append(value)

def remove(self, value):
    self.data.remove(value)

def load_data():
return [1, 2, 3]


运行运行
现在我们需要在另一个模块中,动态加载 data.py 文件,并使用 Data 类的实例对象进行数据操作,同时也需要使用 load_data() 函数进行数据的读操作。

示例代码如下:

import importlib

module_name = ‘data’
class_name = ‘Data’
method_name = ‘get’

module = importlib.import_module(module_name)
class_obj = getattr(module, class_name)(module.load_data())
method = getattr(class_obj, method_name)
print(method()) # 输出 [1, 2, 3]

value = 4
method_name = ‘add’
method = getattr(class_obj, method_name)
method(value)

method_name = ‘get’
method = getattr(class_obj, method_name)
print(method()) # 输出 [1, 2, 3, 4]

value = 2
method_name = ‘remove’
method = getattr(class_obj, method_name)
method(value)

method_name = ‘get’
method = getattr(class_obj, method_name)
print(method()) # 输出 [1, 3, 4]




在上述示例中,我们首先使用 import_module() 函数导入了 data.py 文件,然后使用 getattr() 函数获取了 Data 类的对象。接着,我们使用 () 运算符执行了 get()、add() 和 remove() 方法,从而动态进行了数据操作。

importlib是Python的一个标准库,用于处理模块的导入过程。它提供了对Python导入机制的低级访问,允许你在运行时动态地导入和重载模块。

这个例子中,我们动态地加载了一个 Python 模块,并使用 getattr() 函数获取了模块中的类和函数对象。然后,我们使用这些对象来动态地进行数据操作。这种动态加载和执行的方式,可以使代码更加灵活和可扩展。

三、import_module介绍
import_module() 函数得到的是一个模块对象,即一个表示 Python 模块的对象。模块对象是一个包含模块中所有定义的类、函数、变量等的命名空间,我们可以使用模块对象来访问这些定义。

例如,我们可以使用 module 对象来访问 data.py 文件中定义的 Data 类和 load_data() 函数,代码如下:

import importlib

module_name = ‘data’

module = importlib.import_module(module_name)
data = module.Data([1, 2, 3])
loaded_data = module.load_data()
print(loaded_data)

1
2
3
4
5
6
7
8
9
在上述代码中,我们首先使用 import_module() 函数导入了 data.py 文件,然后通过 module 对象访问了 Data 类和 load_data() 函数。我们还使用 () 运算符创建了 Data 类的实例对象 data,并调用了 load_data() 函数,获取了数据列表 loaded_data。

关于加了括号后相当于调用了相应的方法,这是因为在 Python 中,函数和方法都是可调用对象。我们可以通过使用 () 运算符来调用函数和方法。

例如,在上述代码中,我们使用 () 运算符调用了 Data 类的构造函数和 load_data() 函数,从而创建了 data 对象和获取了 loaded_data 数据。这与直接调用类或函数的方式是相同的,例如:

data = Data([1, 2, 3])
loaded_data = load_data()
print(loaded_data)
1
2
3
在这种情况下,我们直接调用了类和函数,并通过它们来创建了对象和获取了数据,而不需要使用模块对象来访问它们。

原文链接:https://blog.csdn.net/mall_lucy/article/details/130531793

任务队列 ModelQueue

提供一个示例的 model_queue 类的实现,并解释其工作原理。请注意,由于您没有提供 model_queue 的具体实现,我将基于常见的异步任务队列模式进行说明。

示例 model_queue 类

以下是一个可能的 model_queue 类的实现示例

import asyncio

class ModelQueue:
    def __init__(self):
        self.queue = asyncio.Queue()  # 使用 asyncio.Queue 来管理任务

    async def add_task(self, model, task):
        """
        将任务添加到队列并执行。

        Args:
            model: 要使用的模型。
            task: 要执行的异步任务。

        Returns:
            任务的结果。
        """
        # 将任务放入队列
        await self.queue.put((model, task))
        return await self.process_queue()  # 处理队列中的任务

    async def process_queue(self):
        """
        处理队列中的任务。

        Returns:
            任务的结果。
        """
        while not self.queue.empty():
            model, task = await self.queue.get()  # 从队列中获取任务
            try:
                result = await task()  # 执行任务
                return result  # 返回任务的结果
            except Exception as e:
                print(f"Error processing task for model {model}: {e}")
            finally:
                self.queue.task_done()  # 标记任务完成

解释

队列初始化:self.queue = asyncio.Queue():使用 asyncio.Queue 来管理异步任务。这个队列可以存储多个任务,并在需要时逐个处理。

添加任务:async def add_task(self, model, task):这个方法将任务添加到队列中,并调用 process_queue 方法来处理队列中的任务。

await self.queue.put((model, task)):将模型和任务元组放入队列。

处理队列:async def process_queue(self):这个方法处理队列中的任务。

while not self.queue.empty():循环直到队列为空。

model, task = await self.queue.get():从队列中获取任务。

result = await task():执行任务并等待其完成。

self.queue.task_done():标记任务完成。

IP自动切换

未有测试,要用再测试,https://www.xiequ.cn/index.html每天有免费代理

https://englishcode.lanzoul.com/i5B6422srfxi

图片

🎈主要功能和源码

可以识别JSON的API接口,可以获取到IP和端口与地址信息,如果地址信息获取不到也没事,IP和端口一般都能获取到,我用了大概两家的JSON 都是不同的格式的,都可以正常代理 谐趣代理每天免费1000个代理,还可以

可以设置自动切换IP时间间隔 自定义API接口地址

import requests
import json
import os
import subprocess
import time
import sys
import logging
import tkinter as tk
from tkinter import scrolledtext
from threading import Thread, Event

# 设置输出编码为 UTF-8


# 自定义日志处理器
class ScrolledTextHandler(logging.Handler):
    def __init__(self, text_widget):
        super().__init__()
        self.text_widget = text_widget

    def emit(self, record):
        msg = self.format(record)

        def append():
            self.text_widget.insert(tk.END, msg + '\n')
            self.text_widget.see(tk.END)

        self.text_widget.after(0, append)


# 通用函数递归提取代理信息
def extract_proxy_info(data):
    logging.debug(f"解析返回的数据: {data}")

    possible_keys = {
        "ip": ["IP", "ip", "IpAddress", "ip_address"],
        "port": ["Port", "port"],
        "address": ["Address", "address", "Location", "location", "IpAddress", "ip_address"]
    }

    def recursive_extract(data, keys):
        if isinstance(data, dict):
            for key, value in data.items():
                if key in keys:
                    return value
                elif isinstance(value, (dict, list)):
                    result = recursive_extract(value, keys)
                    if result is not None:
                        return result
        elif isinstance(data, list):
            for item in data:
                result = recursive_extract(item, keys)
                if result is not None:
                    return result
        return None

    ip = recursive_extract(data, possible_keys["ip"])
    port = recursive_extract(data, possible_keys["port"])
    address = recursive_extract(data, possible_keys["address"])

    if not ip or not port:
        raise Exception("无法从返回的数据中提取IP和端口")

    return ip, port, address


# 从API获取代理IP和端口
def get_proxy(api_url):
    response = requests.get(api_url)
    response.encoding = 'utf-8'  # 确保正确解析中文

    # 添加详细的日志记录
    logging.debug(f"API返回的原始数据: {response.text}")

    try:
        data = response.json()
        logging.debug(f"API返回的数据: {data}")

        ip, port, address = extract_proxy_info(data)
        logging.info(f"获取到的代理IP: {ip}, 端口: {port}, 地址: {address if address else '未知地址'}")
        return ip, port, address
    except Exception as e:
        logging.error(f"解析数据时发生错误: {e}")
        logging.error(f"无法解析的原始数据: {response.text}")
        raise


# 配置HTTP代理
def set_http_proxy(ip, port):
    os.environ['HTTP_PROXY'] = f"http://{ip}:{port}"
    os.environ['HTTPS_PROXY'] = f"http://{ip}:{port}"


# 关闭HTTP代理
def disable_http_proxy():
    if 'HTTP_PROXY' in os.environ:
        del os.environ['HTTP_PROXY']
    if 'HTTPS_PROXY' in os.environ:
        del os.environ['HTTPS_PROXY']
    logging.info("已关闭Python HTTP代理")


# 设置Windows全局代理
def set_windows_proxy(ip, port):
    proxy = f"{ip}:{port}"
    command_enable = [
        'reg', 'add', 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings', '/v', 'ProxyServer',
        '/t', 'REG_SZ', '/d', proxy, '/f'
    ]
    command_enable_proxy = [
        'reg', 'add', 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings', '/v', 'ProxyEnable',
        '/t', 'REG_DWORD', '/d', '1', '/f'
    ]
    startupinfo = subprocess.STARTUPINFO()
    startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
    subprocess.run(command_enable, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, startupinfo=startupinfo)
    subprocess.run(command_enable_proxy, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, startupinfo=startupinfo)
    logging.info("已设置Windows全局代理")


# 关闭Windows全局代理
def disable_windows_proxy():
    command_disable_proxy = [
        'reg', 'add', 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings', '/v', 'ProxyEnable',
        '/t', 'REG_DWORD', '/d', '0', '/f'
    ]
    startupinfo = subprocess.STARTUPINFO()
    startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
    subprocess.run(command_disable_proxy, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, startupinfo=startupinfo)
    logging.info("已关闭Windows全局代理")


# 访问百度并检查HTTP状态码
def check_baidu():
    url = "http://www.baidu.com"
    try:
        response = requests.get(url)
        logging.info(f"HTTP状态码: {response.status_code}")
        logging.info(f"响应头: {response.headers}")
        if response.status_code == 200:
            logging.info("访问成功")
            return True
        else:
            logging.warning("访问失败: HTTP状态码不为200")
            return False
    except Exception as e:
        logging.error(f"访问失败: {e}")
        return False


def run_proxy(api_url, stop_event, switch_interval):
    try:
        while not stop_event.is_set():
            try:
                disable_windows_proxy()
                disable_http_proxy()
                ip, port, address = get_proxy(api_url)
                set_http_proxy(ip, port)
                if check_baidu():
                    set_windows_proxy(ip, port)
                for _ in range(switch_interval):
                    if stop_event.is_set():
                        break
                    time.sleep(1)
            except Exception as e:
                logging.error(f"发生错误: {e}")
                for _ in range(switch_interval):
                    if stop_event.is_set():
                        break
                    time.sleep(1)
    finally:
        disable_windows_proxy()
        disable_http_proxy()
        logging.info("代理已停止,所有代理设置已清除")


class App:
    def __init__(self, root):
        self.root = root
        self.root.title("IP代理")

        self.api_label = tk.Label(root, text="API地址:")
        self.api_label.grid(row=0, column=0, padx=10, pady=10)

        self.api_entry = tk.Entry(root, width=50)
        self.api_entry.grid(row=0, column=1, padx=10, pady=10)

        self.interval_label = tk.Label(root, text="切换间隔(秒):")
        self.interval_label.grid(row=0, column=2, padx=10, pady=10)

        self.interval_entry = tk.Entry(root, width=10)
        self.interval_entry.insert(0, "10")  # 默认值为10秒
        self.interval_entry.grid(row=0, column=3, padx=10, pady=10)

        self.start_button = tk.Button(root, text="启动代理", command=self.toggle_proxy)
        self.start_button.grid(row=0, column=4, padx=10, pady=10)

        self.console = scrolledtext.ScrolledText(root, width=100, height=30)
        self.console.grid(row=1, column=0, columnspan=5, padx=10, pady=10)

        # 添加自定义日志处理器
        handler = ScrolledTextHandler(self.console)
        handler.setLevel(logging.INFO)
        formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
        handler.setFormatter(formatter)
        logging.getLogger().addHandler(handler)

        # 设置日志记录器的级别
        logging.getLogger().setLevel(logging.DEBUG)

        self.proxy_thread = None
        self.stop_event = Event()

    def toggle_proxy(self):
        if self.proxy_thread is None or not self.proxy_thread.is_alive():
            api_url = self.api_entry.get()
            try:
                switch_interval = int(self.interval_entry.get())
                if api_url and switch_interval > 0:
                    self.stop_event.clear()
                    self.proxy_thread = Thread(target=self.run_proxy_thread, args=(api_url, switch_interval),
                                               daemon=True)
                    self.proxy_thread.start()
                    self.start_button.config(text="停止代理")
                    logging.info("代理已启动")
                else:
                    logging.error("请提供有效的API地址和切换间隔")
            except ValueError:
                logging.error("请提供有效的切换间隔")
        else:
            self.stop_event.set()
            self.root.after(100, self.check_thread_status)

    def check_thread_status(self):
        if self.proxy_thread.is_alive():
            self.root.after(100, self.check_thread_status)
        else:
            self.start_button.config(text="启动代理")
            logging.info("代理已停止")

    def run_proxy_thread(self, api_url, switch_interval):
        run_proxy(api_url, self.stop_event, switch_interval)


if __name__ == "__main__":
    root = tk.Tk()
    app = App(root)
    root.mainloop()

AI绘画细节「替换词」大全

编写结构化提示词

  1. 选择一个「主题」,或将多个主题组合出创意;
  2. 加入各种结构、效果提示词;
  3. 微调结果;
  4. 举个例子,下面[]里表示需要替换的内容:
high-speed photography  of a [vehicle] [description] in [scene], [location], [lighting], [angle],motion blur, cinematic,  symmetrical composition , highly-detailed

[车辆] [描述] 在 [场景]、[位置]、[照明]、[角度]、运动模糊、电影感、对称构图、高度详细的情况下高速摄影

🌿 10大「主题」20+小类型

从自然风光到植物园艺,从历史时期到科幻概念,覆盖你能想到的所有创作主题。

  1. 自然风光:山川、湖泊、日落、星空、极光、海滩、森林、草原…
  2. 建筑风格:哥特式、巴洛克、现代主义、未来派、中式园林、日式庭院…
  3. 艺术流派:印象派、超现实主义、立体主义、表现主义、抽象表现…
  4. 文化元素:中国风、日韩风、波西米亚、复古、朋克、赛博朋克…
  5. 节日主题:圣诞、万圣节、复活节、春节、感恩节、情人节…
  6. 科幻概念:星际旅行、外星生物、时间旅行、黑洞、量子力学…
  7. 历史时期:古埃及、罗马帝国、中世纪、文艺复兴、工业革命…
  8. 人物形象:骑士、精灵、海盗、侦探、武士、女巫、天使、恶魔…
  9. 动物世界:狮子、狼、鹰、马、熊猫、龙、凤凰、独角兽…
  10. 植物园艺:玫瑰、薰衣草、竹、仙人掌、枫叶、樱花、向日葵…

🎨 「提升画质」

以下这些用来按需替换「提升画质」的部分:

  • highresolution 高分辨率
  • FHD,1080P,8k,16k,32k 全高清
  • highdetail 高细节
  • detailed 详细细节
  • intricatedetails 复杂细节
  • hyperquality 高品质
  • hyperrealistic 超真实
  • 35mm,50mm55mm 镜头参数
  • realistic 现实主义
  • Surrealism 超现实主义
  • UltraHDpicturequality 超高清画质
  • HDR 高动态光照渲染图像
  • unrealengine 虚幻引擎
  • 3Drendering 3D渲染
  • MaxonCinema4D 渲染
  • CoronaRender 渲染
  • architecturalvisualisation 建筑视觉化
  • photography 摄影感
  • cinematic 电影感
  • leicalens 徕卡镜头
  • scenedesign 场景设计
  • photoquality 照片质量
  • verydetailedphoto 超细节照片
  • photorealistic 照相写实主义
  • Anti-Aliasing 抗锯齿
  • PostProcessing 后期处理

图片
🖌「材质」

  • Wood 木头
  • Glass 玻璃
  • Cotton 棉花
  • Plastictexture 塑料感
  • Carvedtexture 雕刻质感
  • Lace 蕾丝
  • Stonetexture 石质
  • Celadon 青瓷
  • Enamel 琅(可能缺少了某些字母,如 “enamel”)
  • Metallicpainttexture 金属漆质感
  • Texture 纹理质感
  • Pearllustertexture 珠光质感
  • Glasstexture 玻璃质感
  • Mattetexture 亚光质感
  • Pearltexture 珍珠质感
  • Silk texture 绸缎质感
  • Fluffy texture 毛绒质感
  • metallic surfaces 金属表面
  • fluorescent neon colours 荧光色的霓虹灯
  • Water wave texture 水波纹质感
  • Graphite texture 石墨质感
  • Antique bronze texture 古铜质感
  • Brick texture 砖石质感
  • Paint texture 油漆质感
  • Gauze texture 纱绸质感
  • Clay texture 粘土质感
  • Bamboo texture 竹子质感
  • Leather texture 皮革感
  • Cotton texture 棉质
  • Crystal texture 水晶质感
  • smooth fibre textures 光滑的纤维质感
  • reflective coatings in frosted black 磨砂黑的反光涂层
  • micro and nano materials 微纳米材料
  • artificial leather 人造皮革
  • Sandy texture 沙质
  • Ceramic texture 陶瓷质感

图片
🎨 「灯光」

  • Top light 顶光
  • Raking light 侧光
  • Rim light 轮廓光
  • Edge light 边缘光
  • Volumetric light 立体光
  • Back light 逆光
  • Hard light 硬光
  • Soft light 柔光
  • Cold light 冷光
  • Warm light 暖光
  • Reflection effect 反射
  • Stark shadows 明暗分明
  • Split Lighting 分体照明
  • Mapping light 映射光
  • Volumetric lighting 层次光
  • Bright highlights 明亮高光
  • Backlighting 背光照明
  • Global illuminations 全局照明
  • Frontlighting 正面照明
  • Point light 点光源
  • Spotlight 聚光灯
  • Area light 区域灯光
  • Ambient light 环境光
  • Shadow light 阴影光
  • Ring light 环形灯
  • Texture light 纹理灯
  • Emissive material 自发光材质
  • Laser beam effect 激光束效果
  • Rainbow halo 彩虹光环
  • Glow in the dark 夜光效果
  • rays of shimmering light 微光
  • Iwinkling stars 耀星空
  • Morning light 晨光
  • Sunlight 太阳光
  • Color light 色光
  • Soft moon light 柔和月米
  • Natural light 自然光
  • Indoor lighting 室内照明
  • Outdoor lighting 室外照明
  • studio lighting 影棚光
  • cinematic dappled lighting 电影斑驳的灯光
  • Dramatic light 戏剧性的光
  • Neon light 霓虹灯光
  • Cyberpunk light 赛博朋克灯光
  • Dim light effect 暗光效果

图片
🖌「视图」

  • front 正视
  • bird 俯视
  • side 侧视
  • Bottom view 仰视
  • Full body 全身
  • Busts 半身像
  • Profile 侧面
  • headshot 特写
  • ultrawide shot 超广角
  • macro shot 微距
  • Wide view 宽景
  • Bird view 鸟瞰
  • Horizontal composition 横向构图
  • Center composition 中心构图
  • Focal point composition 焦点构图
  • S-shaped composition S形构图
  • Isolation composition 孤立构图
  • Radial composition 径向构图
  • Tunnel composition 隧道构图
  • Radial composition 放射构图
  • Repetition composition 重复构图
  • Contrast composition 对比构图
  • Overlapping composition 重叠构图
  • Juxtaposition composition 并列构图
  • Negative space composition 负空间构图
  • Collage composition 拼贴构图
  • Golden Ratio composition 黄金分割构图
  • Three-quarter view 四分之三侧视
  • Rule of thirds composition 三分法构图
  • Gisometric diorama 轴测图透视法
  • Symmetrical composition 对称构图
  • Asymmetrical composition 非对称构图
  • Diagonal composition 对角线构图
  • Converging lines composition 汇聚线条构图
  • Vanishing point composition 消失点构图

图片
艺术风格

  1. Art Deco 装饰派艺术
  2. Art Nouveau 新艺术风格
  3. Dadaism 达达主义
  4. Dutch Golden Age 荷兰黄金时代
  5. Expressionism 表现派
  6. Fauvism 野兽派
  7. Futurism 未来主义
  8. Baroque 巴洛克风格
  9. Contemporary 当代
  10. Cubism 立体派
  11. Impressionism 印象派
  12. Installation 现代雕塑
  13. Land Art 大地艺术
  14. Minimalism 极简主义
  15. Pointillism 点彩画法
  16. Pop Art 波普艺术
  17. Portraiture 肖像画
  18. Post-Impressionism 后期印象派
  19. Surrealism 超现实主义
  20. Ukiyo-e 浮世绘
  21. Watercolors 水彩画
  22. Charcoal 木炭画
  23. Pastels 蜡笔画
  24. Chalk 粉笔画
  25. Realism 现实主义
  26. Rococo 洛可可风格
  27. Romanticism 浪漫主义
  28. Still Life 静物画
  29. Graphite Pencils 铅笔画
  30. Colored Pencils 彩色铅笔画
  31. Collages 拼贴画
  32. Assemblage 集成艺术
  33. Chiaroscuro 明暗对比
  34. Foreshortening 前缩透视
  35. Gouache 水粉画
  36. Grisaille 纯灰色画
  37. Miniature Painting 微型画
  38. Mural 壁画
  39. Perspective 透视画
  40. Sand Painting 沙画
  41. Scroll Painting 卷轴画
  42. Sfumato 晕涂法
  43. Acrylic Painting 压克力胶彩绘画
  44. Aerial Perspective 透视构图
  45. Anamorphosis 失真图像
  46. Camaieu 单色配色
  47. Oil Painting 油画
  48. Panel Painting 镶板绘画
  49. Panorama 全景画
  50. Sgraffito 彩釉
  51. Found Objects 粘贴画
  52. Tromp L’Oeil 错视画

图片「艺术家」

  1. Studio Ghibli 吉卜力工作室
  2. Pablo Picasso 巴勃罗·毕加索
  3. Virgil Abloh 维吉尔·阿布洛
  4. Hayao Miyazaki 宫崎骏
  5. Hidetaka Miyazaki 宫崎英高
  6. Katsuhiro Otomo 大友克洋
  7. Katsuya Terada 寺田克也
  8. Dave Rapoza 戴夫·拉波扎
  9. Marko Djurdjevic 马尔科·久尔杰维奇
  10. Craig Mullins 克雷格·穆林斯
  11. Simon Bisley 西蒙·比斯利
  12. James Gurney 詹姆斯·格尼
  13. Peter Tarka 彼得·塔卡
  14. Victo Ngai 倪传婧
  15. Skycloud 天空之城
  16. Cool 清凉
  17. Gemi 戈末
  18. Xiaoqi 小柒
  19. Husky 哈士奇
  20. Billion Girls Dream 十亿少女的梦
  21. Nightsky 夜空
  22. Xiaotang 小棠
  23. Alphonse Mucha 阿尔方斯·穆夏
  24. Hiroshi Yoshida 吉田宏
  25. Max Ernst 马克斯·恩斯特
  26. Paul Signac 保罗·西尼亚克
  27. Salvador Dali 萨尔瓦多·达利
  28. Fli Fli 艾莉艾莉
  29. Little Trick 小妙招
  30. Anni Babe 安妮宝贝
  31. Windy Moon 风中月影
  32. Golden Bell 金铃铛
  33. M.C. Escher 埃舍尔
  34. Thomas Kinkade 托马斯·金凯德
  35. Ivan Aivazovsky 伊万·艾瓦佐夫斯基
  36. Italo Calvino 伊塔洛·卡尔维诺
  37. Donato Giancola 多纳托·詹科拉
  38. Michael Whelan 迈克尔·惠兰
  39. Frank Frazetta 弗兰克·弗雷泽塔
  40. Boris Vallejo 鲍里斯·瓦列霍
  41. Julie Bell 朱莉·贝尔
  42. Luis Royo 路易斯·罗约
  43. Ian McCaig 伊恩·麦凯格
  44. Joe Madureira 乔·马杜雷拉
  45. Norman Rockwell 诺曼·洛克威尔
  46. Albert Bierstadt 阿尔伯特·比尔施塔特
  47. Giorgio de Chirico 乔治·德·基里科
  48. Rene Magritte 雷内·马格利特
  49. Ross Tran 罗斯·特兰
  50. Marc Simonetti 马克·西蒙内蒂
  51. John Harris 约翰·哈里斯
  52. Hilma af Klint 希尔玛·克林特
  53. George Inness 乔治·因内斯
  54. William Blake 威廉·布莱克
  55. Rose Fairy 蔷薇仙子
  56. Aphrodite 阿芙罗狄蒂
  57. Red Medicine 红药
  58. RedPupil 赤瞳

画一画

猪八戒

一头黑猪,带着人体特征,尖牙,穿着中国古代衣服,愤怒眼神中有红色火焰,CG泼墨画风。

图片

哪吒:

青年哪吒,冷峻眼神,双髻发型,插画,红色和金色,火轮、天丝、龙元素,手臂着火,终极渲染,CG泼墨画风。

图片

git

安装及配置
下载地址: https://github.com/git-for-windows/git/releases/download/v2.41.0.windows.3/Git-2.41.0.3-64-bit.exe

安装注意事项:

傻瓜式安装,一直下一步就好.

安装目录不要有中文.

尽量也不要有空格.

配置环境变量:

找到安装目录,将bin目录配置到path目录即可;

win + r, 输入cmd. 打开黑窗口. git \–-version,如果不报错,表示安装成功;

1.2 配置基本信息
配置你的用户名称和邮箱:

git config –global user.name “Your Name” #用户名

git config –global user.email “email@example.com” #邮箱

git config -l # 检查一下配置是否成功了.

1.3 初始化本地仓库
新建一个文件夹「目录」, 通过打开黑窗口

git init 初始化一个空的git仓库,操作后这个文件夹下会多一个隐藏的.git

1.4 向git仓库添加文件或者目录
git add 文件名称, 一般情况添加所有:

git add . // 为注意这个.g表示目录下的所有文件/目录

1.5 提交到本地仓库
git commit -m ‘提交日志‘

提交日志,之后咱们再说,这个提交日志非常的重要,它并不是随便写的.一般情况.公司都有要求.如果写这个提交日志.如果公司没有要求,各位自己参照一些好的写法.规范起来.

C:\Users\ldcig\Desktop\git study>git commit -m ‘第一次提交哦’ # 提交
hint: core.useBuiltinFSMonitor=true is deprecated;please set core.fsmonitor=true instead
hint: Disable this message with “git config advice.useCoreFSMonitorConfig false”
[master (root-commit) 74346b7] ‘第一次提交哦’ # 7434b… 表示生成了一个版本号.
4 files changed, 0 insertions(+), 0 deletions(-) # 提交文件的详细信息.
create mode 100644 hehe/222.txt
create mode 100644 hello.txt
create mode 100644 tom.txt
create mode 100644 “\344\270\215\350\246\201\347\235\241\350\247\211\345\225\246.txt”
git status

表示暂存区没有东西可以被提交了.

一般情况下,我们可以这样做:

初始化git仓库: git init

提交文件到暂存区: git add .

提交暂存区的文件到本地仓库: git commit – m ‘提交日志‘

查看当前仓库的状态: git status

查看日志: git log, 查看日志

远程仓库
github.com

gitlab

gitee.com , // 以它为例子,说说如何使用;

2.1 gitee使用
注册一个.

gitee.com

新建一个仓库, 用于关联我的本地仓库.

2.2 查看本地仓库是否已经关联了远程仓库
git remote -v # 查看本地仓库是否关联了远程仓库.
2.3 本地仓库关联远程库
git remote -v # 查看一下.
git remote add origin 远程仓库地址; # 将本地库和指定的远程关联起来
2.4 推送到已经关联的远程仓库上
把本地仓库的所有内容,推送到远程仓库上去;

git push origin master
注意事项:

你们第一次操作,肯定会让你填写用户名称和密码.「Gitee的用户名称和密码.」

如果出现了各种问题,百度一下.

一个本地仓库可以关联上多个远程仓库.

2.5 查看帮助手册
-h, 跟在咱们的命令后边,表示查看命令的帮助手册.

git remote -h # 查看remote命令如何使用.
2.6 分支操作
查看分支, 当前在哪个分支上操作呢:

git branch

创建分支

git branch 分支名称

切换分支

git checkout 分支名称

合并分支

git merge 分支名称

删除本地支

git branch -d 分支名称 # 删除已经合并的分支

git branch -D 分支名称 # 不管你合没合并,都能删除.

切换分支,如果发现切换的分支没有存在,则新建一个分支

git checkout -b 分支名称

删除远程分支

git push origin –delete 你要删除的分支名称

2.7 版本回退
git reset –hard HEAD~1, 表示回退一个版本号.

git reset –hard HEAD~3, 表示回退三个版本号.

图提示词

https://promlib.com文生图提示参数

Double Exposure用于将不同场景或主题融合在一起,增强视觉叙事的层次感和深度,原文示例:Double exposure, the silhouette of a woman, the silhouette of a man,Background is burning flame

治愈系风景短视频 卡通插画,线条插画,鲜艳配色,夕阳,落日,温暖,多层房屋,一楼有院子有泳池,小孩子在泳池玩耍,有大帐篷。景深,全景,有宽敞而温馨的起居区,角落里还有一棵小植物,装饰着郁郁葱葱的绿色植物和盆栽花卉,道路边扎兰花园,落日斜阳照在房子上,温馨舒适。 原文