selenium性能优化

selenium性能优化

当我们使用selenium制作爬虫或自动化测试时,往往会消耗更大的资源以及多余的等待页面资源加载时间。

大部分的优化手段都在options中进行设置

1
2
3
4
5
6
7
8
9
10
11
chrome_options = Options()
chrome_options.add_argument("--window-size=1920,1080")
chrome_options.add_argument("--disable-extensions")
chrome_options.add_argument("--headless")
chrome_options.add_argument("--disable-gpu")
chrome_options.add_argument("--disable-software-rasterizer")
chrome_options.add_argument('--no-sandbox')
chrome_options.add_argument('--ignore-certificate-errors')
chrome_options.add_argument('--allow-running-insecure-content')
chrome_options.add_argument("blink-settings=imagesEnabled=false")
driver = webdriver.Chrome(options=chrome_options)

配置headless模型

没有界面的模型,可以节省很多的内存和cpu占用,而且禁用了cpu渲染,提高加载速度。

1
2
3
chrome_options.add_argument("--headless")
chrome_options.add_argument("--disable-gpu")
chrome_options.add_argument("--disable-software-rasterizer")

配置不加载图片

图片通常在网络中加载消耗较多的时间,而且渲染较慢,如果不需要即时加载图片的话可以禁用图片加载

1
chrome_options.add_argument("blink-settings=imagesEnabled=false")

禁用插件加载

1
chrome_options.add_argument("--disable-extensions")

windows发现linux中的samba服务

0X0

通常我们在windows上搭建samba(smb)服务,如果两台机子再同一个内网的话,并且都开机主机发现功能的话,两个主机是可以再网络中发现的,并且可以使用smb服务。但是通常我们linux中搭建的samba服务通常都是使用ip进行访问,如果是静态地址还好,可以获取到固定的ip,但是如果是动态地址的话,还必须使用ddns进行动态ip映射。

对于Windows 10版本1511以后的版本,默认情况下禁用对SMBv1的支持,因此NetBIOS设备发现被禁用。所以再ubuntu中安装的samba服务无法被windows发现。
WSDD 是 Web Service Discovery host daemon的简称,实现了web服务可以被windows主机发现,这可以有效的帮助smb服务被发现。

安装

安装wsdd

ubuntu

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# cd tmp
cd /tmp

# 下载和解压
wget https://github.com/christgau/wsdd/archive/master.zip
unzip master.zip

# 重命名wsdd.py
sudo mv wsdd-master/src/wsdd.py wsdd-master/src/wsdd

# 复制到bin中
sudo cp wsdd-master/src/wsdd /usr/bin

# 将wsdd配置为服务
sudo cp wsdd-master/etc/systemd/wsdd.service /etc/systemd/system
sudo nano /etc/systemd/system/wsdd.service

#########################################################################
[Unit]
Description=Web Services Dynamic Discovery host daemon
; Start after the network has been configured
After=network-online.target
Wants=network-online.target
; It makes sense to have Samba running when wsdd starts, but is not required
;Wants=smb.service

[Service]
Type=simple
ExecStart=/usr/bin/wsdd --shortlog
; Replace those with an unprivledged user/group that matches your environment,
; like nobody/nogroup or daemon:daemon or a dedicated user for wsdd
; User=nobody
; Group=nobody
; The following lines can be used for a chroot execution of wsdd.
; Also append '--chroot /run/wsdd/chroot' to ExecStart to enable chrooting
;AmbientCapabilities=CAP_SYS_CHROOT
;ExecStartPre=/usr/bin/install -d -o nobody -g nobody -m 0700 /run/wsdd/chroot
;ExecStopPost=rmdir /run/wsdd/chroot

[Install]
WantedBy=multi-user.target

# reload start and enable
sudo systemctl daemon-reload
sudo systemctl start wsdd
sudo systemctl enable wsdd

# 查看状态

sudo service wsdd status

CentOS, Fedora, RHEL

1
dnf install wsdd

引用

https://devanswers.co/discover-ubuntu-machines-samba-shares-windows-10-network/
https://github.com/christgau/wsdd


ubuntu18.04安装g++10

由于想使用c++20中最新的协程特性,这应该是最简单的安装方式,避免了编译的g++。

1
2
3
4
5
# 添加ppa库
sudo add-apt-repository ppa:ubuntu-toolchain-r/test

# 安装g++
sudo apt install g++-10

ubuntu系统调节GPU风扇转速

ubuntu系统调节GPU风扇转速

查看NVIDIA GPU温度和转速

1
nvidia-smi

