js玩兒爬蟲

来源:https://www.cnblogs.com/xi12/archive/2023/08/10/17621682.html
-Advertisement-
Play Games

# 前言 提到爬蟲可能大多都會想到python,其實爬蟲的實現並不限制任何語言。 下麵我們就使用js來實現,後端為express,前端為vue3。 # 實現功能 話不多說,先看結果: ![image](https://img2023.cnblogs.com/blog/1769804/202308/1 ...


前言

提到爬蟲可能大多都會想到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

原創者:曦12

原文鏈接:https://www.cnblogs.com/xi12/p/17621682.html

轉載請註明原創者添加原文鏈接!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • ## 首先 - 聲明:這是轉載,我只是做一個記錄,以下內容可解決問題(本人已嘗試並已解決),當然也可以去轉載出處查看大佬的原回答: >[Win10系統, administrator賬戶被微軟賬戶強行綁定,怎麼破? - 海爾森肯威的回答 - 知乎](https://www.zhihu.com/ques ...
  • 博客推行版本更新,成果積累制度,已經寫過的博客還會再次更新,不斷地琢磨,高質量高數量都是要追求的,工匠精神是學習必不可少的精神。因此,大家有何建議歡迎在評論區踴躍發言,你們的支持是我最大的動力,你們敢投,我就敢肝 ...
  • ![](https://img2023.cnblogs.com/blog/3076680/202308/3076680-20230809235422409-1190410594.png) # 1. 2版DB版本 ## 1.1. DB2 11.5 ## 1.2. Oracle 19c ## 1.3.  ...
  • “數據孤島”簡單的講,各組織都持有各自的數據,這些數據之間互有關係但又獨立存儲於各組織。出於安全性、合規性等方面考慮,各組織只能查詢、使用己方數據,無法交換其它組織的數據。在聯邦學習出現前,針對數據隱私保護的密碼學已應用於本地數據機器學習,隨著“數據孤島”問題的浮現,聯邦學習的概念出現併發展的日益成... ...
  • 1 pentaho簡介 `pentaho`是一款開源`ETL`工具,純java編寫的C/S模式的工具,可綠色免安裝,開箱即用。支持Windows、macOS、Linux平臺。 ...
  • 本文分享自華為雲社區《openGauss資料庫在CentOS上的安裝實踐》,作者:Gauss小松鼠 。 1.安裝前準備 安裝資料庫前先要有已安裝centOS 7.6的伺服器+資料庫安裝包。 首先找小伙伴申請了華為雲ECS伺服器安裝好了OS,這裡使用的是x86_64+centos。 華為雲伺服器現在可 ...
  • 根據 2023 年的 Stack Overflow 調研 (https://survey.stackoverflow.co/2023/) ,Postgres 已經取代 MySQL 成為最受敬仰和渴望 (the most admired, desired) 的資料庫。 隨著 Postgres 的發展勢 ...
  • 一、升級webview版本 (1). 下載需要更新的Webview apk。如果不能翻牆可以用下載好的版本(相容32/64位):Webview-115.0.5790.138 (2). 在路徑\aosp\external\chromium-webview\prebuilt\下替換arm或arm64架構 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...