日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

js foreach用法_使用 nodejs 写爬虫(一): 常用模块和 js 语法

發(fā)布時間:2025/4/5 编程问答 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 js foreach用法_使用 nodejs 写爬虫(一): 常用模块和 js 语法 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

本篇是使用 nodejs 寫爬蟲系列教程的第一篇, 介紹了使用 nodejs 寫爬蟲過程中常用的模塊和一些必須掌握的 js 語法

<!-- more -->

常用模塊

常用模塊有以下幾個:

  • fs-extra
  • superagent
  • cheerio
  • log4js
  • sequelize
  • chalk
  • puppeteer
  • fs-extra

    使用 async/await 的前提是必須將接口封裝成 promise, 看一個簡單的例子:

    const sleep = (milliseconds) => {return new Promise((resolve, reject) => {setTimeout(() => resolve(), milliseconds)}) } ? const main = async () => {await sleep(5000);console.log('5秒后...'); } ? main();

    通過在 async 函數(shù)中使用 await + promise 的方式來組織異步代碼就像是同步代碼一般,非常的自然和有助于我們分析代碼的執(zhí)行流程。

    在 node 中, fs 模塊是一個很常用的操作文件的 native 模塊,fs (file system) 模塊提供了和文件系統(tǒng)相關(guān)的一些同步和異步的 api, 有時候使用同步 api 非常有必要,比如你要在一個自己寫的模塊中的在訪問文件后導出一些接口時,這個時候用同步的 api 就很實用。看個例子:

    const path = require('path'); const fs = require('fs-extra'); const { log4js } = require('../../config/log4jsConfig'); const log = log4js.getLogger('qupingce'); ? const createModels = () => {const models = {};const fileNames = fs.readdirSync(path.resolve(__dirname, '.')); ?fileNames.filter(fileName => fileName !== 'index.js').map(fileName => fileName.slice(0, -3)).forEach(modelName => {log.info(`Sequelize define model ${modelName}!`);models[modelName] = require(path.resolve(__dirname, `./${modelName}.js`));})return models; } ? module.exports = createModels();

    這個模塊訪問了當前目錄下所有的 model 模塊并導出 models, 如果是使用異步接口也就是 fs.readdir, 這樣的話你在別的模塊導入這個模塊是獲取不到 models 的, 原因是 require 是同步操作,而接口是異步的,同步代碼無法立即獲得異步操作的結(jié)果。

    為了充分發(fā)揮 node 異步的優(yōu)勢,我們還是應(yīng)該盡量使用異步接口。

    我們完全可以使用 fs-extra 模塊來代替 fs 模塊, 類似的模塊還有 mz。fs-extra 包含了所有的 fs 模塊的接口,還對每個異步接口提供了promise 支持,更棒的是 fs-extra 還提供了一些其它的實用文件操作函數(shù), 比如刪除移動文件的操作。更詳細的介紹請查看官方倉庫 fs-extra。

    superagent

    superagent 是一個 node 的 http client, 可以類比 java 中的 httpclient 和 okhttp, python 中的 requests。可以讓我們模擬 http 請求。superagent 這個庫有很多實用的特點。

  • superagent 會根據(jù) response 的 content-type 自動序列化,通過 response.body 就可以獲取到序列化后的返回內(nèi)容
  • 這個庫會自動緩存和發(fā)送 cookies, 不需要我們手動管理 cookies
  • 再來就是它的 api 是鏈式調(diào)用風格的,調(diào)用起來很爽,不過使用的時候要注意調(diào)用順序
  • 它的異步 api 都是返回 promise的。
  • 非常方便有木有 。官方文檔就是很長的一頁,目錄清晰,很容易就搜索到你需要的內(nèi)容。最后,superagent 還支持插件集成,比如你需要在超時后自動重發(fā),可以使用 superagent-retry。更多插件可以去 npm 官網(wǎng)上搜索關(guān)鍵字 superagent-。更多詳情查看官方文檔superagent

    // 官方文檔的一個調(diào)用示例 request.post('/api/pet').send({ name: 'Manny', species: 'cat' }).set('X-API-Key', 'foobar').set('Accept', 'application/json').then(res => {alert('yay got ' + JSON.stringify(res.body));});

    cheerio

    寫過爬蟲的人都知道, 我們經(jīng)常會有解析 html 的需求, 從網(wǎng)頁源代碼中爬取信息應(yīng)該是最基礎(chǔ)的爬蟲手段了。python 中有 beautifulsoup, java 中有 jsoup, node 中有 cheerio。

    cheerio 是為服務(wù)器端設(shè)計的,給你近乎完整的 jquery 體驗。使用 cheerio 來解析 html 獲取元素,調(diào)用方式和 jquery 操作 dom 元素用法完全一致。而且還提供了一些方便的接口, 比如獲取 html, 看一個例子:

    const cheerio = require('cheerio') const $ = cheerio.load('<h2 class="title">Hello world</h2>') ? $('h2.title').text('Hello there!') $('h2').addClass('welcome') ? $.html() //=> <h2 class="title welcome">Hello there!</h2>

    官方倉庫: cheerio

    log4js

    log4j 是一個為 node 設(shè)計的日志模塊。 場景簡單情況下可以考慮使用 debug 模塊。 log4js 比較符合我對日志庫的需求,其實他倆定位也不一樣,debug 模塊是為調(diào)試而設(shè)計的,log4js 則是一個日志庫,肯定得提供文件輸出和分級等常規(guī)功能。

    log4js 模塊看名字有點向 java 中很有名的日志庫 log4j 看齊的節(jié)奏。log4j 有以下特點:

  • 可以自定義 appender(輸出目標),lo4js 甚至提供了輸出到郵件等目標的 appender
  • 通過組合不同的 appender, 可以實現(xiàn)不同目的的 logger(日志器)
  • 提供了日志分級功能,官方的 FAQ 中提到了如果要對 appender 實現(xiàn)級別過濾,可以使用 logLevelFilter
  • 提供了滾動日志和自定義輸出格式
  • 下面通過我最近一個爬蟲項目的配置文件來感受以下這個庫的特點:

    const log4js = require('log4js'); const path = require('path'); const fs = require('fs-extra'); ? const infoFilePath = path.resolve(__dirname, '../out/log/info.log'); const errorFilePath = path.resolve(__dirname, '../out/log/error.log'); log4js.configure({appenders: {dateFile: {type: 'dateFile',filename: infoFilePath,pattern: 'yyyy-MM-dd',compress: false},errorDateFile: {type: 'dateFile',filename: errorFilePath,pattern: 'yyyy-MM-dd',compress: false,},justErrorsToFile: {type: 'logLevelFilter',appender: 'errorDateFile',level: 'error'},out: {type: 'console'}},categories: {default: {appenders: ['out'],level: 'trace'},qupingce: {appenders: ['out', 'dateFile', 'justErrorsToFile'],level: 'trace'}} }); ? ? const clear = async () => {const files = await fs.readdir(path.resolve(__dirname, '../out/log'));for (const fileName of files) {fs.remove(path.resolve(__dirname, `../out/log/${fileName}`));} } ? ? module.exports = {log4js,clear }

    sequelize

    寫項目我們往往會有持久化的需求,簡單的場景可以使用 JSON 保存數(shù)據(jù),如果數(shù)據(jù)量比較大還要便于管理,那么我們就要考慮用數(shù)據(jù)庫了。如果是操作 mysql 和 sqllite 建議使用 sequelize, 如果是 mongodb, 我更推薦用專門為 mongodb 設(shè)計的 mongoose

    sequelize 有幾點我覺得還是有點不太好,比如默認生成 id (primary key), createdAt 和 updatedAt。

    拋開一些自作主張的小毛病,sequelize 設(shè)計的還是很好的。內(nèi)置的操作符,hooks, 還有 validators 很有意思。sequelize 還提供了 promise 和 typescript 支持。如果是使用 typescript 開發(fā)項目,還有另外一個很好的 orm 選擇 : typeorm。更多內(nèi)容可以查看官方文檔: sequelize

    chalk

    chalk 中文意思是粉筆的意思,這個模塊是 node 很有特色和實用的一個模塊,它可以為你輸出的內(nèi)容添加顏色, 下劃線, 背景色等裝飾。當我們寫項目的時候往往需要記錄一些步驟和事件,比如打開數(shù)據(jù)庫鏈接,發(fā)出 http 請求等。我們可以適當使用 chalk 來突出某些內(nèi)容,例如請求的 url 加上下劃線。

    const logRequest = (response, isDetailed = false) => {const URL = chalk.underline.yellow(response.request.url);const basicInfo = `${response.request.method} Status: ${response.status} Content-Type: ${response.type} URL=${URL}`;if (!isDetailed) {logger.info(basicInfo);} else {const detailInfo = `${basicInfo}ntext: ${response.text}`;logger.info(detailInfo);} };

    調(diào)用上面的 logRequest效果:

    更多內(nèi)容查看官方倉庫chalk

    puppeteer

    如果這個庫沒聽說過,你可能聽說過 selenium。puppeteer 是 Google Chrome 團隊開源的一個通過 devtools 協(xié)議操縱 chrome 或者Chromium 的 node 模塊。Google 出品,質(zhì)量有所保證。這個模塊提供了一些高級的 api, 默認情況下,這個庫操縱的瀏覽器用戶是看不到界面的,也就是所謂的無頭(headless)瀏覽器。當然可以通過配置一些參數(shù)來啟動有界面的模式。在 chrome 中還有一些專門錄制 puppeteer 操作的擴展, 比如Puppeteer Recorder。使用這個庫我們可以用來抓取一些通過 js 渲染而不是直接存在于頁面源代碼中的信息。比如 spa 頁面,頁面內(nèi)容都是 js 渲染出來的。這個時候 puppeteer 就為我們解決了這個問題,我們可以調(diào)用 puppeteer 在頁面某個標簽出現(xiàn)時獲取到頁面當時的渲染出來的 html。事實上,往往很多比較困難的爬蟲解決的最終法寶就是操縱瀏覽器。

    前置的 js 語法

    async/await

    首先要提的就是 async/await, 因為 node 在很早的時候(node 8 LTS)就已經(jīng)支持 async/await, 現(xiàn)在寫后端項目沒理由不用 async/await了。使用 async/await 可以讓我們從回調(diào)煉獄的困境中解脫出來。這里主要提一下關(guān)于使用async/await 時可能會碰到的問題

    使用 async/await 怎樣并發(fā)?

    來看一段測試代碼:

    const sleep = (milliseconds) => {return new Promise((resolve, reject) => {setTimeout(() => resolve(), milliseconds)}) } ? const test1 = async () => {for (let i = 0, max = 3; i < max; i++) {await sleep(1000);} } ? const test2 = async () => {Array.from({length: 3}).forEach(async () => {await sleep(1000);}); } ? const main = async () => {console.time('測試 for 循環(huán)使用 await');await test1();console.timeEnd('測試 for 循環(huán)使用 await'); ?console.time('測試 forEach 調(diào)用 async 函數(shù)')await test2();console.timeEnd('測試 forEach 調(diào)用 async 函數(shù)') } ? main();

    運行結(jié)果是:

    測試 for 循環(huán)使用 await: 3003.905ms 測試 forEach 調(diào)用 async 函數(shù): 0.372ms

    我想可能會有些人會認為測試 forEach 的結(jié)果會是 1 秒左右,事實上測試2等同于以下代碼:

    const test2 = async () => {// Array.from({length: 3}).forEach(async () => {// await sleep(1000);// });Array.from({length: 3}).forEach(() => {sleep(1000);}); }

    從上面的運行結(jié)果也可以看出直接在 for 循環(huán)中使用 await + promise, 這樣等同于同步調(diào)用, 所以耗時是 3 秒左右。如果要并發(fā)則應(yīng)該直接調(diào)用 promise, 因為 forEach 是不會幫你 await 的,所以等價于上面的代碼,三個任務(wù)直接異步并發(fā)了。

    處理多個異步任務(wù)

    上面的代碼還有一個問題,那就是測試2中并沒有等待三個任務(wù)都執(zhí)行完就直接結(jié)束了,有時候我們需要等待多個并發(fā)任務(wù)結(jié)束之后再執(zhí)行后續(xù)任務(wù)。其實很簡單,利用下 Promise 提供的幾個工具函數(shù)就可以了。

    const sleep = (milliseconds, id='') => {return new Promise((resolve, reject) => {setTimeout(() => {console.log(`任務(wù)${id}執(zhí)行結(jié)束`)resolve(id);}, milliseconds)}) }const test2 = async () => {const tasks = Array.from({length: 3}).map((ele, index) => sleep(1000, index));const resultArray = await Promise.all(tasks);console.log({ resultArray} )console.log('所有任務(wù)執(zhí)行結(jié)束'); }const main = async () => {console.time('使用 Promise.all 處理多個并發(fā)任務(wù)')await test2();console.timeEnd('使用 Promise.all 處理多個并發(fā)任務(wù)') }main()

    運行結(jié)果:

    任務(wù)0執(zhí)行結(jié)束 任務(wù)1執(zhí)行結(jié)束 任務(wù)2執(zhí)行結(jié)束 { resultArray: [ 0, 1, 2 ] } 所有任務(wù)執(zhí)行結(jié)束 使用 Promise.all 處理多個并發(fā)任務(wù): 1018.628ms

    除了 Promise.all, Promise 還有 race 等接口,不過最常用應(yīng)該就是 all 和 race 了。

    正則表達式

    正則表達式是處理字符串強有力的工具。核心是匹配,由此衍生出提取,查找, 替換的等操作。

    有時候我們通過 cheerio 中獲取到某個標簽內(nèi)的文本時,我們需要提取其中的部分信息,這個時候正則表達式就該上場了。正則表達式的相關(guān)語法這里就不詳細說明了, 入門推薦看廖雪峰的正則表達式教程。來看個實例:

    // 服務(wù)器返回的 img url 是: /GetFile/getUploadImg?fileName=9b1cc22c74bc44c8af78b46e0ca4c352.png // 現(xiàn)在我只想提取文件名,后綴名也不要 const imgUrl = '/GetFile/getUploadImg?fileName=9b1cc22c74bc44c8af78b46e0ca4c352.png'; const imgReg = //GetFile/getUploadImg?fileName=(.+)..+/; const imgName = imgUrl.match(imgReg)[1]; console.log(imgName); // => 9b1cc22c74bc44c8af78b46e0ca4c352

    暫時先介紹到這里了,后續(xù)有更多內(nèi)容會繼續(xù)補充。

    本文為原創(chuàng)內(nèi)容,首發(fā)于個人博客, 轉(zhuǎn)載請注明出處。如果有問題歡迎郵件騷擾 ytj2713151713@gmail.com。

    《新程序員》:云原生和全面數(shù)字化實踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀

    總結(jié)

    以上是生活随笔為你收集整理的js foreach用法_使用 nodejs 写爬虫(一): 常用模块和 js 语法的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。