js玩儿爬虫

news2024/11/24 6:03:37

前言

提到爬虫可能大多都会想到python,其实爬虫的实现并不限制任何语言。
下面我们就使用js来实现,后端为express,前端为vue3。

实现功能

话不多说,先看结果:
image
这是项目链接:https://gitee.com/xi1213/worm
项目用到的库有:vue、axios、cheerio、cron、express、node-dev
计划功能有:

  1. 微博热榜爬取。
  2. 知乎热榜爬取。
  3. B站排行榜爬取。
  4. 三个壁纸网站爬取。
  5. 随机生成人脸。
  6. 爬取指定页面所有图片。
  7. 删除爬取的数据。
  8. 定时任务(开发中)。

使用形式为:
双击打包出的exe(最好右键管理员运行,以防权限不足)。
image
双击exe后会弹出node后端启动的黑框。
image
自动在浏览器中打开操作界面(用户界面)。
image
爬取出的数据在exe同级目录下的exportData中。
image

具体实现

微博热榜

image
打开微博官网,f12分析后台请求,会发现它的热榜数据列表在请求接口:https://weibo.com/ajax/side/hotSearch 中,无参。
image
在接口列表realtime中根据页面信息,推测其字段含义:

  1. word为关键字,
  2. category为类别,
  3. https://s.weibo.com/weibo?q=%23 + word为链接,
  4. num为热度。

既然数据是现成的,那我们直接使用axios即可。
获取到数据列表后将其遍历拼接成指定格式的字符串,写入txt,下面是具体方法:
weibo.js

let axios = require('axios'),
    writeTxt = require("../utils/writeTxt"),
    { addMsg } = require("../store/index");

//抓取weibo
async function weiboWorm(dir, time) {
    let com = 'https://weibo.com';
    addMsg(`${com} 爬取中...`)
    let res = await axios.get(`${com}/ajax/side/hotSearch`);
    //拼接数据
    let strData = `微博热榜\r\n爬取时间:${time}\r\n`
    await res.data.data.realtime.forEach((l, index) => {
        strData = strData +
            '\r\n序号:' + (index + 1) + '\r\n' +
            '关键字:' + l.word + '\r\n' +
            '类别:' + l.category + '\r\n' +
            '链接:https://s.weibo.com/weibo?q=%23' + l.word.replace(/\s+/g, "") + '\r\n' +
            '热度:' + l.num + '\r\n' +
            '\r\n\r\n=================================================================================================================='
    })
    writeTxt(`${dir}/weibo_${Date.now()}.txt`, strData);//写入txt
    addMsg('$END');
}

module.exports = weiboWorm;

writeTxt.js

let fs = require('fs');

//写入txt
function writeTxt(filePath, data) {
    fs.writeFile(filePath, data, (err) => {
    })
}

module.exports = writeTxt;

需要注意的是在windows中换行使用的是\r\n,在链接中需要去掉空格。

知乎热榜

image
打开知乎官网,会发现它是需要登录的。
f12后点击左上角第二个按钮,在浏览器中切换为手机布局,刷新后即可不登录显示文章信息。
分析请求发现文章数据在请求接口:https://www.zhihu.com/api/v3/explore/guest/feeds 中,参数为limit,限制文章数。
image
根据页面信息推测接口字段含义:

  1. target.question.title为问题标题,
  2. https://www.zhihu.com/question/ + target.question.id为问题链接,
  3. target.question.answer_count为问答数,
  4. target.question.author.name为提问的用户名,
  5. https://www.zhihu.com/org/ + target.question.author.url_token为提问的用户链接,
  6. target.content为高赞回答的内容。

需要注意的是高赞回答的内容中有html的标签,需要自己str.replace(/xxx/g,‘’)去除。
数据的具体获取方法同微博类似。

B站排行榜

