在 NextJS 中通过网页抓取创建公共 API

2025-06-05

在 NextJS 中通过网页抓取创建公共 API

封面照片由Kelly Sikkema拍摄

通常,当需要将外部数据引入应用程序时,人们会使用 API。但是,如果我们想要的数据不在 API 中,或者 API 不易访问,该怎么办?

这个问题就是我们将在本教程中回答的内容。

项目概述

本教程的最终结果是在 Vercel 上部署一个开放的 API 和文档页面。

至于项目本身,我们将让用户传入一个 Twitter 账号,然后返回该用户的关注者数量。请注意,我们不会使用 Twitter API,而是通过网页抓取数据。

如果您想查看用于从 Twitter 抓取关注者的代码,请随意查看我整理的要点并将其应用到您的项目中 🙂

但如果你想了解我们共同创造的过程、问题和推理,请继续阅读😄

了解网页抓取

网络抓取(或数据挖掘)可能听起来很可怕或危险,但它实际上只是使用基本工具来访问我们已经可以访问的数据。

首先,我们将从我们想要收集的数据的图像开始这个项目:

mtliendo 推特个人资料

以我的Twitter 个人资料为例,我们希望获取我的关注者数量。

但怎么做呢?

如果您以前使用过Twitter API,您就会知道使用其 API 需要经过批准流程,并且这些年来限制越来越严格。

通常,在这种情况下,我喜欢检查开发人员工具中的网络选项卡,尝试找到一个可以尝试使用的作为 JSON 端点的 XHR 请求。

将请求复制为节点获取

在这种情况下,运气不佳,传入的请求被污染了。所以现在是时候使用元素选择器来查看是否有办法用基本的 JavaScript 来获取我们想要的内容了:

使用开发工具中的元素选择器检查 DOM

“我想抓取锚标签中带有“ of ”的文本3,605 ”href/mtliendo/followers

尽可能具体很重要,但现在不再如此。也就是说,我不想选择那些看起来晦涩难懂的类名,因为它们很可能来自 CSS 模块或计算类名,每次访问页面时都会发生变化。

🗒️ 如果你好奇我们怎么走到这一步,而且还没写任何代码,欢迎来到网络抓取!决定如何抓取你想要的数据才是真正困难的部分。

搭建我们的应用程序

现在我们了解了为什么要采用网络抓取,并找到了获取所需数据的方法,让我们开始编码吧。

首先,我们将通过运行以下命令来初始化 NextJS 项目:



npx create-next-app twitter-followers && cd $_


Enter fullscreen mode Exit fullscreen mode

💡 && $_NextJS 脚手架完成后,末尾将变为我们刚刚创建的目录。

接下来,让我们添加用于执行网页抓取的包。



npm i cheerio


Enter fullscreen mode Exit fullscreen mode

cheerio是一个很棒的库,它使用类似于jQuery的方法在后端遍历类似 HTML 的字符串。

在我们的项目框架搭建完成并且主要依赖项安装完成后,我们就可以开始编写我们的函数了。

在编辑器中打开我们的项目之前的最后一步是为我们的API 路由创建一个文件。



touch pages/api/followers.js


Enter fullscreen mode Exit fullscreen mode

编写爬虫

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,
      })
    }
  }
}



Enter fullscreen mode Exit fullscreen mode

分解代码

  1. 首先我们cheerio使用 commonJS 导入模块(require而不是import

  2. 我们导出一个函数。NextJS 将为我们创建一个无服务器端点。这样,它就使我们能够查看通过req(请求)传入的数据,并能够通过(响应)发送数据res。因为我们在这个函数中执行了一些异步操作,所以我将其标记为async

  3. 如上所述,它req为我们提供了有关传入内容的信息。这里我们说,“如果这个传入请求是一个POST请求,请查看body并获取TWuser数据。您很快就会看到我们如何发送它TWuser

  4. 这是我们应用程序的核心。我们逐行从 Twitter 获取数据。将响应解析为 ,.text()而不是.json()

这会将页面的 HTML 以字符串形式返回给我们——这正是我们cheerio期望的。从那里,你会注意到一段a[href='/${username}/followers']代码。它抓取了包含关注者数量的锚标签。问题是它是一个长字符串,如下所示:



 "         3,606\n
           Followers "


Enter fullscreen mode Exit fullscreen mode

为了解决这个问题,我们使用了这个match方法。它使用了一些正则表达式,从字符串中抓取数字,然后将这些数字重新组合在一起,最后以 JSON 对象的形式返回给用户。

5.. 最后进行错误处理,如果无法找到用户,则发回一些数据。

🚨 敏锐的开发者可能已经注意到,我将 URL 从 改为twitter.commobile.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>
  )
}


Enter fullscreen mode Exit fullscreen mode

这里没有发生太多事情,但我要调用的主要部分是函数fetch中的调用handleSubmit

NextJS 的优点在于,我们只需使用语法api引用目录中的文件即可api/<filename>。之后,我们将方法设置为post,添加json标头,并将数据作为字符串化对象传递。

特别要注意TWuserbody 中的对象。它必须与我们req.body在 API 中获取的对象相匹配。

如果您还没有这样做,请在终端中运行以下命令来测试您的应用程序



npm run dev


Enter fullscreen mode Exit fullscreen mode

🚀 部署!

确保您可以获得关注者数量和正确的错误消息后,就可以部署了!

Vercel是一款非常棒的服务,可用于构建和部署使用 NextJS 构建的应用程序。登录后,他们会要求您提供项目的 git URL,因此您需要确保先将项目上传到 Github。

部署到 Vercel

完成后,您的项目将上线,示例页面也应该可以正常工作。但是,如果您从其他浏览器获取了上线的 API 端点,则无法正常工作。这是因为 CORS 默认被阻止。

CORS 错误

作为最后一步,让我们修复它并重新部署它。

首先,让我们添加cors



npm i cors


Enter fullscreen mode Exit fullscreen mode

然后,我们将更新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


Enter fullscreen mode Exit fullscreen mode

现在,一旦您将代码推送到 GitHub,Vercel 就会自动检测您的更改并开始新的构建。

通过编辑下面的沙箱来测试您的功能,并注意现在您可以在应用程序之外使用您的 API 端点!

结论

🎉 恭喜你走到这一步🎉

我希望您学到了很多东西,并且现在拥有了坚实的基础,不仅知道如何进行网络抓取,还知道何时进行网络抓取以及如何使用 NextJS 进行网络抓取!

唯一缺少的是一个用于存储信息的数据库🤔
所以如果您喜欢本教程,那么您一定会喜欢下一个教程,我们将采用稍微不同的方法,将AWS Amplify添加到组合中!

文章来源:https://dev.to/focusotter/create-a-public-api-by-web-scraping-in-nextjs-2f5n
PREV
使用 AWS Amplify 的无服务器联系表单
NEXT
为工作和个人事务配置 Git