Node-RED提供了一个基于浏览器的流程编辑器,可以非常方便的写一些api接口,进行一些自动化的操作。
例如:

  1. 天气预报提醒:创建一个 API 接口,每天早上自动获取天气信息并发送到用户的电子邮件。
  2. 智能家居控制:通过 Node-RED 集成智能家居设备,实现语音命令控制灯光、空调等设备。
  3. 数据收集和存储:从多个传感器采集数据并存储到数据库中,用于实时监控和分析。
  4. 社交媒体监控:自动收集和分析特定主题的社交媒体帖子,并将结果发送到指定的聊天群组。
  5. 任务自动化:集成第三方服务(如 Trello 和 Slack),根据特定事件自动创建任务和发送通知。
  6. IoT设备管理:管理和监控多个物联网设备,实时显示设备状态并在异常情况下发送警报。
  7. 智能聊天机器人:创建一个聊天机器人,通过集成自然语言处理服务自动回答常见问题。
  8. 自动备份系统:定期从多个数据源备份数据到云存储,并在备份完成后发送通知。
  9. 流程自动化:结合企业内部系统,实现复杂业务流程的自动化处理,提升工作效率。
  10. 实时数据仪表盘:创建一个实时更新的数据仪表盘,显示关键业务指标和性能数据。

中文文档:https://nodered.17coding.net/

我部署的一个在线版本的:https://forge.flowfuse.glwsq.cn/

node red 代码小例子:https://www.glwsq.cn/post/nodered-example

快速体验

可以访问这个公开的node-red进行游玩,任何人都可以在这个上面部署内容,所以尽量不要删除别人的东西

http://play-node-red.glwsq.cn/

实际的例子

例子都会提供一个json的字符串,只需要复制,然后导入到工作区域,然后点击部署即可正常使用。
Image

http hello word例子

Image

访问 http://play-node-red.glwsq.cn/hello 即可返回hello world