image
打开B站官网,找到排行榜,f12后发现数据在接口请求:https://api.bilibili.com/x/web-interface/ranking/v2 中,无参。
image
推测接口字段含义:

  1. title为视频标题,
  2. short_link_v2为视频短链,
  3. stat.view为视频浏览量,
  4. desc为视频描述,
  5. pic为视频封面,
  6. owner.name为视频作者,
  7. pub_location为发布地址,
  8. https://space.bilibili.com/ + owner.mid为作者链接。

数据的具体获取方法同微博类似。

壁纸网站爬取

项目使用了下面三个网站作为例子:
http://www.netbian.com/
image
https://www.logosc.cn/so/
image
https://bing.ioliu.cn/
image
具体思路如下:

  1. 用axios请求页面。
  2. 将请求到的数据使用cheerio.load解析(cheerio为node中的jq,语法同jq)。
  3. f12分析需要的数据在什么元素中,使用cheerio获取到该目标元素。
  4. 获取到元素中img的src内容。
  5. axios请求src(需要encodeURI转码,防止中文报错),记得设置responseType为stream。
  6. 有分页的需要考虑到动态改变url中的页码。
  7. 需要保证下载顺序,一张图片下载完成后才能下载另一张,否则下载量过大会有下载失败的可能,使用for配合async与await即可。

具体实现代码如下:
bian.js

let fs = require('fs'),
    cheerio = require('cheerio'),
    axios = require('axios'),
    downloadImg = require("../utils/downloadImg.js"),
    { addMsg } = require("../store/index");

//抓取彼岸图片
async function bianWorm(dir, pageNum) {
    let page = pageNum,//抓取页数
        pagUrlList = [],
        imgList = [],
        index = 0,
        com = 'https://pic.netbian.com';
    addMsg(`${com} 爬取中...`)
    for (let i = 1; i <= page; i++) {
        let url = i == 1 ? `${com}/index.html` : `${com}/index_${i}.html`;
        let res = await axios.get(url);
        let $ = cheerio.load(res.data);//解析页面
        let slistEl = $('.slist');//找到元素列表
        slistEl.find('a').each(async (j, e) => {
            pagUrlList.push(`${com}${$(e).attr('href')}`);//获取到页面url列表
        })
    }
    pagUrlList.forEach(async (p, i) => {
        let pRes = await axios.get(p);
        let p$ = cheerio.load(pRes.data);//解析页面
        let imgEl = p$('.photo-pic').find('img');//找到元素列表
        let imgUrl = `${com}${imgEl.attr('src')}`;//获取图片url
        imgList.push(imgUrl);
        index++;
        //循环的次数等于列表长度时获取图片
        if (index == pagUrlList.length) {
            let dirStr = `${dir}/bian_${Date.now()}`;
            fs.mkdir(dirStr, (err) => { })
            downloadImg(imgList, dirStr);//下载图片
        }
    })
}

module.exports = bianWorm;

downloadImg.js

let fs = require('fs'),
    axios = require('axios'),
    { addMsg } = require("../store/index");

//下载图片
async function downloadImg(list, path) {
    if (list.length == 0) {
        addMsg('$END');
        return;
    }
    // console.log(list.length);
    for (let i = 0; i < list.length; i++) {
        let url = encodeURI(list[i]);//转码,防止url中文报错
        try {
            //计算下载的百分比
            let percent = ((i + 1) / list.length * 100).toFixed(2);
            let msgStr = `${percent}% 爬取中... ${url}`;
            addMsg(msgStr);
            if (i == list.length - 1) {
                msgStr = `图片爬取完成,共${list.length}项。`
                addMsg(msgStr);
                addMsg('$END');
            }
            let typeList = ['jpg', 'png', 'jpeg', 'gif', 'webp', 'svg', 'psd', 'bmp', 'tif', 'tiff', 'ico'];
            let type = typeList.find((item) => {
                return url.includes(item);
            });//获取图片类型
            (type == undefined) && (type = 'jpg');//判断type是否为undefined
            const imgPath = `${path}/${i + 1}.${type}`;//拼接本地路径
            const writer = fs.createWriteStream(imgPath);
            const response = await axios
                .get(url, { responseType: 'stream', timeout: 5000 }).catch(err => { });
            response.data.pipe(writer);
            await new Promise((resolve, reject) => {
                writer.on('finish', resolve);
                writer.on('error', reject);
            });

        } catch (error) { }
    }
}
module.exports = downloadImg;

