草庐IT

UED Landing 页 - 定时抓取掘金文章

袋鼠云数栈前端 2023-03-28 原文

我们是袋鼠云数栈 UED 团队,致力于打造优秀的一站式数据中台产品。我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值。

本文作者:琉易 https://liuxianyu.cn

本次分享基于『袋鼠云数栈UED团队』新发布的 UED Landing 页 实践得来,UED Landing 页集合了团队目前所有的基础建设以及精选文章,是团队展现风采的一个地方。
项目基于 next.js、ts、pnpm、koa2、MongoDB 等技术方式实现,代码仓库:https://github.com/DTStack/UED,欢迎 star。

需求介绍

Landing 页有一个专栏页面,需要展示团队以往发在掘金社区的文章、对应的标签以及其他社区入口。

设计思路

基于上述的需求分析后,进行以下设计:

1、通过 node-schedule设置一个定时任务,每天去请求掘金的接口查询最新的文章数据,包括每篇文章的标题、发布人、阅读量、发布日期、标签等;

2、将上述方法拿到的数据处理后存入 MongoDB 数据库,只保留需要的字段;

3、提供一个 RESTful 风格的接口,分页返回文章列表和标签列表,供专栏页面查询使用;

4、页面请求接口,查询标签、文章数据 ,渲染页面。

实现步骤

以下实现步骤比较详细,类似的需求也可以按以下步骤去实现。

Docker 安装 MongoDB

1、下载镜像

docker pull mongo

2、创建挂载文件夹

mkdir -p /opt/dtstack/docker/mongo
cd /opt/dtstack/docker/mongo

3、启动容器

docker run -v /opt/dtstack/docker/mongo:/data/db --name mongodb -p 27019:27017 -e MONGO_INITDB_ROOT_USERNAME=root -e MONGO_INITDB_ROOT_PASSWORD='Admin123!@#' -d mongo --auth

**命令解释 **

  • -v 挂载本地文件夹,存储数据
  • -name 给容器指定名称
  • -p 表示端口映射,-p 宿主机port:容器port,这里不使用相同端口是为了防止攻击
  • -e 携带密码等参数
  • -d 后台运行容器
  • --auth MongoDB 进行权限校验

4、进入容器

docker exec -it mongodb mongo admin

注意:
rpc error: code = 2 desc = oci runtime error: exec failed: container_linux.go:235: starting container process caused “exec: “mongo”: executable file not found in $PATH”.
如果出现上述报错,是下载的 mongodb 镜像版本比较高,mongodb 5.0 以上的版本需要使用 mongosh命令来代替原来的 mongo 命令。
docker exec -it mongodb mongosh admin

5、验证用户名密码登录
返回 1 代表登录成功。

db.auth('root', 'Admin123!@#')

6、使用数据库

use landingDB

7、创建数据库的管理员

db.createUser({ user: "landing-user", pwd: "landing-admin.1234", roles: [{ role: "readWrite", db: "landingDB" }] })

MongoDB 不允许同一窗口有多个用户登录,退出再次进入终端:

db.auth('landing-user', 'landing-admin.1234')

8、创建表

db.createCollection('article')
db.createCollection('tag')

9、测试插入数据

db.article.insert({ id: 1, title: '测试文章标题' })

10、通过 MongoDB Compass 连接数据库

mongodb://landing-user:landing-admin.1234@127.0.0.1:27019/landingDB


编写 node 服务

1、借助 koa2 启动 node 服务

服务入口处新建定时任务,每天去掘金获取文章数据

// 引入模块
const Koa = require('koa')
const schedule = require('node-schedule')

// 实例化
const app = new Koa()

const main = async () => {
    await initDB()

    // 保存文章列表
    const articleList = await getJueJinArticleList()
    await insertArticles(articleList)

    // 保存标签列表
    const tagList = getTagList(articleList)
    await insertTags(tagList)
}

app.listen(envJson.appPort, () => {
    console.log(`app runs on port ${ envJson.appPort }`)
    schedule.scheduleJob(cron, main)
})

2、将查询的数据存入数据库,并处理历史数据

const { MongoClient } = require('mongodb')

const url = `mongodb://${username}:${password}@${host}:${port}/${dbName}`
const client = new MongoClient(url)

// 初始化数据库链接
const initDB = async () => {
    await client.connect()
    console.log('Connected successfully to mongodb')
}

