如何盘点出掘金的年度高赞文章?

sxkk20082年前知识分享246

前言

各位掘友,新年好,今天是 2022 年的第一天,掘金的人气作者投票活动如火如荼,榜单已经落幕,当然跟我半毛钱关系都没有,我的新年 Flag ,就是掘金等级到达 V4,而对于绝大多数读者来说,新年 Flag 中是否有“学习”这一项呢,对于我来说,我也是,于是就有萌生一个想法,我想统计出掘金的年度高赞文章。

  1. 是可以收藏这些高赞文章,然后慢慢学习;
  2. 是想通过这些文章学习下,哪些文章是适合读者的,这些文章的优点在哪?我们该如何写文章?

统计年度活跃作者

掘金年度活跃作者

正好我们可以通过“年底投票页面”统计出今年活跃的作者,这个页面是滚动翻页,通过 has_more 来判断是否有下一页,那么我们就可以通过 nodejs 获取到所有作者的 ID。

const axios = require('axios')
const _ = require('lodash')
const fs = require('fs')

const url = 'https://api.juejin.cn/list_api/v1/annual/list'

const headers = {
  'content-type': 'application/json; ',
  'user-agent':
    'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36',
  cookies: 'xxx',
}

let userId = []

const fetchUserId = (cursor = 0) => {
  console.log('请求第' + cursor + '页')
  axios
    .post(
      url,
      { annual_id: '2021', list_type: 0, cursor: cursor + '', keyword: '' },
      {
        headers,
      }
    )
    .then((res) => {
      const data = res.data
      userId = userId.concat(_.map(data.data, 'user_id'))
      if (data.has_more && userId.length < 1000) {
        fetchUserId(cursor + 10)
      } else {
        fs.writeFileSync('./0-1000.json', JSON.stringify(userId))
      }
    })
}
fetchUserId()

cookies 可以在浏览器中复制,这样就可以统计排名前 1000 的作者, 分次统计是为了防止掘金后台接口限制。通过 3 次运行,结果统计出这次报名的有 2035 名作者进行报名,当然这个数据不一定准确,接下来我们可以根据所有的用户 ID 获取每位作者的文章了。

获取每位作者的文章列表

我们可以根据投票详情页获取每位作者的文章列表。这里不得不吐槽下掘金的这个接口,前端只展示 3 篇文章,后端却给了全部数据。。。 😅

年度作者文章页面

一起来看下每条数据详情:

年度作者接口返回

这里的文章默认是根据热度排列的,但是我们不知道是根据点赞排列,还是收藏排列的,我们不清楚。

还好,我们可以根据读者页面获取每位掘金作者的文章,如下图:

掘金个人主页接口返回

再次吐槽下,user_info 数据重复了 N 次, 这里的接口有点赞数,评论数和收藏数。(弱弱问 digg_count 是什么意思?哪个单词的前缀?)

掘金文章接口返回详情

建表统计

接下来我们要统计数据了,这么大的数据量,我们不可能用 json 存储,我这边选用了 psql, ORM 选用了 prisma,不了解的同学可以看我之前的翻译文章《适用于 Node.js 和 TypeScript 的完整 ORM —— Prisma》

建立 schema

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}

model Article {
  article_id    String           @id
  title         String
  brief_content String
  content       String?
  cover_image   String
  user_id       String
  ctime         String
  digg_count    Int
  view_count    Int
  comment_count Int
  collect_count Int
  author_id     String
  author        Author           @relation(fields: [author_id], references: [id])
  category_id   String
  category      Category         @relation(fields: [category_id], references: [id])
  tags          TagsOnArticles[]
}

model Author {
  id           String    @id
  name         String
  avatar_large String
  articles     Article[]
}

model Category {
  id       String    @id
  name     String
  articles Article[]
}

model Tag {
  id       String           @id
  name     String
  articles TagsOnArticles[]
}

model TagsOnArticles {
  article    Article @relation(fields: [article_id], references: [article_id])
  article_id String
  tag        Tag     @relation(fields: [tag_id], references: [id])
  tag_id     String

  @@id([article_id, tag_id])
}