值得注意的是需要保证准确获取图片资源的不同后缀。

随机生成人脸

这里可没有人脸算法之类的,调用的是https://thispersondoesnotexist.com/ 站点的接口,此接口每次刷新可生成不同人脸。
axios请求接口后,使用fs的createWriteStream创建可写流,将数据流写入文件中,下面是具体实现方法:
randomFace.js

let fs = require('fs'),
    axios = require('axios'),
    { addMsg } = require("../store/index");

//生成随机人脸
async function randomFace(dir, faceNum) {
    let com = 'https://thispersondoesnotexist.com';
    addMsg(`人脸生成中...`);
    let dirStr = `${dir}/randomFace_${Date.now()}`;
    fs.mkdir(dirStr, (err) => { })
    for (let i = 1; i <= faceNum; i++) {
        await axios.get(com, { responseType: 'stream' })
            .then((resp) => {
                const writer = fs.createWriteStream(`${dirStr}/${i}.jpg`);// 创建可写流
                resp.data.pipe(writer);// 将响应的数据流写入文件
                writer.on('finish', () => {
                    //计算下载的百分比
                    let percent = ((i) / faceNum * 100).toFixed(2);
                    let msgStr = `${percent}% 人脸生成中... ${dirStr}/${i}.jpg`;
                    addMsg(msgStr);
                    if (i == faceNum) {
                        msgStr = `人脸生成完成,共${faceNum}张。`
                        addMsg(msgStr);
                        addMsg('$END');
                    }
                });
                writer.on('error', (err) => { addMsg('$END'); });
            })
    }
}

module.exports = randomFace;

爬取指定页面所有图片

思路同上面获取壁纸类似,只不过这次是获取页面所有的img标签的src。
由于范围扩大到所有页面了,所以需要考虑的情况就会比较多。
有的src中是没有http或者https的,有的src使用的是相对路径,有的可能有中文字符,还有很多我没考虑到的情况。
所以并不能爬取任意页面的所有图片,比如页面加载过慢,或者用了懒加载、防盗链等技术。
下面是我实现的方法:
allWebImg.js

let fs = require('fs'),
    cheerio = require('cheerio'),
    axios = require('axios'),
    downloadImg = require("../utils/downloadImg.js"),
    { addMsg } = require("../store/index");

//网站所有图片
async function allWebImgWorm(dir, com) {
    let imgList = [];
    addMsg(`${com} 爬取中...`);
    let res = await axios.get(com).catch(err => { });
    if (!res) {
        addMsg('$END');
        return
    }
    let $ = cheerio.load(res.data);//解析页面
    //获取到页面所有图片标签组成的列表
    $('img').each(async (j, e) => {
        let imgUrl = e.attribs.src;//获取图片链接
        if (imgUrl) {
            !imgUrl.includes('https') && (imgUrl = `https:${imgUrl}`);//判断是否有https,没有则加上
            imgList.push(imgUrl);
        }
    })
    let dirStr = `${dir}/allWebImg_${Date.now()}`;
    fs.mkdir(dirStr, (err) => { })
    downloadImg(imgList, dirStr);//下载图片
}

module.exports = allWebImgWorm;

删除爬取的数据

使用fs.unlinkSync删除文件,fs.rmdirSync删除目录。
需要提前判断文件夹是否存在。
需要遍历文件,判断是否为文件。为文件则删除,否则递归遍历。
下面是我的方法:
deleteFiles.js