// 新增查询到的文章列表
const insertArticles = async (articleList) => {
    const db = client.db(dbName)
    const collection = db.collection('article')

    const updateResult = await collection.updateMany({ isDelete: 0 }, { $set: { isDelete: 1, updateTime: getDateStr() } })
    console.log('updateArticles documents =>', updateResult)

    const insertResult = await collection.insertMany(articleList)
    console.log('insertArticles documents =>', insertResult)
}

3、提供接口,从数据库读取数据
接口文档

const Router = require('koa-router')
const router = new Router()

router.get('/api/getTagList', async (ctx) => {
    try {
        const db = client.db(dbName)
        const collection = db.collection('tag')
        
        const data = await collection.find({ isDelete: 0 }).toArray()
        ctx.body = {
            code: 200,
            data,
            message: '成功',
        }
    } catch (error) {
        ctx.body = {
            code: 1,
            error
        }
    }
})

编写页面

1、页面请求接口,拿到文章数据进行渲染,在标签、分页等参数变化时重新请求接口

useEffect(() => {
    const params = {
        page,
        pageSize,
        tag_id,
        sort_type,
    }
    fetch(`/api/getArticleList?${new URLSearchParams(params).toString()}`)
        .then(res => res.json())
        .then(res => {
            const { articleList, total } = res.data
            setArticleList(articleList || [])
            setTotal(total || [])
        })
}, [tag_id, sort_type, page])

部署方式

一台 CentOS 服务器,安装 node 14+,pnpm,pm2,Docker(可选),MongoDB,nginx。

mkdir -p /opt/dtstack
git clone https://github.com/DTStack/UED.git
cd UED
pnpm i
pnpm deploy

因为后端服务的接口一般不对外暴露,此处通过 nginx 进行转发:

# ued landing 的 nginx 配置

# http
server {
  listen          80;
  server_name     ued.dtstack.cn;

  location / {
    proxy_pass http://localhost:3004/;
  }

  location /api {
    proxy_pass http://localhost:3002/api;
  }
}

实现效果

http://ued.dtstack.cn/article