表关系

  • 文章跟用户 —— 多对一
  • 文章跟跟分类 —— 多对一
  • 文章跟标签 —— 多对多

获取用户的文章列表代码

/**
 * 获取用户的文章列表
 * @param userId
 * @returns
 */

const fetchList = async (userId: string) => {
  console.log('开始采集' + userId)
  return new Promise((reslove) => {
    setTimeout(async () => {
      await axios
        .post(
          'https://api.juejin.cn/content_api/v1/article/query_list?aid=2608&uuid=6899676175061648910',
          {
            user_id: userId,
            sort_type: 1,
            cursor: '0',
          },
          { headers }
        )
        .then((res: any) => {
          const data = res.data.data
          if (data && data.length) {
            // 插入数据库
            insert(data)
              .catch((e) => {
                console.error(e)
                process.exit(1)
              })
              .finally(() => {
                reslove('')
              })
          } else {
            reslove('')
          }
        })
    }, 2000)
  })
}

为了防止提交过于频繁,我这边设置 2 秒延迟。

插入数据库代码

/**
 * 插入数据库
 * @param data
 */
async function insert(data: any) {
  for (const item of data) {
    const article_info = _.pick(item.article_info, [
      'article_id',
      'title',
      'brief_content',
      'cover_image',
      'user_id',
      'ctime',
      'digg_count',
      'view_count',
      'comment_count',
      'collect_count',
    ])

    const author_user_info = await prisma.author.findUnique({
      where: {
        id: item.author_user_info.user_id,
      },
    })
    if (!author_user_info) {
      await prisma.author.create({
        data: {
          id: item.author_user_info.user_id,
          name: item.author_user_info.user_name,
          avatar_large: item.author_user_info.avatar_large,
        },
      })
    }

    const category = await prisma.category.findUnique({
      where: {
        id: item.category.category_id,
      },
    })

    if (!category) {
      await prisma.category.create({
        data: {
          id: item.category.category_id,
          name: item.category.category_name,
        },
      })
    }
    const article = await prisma.article.findUnique({
      where: {
        article_id: article_info.article_id,
      },
    })
    const creates_tags = _.map(item.tags, (tag: any) => {
      return {
        tag: {
          connectOrCreate: {
            create: {
              id: tag.tag_id,
              name: tag.tag_name,
            },
            where: {
              id: tag.tag_id,
            },
          },
        },
      }
    })
    if (!article) {
      console.log('create---' + article_info.title)

      await prisma.article.create({
        data: {
          ...article_info,
          author_id: item.article_info.user_id,
          category_id: item.category.category_id,
          tags: {
            create: creates_tags,
          },
        },
      })
    }
  }
}

执行 fetchList 就可以获取单个用户的文章了,接下来就是遍历所有 userId, 将所有文章列表保存到数据库中。

这里我们不能通过 Promise.all去执行,因为 Promise.all会将所有的 Promise 同步执行,这样后端为了防止过载,就会直接拒绝你的请求。我们需要将每个请求,每隔 2s 依次请求,然后保存到数据库,该使用什么方法呢?(这个一道常规面试题,如何让多个 Promise 依次执行?)看到这里的小伙伴,不妨在评论区留言。

效果

等待全部运行完成,我们就将年度作者的全部文章保存到数据库了。 运行下面命令,通过 prisma studio 查看数据

npx prisma studio

查询创建时间大于 2021-01-01

new Date('2021/01/01').getTime() //1609430400000

统计结果

根据点赞数降序排列,就得到我们的高赞文章列表了。

小结

根据这些结果我也总结出了几点,也就是如何写出高赞的文章?

  1. 读者群体要广

    写 ES6 > Vue > React, 就像我之前写的文章《如何测试 React 异步组件?》,阅读量也就可想而知了,会的肯定不需要看你的文章,不会的也没这个需求。

  2. 文章一定要容易理解,一定要让读者理解知识点。

    就像作者 林三心 说的

    用最通俗的话讲最难的知识点是我的座右铭。

最后

小伙伴们,你们是否看懂了我的这篇文章了呢,请给我一个小赞,你的赞是对我最大的支持。


以上就是本文全部内容,希望这篇文章对大家有所帮助,也可以参考我往期的文章或者在评论区交流你的想法和心得,欢迎一起探索前端。

本文首发掘金平台,来源小马博客

相关文章

百度知识图谱:打开人类知识的新大门

百度知识图谱:打开人类知识的新大门

  百度知识图谱是由百度公司开发的一种基于人工智能技术的知识表示和推理系统。它通过构建大规模的知识图谱,将海量的结构化和非结构化数据进行整合,并将这些知识与实体之间的关系进行...

这是启动后的界面,这个 demo 不是一个简单的页面,而是一个包含了深度嵌套路由的例子。

下图我开发时的截图,Turbopack 直接在命令行中打印出了构建时间,我们看到启动时间只需要 2.3ms

更新速度

试着修改代码,程序会自动热更新,绝大多数次数更新时间都很快,但偶尔有几次更新时间却很长,图片中有一处需要 16s(我使用的是 Mac M1),这其中的原因就不得而知了,尤大也发布了测评,使用 1000 个节点来对比更新速度,数据显示:根组件与 vite 时间几乎相同,叶子节点比 vite 快 68%,与官方称比 vite 快 10 倍相差甚远。当然目前 Turbopack 还处于 alpha 阶段,期待 Turbopack 能够尽快推出正式版。

Turbopack 特点

  • 开箱即用 TypeScript, JSX, CSS, CSS Modules, WebAssembly 等
  • 增量计算: Turbopack 是建立在 Turbo 之上的,Turbo 是基于 Rust 的开源、增量记忆化框架,除了可以缓存代码,还可以缓存函数运行结果。
  • 懒编译:例如,如果访问 localhost:3000,它将仅打包 pages/index.jsx,以及导入的模块。

为什么不选择 Vite 和 Esbuild?

Vite 依赖于浏览器的原生 ES Modules 系统,不需要打包代码,这种方法只需要转换单个 JS 文件,响应更新很快,但是如果文件过多,这种方式会导致浏览器大量级联网络请求,会导致启动时间相对较慢。所以作者选择同 webpack 一样方式,打包,但是使用了 Turbo 构建引擎,一个增量记忆化框架,永远不会重复相同的工作。

Esbuild 是一个非常快速的打包工具,但它并没有做太多的缓存,也没有 HMR(热更新),所以在开发环境下不适用。

你好,Next.js 13

theme: vuepress highlight: monokai文章为稀土掘金技术社区首发签约文章,14 天内禁止转载,14 天后未获授权禁止转载,侵权必究!前言上周发布了 Next.js 的一个...

百度AI人脸识别系统:破局者与引领者

百度AI人脸识别系统:破局者与引领者

  在当今数字化时代,人脸识别技术被广泛应用于各行各业中,为我们的生活和工作带来了便利和安全。作为中国领先的互联网公司,百度旗下的AI人脸识别系统以其准确性和高效性成为业界瞩...

人工智能 - 在未来的工作中如何扮演核心角色

人工智能 - 在未来的工作中如何扮演核心角色

  随着技术的不断进步和发展,人工智能目前不仅仅是它自身的一个领域,而且已经渗透进了人们的生活、工作等各个领域。AI的应用也变得越来越广泛,从语音识别到图像处理,在各个行业都...

百度AI语音合成改变了新媒体传播方式

百度AI语音合成改变了新媒体传播方式

  百度AI语音合成是一项创新的技术,在新媒体领域引起了广泛关注和兴趣。随着技术的不断发展和应用,语音合成正在改变着人们获取信息和传播信息的方式。  传统的文本阅读已经悄然淡...

百度图片识别文字的技术及其应用

百度图片识别文字的技术及其应用

  百度图片识别文字是一项基于人工智能技术的创新应用,它能够将图片中的文字内容转化为可编辑和可搜索的文本信息,极大地方便了用户的日常生活和工作。这项技术的应用领域非常广泛,无...

发表评论    

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。