let fs = require('fs'),
    path = require('path');

//删除文件夹及文件夹下所有文件
const deleteFiles = (directory) => {
    if (fs.existsSync(directory)) {
        fs.readdirSync(directory).forEach((file) => {
            const filePath = path.join(directory, file);
            const stat = fs.statSync(filePath);
            if (stat.isFile()) {
                fs.unlinkSync(filePath);
            } else if (stat.isDirectory()) {
                deleteFiles(filePath);
            }
        });
        if (fs.readdirSync(directory).length === 0) {
            fs.rmdirSync(directory);
        }
    }
    fs.mkdir('./exportData', (err) => { })
};

module.exports = deleteFiles;

定时任务

项目中该功能正在开发中,只放了一个按钮,但思路已有了。
在node中的定时操作可用cron实现。
下面是一个小例子,每隔10秒打印一次1:

const cron = require('cron');

async function startTask() {
     let cronJob = new cron.CronJob(
    //秒、分、时、天、月、周
    //通配符:,(时间点)-(时间域)*(所有值)/(周期性,/之前的0与*等效)?(不确定)
     '0/10 * * * * *',
     async () => {
     console.log(1);
         },
         null,
         true,
         'Asia/Shanghai'//时区标识符
     );
};

注意事项

Server-Sent Events(SSE)

image
该项目中前后端数据交互接口大多使用的是get请求,但有一个除外,反显爬取进度的接口:/getTaskState。
该接口使用的是SSE,爬取的进度与链接是实时显示的。
最近火热的ChatGPT的流式输出(像人打字一样一个字一个字的显示)使用的便是这个。
SSE虽然与WebSocket一样都是长链接,但不同的是,WebSocket为双工通信(服务器与客户端双向通信),SSE为单工通信(只能服务器向客户端单向通信)。
项目中node服务端发送数据是这样的:

// 事件流获取任务状态
    app.get('/getTaskState', async (req, res, next) => {
        res.writeHead(200, {
            'Content-Type': 'text/event-stream',
            'Cache-Control': 'no-cache',
            'Connection': 'keep-alive',
        });
        let sendStr = ''//发送的消息
        let id = setInterval(() => {
            msgList = getMsg();
            //消息列表不为空且最后一条消息不等于上一次发送的消息才能执行
            if (msgList.length != 0 && msgList[msgList.length - 1] != sendStr) {
                sendStr = msgList[msgList.length - 1];
                console.log('\x1B[32m%s\x1B[0m', sendStr)
                res.write(`data: ${sendStr}\n\n`);//发送消息
            }
        }, 10);
        req.on('close', () => {
            clearMsg();//清空消息
            res.end();//结束响应
            clearInterval(id);//清除定时器(否则内存泄漏)
        });
    });

需要在res.writeHead中Content-Type设置为text/event-stream即表示使用SSE发送数据。
res.write(‘data: test\n\n’)即表示发送消息:test,每次发送消息需要以data:开头,\n\n结尾。
使用setInterval控制消息发送频率。
需要在服务端监听何时关闭,使用req.on(‘close’,()=>{})。
监听到关闭时执行响应结束res.end()与清除定时器clearInterval(id)。
在vue客户端接收数据是这样的:

//事件流获取任务状态
const getTaskState = () => {
  stateMsg.value = "";
  isState.value = true;
  let eventSource = new EventSource(origin.value + '/getTaskState');
  eventSource.onmessage = (event) => {
    if (event.data != '$END') {
      stateMsg.value = event.data;
    } else {
      eventSource.close();//关闭连接(防止浏览器3秒重连)
      stateMsg.value = '执行完成!是否打开数据文件夹?';
      isState.value = false;
      setTimeout(() => {
        confirm(stateMsg.value) &&
          axios.get(origin.value + '/openDir').then(res => { })//打开数据文件夹
      }, 100);
    }
  };
  //处理错误
  eventSource.onerror = (err) => {
    eventSource.close();//关闭连接
    stateMsg.value = ''
    isState.value = false;
  };
};

