這篇文章將使用Puppeteer創建一個“JavaScript工作版”,用於彙總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 }
配置對象傳遞給launch()
方法來顯示Chrome,讓Puppeteer執行操作時我們可以看到發生的情況,這在構建應用程序時很有幫助。
接下來,我們可以使用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()
函數。
在回調函數內部,我們基本上要轉到瀏覽器當中,這樣我們就可以使用document
對象來指向頁面的DOM,盡管代碼將在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')
/* 在頁面內部運行JavaScript */
const data = await page.evaluate(() => {
return ....
})
console.log(data)
await browser.close()
})()
在這個函數內部,我們首先創建一個空數組,然後將要返回的值填充到這個數組中。
我們找到每個工作,它們被包裝在帶有job
類的tr
HTML元素中,然後我們使用querySelector()
和getAttribute()
從每個工作中獲取數據:
/* 在頁面內部運行JavaScript */
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
})
通過觀察瀏覽器開發工具,我找到了應該使用的確切選擇器:
以下是完整的源代碼:
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")
/* 在頁面內部運行JavaScript */
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")
/* 在頁面內部運行JavaScript */
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"))
大部分這段代碼和我們在網頁爬蟲中使用的代碼是相同的。不同之處在於,現在我們使用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}
以下是結果: