在这篇文章中,我们将创建一个 "JavaScript Job Board",聚合JavaScript开发人员的远程工作。
以下是完成我们项目的步骤。
- 创建一个使用Puppeteer构建的Node.js搜刮器,从remoteok.io网站获取工作机会
- 将作业存储到数据库中
- 创建一个Node.js应用程序,在我们自己的网站上显示这些工作。
需要注意的是。我只是把这个网站作为一个例子。我并不是推荐你去搜刮它,因为如果你想使用它的数据,它有一个官方的API。这只是为了解释Puppeteer如何与一个大家都知道的网站一起工作,并向你展示如何在实践中使用它。
开始吧!
为JavaScript作业创建一个搜刮器
我们将从remoteok.io,一个伟大的远程工作网站上搜刮JavaScript工作。
该网站提供了许多不同类型的工作。JavaScript工作被列在 "JavaScript "标签下,在撰写本文时,它们都在这个页面上:https://remoteok.io/remote-javascript-jobs
我说 "在写的时候 "是因为这是一个重要的认识:网站可能随时会改变。我们不能保证任何东西。通过搜刮,网站的任何变化都可能使我们的应用程序停止工作。这不是一个API,它是两方之间的一种合同。
所以,根据我的经验,刮削应用程序需要更多的维护。但有时我们没有其他选择来完成一个特定的任务,所以它们仍然是我们可以利用的有效工具。
设置Puppeteer
我们先创建一个新的文件夹,在该文件夹内运行
npm init -y
然后使用Puppeteer安装
npm install puppeteer
现在创建一个app.js 文件。在顶部,要求我们刚刚安装的puppeteer 库。
const puppeteer = require("puppeteer")
然后我们可以使用launch() 方法来创建一个浏览器实例。
;(async () => {
const browser = await puppeteer.launch({ headless: false })
})()
我们通过{ headless: false } 配置对象,在Puppeteer执行操作时显示Chrome,这样我们就可以看到正在发生的事情,这在构建应用程序时很有帮助。
接下来我们可以使用browser 对象上的newPage() 方法来获取page 对象,我们调用page 对象上的goto() 方法来加载JavaScript作业页面。
const puppeteer = require('puppeteer')
;(async () => {
const browser = await puppeteer.launch()
const page = await browser.newPage()
await page.goto("https://remoteok.io/remote-javascript-jobs
")
})()
现在从终端运行node app.js ,一个Chromium实例将启动,加载我们告诉它要加载的页面。

从该页面获取工作机会
现在,我们需要找出一种方法,从页面上获取工作的详细信息。
为此,我们将使用Puppeteer给我们的page.evaluate() 函数。
在其回调函数中,我们基本上过渡到了浏览器,所以我们可以使用现在指向页面DOM的document 对象,尽管代码将在Node.js环境中运行。这是由Puppeteer执行的一个有点神奇的部分。
在这个回调函数中,我们不能向控制台打印任何东西,因为那会被打印到浏览器控制台,而不是Node.js终端。
我们可以做的是,我们可以从中返回一个对象,这样我们就可以访问它,作为page.evaluate() 的返回值。
const puppeteer = require('puppeteer')
;(async () => {
const browser = await puppeteer.launch({ headless: false })
const page = await browser.newPage()
await page.goto('https://remoteok.io/remote-javascript-jobs')
/* Run javascript inside the page */
const data = await page.evaluate(() => {
return ....something...
})
console.log(data)
await browser.close()
})()
在该函数中,我们首先创建一个空数组,将其填入我们想要返回的值。
tr 我们找到每个工作,它被包裹在一个job 类的HTML元素中,然后我们使用querySelector() 和getAttribute() 从每个工作中获取数据。
/* Run javascript inside the page */
const data = await page.evaluate(() => {
const list = []
const items = document.querySelectorAll("tr.job")
for (const item of items) {
list.push({
company: item.querySelector(".company h3").innerHTML,
position: item.querySelector(".company h2").innerHTML,
link: "https://remoteok.io" + item.getAttribute("data-href"),
})
}
return list
})
我通过使用浏览器的devtools查看页面源代码,发现哪些是要使用的确切选择器。

这里是完整的源代码。
const puppeteer = require("puppeteer")
;(async () => {
const browser = await puppeteer.launch({ headless: false })
const page = await browser.newPage()
await page.goto("https://remoteok.io/remote-javascript-jobs")
/* Run javascript inside the page */
const data = await page.evaluate(() => {
const list = []
const items = document.querySelectorAll("tr.job")
for (const item of items) {
list.push({
company: item.querySelector(".company h3").innerHTML,
position: item.querySelector(".company h2").innerHTML,
link: "https://remoteok.io" + item.getAttribute("data-href"),
})
}
return list
})
console.log(data)
await browser.close()
})()
如果你运行这个,你会得到一个对象数组,每个对象都包含作业的详细信息。

在数据库中存储作业
现在我们准备将这些数据存储到本地数据库中。
我们将在一段时间内运行Puppeteer脚本,首先删除所有存储的作业,然后用发现的新作业重新填充数据库。
我们将使用MongoDB。从终端,运行。
npm install mongodb
然后在app.js ,我们添加这个逻辑来初始化jobs 数据库,以及它里面的一个jobs 集合。
const puppeteer = require("puppeteer")
const mongo = require("mongodb").MongoClient
const url = "mongodb://localhost:27017"
let db, jobs
mongo.connect(
url,
{
useNewUrlParser: true,
useUnifiedTopology: true,
},
(err, client) => {
if (err) {
console.error(err)
return
}
db = client.db("jobs")
jobs = db.collection("jobs")
//....
}
)
现在我们把我们做刮削的代码放到这个函数中,在这里我们有那个//.... 的注释。这将使代码在我们连接到MongoDB后运行。
const puppeteer = require("puppeteer")
const mongo = require("mongodb").MongoClient
const url = "mongodb://localhost:27017"
let db, jobs
mongo.connect(
url,
{
useNewUrlParser: true,
useUnifiedTopology: true,
},
(err, client) => {
if (err) {
console.error(err)
return
}
db = client.db("jobs")
jobs = db.collection("jobs")
;(async () => {
const browser = await puppeteer.launch({ headless: false })
const page = await browser.newPage()
await page.goto("https://remoteok.io/remote-javascript-jobs")
/* Run javascript inside the page */
const data = await page.evaluate(() => {
const list = []
const items = document.querySelectorAll("tr.job")
for (const item of items) {
list.push({
company: item.querySelector(".company h3").innerHTML,
position: item.querySelector(".company h2").innerHTML,
link: "https://remoteok.io" + item.getAttribute("data-href"),
})
}
return list
})
console.log(data)
jobs.deleteMany({})
jobs.insertMany(data)
await browser.close()
})()
}
)
在这个函数的末尾,我添加了
jobs.deleteMany({})
jobs.insertMany(data)
以首先清除MongoDB表,然后插入我们的数组。
现在,如果你再次尝试运行node app.js ,并且用终端控制台或像TablePlus这样的应用程序检查MongoDB数据库内容,你会看到数据正在出现。

很好!我们现在可以设置一个cron job或任何其他自动化程序,每天或每6小时运行这个应用程序,以始终拥有新鲜的数据。
创建Node.js应用程序,使作业可视化
现在,我们需要的是一种将这些工作可视化的方法。我们需要一个应用程序。
我们将建立一个基于Express和Pug的服务器端模板的Node.js应用程序。
创建一个新的文件夹,并在其中运行npm init -y 。
然后安装Express、MongoDB和Pug。
npm install express mongodb pug
我们首先初始化Express。
const express = require("express")
const path = require("path")
const app = express()
app.set("view engine", "pug")
app.set("views", path.join(__dirname, "."))
app.get("/", (req, res) => {
//...
})
app.listen(3000, () => console.log("Server ready"))
然后我们初始化MongoDB,并将工作数据放入jobs 数组。
const express = require("express")
const path = require("path")
const app = express()
app.set("view engine", "pug")
app.set("views", path.join(__dirname, "."))
const mongo = require("mongodb").MongoClient
const url = "mongodb://localhost:27017"
let db, jobsCollection, jobs
mongo.connect(
url,
{
useNewUrlParser: true,
useUnifiedTopology: true,
},
(err, client) => {
if (err) {
console.error(err)
return
}
db = client.db("jobs")
jobsCollection = db.collection("jobs")
jobsCollection.find({}).toArray((err, data) => {
jobs = data
})
}
)
app.get("/", (req, res) => {
//...
})
app.listen(3000, () => console.log("Server ready"))
其中大部分代码与我们在Puppeteer脚本中用于插入数据的代码相同。不同的是,现在我们使用find() ,从数据库中获取数据。
jobsCollection.find({}).toArray((err, data) => {
jobs = data
})
最后,当用户点击/ 端点时,我们渲染一个Pug模板。
app.get("/", (req, res) => {
res.render("index", {
jobs,
})
})
这里是完整的app.js 文件。
const express = require("express")
const path = require("path")
const app = express()
app.set("view engine", "pug")
app.set("views", path.join(__dirname, "."))
const mongo = require("mongodb").MongoClient
const url = "mongodb://localhost:27017"
let db, jobsCollection, jobs
mongo.connect(
url,
{
useNewUrlParser: true,
useUnifiedTopology: true,
},
(err, client) => {
if (err) {
console.error(err)
return
}
db = client.db("jobs")
jobsCollection = db.collection("jobs")
jobsCollection.find({}).toArray((err, data) => {
jobs = data
})
}
)
app.get("/", (req, res) => {
res.render("index", {
jobs,
})
})
app.listen(3000, () => console.log("Server ready"))
index.pug 文件,与app.js 存放在同一文件夹中,将对作业数组进行迭代,以打印我们存储的细节。
html
body
each job in jobs
p
| #{job.company}
br
a(href=`${job.link}`) #{job.position}
这就是结果。