直接在方法中new一个EventSource(url),这是H5中新提出的对象,可用于接收服务器发送的事件流数据。
使用EventSource接收数据,直接在onmessage中获取event.data即可。
关闭连接记得使用eventSource.close()方法,因为服务器单方面关闭连接会触发浏览器3秒重连。
处理错误使用eventSource.onerror方法。
关于关闭SSE连接的时机,这是由node服务端决定的。
我在后端有一个store专门用于存储消息数据:
store/index.js

let msgList = [];//消息列表

function addMsg(msg) {
    msgList.push(msg);
}

function getMsg() {
    return msgList;
}

function clearMsg() {
    //清空msgList中元素
    msgList = [];
}

module.exports = {
    addMsg,
    getMsg,
    clearMsg
};
  1. 在爬取数据时,后端会计算爬取的进度,将生成的消息字符串push到msgList列表中,每隔10ms发送给前端msgList列表中的最后一个元素。
  2. 当后端数据爬取完成时会向msgList中push存入指定字符串:$END,表示获取完成。
  3. 当前端识别到获取的消息为$END时,关闭连接。
  4. 后端监听到前端连接被关闭,则后端也关闭连接。

pkg打包

全局安装pkg时最好网络环境为可访问github的环境,否则你只能手动下载那个失败的包再扔到指定路径。
pkg安装完成后需要在package.json中配置一番(主要是配置assets,将public与需要的依赖包打包进exe中)。
这是我的package.json配置:

{
  "name": "worm",
  "version": "0.1.3",
  "description": "",
  "bin": "./index.js",
  "scripts": {
    "start": "node-dev ./index.js",
    "dist": "node pkg-build.js"
  },
  "pkg": {
    "icon": "./public/img/icon.ico",
    "assets": [
      "public/**/*",
      "node_modules/axios/**/*.*",
      "node_modules/cheerio/**/*.*",
      "node_modules/cron/**/*.*",
      "node_modules/express/**/*.*"
    ]
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "axios": "^0.27.2",
    "cheerio": "^1.0.0-rc.12",
    "cron": "^2.3.1",
    "express": "^4.18.2",
    "node-dev": "^8.0.0"
  }
}

我的打包命令是通过scripts中的dist在pkg-build.js中引入的,因为我需要将版本号输出在打包出的exe文件名中。
若打包命令直接写在package.json的scripts中会无法读取打包进程中项目的version。
这是我的pkg-build.js:

//只有通过node xxx.js方式执行的命令才能获取到package.json的version
const pkg = require('./package.json'),
    { execSync } = require('child_process');

const outputName = `dist/worm_v${pkg.version}.exe`;//拼接文件路径
const pkgCommand = `pkg . --output=${outputName} --target=win --compress=GZip`;//打包命令
execSync(pkgCommand);//执行打包命令

上面命令中的output表示输出路径(包含exe文件名),target表示打包的平台,compress表示压缩格式。
需要注意的是使用pkg打包时,项目中axios的版本不能太高。
否则即使你将axios写在pkg的打包配置里也无济于事,我使用的axios版本为0.27.2。

解决跨域

我node使用的是express,直接在header中配置Access-Control-Allow-Origin为* 即可。

app.all('*', (req, res, next) => {
        res.header("Access-Control-Allow-Origin", "*");//允许所有来源访问(设置跨域)
        res.header("Access-Control-Allow-Headers", "X-Requested-With,Content-Type");//允许访问的响应头
        res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");//允许访问的方法
        res.header("X-Powered-By", ' 3.2.1');//响应头
        res.header("Content-Type", "application/json;charset=utf-8");//响应类型
        next();
    });

child_process模块