[{"id":"48b5fbd81aaf95dd","type":"group","z":"0209114c20bac1d2","name":"hello world","style":{"label":true},"nodes":["846d11fd6fcc78a9","3964428ef75ee499","5671c043e7e8a2ba","f9a3550a987773ed","6c2568d54b7e5a90"],"x":174,"y":139,"w":492,"h":122},{"id":"846d11fd6fcc78a9","type":"http in","z":"0209114c20bac1d2","g":"48b5fbd81aaf95dd","name":"","url":"/hello","method":"get","upload":false,"swaggerDoc":"","x":260,"y":220,"wires":[["5671c043e7e8a2ba"]]},{"id":"3964428ef75ee499","type":"http response","z":"0209114c20bac1d2","g":"48b5fbd81aaf95dd","name":"","statusCode":"","headers":{},"x":570,"y":220,"wires":[]},{"id":"5671c043e7e8a2ba","type":"template","z":"0209114c20bac1d2","g":"48b5fbd81aaf95dd","name":"","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"hello world","output":"str","x":410,"y":220,"wires":[["3964428ef75ee499"]]},{"id":"f9a3550a987773ed","type":"comment","z":"0209114c20bac1d2","g":"48b5fbd81aaf95dd","name":"修改 /hello 为任意内容","info":"","x":300,"y":180,"wires":[]},{"id":"6c2568d54b7e5a90","type":"comment","z":"0209114c20bac1d2","g":"48b5fbd81aaf95dd","name":"模板里面是要返回的内容","info":"","x":530,"y":180,"wires":[]}]

http请求例子

Image

点击可以发送http请求获取一句话

[{"id":"cb8f5d5e9e802987","type":"group","z":"0209114c20bac1d2","name":"点击获取一句话","style":{"label":true},"nodes":["95770cfd1d4677ca","e6b4f205a964fc99","07964dc52cd12d2f"],"x":74,"y":439,"w":552,"h":82},{"id":"95770cfd1d4677ca","type":"http request","z":"0209114c20bac1d2","g":"cb8f5d5e9e802987","name":"一言API","method":"GET","ret":"txt","paytoqs":"ignore","url":"https://v1.hitokoto.cn/?c=f&encode=text","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":360,"y":480,"wires":[["07964dc52cd12d2f"]]},{"id":"e6b4f205a964fc99","type":"inject","z":"0209114c20bac1d2","g":"cb8f5d5e9e802987","name":"点击触发","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":180,"y":480,"wires":[["95770cfd1d4677ca"]]},{"id":"07964dc52cd12d2f","type":"debug","z":"0209114c20bac1d2","g":"cb8f5d5e9e802987","name":"debug 10","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":520,"y":480,"wires":[]}]

发邮件

Image

结合上面的例子,写了一个点击发邮件,并且会随机写一句话

[{"id":"0a38033fa6c3cc8b","type":"group","z":"0209114c20bac1d2","name":"","style":{"label":true},"nodes":["cb8f5d5e9e802987","e0a2bb6a5273be5a"],"x":28,"y":373,"w":864,"h":354},{"id":"cb8f5d5e9e802987","type":"group","z":"0209114c20bac1d2","g":"0a38033fa6c3cc8b","name":"点击获取一句话","style":{"label":true},"nodes":["95770cfd1d4677ca","e6b4f205a964fc99","07964dc52cd12d2f","e3d6a3ebf1398d5a","847bf12571a8e87e"],"x":54,"y":399,"w":552,"h":142},{"id":"95770cfd1d4677ca","type":"http request","z":"0209114c20bac1d2","g":"cb8f5d5e9e802987","name":"一言API","method":"GET","ret":"txt","paytoqs":"ignore","url":"https://v1.hitokoto.cn/?c=f&encode=text","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":340,"y":440,"wires":[["07964dc52cd12d2f","847bf12571a8e87e"]]},{"id":"e6b4f205a964fc99","type":"inject","z":"0209114c20bac1d2","g":"cb8f5d5e9e802987","name":"点击触发","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":160,"y":440,"wires":[["95770cfd1d4677ca"]]},{"id":"07964dc52cd12d2f","type":"debug","z":"0209114c20bac1d2","g":"cb8f5d5e9e802987","name":"debug 10","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":500,"y":440,"wires":[]},{"id":"e3d6a3ebf1398d5a","type":"link in","z":"0209114c20bac1d2","g":"cb8f5d5e9e802987","name":"一言API","links":[],"x":160,"y":500,"wires":[["95770cfd1d4677ca"]],"l":true},{"id":"847bf12571a8e87e","type":"link out","z":"0209114c20bac1d2","g":"cb8f5d5e9e802987","name":"link out 1","mode":"return","links":[],"x":485,"y":500,"wires":[]},{"id":"e0a2bb6a5273be5a","type":"group","z":"0209114c20bac1d2","g":"0a38033fa6c3cc8b","name":"发邮件","style":{"label":true},"nodes":["ebf64a21afe5ab39","6d065516da8f2dbd","b4ef9d08cf2f6c3b","fec4663d75cb536b","9df474085b0e8f8f","0e19dd73f5d9191f"],"x":54,"y":579,"w":812,"h":122},{"id":"ebf64a21afe5ab39","type":"template","z":"0209114c20bac1d2","g":"e0a2bb6a5273be5a","name":"","field":"payload","fieldType":"msg","format":"json","syntax":"mustache","template":"{\n    \"to\": \"xxx@qq.com\",\n    \"body\": \"{{payload}}\",\n    \"subject\": \"发送随机一句话\"\n}","output":"json","x":490,"y":660,"wires":[["6d065516da8f2dbd"]]},{"id":"6d065516da8f2dbd","type":"http request","z":"0209114c20bac1d2","g":"e0a2bb6a5273be5a","name":"发邮件","method":"POST","ret":"obj","paytoqs":"ignore","url":"http://nodered.glwsq.cn/email","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":610,"y":660,"wires":[["fec4663d75cb536b"]]},{"id":"b4ef9d08cf2f6c3b","type":"inject","z":"0209114c20bac1d2","g":"e0a2bb6a5273be5a","name":"点击发邮件","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":160,"y":660,"wires":[["0e19dd73f5d9191f"]]},{"id":"fec4663d75cb536b","type":"debug","z":"0209114c20bac1d2","g":"e0a2bb6a5273be5a","name":"debug 11","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload.to","statusType":"msg","x":760,"y":660,"wires":[]},{"id":"9df474085b0e8f8f","type":"comment","z":"0209114c20bac1d2","g":"e0a2bb6a5273be5a","name":"这里需要修改为自己的邮箱","info":"","x":570,"y":620,"wires":[]},{"id":"0e19dd73f5d9191f","type":"link call","z":"0209114c20bac1d2","g":"e0a2bb6a5273be5a","name":"","links":["e3d6a3ebf1398d5a"],"linkType":"static","timeout":"30","x":340,"y":660,"wires":[["ebf64a21afe5ab39"]]}]

执行浏览器操作

使用 puppeteer 对浏览器进行操作
利用这个开发的功能
一个接口,可以获取一个网页所有文本内容,如果传入了abstract_len则会形成一个对应字数的网页摘要。
https://nodered.glwsq.cn/web_content?url=https://www.glwsq.cn&abstract_len=50
url 如果也加了参数,需要URL编码一下,防止参数识别错误
[{"id":"81a3b576f77b3633","type":"inject","z":"8675776797b86da3","name":"","props":[{"p":"chrome_url","v":"http://www.glwsq.cn","vt":"str"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":130,"y":1480,"wires":[["2925fbadb57563e0"]]},{"id":"2925fbadb57563e0","type":"function","z":"8675776797b86da3","name":"function 93","func":"msg.url = msg.chrome_url\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":290,"y":1480,"wires":[["d0c1249d225de573"]]},{"id":"d0c1249d225de573","type":"function","z":"8675776797b86da3","name":"获取网页纯文本内容","func":"const puppeteer = global.get('puppeteer');\nlet browser = global.get('browser'); // Check if the browser instance is already stored\n\nif (!browser) {\n    browser = await puppeteer.launch({\n        headless: true, // Enables headless mode\n        args: ['--no-sandbox', '--disable-setuid-sandbox'],\n    });\n    global.set('browser', browser); // Store the browser instance globally\n    node.warn(\"新开浏览器\");\n}\n\nlet page;\nmsg.text = \"获取失败\"\ntry {\n    node.warn(`浏览器打开了${msg.url}`);\n    page = await browser.newPage();\n    await page.setUserAgent(\" Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3477.0 Safari/537.36\");\n    await page.evaluateOnNewDocument(() => {\n        `Object.defineProperty(navigator, 'webdriver', {\n            get: () => false,\n        });`\n    });\n\n\n    await page.evaluateOnNewDocument(() => {\n        `Object.defineProperty(navigator, 'plugins', {\n            get: () => [1, 2, 3],\n        });`\n    });\n\n    await page.evaluateOnNewDocument(() => {\n        `Object.defineProperty(navigator, 'plugins', {\n            get: () => [\n                {\n                    0: {type: \"application/x-google-chrome-pdf\", suffixes: \"pdf\", description: \"Portable Document Format\", enabledPlugin: Plugin},\n                    description: \"Portable Document Format\",\n                    filename: \"internal-pdf-viewer\",\n                    length: 1,\n                    name: \"Chrome PDF Plugin\"\n                },\n                {\n                    0: {type: \"application/pdf\", suffixes: \"pdf\", description: \"\", enabledPlugin: Plugin},\n                    description: \"\",\n                    filename: \"mhjfbmdgcfjbbpaeojofohoefgiehjai\",\n                    length: 1,\n                    name: \"Chrome PDF Viewer\"\n                },\n                {\n                    0: {type: \"application/x-nacl\", suffixes: \"\", description: \"Native Client Executable\", enabledPlugin: Plugin},\n                    1: {type: \"application/x-pnacl\", suffixes: \"\", description: \"Portable Native Client Executable\", enabledPlugin: Plugin},\n                    description: \"\",\n                    filename: \"internal-nacl-plugin\",\n                    length: 2,\n                    name: \"Native Client\"\n                }\n            ],\n        });`\n    });\n\n    await page.evaluateOnNewDocument(() => {\n    `    window.navigator.chrome = {\n            runtime: {},\n            loadTimes: function() {},\n            csi: function() {},\n            app: {}\n        };`\n    });\n\n\n    // node.warn(`新开标签页${msg.url}`);\n    await page.goto(msg.url, { waitUntil: 'networkidle2' }); // load networkidle2 , { waitUntil: 'load' }\n    // node.warn(`访问网页了 ${msg.url}`);\n    msg.text = await page.evaluate(`\n(function(){function e(a){return\"none\"!==window.getComputedStyle(a).display&&\"hidden\"!==window.getComputedStyle(a).visibility}function f(a){if(a.nodeType===Node.TEXT_NODE)return a.nodeValue.trim();if(a.nodeType===Node.ELEMENT_NODE&&e(a)){let b=\"\";for(let c of a.childNodes)b+=f(c)+\" \";return b.trim()}return\"\"}return f(document.body)})();\n    `)\n    // node.warn(msg.text);\n    const pages = await browser.pages();\n    node.status({ fill: \"yellow\", shape: \"ring\", text: `page size: ${pages.length}` });\n} catch (error) {\n    node.error(`Error in puppeteer operation: ${error.message}`, msg);\n} finally {\n    if (page) {\n        await page.close(); // Ensure the page is closed even if an error occurs\n    }\n}\n\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":500,"y":1480,"wires":[["741c5e8effced7e6","0ad011c14c6df430"]]},{"id":"741c5e8effced7e6","type":"debug","z":"8675776797b86da3","name":"debug 198","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"text","targetType":"msg","statusVal":"","statusType":"auto","x":830,"y":1460,"wires":[]}]

获取可用的谷歌学术镜像站网址

[{"id":"1dd60d33df72dbb9","type":"group","z":"69058845e3bf8b4d","name":"获取可用的谷歌学术镜像站网址","style":{"label":true},"nodes":["66399dd0659abaa4","4ad7bbe925d90e88","bb91502c0121ddd5","9e4a8152ae8befea","0cf0ddbd98fd53a4","b0c804fa77583a00","d73e1fe255930923","85c2cba4f299c300","a3884a2d48aca142","a38aa35cbb229af1"],"x":54,"y":7139,"w":1152,"h":242},{"id":"66399dd0659abaa4","type":"function","z":"69058845e3bf8b4d","g":"1dd60d33df72dbb9","name":"function 105","func":"\n\nlet Gword = msg.gwordValue;\n// let url = \"\"\n// var mbcss = 'res/mb.css?v=1721230642';\nvar hn = 'ac.scmor.com';\n// var autourl = [\"ZAM0RgYmLl0Ne3pZfjZxATgbMlcpJD8HKEMyGDdlfBVsMDcBbzgAf2xjJEhoDDBWYBkAFyE1eAghLA8M\", \"U3kwBQctIQAjMHYBVBwFXQJSLUQRJAEFE1dWBQ8AARQ=\", \"U3kwBQctIQAjMHZPbldbRQApNUc/DlQWKFdWBQ9bCV8=\", \"U3kwBQctIQAjMHZPVRwFWwMmEx4QUSMEPkMtGQ9hCV8=\", \"U3kwBQctIQAjMHYBUzJmSwApNlsqNAEBEUNaAQlhBAhXVlUa\", \"U3kwBQctIQAjMHZPbhwFRjg2VkUqNwZbPH1WBQ8AARQ=\", \"U3kwBQctIQAjMHYGU1ZTRDsmWh4qNAEaKGk1WyFffxRXN1wWb10hOVdwI0k=\", \"U3kwBQctIQAjMHZPbldYRwMmJVc/DhUHEEM5Bw9cfw1RN1AGVAZxcg==\", \"U3kwBQctIQAjMHYBVBwFXAIMWlkpOzwaKxxaGyFFDF8=\"];\n// var ts = [0, 0, 0, 0, 0, 0, 0, 0, 0];\n// var ns = 8;\n// var nt = 8;\n\n\n\n\n// visit('U3kwBQctIQAjMHYGU1ZTRDsmWh4qNAEaKGk1WyFffxRXN1wWb10hOVdwI0k=')\n\nfunction strdecode(string) {\n string = base64decode(string);\n let key = Gword + hn;\n let len = key.length;\n let code = '';\n for (let i = 0; i < string.length; i++) {\n    let k = i % len;\n    code += String.fromCharCode(string.charCodeAt(i) ^ key.charCodeAt(k));\n }\n return base64decode(code);\n}\nvar base64DecodeChars = new Array(-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 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, -1, -1, -1, -1, -1, -1, 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, 51, -1, -1, -1, -1, -1);\n\nfunction base64decode(str) {\n var c1, c2, c3, c4;\n var i, len, out;\n len = str.length;\n i = 0;\n out = \"\";\n while (i < len) {\n do {\n c1 = base64DecodeChars[str.charCodeAt(i++) & 0xff];\n } while (i < len && c1 == -1);\n if (c1 == -1) break;\n do {\n c2 = base64DecodeChars[str.charCodeAt(i++) & 0xff];\n } while (i < len && c2 == -1);\n if (c2 == -1) break;\n out += String.fromCharCode((c1 << 2) | ((c2 & 0x30) >> 4));\n do {\n c3 = str.charCodeAt(i++) & 0xff;\n if (c3 == 61) return out;\n c3 = base64DecodeChars[c3];\n } while (i < len && c3 == -1);\n if (c3 == -1) break;\n out += String.fromCharCode(((c2 & 0XF) << 4) | ((c3 & 0x3C) >> 2));\n do {\n c4 = str.charCodeAt(i++) & 0xff;\n if (c4 == 61) return out;\n c4 = base64DecodeChars[c4];\n } while (i < len && c4 == -1);\n if (c4 == -1) break;\n out += String.fromCharCode(((c3 & 0x03) << 6) | c4);\n }\n return out;\n}\n// eval(function(p,a,c,k,e,r){e=String;if(!''.replace(/^/,String)){while(c--)r[c]=k[c]||c;k=[function(e){return r[e]}];e=function(){return'\\\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\\\b'+e(c)+'\\\\b','g'),k[c]);return p}('3(1.0!=2.0){1.0=2.0}',4,4,'location|top|self|if'.split('|'),0,{}));\n\n\n\n\n// msg.payload = strdecode(\"U3kwBQctIQAjMHYGU1ZTRDsmWh4qNAEaKGk1WyFffxRXN1wWb10hOVdwI0k=\")\n\nmsg.google_scholar_urls = msg.google_scholar_urls.map(strdecode)\n\nmsg.payload = msg.google_scholar_urls\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":790,"y":7180,"wires":[["bb91502c0121ddd5","a3884a2d48aca142"]]},{"id":"4ad7bbe925d90e88","type":"inject","z":"69058845e3bf8b4d","g":"1dd60d33df72dbb9","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":570,"y":7180,"wires":[["66399dd0659abaa4"]]},{"id":"bb91502c0121ddd5","type":"debug","z":"69058845e3bf8b4d","g":"1dd60d33df72dbb9","name":"debug 216","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":950,"y":7180,"wires":[]},{"id":"9e4a8152ae8befea","type":"inject","z":"69058845e3bf8b4d","g":"1dd60d33df72dbb9","name":"","props":[{"p":"chrome_url","v":"http://www.glwsq.cn","vt":"str"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":150,"y":7260,"wires":[["0cf0ddbd98fd53a4"]]},{"id":"0cf0ddbd98fd53a4","type":"function","z":"69058845e3bf8b4d","g":"1dd60d33df72dbb9","name":"function 106","func":"msg.url = \"https://ac.scmor.com/\"\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":350,"y":7260,"wires":[["b0c804fa77583a00"]]},{"id":"b0c804fa77583a00","type":"function","z":"69058845e3bf8b4d","g":"1dd60d33df72dbb9","name":"获取网页纯文本内容","func":"const puppeteer = global.get('puppeteer');\nlet browser = global.get('browser'); // Check if the browser instance is already stored\n\nif (!browser) {\n    browser = await puppeteer.launch({\n        headless: true, // Enables headless mode\n        args: ['--no-sandbox', '--disable-setuid-sandbox'],\n    });\n    global.set('browser', browser); // Store the browser instance globally\n    node.warn(\"新开浏览器\");\n}\n\nlet page;\nmsg.text = \"获取失败\"\ntry {\n    node.warn(`浏览器打开了${msg.url}`);\n    page = await browser.newPage();\n    await page.setUserAgent(\" Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3477.0 Safari/537.36\");\n    await page.evaluateOnNewDocument(() => {\n        `Object.defineProperty(navigator, 'webdriver', {\n            get: () => false,\n        });`\n    });\n\n\n    await page.evaluateOnNewDocument(() => {\n        `Object.defineProperty(navigator, 'plugins', {\n            get: () => [1, 2, 3],\n        });`\n    });\n\n    await page.evaluateOnNewDocument(() => {\n        `Object.defineProperty(navigator, 'plugins', {\n            get: () => [\n                {\n                    0: {type: \"application/x-google-chrome-pdf\", suffixes: \"pdf\", description: \"Portable Document Format\", enabledPlugin: Plugin},\n                    description: \"Portable Document Format\",\n                    filename: \"internal-pdf-viewer\",\n                    length: 1,\n                    name: \"Chrome PDF Plugin\"\n                },\n                {\n                    0: {type: \"application/pdf\", suffixes: \"pdf\", description: \"\", enabledPlugin: Plugin},\n                    description: \"\",\n                    filename: \"mhjfbmdgcfjbbpaeojofohoefgiehjai\",\n                    length: 1,\n                    name: \"Chrome PDF Viewer\"\n                },\n                {\n                    0: {type: \"application/x-nacl\", suffixes: \"\", description: \"Native Client Executable\", enabledPlugin: Plugin},\n                    1: {type: \"application/x-pnacl\", suffixes: \"\", description: \"Portable Native Client Executable\", enabledPlugin: Plugin},\n                    description: \"\",\n                    filename: \"internal-nacl-plugin\",\n                    length: 2,\n                    name: \"Native Client\"\n                }\n            ],\n        });`\n    });\n\n    await page.evaluateOnNewDocument(() => {\n    `    window.navigator.chrome = {\n            runtime: {},\n            loadTimes: function() {},\n            csi: function() {},\n            app: {}\n        };`\n    });\n    // scholar.google.com.hk\n\n    // node.warn(`新开标签页${msg.url}`);\n    await page.goto(msg.url, { waitUntil: 'load' }); // load networkidle2 , { waitUntil: 'load' }\n    node.warn(`网页加载完成 ${msg.url}`);\n\n    // await page.waitForFunction(() =>\n    //     Array.from(document.querySelectorAll('a.ok')).some(e => e.textContent.trim() === '现在访问')\n    // );\n    // 等待并提取数据\n    await page.waitForSelector('a.ok');\n    msg.google_scholar_urls = await page.$$eval('a.ok', elements =>\n        elements\n            .filter(e => e.textContent.trim() === '现在访问')\n            .map(e => e.getAttribute('onclick').match(/visit\\('([^']+)'\\)/)[1])\n    );\n    node.warn(`检查到了可以访问的链接 ${msg.url}`);\n\n    // 获取 Gword 的值\n    // 获取页面的完整HTML内容\n    const content = await page.content();\n\n    // 使用正则表达式查找 Gword 的值\n    const gwordMatch = content.match(/Gword\\s*=\\s*\"([^\"]+)\"/);\n    msg.gwordValue = gwordMatch ? gwordMatch[1] : null;\n    node.warn(msg.gwordValue);\n//     msg.google_scholar_urls = await page.evaluate(`\n// Array.from(document.querySelectorAll('.ok')).filter(e => e.tagName.toLowerCase() === 'a' && e.textContent.trim() === '现在访问').map(e => e.getAttribute('onclick').match(/visit\\('([^']+)'\\)/)[1]);\n//     `)\n    // node.warn(msg.text);\n    const pages = await browser.pages();\n    node.status({ fill: \"yellow\", shape: \"ring\", text: `page size: ${pages.length}` });\n} catch (error) {\n    node.error(`Error in puppeteer operation: ${error.message}`, msg);\n} finally {\n    if (page) {\n        await page.close(); // Ensure the page is closed even if an error occurs\n    }\n}\n\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":560,"y":7260,"wires":[["d73e1fe255930923","66399dd0659abaa4"]]},{"id":"d73e1fe255930923","type":"debug","z":"69058845e3bf8b4d","g":"1dd60d33df72dbb9","name":"debug 218","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"google_scholar_urls","targetType":"msg","statusVal":"","statusType":"auto","x":890,"y":7240,"wires":[]},{"id":"85c2cba4f299c300","type":"http in","z":"69058845e3bf8b4d","g":"1dd60d33df72dbb9","name":"","url":"/google_scholar","method":"get","upload":false,"swaggerDoc":"","x":190,"y":7320,"wires":[["0cf0ddbd98fd53a4"]]},{"id":"a3884a2d48aca142","type":"http response","z":"69058845e3bf8b4d","g":"1dd60d33df72dbb9","name":"","statusCode":"","headers":{},"x":1130,"y":7240,"wires":[]},{"id":"a38aa35cbb229af1","type":"comment","z":"69058845e3bf8b4d","g":"1dd60d33df72dbb9","name":"https://nodered.glwsq.cn/google_scholar","info":"","x":580,"y":7340,"wires":[]}]

安装

nodejs

如果你会nodejs,那么直接一行命令安装

sudo npm install -g --unsafe-perm node-red

docker

通过下面的 docker compose 直接启动即可

version: '3'

services:
  nodered:
    image: registry.cn-beijing.aliyuncs.com/dockerhub_happen/node-red:4.0.0-debian
    container_name: mynodered
    ports:
      - "1880:1880"
    volumes:
      - ./data:/data
    environment:
      - NPM_CONFIG_REGISTRY=https://registry.npmmirror.com
    restart: always