有桌面的ubuntu

可以通过nvidia-settings进项图像化界面的设置。

无桌面ubuntu[headless linux server]

使用coolgpus脚本进行调节

https://github.com/andyljones/coolgpus
安装:
使用pypi进行安装

1
pip install coolgpus

使用用例:

1
2
3
4
5
6
7
8
9
# 将gpu风扇转速设置为99%
sudo $(which coolgpus) --speed 99 99

# 关闭设置
sudo $(which coolgpus)

# 或者也可以设置线性控制
# 这个模式下20℃以下转速为5%, 20-55℃之间转速为30%,依次类推
sudo $(which coolgpus) --temp 20 55 80 --speed 5 30 99

如果需要将coolgpus脚本当作一个系统服务长期运行的话,如果你的服务器采用systemd管理server的话,可以在/etc/systemd/system/coolgpus.service创建模板

1
2
3
4
5
6
7
8
9
10
11
12
13
[Unit]
Description=Headless GPU Fan Control
After=syslog.target

[Service]
ExecStart=/home/ajones/conda/bin/coolgpus --kill
Restart=on-failure
RestartSec=5s
ExecStop=/bin/kill -2 $MAINPID
KillMode=none

[Install]
WantedBy=multi-user.target

可以通过如下命令进行控制

1
2
sudo systemctl enable coolgpus
sudo systemctl start coolgpus

vscode中docker插件无法连接

vscode中docker插件无法连接

报错Failed to connect. Is Docker running
Error: connect EACCES /var/run/docker.sock

原因分析

原因是docker使用unix socket进行通讯,但是unix socket属于root用户,但是普通用户需要使用sudo才能开启root权限,但是普通的操作并没有root权限。

解决方案

  1. 使用root用户登录,但是安全性没有保障
  2. 普通用户增加到docker组中
    1
    2
    3
    sudo groupadd docker          #添加docker用户组
    sudo gpasswd -a $USER docker #将当前用户添加至docker用户组
    newgrp docker #更新docker用户组
    添加后重新登录vscode,用户重新连接后docker插件会正常运行。

测试

可以运行docker ps命令,如果有正常的输出,则正常。


张拉结构小摆饰[stl文件]

张拉整体(Tensegrity)是一种基于在连续张力网络内部应用受压构建的结构原理。其中,受压构件之间并不接触,而预先张拉的构件构成了空间外形

3D渲染图

stl文件下载

最后成品

通过丝线连接后,接头处通过502胶水粘结,虽然有点粗糙但还有点意思🤗


fetch请求中的跨域和携带Cookies问题

fetch解读

Fetch API 提供了一个 JavaScript 接口,用于访问和操纵 HTTP 管道的一些具体部分,例如请求和响应。它还提供了一个全局 fetch() 方法,该方法提供了一种简单,合理的方式来跨网络异步获取资源。这种功能以前是使用 XMLHttpRequest 实现的。Fetch 提供了一个更理想的替代方案,可以很容易地被其他技术使用,例如Service Workers。

[问题]fetch请求中的跨域和携带Cookies问题

在有些请求中会遇到希望通过请求来访问不同源的api,并且希望携带cookies。

解决跨域问题

fetch可以设置不同的模式使得请求有效. 模式可在fetch方法的第二个参数对象中定义.

1
fetch(url, {mode: 'cors'});

可以定义的模式如下:

  • same-origin:表示同源可以进行访问,反之浏览器拒绝
  • cors: 表示同源和带有cors响应头的跨域可以请求成功,其他拒绝
  • cors-with-forced-preflight: 表示在发出请求前, 将执行preflight检查.
  • no-cors: 表示跨域请求不带cors响应头场景,此时的相应类型为opaque,但是在opaque的返回类型中,我们几乎不能查看到任何有价值的信息,比如不能查看response, status, url。

解决跨域携带cookies问题

跨域请求中需要带有cookie时, 可在fetch方法的第二个参数对象中添加credentials属性, 并将值设置为”include”.

1
2
3
fetch(url,{
credentials: 'include'
});

除此之外, credentials 还可以取以下值:

  • omit: 缺省值, 默认为该值.
  • same-origin: 同源, 表示同域请求才发送cookie.

但是同时需要目标服务器可以接受接受跨域发送cookies请求,否则会被浏览器的同源策略阻挡
服务器端需要支持Access-Control-Allow-Credentials策略,服务器同时设置Access-Control-Allow-Credentials响应头为"true", 即可允许跨域请求携带 Cookie。


3D打印鼓风机清灰

外壳