众所周知,node是单线程运行的,在主线程中执行大量计算任务时会产生无响应的问题。
但node内置的child_process模块却可以创建新的进程,在新进程中执行操作不会影响到主进程的运行。
在此项目中自动打开浏览器、打开指定文件夹、执行打包命令用的就是它。

// 打开数据文件夹
app.get('/openDir', (req, res) => {
    res.send('ok');
   //打开文件夹,exe环境下需要使用exe所在目录
    let filePath = isPkg ?
        `${path.dirname(process.execPath)}${dir.replace('./', '\\')}` :
        path.resolve(__dirname, dir);
    exec(`start ${filePath}`);
});

//监听端口
app.listen(port, () => {
    let url = `http://${ipStr}:${port}`;
    isPkg && exec(`start ${url}`);//打包环境下自动打开浏览器
    //判断是否存在exportData文件夹,没有则创建
    fs.exists(dir, async (exists) => {
        !exists && fs.mkdir(dir, (err) => { });
    })
    console.log(
        '\x1B[31m%s\x1B[0m',
        `\n
${time} 爬虫服务开启!\n
运行过程中禁止点击此窗口!\n
如需关闭爬虫关闭此窗口即可!\n`
    );
});

设置静态资源

前端使用vue开发时,需要将vue.config.js中的publicPath配置设置为./之后再打包。
将vue打包后dist内的文件拷贝到node项目的public目录下。
需要在express设置请求头之前使用static(path.join(__dirname, ‘./public’))设置静态资源:

const app = express();
const isPkg = process.pkg;//判断是否为打包环境
const port = isPkg ? 2222 : 1111;//端口
const ipStr = getLocalIp();//获取本机ip
let time = getFormatTime();//获取格式化时间
let dir = './exportData';
app.use(express.json());//解析json格式
app.use(express.static(path.join(__dirname, './public')));//设置静态资源
app.all('*', (req, res, next) => {
    res.header("Access-Control-Allow-Origin", "*");//允许所有来源访问(设置跨域)
    res.header("Access-Control-Allow-Headers", "X-Requested-With,Content-Type");//允许访问的响应头
    res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");//允许访问的方法
    res.header("X-Powered-By", ' 3.2.1');//响应头
    res.header("Content-Type", "application/json;charset=utf-8");//响应类型
    next();
});

关于运行时的黑框

双击exe时,不仅会弹出浏览器用户页面,还会弹出黑框,点击黑框内部还会暂停程序运行。
我有想过使用pm2守护进程干掉黑框,但想到关闭爬虫时只需关闭黑框即可,便留下了黑框。
image

限制爬取次数

