在 NextJS 中通过网页抓取创建公共 API
封面照片由Kelly Sikkema拍摄
通常,当需要将外部数据引入应用程序时,人们会使用 API。但是,如果我们想要的数据不在 API 中,或者 API 不易访问,该怎么办?
这个问题就是我们将在本教程中回答的内容。
项目概述
本教程的最终结果是在 Vercel 上部署一个开放的 API 和文档页面。
至于项目本身,我们将让用户传入一个 Twitter 账号,然后返回该用户的关注者数量。请注意,我们不会使用 Twitter API,而是通过网页抓取数据。
如果您想查看用于从 Twitter 抓取关注者的代码,请随意查看我整理的要点并将其应用到您的项目中 🙂
但如果你想了解我们共同创造的过程、问题和推理,请继续阅读😄
了解网页抓取
网络抓取(或数据挖掘)可能听起来很可怕或危险,但它实际上只是使用基本工具来访问我们已经可以访问的数据。
首先,我们将从我们想要收集的数据的图像开始这个项目:
以我的Twitter 个人资料为例,我们希望获取我的关注者数量。
但怎么做呢?
如果您以前使用过Twitter API,您就会知道使用其 API 需要经过批准流程,并且这些年来限制越来越严格。
通常,在这种情况下,我喜欢检查开发人员工具中的网络选项卡,尝试找到一个可以尝试使用的作为 JSON 端点的 XHR 请求。
在这种情况下,运气不佳,传入的请求被污染了。所以现在是时候使用元素选择器来查看是否有办法用基本的 JavaScript 来获取我们想要的内容了:
“我想抓取锚标签中带有“ of ”的文本3,605 ”
href
/mtliendo/followers
尽可能具体很重要,但现在不再如此。也就是说,我不想选择那些看起来晦涩难懂的类名,因为它们很可能来自 CSS 模块或计算类名,每次访问页面时都会发生变化。
🗒️ 如果你好奇我们怎么走到这一步,而且还没写任何代码,欢迎来到网络抓取!决定如何抓取你想要的数据才是真正困难的部分。
搭建我们的应用程序
现在我们了解了为什么要采用网络抓取,并找到了获取所需数据的方法,让我们开始编码吧。
首先,我们将通过运行以下命令来初始化 NextJS 项目:
npx create-next-app twitter-followers && cd $_
💡
&& $_
NextJS 脚手架完成后,末尾将变为我们刚刚创建的目录。
接下来,让我们添加用于执行网页抓取的包。
npm i cheerio
cheerio是一个很棒的库,它使用类似于jQuery的方法在后端遍历类似 HTML 的字符串。
在我们的项目框架搭建完成并且主要依赖项安装完成后,我们就可以开始编写我们的函数了。
在编辑器中打开我们的项目之前的最后一步是为我们的API 路由创建一个文件。
touch pages/api/followers.js
编写爬虫
在followers.js
我们刚刚创建的文件中,添加以下代码:
const cheerio = require('cheerio') // 1
export default async (req, res) => { // 2
if (req.method === 'POST') { // 3
const username = req.body.TWuser
try { // 4
const response = await fetch(`https://mobile.twitter.com/${username}`)
const htmlString = await response.text()
const $ = cheerio.load(htmlString)
const searchContext = `a[href='/${username}/followers']`
const followerCountString = $(searchContext)
.text()
.match(/[0-9]/gi)
.join('')
res.statusCode = 200
return res.json({
user: username,
followerCount: Number(followerCountString),
})
} catch (e) { // 5
res.statusCode = 404
return res.json({
user: username,
error: `${username} not found. Tip: Double check the spelling.`,
followerCount: -1,
})
}
}
}
分解代码
-
首先我们
cheerio
使用 commonJS 导入模块(require而不是import) -
我们导出一个函数。NextJS 将为我们创建一个无服务器端点。这样,它就使我们能够查看通过
req
(请求)传入的数据,并能够通过(响应)发送数据res
。因为我们在这个函数中执行了一些异步操作,所以我将其标记为async
。 -
如上所述,它
req
为我们提供了有关传入内容的信息。这里我们说,“如果这个传入请求是一个POST
请求,请查看body
并获取TWuser
数据。您很快就会看到我们如何发送它TWuser
。 -
这是我们应用程序的核心。我们逐行从 Twitter 获取数据。将响应解析为 ,
.text()
而不是.json()
。
这会将页面的 HTML 以字符串形式返回给我们——这正是我们cheerio
期望的。从那里,你会注意到一段a[href='/${username}/followers']
代码。它抓取了包含关注者数量的锚标签。问题是它是一个长字符串,如下所示:
" 3,606\n
Followers "
为了解决这个问题,我们使用了这个match
方法。它使用了一些正则表达式,从字符串中抓取数字,然后将这些数字重新组合在一起,最后以 JSON 对象的形式返回给用户。
5.. 最后进行错误处理,如果无法找到用户,则发回一些数据。
🚨 敏锐的开发者可能已经注意到,我将 URL 从 改为
twitter.com
。mobile.twitter.com
这是因为网站的桌面版使用客户端渲染,而移动版(旧版桌面版)在服务器上渲染数据。
测试端点
现在最糟糕的部分已经过去了,让我们在浏览器中测试一下这个端点。为此,请pages/index.js
用以下代码替换:
import Head from 'next/head'
import styles from '../styles/Home.module.css'
import React from 'react'
export default function Home() {
const [inputValue, setInputValue] = React.useState('')
const [userFollowers, setUserFollowers] = React.useState({})
const handleSubmit = (e) => {
e.preventDefault()
fetch('/api/followers', {
method: 'post',
headers: {
'content-type': 'application/json',
},
body: JSON.stringify({ TWuser: inputValue }),
})
.then((res) => res.json())
.then((userData) => {
setUserFollowers(userData)
})
}
return (
<div className={styles.container}>
<Head>
<title>Fetch Twitter Follower</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<main className={styles.main}>
<h1>Fetch A Twitter Follower</h1>
<form onSubmit={handleSubmit}>
<label>
Enter a Twitter username
<input
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
/>
</label>
<button>Submit</button>
</form>
{userFollowers.followerCount >= 0 ? (
<p>Followers: {userFollowers.followerCount}</p>
) : (
<p>{userFollowers.error}</p>
)}
</main>
</div>
)
}
这里没有发生太多事情,但我要调用的主要部分是函数fetch
中的调用handleSubmit
。
NextJS 的优点在于,我们只需使用语法api
引用目录中的文件即可api/<filename>
。之后,我们将方法设置为post
,添加json
标头,并将数据作为字符串化对象传递。
特别要注意TWuser
body 中的对象。它必须与我们req.body
在 API 中获取的对象相匹配。
如果您还没有这样做,请在终端中运行以下命令来测试您的应用程序
npm run dev
🚀 部署!
确保您可以获得关注者数量和正确的错误消息后,就可以部署了!
Vercel是一款非常棒的服务,可用于构建和部署使用 NextJS 构建的应用程序。登录后,他们会要求您提供项目的 git URL,因此您需要确保先将项目上传到 Github。
完成后,您的项目将上线,示例页面也应该可以正常工作。但是,如果您从其他浏览器获取了上线的 API 端点,则无法正常工作。这是因为 CORS 默认被阻止。
作为最后一步,让我们修复它并重新部署它。
首先,让我们添加cors
包
npm i cors
然后,我们将更新api/followers.js
文件以在文件顶部包含以下内容:
const cheerio = require('cheerio')
const Cors = require('cors')
// Initializing the cors middleware
const cors = Cors({
methods: ['POST'],
})
// Helper method to wait for a middleware to execute before continuing
// And to throw an error when an error happens in a middleware
function runMiddleware(req, res, fn) {
return new Promise((resolve, reject) => {
fn(req, res, (result) => {
if (result instanceof Error) {
return reject(result)
}
return resolve(result)
})
})
}
export default async (req, res) => {
await runMiddleware(req, res, cors)
if (req.method === 'POST') {
const username = req.body.TWuser
// ...rest of code
现在,一旦您将代码推送到 GitHub,Vercel 就会自动检测您的更改并开始新的构建。
通过编辑下面的沙箱来测试您的功能,并注意现在您可以在应用程序之外使用您的 API 端点!
结论
🎉 恭喜你走到这一步🎉
我希望您学到了很多东西,并且现在拥有了坚实的基础,不仅知道如何进行网络抓取,还知道何时进行网络抓取以及如何使用 NextJS 进行网络抓取!
唯一缺少的是一个用于存储信息的数据库🤔
所以如果您喜欢本教程,那么您一定会喜欢下一个教程,我们将采用稍微不同的方法,将AWS Amplify添加到组合中!