叶轮

最后的成品


无聊的考试中干什么都有意思,打印个鼓风机发现还能给键盘清灰还算有点用。


nodejs搭建mqtt服务器(Broker)

MQTT通讯协议

MQTT(消息队列遥测传输)是一种开放式OASIS和ISO标准的轻量级发布订阅网络协议,可在设备之间传输消息,是当下使用广泛的物联网通信协议之一。

IBM公司开发了MQTT协议的第一个版本, 设计思想是轻巧、开放、简单、规范,易于实现。
这些特点使得它对很多场景来说都是很好的选择,特别是对于受限的环境如机器与机器的通信(M2M)以及物联网环境(IoT)。

图中包括了MQTT协议中的两个角色和基本的订阅和发布模式

  • broker/client

    broker为代理端也就是我们要搭建的服务器,client也就是每个终端温度传感器或者是电脑笔记本等

  • 发布/订阅

    手机或者电脑client向broker订阅一个temp topic,当温度传感器client发布一个temp topic则手机或者电脑同时可以接收到订阅的内容

搭建mqtt Broker

mqtt Broker使用aedes进行搭建

1
2
3
4
5
6
7
const aedes = require('aedes')()
const server = require('net').createServer(aedes.handle)
const port = 18080

server.listen(port, function () {
console.log('server started and listening on port ', port)
})

测试mqtt Broker

搭建两个客户端进行模拟,每隔一秒发送一次temp。客户端使用mqtt.js

  1. client_pub.js
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var mqtt = require("mqtt")
    var client = mqtt.connect("mqtt://localhost:18080")

    // 连接后不断发布temp topic
    client.on("connect", (e) => {
    console.log("success connect mqtt server");
    setInterval(() => {
    client.publish("temp", "25.6")
    console.log("send it")
    }, 1000)
    })
  2. client_sub.js
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    var mqtt = require("mqtt")
    var client = mqtt.connect("mqtt://localhost:18080")

    // 连接后订阅temp topic
    client.on('connect', (e) => {
    console.log("success connect mqtt server");
    client.subscribe('temp', function (err) {
    console.log("subscribe temp topic")
    })
    })

    // 监听订阅的message
    client.on('message', function (topic, message) {
    // message is Buffer
    console.log(topic + ":\t" + message.toString())
    })

启动服务后运行两个客户端得到以下结果:

  1. 服务器
    1
    server started and listening on port  18080
  2. 客户端
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    # node mqtt-pub.js 
    success connect mqtt server
    send it
    send it
    send it
    send it
    send it
    send it
    send it
    send it
    send it
    send it
    send it

    # node mqtt-sub.js
    success connect mqtt server
    subscribe temp topic
    temp: 25.6
    temp: 25.6
    temp: 25.6
    temp: 25.6
    temp: 25.6
    temp: 25.6
    temp: 25.6
    temp: 25.6

使用ffmpeg生成视频缩略图

整个项目在https://github.com/ximikang/ffmpegThumbnail发布

生成缩略图的步骤

  1. 使用ffmpeg解码视频
  2. 帧格式转换
  3. 根据缩略图的数量从视频流中取帧
  4. 使用opencv建立画布并生成缩略图

ffmpeg解码视频

根据缩略图的数量从视频流中取帧

  1. 获取图片之间的时间间隔
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // Read media file and read the header information from container format
    AVFormatContext* pFormatContext = avformat_alloc_context();
    if (!pFormatContext) {
    logging("ERROR could not allocate memory for format context");
    return -1;
    }

    if (avformat_open_input(&pFormatContext, inputFilePath.string().c_str(), NULL, NULL) != 0) {
    logging("ERROR could not open media file");
    }

    logging("format %s, duration %lld us, bit_rate %lld", pFormatContext->iformat->name, pFormatContext->duration, pFormatContext->bit_rate);
    cout << "视频时常:" << pFormatContext->duration / 1000.0 / 1000.0 << "s" << endl;
    int64_t video_duration = pFormatContext->duration;
    int sum_count = rowNums * colNums;
    //跳转的间隔 ms
    int64_t time_step = video_duration / sum_count / 1000;
  2. 设置跳转时间获取不同的视频Packet
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    for (int i = 0; i < sum_count ; ++i) {
    cv::Mat tempImage;
    // 每次读取相同时间间隔的图像并存入vImage中
    while (av_read_frame(pFormatContext, pPacket) >= 0) {
    if (pPacket->stream_index == video_stream_index) {
    response = decode_packet_2mat(pPacket, pCodecContext, pFrame, tempImage);// 返回
    }
    if (response == 0)// 成功读取一帧
    break;
    if (response < 0)
    continue;
    }
    vImage.push_back(tempImage);
    // 跳转视频
    av_seek_frame(pFormatContext, -1, ((double)time_step / (double)1000)* AV_TIME_BASE*(double)(i+1) + (double)pFormatContext->start_time, AVSEEK_FLAG_BACKWARD);
    }
  3. 获取Frame
    在固定的时间点可能无法获取从当前时间点的Packet获取对应的Frame,所以需要对获取的Packet进行判断,如果没有获取到对应的Frame应该继续获取下一Packet直到获取到对应的Frame为止。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    static int decode_packet_2mat(AVPacket* pPacket, AVCodecContext* pCodecContext, AVFrame* pFrame, cv::Mat& image) {
    int response = avcodec_send_packet(pCodecContext, pPacket);

    if (response < 0) {
    logging("Error while sending a packet to the decoder");
    return response;
    }

    while (response >= 0) {
    // return decoded out data from a decoder
    response = avcodec_receive_frame(pCodecContext, pFrame);
    if (response == AVERROR(EAGAIN) || response == AVERROR_EOF) {
    logging("averror averror_eof");
    break;
    }
    else if (response < 0) {
    logging("Error while receiving frame");
    return response;
    }

    if (response >= 0) {
    // 获取到Frame
    image = frame2Mat(pFrame, pCodecContext->pix_fmt);
    }
    return 0;
    }
    }

    帧格式转换

    由于从视频流获取的帧是YUV格式的Frame格式,后面使用opencv进行操作所以进行格式转换。

先使用ffmpeg中的SwsContext将从视频中抽取到的帧从YUV转换到BGR格式,再从BGRFrame中的内存中获取原始数据,并转换到opencv的Mat类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
cv::Mat frame2Mat(AVFrame* pFrame, AVPixelFormat pPixFormat)
{
// image init
AVFrame* pRGBFrame = av_frame_alloc();
uint8_t* out_buffer = new uint8_t[avpicture_get_size(AV_PIX_FMT_BGR24, pFrame->width, pFrame->height)];
avpicture_fill((AVPicture*)pRGBFrame, out_buffer, AV_PIX_FMT_BGR24, pFrame->width, pFrame->height);
SwsContext* rgbSwsContext = sws_getContext(pFrame->width, pFrame->height, pPixFormat, pFrame->width, pFrame->height, AV_PIX_FMT_BGR24,SWS_BICUBIC, NULL, NULL, NULL);
if (!rgbSwsContext) {
logging("Error could not create frame to rgbframe sws context");
exit(-1);
}
if (sws_scale(rgbSwsContext, pFrame->data, pFrame->linesize, 0, pFrame->height, pRGBFrame->data, pRGBFrame->linesize) < 0) {
logging("Error could not sws to rgb frame");
exit(-1);
}

cv::Mat mRGB(cv::Size(pFrame->width, pFrame->height), CV_8UC3);
mRGB.data = (uchar*)pRGBFrame->data[0];//注意不能写为:(uchar*)pFrameBGR->data

av_free(pRGBFrame);
sws_freeContext(rgbSwsContext);
return mRGB;
}

使用opencv建立画布并生成缩略图

通过画布需要的大小参数,画出白色画布,再对画布进行填充。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
cv::Mat makeThumbnail(vector<cv::Mat> vImage, const unsigned int rowNums, const unsigned int colNums)
{
// 判断图片时候满足条件
if (vImage.size() != rowNums * colNums) {
logging("Error image size not equal input size");
logging("vImage length: %d, rowNums: %d, col number: %d", vImage.size(), rowNums, colNums);
exit(-1);
}
int interval = 100;
int height = vImage[0].size().height * rowNums + interval * (rowNums + 1);
int width = vImage[0].size().width * colNums + interval * (colNums + 1);
logging("thumbnail size: %d * %d", height, width);
cv::Mat thumbnail(cv::Size(width, height), CV_8UC3);
thumbnail.setTo(255);

// 进行填充
for (int i = 0; i < rowNums; ++i) {
for (int j = 0; j < colNums; ++j) {
int no = i * rowNums + j;
int widthOffset = (vImage[0].size().width + interval) * j + interval;
int heightOffset = (vImage[0].size().height + interval) * i + interval;
vImage[no].copyTo(thumbnail(cv::Rect(widthOffset, heightOffset, vImage[0].size().width, vImage[0].size().height)));
}
}
return thumbnail;
}

最后的效果