做人,特别是做开发,你得有道德,你把人家网站给玩儿崩了这好吗(′⌒`)?
没有任何东西是无限制的,我的限制是放在前端的(可能不太严谨),以爬取壁纸为例,调用inputLimit(num),入参为执行次数,方法是这样的:

//输入限制
const inputLimit = (pageNum) => {
  let val = prompt(`输入执行次数(小于等于${pageNum})`, "");
  if (val == null || isNaN(val) || parseInt(val) < 1 || parseInt(val) > pageNum) {
    return false;
  }
  return parseInt(val);
};

//彼岸壁纸
const bianWorm = () => {
  let val = inputLimit(10);
  if (val) {
    axios.get(origin.value + '/bianWorm?pageNum=' + val).then(res => { });
    getTaskState();
  }
};

后端获取到pageNum参数后,以此作为执行爬虫逻辑的循环依据。

结语

这是我第一次用js玩儿爬虫,很多地方可能不太完善,还请大佬们指出,谢谢啦!
此项目仅供学习研究,勿作他用。

原文链接:https://xiblogs.top/?id=60

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

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

相关文章

时间复杂度与空间复杂度的详解

目录 1.时间复杂度 2.时间复杂度计算例题 3.空间复杂度 1.时间复杂度 算法中的基本操作的执行次数&#xff0c;为算法的时间复杂度。 如何表达 时间复杂度&#xff1f; 大O的渐进表示法 实际中我们计算时间复杂度时&#xff0c;我们其实并不一定要计算精确的执行次数&#xf…

105. 从前序与中序遍历序列构造二叉树

题目描述 给定两个整数数组 preorder 和 inorder &#xff0c;其中 preorder 是二叉树的先序遍历&#xff0c; inorder 是同一棵树的中序遍历&#xff0c;请构造二叉树并返回其根节点。 示例 1: 输入: preorder [3,9,20,15,7], inorder [9,3,15,20,7] 输出: [3,9,20,null,n…

【高频面试题】多线程篇

文章目录 一、线程的基础知识1.线程与进程的区别2.并行和并发有什么区别&#xff1f;3.创建线程的方式有哪些&#xff1f;3.1.Runnable 和 Callable 有什么区别&#xff1f;3.2.run()和 start()有什么区别&#xff1f; 4.线程包括哪些状态&#xff0c;状态之间是如何变化的4.1.…

一文详述流媒体传输网络MediaUni

一张「多元融合」的网络。 黄海宇&#xff5c;演讲者 大家好&#xff0c;我是阿里云视频云的黄海宇&#xff0c;今天分享主题是MediaUni——面向未来的流媒体传输网络设计与实践。 下面我将会从应用对流媒体传输网络的要求、MediaUni定位与系统架构、MediaUni技术剖析、基于Me…

vr虚拟仿真消防模拟演练提升受训者的安全观念和防范技能

纵观多年来的火灾事故教训得知&#xff0c;火灾发生的原因复杂多样&#xff0c;仅采取单一教育形式无法达到预期效果。消防安全重在预防&#xff0c;VR消防模拟演练系统将火灾安全问题&#xff0c;经采集和汇集处理&#xff0c;以可视化的形式在安全培训平台上进行实时展现&…

微服务与Nacos概述-3

流量治理 在微服务架构中将业务拆分成一个个的服务&#xff0c;服务与服务之间可以相互调用&#xff0c;但是由于网络原因或者自身的原因&#xff0c;服务并不能保证服务的100%可用&#xff0c;如果单个服务出现问题&#xff0c;调用这个服务就会出现网络延迟&#xff0c;此时…

基于STM32 FOC下桥三电阻采样方式的电机相电流重构方法

文章目录 1、本文中的PWM生成模式2、 注意事项3、与SVPWM相关的问题4、采样点的选择4.1、在低调制系数时&#xff08;1&#xff09;4.2、在高调制系数时&#xff08;2&#xff09;4.3、在高调制系数时&#xff08;3&#xff09;4.4、在高调制系数时&#xff08;4&#xff09; 5…

Oracle 使用 CONNECT_BY_ROOT 解锁层次结构洞察:在 SQL 中导航数据关系

CONNECT_BY_ROOT 是一个在 Oracle 数据库中使用的特殊函数&#xff0c;它通常用于在层次查询中获取根节点的值。在使用 CONNECT BY 子句进行层次查询时&#xff0c;通过 CONNECT_BY_ROOT 函数&#xff0c;你可以在每一行中获取根节点的值&#xff0c;而不仅仅是当前行的值。 假…

Window下安装MinGW64

欢迎来到我的酒馆 介绍Windows下&#xff0c;安装MinGW64。 目录 欢迎来到我的酒馆二.MinGW64三.配置系统环境变量 二.MinGW64 从sourceforge下载mingw64&#xff0c; sourceforge下载MinGW https://sourceforge.net/projects/mingw-w64/files/mingw-w64/mingw-w64-release/ 下…

效率指数级提升的Intellij IDEA快捷键集合

温馨提示&#xff1a;全文有18个小技巧&#xff0c;为了阅读体验&#xff0c;可以直接先看文章目录。 1&#xff0c;打开一个文件中的所有方法展示框 CtrlF12 Alt7 2&#xff0c;打开一个类的所有使用位置 AltF7 3&#xff0c;打开一个类在项目使用的位置 CtrlAltF7 4&#…

谁才是真正的协议之王?fastjson2 vs fury

文章目录 写在前面简单介绍官网和引入设备&#xff0c;环境及样本设备JDK样本 测评数据包体大小序列化反序列化垃圾回收JIT优化耗时 结论序列化对比反序列化对比包体压缩比上API易用性上多语言生态上垃圾回收上JIT优化耗时上 综述 写在前面 前阵子&#xff0c;我们写过一篇关于…

Tomcat 部署及优化

Tomcat概述 Tomcat 是 Java 语言开发的&#xff0c;Tomcat 服务器是一个免费的开放源代码的 Web 应用服务器&#xff0c;是 Apache 软件基金会的 Jakarta 项目中的一个核心项目&#xff0c;由 Apache、Sun 和其他一些公司及个人共同开发而成。在中小型系统和并发访问用户不是很…

Vite 创建 Vue项目之后,eslint 错误提示的处理

使用 npm create vuelatest创建 vue 项目&#xff08;TS&#xff09;之后&#xff0c;出现了一些 eslint 错误提示&#xff0c;显然&#xff0c;不是代码真实的错误&#xff0c;而是提示搞错了。 vuejs/create-vue: &#x1f6e0;️ The recommended way to start a Vite-pow…

利用NtDuplicateObject进行Dump

前言 由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;文章作者不为此承担任何责任。&#xff08;本文仅用于交流学习&#xff09; 这是国外老哥2020年提出的一种蛮有意思的思路。 我们先来看看大致的思路是…

Technical debt (技术负债 / 技术债)

Technical debt (技术负债 / 技术债) In software development, or any other IT field (e.g., Infrastructure, Networking, etc.) technical debt (also known as design debt or code debt) is the implied cost of future reworking required when choosing an easy but li…

成集云 | 报销单同步到金蝶云星空 | 解决方案

方案介绍 金蝶云星空是金蝶集团针对企业数字化转型需求推出的一款云端产品。它是一套集成了多个业务模块的全面企业管理解决方案&#xff0c;旨在帮助企业实现全面管控和高效运营。 旗下涵盖了多个功能模块&#xff0c;包括财务、人力资源、供应链、生产制造、销售与市场、客…

SpringBoot 整合JDBC

SpringData简介 Sping Data 官网&#xff1a;https://spring.io/projects/spring-data数据库相关的启动器 &#xff1a;可以参考官方文档&#xff1a;https://docs.spring.io/spring-boot/docs/2.6.5/reference/htmlsingle/#using-boot-starter 整合JDBC 创建测试项目测试数据…

云技术-混沌工程

目录 混沌工程 故障注入 监控和观测 自动化和持续集成 混沌工程 混沌工程&#xff08;Chaos Engineering&#xff09;是一种实验性的系统可靠性工程方法&#xff0c;主动引入故障和异常来测试系统的弹性和容错能力。混沌工程的核心思想是通过模拟故障场景来验证系统在各种异…

Android:换肤框架Android-Skin-Support

gihub地址&#xff1a;https://github.com/ximsfei/Android-skin-support 样例&#xff1a; 默认&#xff1a; 更换后&#xff1a; 一、引入依赖&#xff1a; // -- 换肤依赖implementation skin.support:skin-support:4.0.5// skin-supportimplementation skin.support:ski…

ctf中linux内核态的漏洞挖掘与利用系列(一)

说明 该系列文章主要是从ctf比赛入手&#xff0c;针对linux内核上的漏洞分析、挖掘与利用做讲解&#xff0c;本篇文章主要介绍内核漏洞利用所需的前置知识以及准备工作。 linux内核态与用户态的区别 以 Intel CPU 为例&#xff0c;按照权限级别划分&#xff0c;Intel把 CPU指…