有关UED Landing 页 - 定时抓取掘金文章的更多相关文章

  1. ruby - 我需要从 facebook 游戏中抓取数据——使用 ruby - 2

    修改(澄清问题)我已经花了几天时间试图弄清楚如何从Facebook游戏中抓取特定信息;但是,我遇到了一堵又一堵砖墙。据我所知,主要问题如下。我可以使用Chrome的检查元素工具手动查找我需要的html-它似乎位于iframe中。但是,当我尝试抓取该iframe时,它​​是空的(属性除外):如果我使用浏览器的“查看页面源代码”工具,这与我看到的输出相同。我不明白为什么我看不到iframe中的数据。答案不是它是由AJAX之后添加的。(我知道这既是因为“查看页面源代码”可以读取Ajax添加的数据,也是因为我有b/c我一直等到我可以看到数据页面之后才抓取它,但它仍然不存在)。发生这种情况是因为

  2. RUBY - 网页抓取 - (OpenURI::HTTPError) - 2

    我正在尝试用ruby​​编写一个简单的网络抓取代码。它一直工作到第29个url,然后我收到此错误消息:C:/Ruby193/lib/ruby/1.9.1/open-uri.rb:346:in`open_http':500InternalServerError(OpenURI::HTTPError)fromC:/Ruby193/lib/ruby/1.9.1/open-uri.rb:775:in`buffer_open'fromC:/Ruby193/lib/ruby/1.9.1/open-uri.rb:203:in`blockinopen_loop'fromC:/Ruby193/lib/r

  3. ruby-on-rails - 如何使用 JSON 的 Nokogiri 中的 XPath 从 eBay 和亚马逊抓取图像 - 2

    我正在尝试使用Nokogiri和XPath从网站上抓取图像,但到目前为止收效甚微。对于其HTML具有img和src的典型网站,我可以使用:tmp2=Nokogiri::HTML(open(site_url))tmp2.xpath("//img/@src").eachdo|src|...dowhateverend但是,某些网站(如Amazon和eBay)仅使用JavaScript触发特定图像。如果我查看代码,我可以看到数组中的数据。例如,来自Amazon:P.when('jQuery','cf').execute(function($,cf){P.load.js('http://z-ec

  4. ruby - Heroku 和网络抓取 - 2

    我有一个nokigiri网络抓取工具,它发布到我试图发布到heroku的数据库。我有一个sinatra应用程序前端,我想从数据库中获取它。我是Heroku和Web开发的新手,不知道处理此类问题的最佳方法。我是否必须将上传到数据库的网络爬虫脚本放在sinatra路由下(如mywebsite.com/scraper),并让它变得如此模糊以至于没有人访问它?最后,我想让sinatra部分成为一个从数据库中提取的restapi。感谢大家的参与 最佳答案 您可以采用两种方法。第一个是通过控制台使用herokurunYOURCMD运行scrap

  5. ruby-on-rails - rails delete_if 使用哈希忽略当前文章(中间人) - 2

    我为你们准备了一个简单的。我想要一个特色内容部分,其中排除了当前文章所以这可以通过delete_if使用MiddlemanBlog:但是我使用的是中间人代理,所以我无法访问current_article方法...我有一个YAML结构,其中包含以下模拟数据(以及其他数据),文件夹设置如下:data>site>caseStudy>RANDOM-ID423536.yaml(由CMS生成)在每个yaml文件中,您会发现如下内容::id:2k1YccJrQsKE2siSO6o6ac:title:Heyplace我的config.rb看起来像这样data.site.caseStudy.eachdo

  6. ruby-on-rails - 在 Model 类方法中指定当前抓取的记录 - 2

    我有一个类方法,我想在其中修改当前由ActiveRecord::Relation对象抓取的记录。但是我不知道如何在类方法中引用当前范围。self不会这样做。例子:classUser我会这样使用它:User.some_scope.modify_those_records所以User.some_scope会返回给我一个ActiveRecord::Relation,其中包含一堆User记录。然后我想在该类方法中修改这些记录,然后返回它们。问题是:我不知道如何在类方法中明确引用“那组记录”。 最佳答案 您可以使用current_scope:

  7. springboot定时任务 - 2

    如果您希望在Spring中启用定时任务功能,则需要在主类上添加 @EnableScheduling 注解。这样Spring才会扫描 @Scheduled 注解并执行定时任务。在大多数情况下,只需要在主类上添加 @EnableScheduling 注解即可,不需要在Service层或其他类中再次添加。以下是一个示例,演示如何在SpringBoot中启用定时任务功能:@SpringBootApplication@EnableSchedulingpublicclassApplication{publicstaticvoidmain(String[]args){SpringApplication.ru

  8. ruby - 在 Middleman 中移动博客文章位置 - 2

    我正在为我的网站使用MiddlemanBloggem,但默认情况下,博客文章似乎需要位于/source中,这在查看vim中的树时并不是特别好并尝试在其中找到其他文件之一(例如模板)。通过查看文档,我看不出是否有任何方法可以移动博客文章,以便将它们存储在其他地方,例如blog_articles文件夹或类似文件夹。这可能吗? 最佳答案 将以下内容放入您的config.rb文件中。activate:blogdo|blog|blog.permalink=":year-:month-:day-:title.html"blog.sources=

  9. ruby-on-rails - 文章#index 中的 Ruby on Rails 教程 NoMethodError - 2

    所以我正在关注http://guides.rubyonrails.org/getting_started.html上的官方ROR教程我被困在第5.8节,它教我如何列出所有文章下面是我的controller和index.html.erbControllerclassArticlesControllerindex.html.erbListingarticlesTitleText我收到带有错误消息的NoMethodErrorinArticles#indexundefinedmethod`each'fornil:NilClass"怎么了?我从网站上复制并粘贴了代码以查看我做错了什么,但仍然无法

  10. C#初级_定时器 - 2

    文章目录一、引言二、Timers1.System.Threading.Timer1.1.简单使用1.2.注意点2.System.Timers.Timer2.1.概述🔺2.2.注意点三、总结一、引言在开发中,会遇到并行处理的需求。有时只需要使用task(底层是创建个线程)来处理一下就好了。而有时则在并行处理的基础上还有时间的要求,较常见的就是每隔一定时间处理一次。当然,这用task肯定可以实现,但是时间这块得自己控制,无疑增加了工作量和不确定性。.NET提供了叫做定时器(timer,也叫计时器)的类,它在并行处理的基础上,带了时间参数的设置,可以满足这一需求。其实本文标题与其叫C#定时器,不如叫

随机推荐