предисловие
Недавно Nuggets обновили значение мощности копания и правила уровня. Большинство пользователей надели значки уровня, и значение силы копания у всех также очень ясное. Я думаю, что это также способ для Nuggets побудить пользователей выводить высококачественные Таким образом, когда вы видите, что ваше значение силы копания продолжает расти, а ваш уровень продолжает расти, вы должны иметь чувство выполненного долга в своем сердце. Увидев ценность моих копаний, я обнаружил, что мне все еще нужно продолжать усердно работать и продолжать делиться своим собственным опытом разработки и хорошими идеями.
Итак, что мы будем делать на этот раз? Чтобы продать пропуск, сначала поставьте рендеринг картинки
idea
Друзья, прочитавшие мою статью, должны подумать, что мне нравится делиться своим опытом создания небольшого проекта или небольшого инструмента, и я делюсь очень небольшим количеством деталей или определенных знаний. Я думаю, что это во многом связано с моими увлечениями: мне нравится завершать проект с нуля, от одной из моих собственных идей до прототипирования, затем до дизайна пользовательского интерфейса, а затем писать интерфейсный и внутренний код на языке, который Я знаком с, а затем на переднюю и внутреннюю связь настроить, и, наконец, оптимизировать и развернуть на сервере. Я чувствую, что в этой серии процессов я могу узнать больше вещей, а также могу расширить множество точек знаний из этой серии процессов и обнаружить детали, которые я склонен игнорировать при выполнении проектов.
Фанаты Наггетс в этот раз (на самом деле не могу сказать фанаты, в основном потому что сложно отличить фолловеров от фолловеров😂) Инструмент анализа тоже блажь, ведь первый документ написанный Наггетсами«Анализ комментариев к сборнику самых популярных статей Nuggets», но тот проект просто получил основные данные статьи, на самом деле никакого анализа нет, и теперь оглядываться на интерфейс немного грубо (такое ощущение, что оглядываешься назад на фото убийства Мэтта в средней школе, хаха). Итак, на этот раз я воспользуюсь преимуществами Nuggets, чтобы подключить мощность и уровень онлайн, и перейду к анализу личных данных, на самом деле, это в основном анализ данных фанатов и анализ пользователей.
Основная функция
- Получить подписчиков пользователя или следующие данные о пользователе на основе идентификатора пользователя
- Анализируйте поклонников или следите за пользователями, публикуйте статьи, статьи нравятся, количество прочитанных статей, количество подписчиков, значение силы раскопок TOP10
- Анализируйте поклонников или следите за распределением на уровне пользователей
- панель личных достижений
- Дополнительные функции анализа разрабатываются позже... (Ждем ваших предложений)
Опыт и исходный код
Адрес опыта:juejinfan.xkboke.com
github:GitHub.com/gengchen528…(Если вам понравилось, поставьте звезду)
Чтобы облегчить работу всех, он был развернут на моем сервере, и к нему можно получить доступ черезjuejinfan.xkboke.comДля доступа это должно быть https.Из-за ограничений пропускной способности сервера вначале может быть медленная загрузка, пожалуйста, подождите терпеливо, или вы можете напрямую развернуть исходный код локально, что будет быстрее. Если у вас много поклонников, вы будете долго ждать после нажатия на анализ, но после нажатия вы можете подождать четыре-пять минут, чтобы посмотреть еще раз, и данные будут загружены сразу после завершения сканирования.
Примечание:
-
uid относится к идентификатору пользователя, а не к имени пользователя, которое можно увидеть в адресной строке над обозревателем домашней страницы Nuggets.
-
Токен нужно щелкнуть на домашней странице любого пользователя, затем открыть консоль, обновить страницу, вы можете увидеть запрос get_multi_user и найти токен в столбце параметров запроса.
uid поиск
Найти токен
Установить
Предпосылкой является установка mongodb, и это порт по умолчанию. Если порт изменился, пожалуйста/monogodb/config.js
Измените номер порта в
git clone https://github.com/gengchen528/juejinAnalyze.git
cd juejinAnalyze
npm install
npm run start
Если вы выполнитеnpm run dev
, пожалуйста, установите его глобальноnodemon
, если используете pm2, установите его глобальноpm2
стек технологий
- koa
- mongoose
- superagent
- pm2
Этот анализ использует koa, который относительно легковесен по сравнению с экспрессами, и использует mongodb для базы данных.Главное для сканирования данных — хороший партнерский суперагент.
Анализ интерфейса самородков
Получение информации о личной домашней странице
Когда вы видите страницу входа, вы можете спросить, зачем вам токен, если он сканируется, Это о том, как Nuggets отображают свою личную домашнюю страницу. После анализа я обнаружил, что самородки рендерятся методом ssr (рендеринг на стороне сервера на основе vue) без входа в систему. Страницы, отображаемые таким образом, громоздки для сканирования. Однако, войдя в систему, вы обнаружите, что данные на странице получены из интерфейса, эти данные выглядят очень счастливыми, и в основном все необходимые данные доступны. Так какие же параметры нужны этому интерфейсу?После тестирования выяснилось, что параметров в основном четыреids
,token
,src
,cols
, так вот почему на странице входа есть токен.
-
ids
: идентификатор пользователя, который можно найти в адресной строке браузера.
-
token
: Откройте консоль и найдитеget_multi_user
Вы можете найти его после этого запроса. Этот интерфейс должен быть открыт после входа на чужую домашнюю страницу. На вашей собственной странице такого запроса нет. -
src
: источникweb
(может по умолчанию в Интернете) -
cols
: Информация о пользователе, которую необходимо получить (по умолчанию)
Не вошли в
после входа
Обязательные параметры
Получить список поклонников и следующих пользователей
Список поклонников и пользователей, за которыми нужно следить, поначалу столкнулся с множеством проблем, потому что после первого нахождения интерфейса выяснилось, что это не простая пагинация, и за раз можно было получить только 20 фрагментов данных. И каждый раз параметры разные, при первом получении вы обнаружите, что основных параметров всего триuid
,currentUid
,src
, но вы найдете еще один параметр при загрузке следующей страницы данныхbefore
, то этоbefore
Как оно пришло. В начале, чтобы найти эту закономерность, я спрашивал десятки раз и ставилbefore
Написав параметры, я наконец обнаружил, что закономерности нет вообще, я моментально засомневался в своей жизни😒, неужели моя идея так и умерла? К счастью, когда я открывал каждый массив, чтобы найти закономерность, я находил оригинал.before
Обратите внимание пользователя в последний раз последний запрос, так как мы знаем, что закон очень прост, код строки и начали засучить рукава.
Первый запрос
запрос на разбивку на страницы
основной код
Структура каталогов
дизайн схемы
Вначале проектировалась только таблица пользователей, и хранилась вся основная информация о пользователях и список поклонников.follower
и список подписчиковfollowees
, Позже, после завершения разработки первой версии, я обнаружил, что если пользователь, который уже сделал запрос, будет снова сканировать и записывать данные после повторного запроса, это будет потреблять ресурсы сервера, а затем будет добавлена подтаблица. .searchSchema.js
Используется для хранения статуса пользователей, которые были запрошены
mongodb/schema.js
const mongoose = require('./config')
const Schema = mongoose.Schema
let jueJinUser = new Schema({
uid: {type:String,unique:true,index: true,}, // 用户Id
username: String, // 用户名
avatarLarge: String, // 头像
jobTitle: String, // 职位
company: String, // 公司
createdAt: Date, // 账号注册时间
rankIndex: Number, // 排名,级别
juejinPower: Number, // 掘力值
postedPostsCount: Number, // 发布文章数
totalCollectionsCount: Number, // 获得点赞数
totalCommentsCount: Number, // 获得评论总数
totalViewsCount: Number, // 文章被阅读数
subscribedTagsCount: Number, // 关注标签数
collectionSetCount: Number, // 收藏集数
likedPinCount: Number, // 点赞的沸点数
collectedEntriesCount: Number, // 点赞的文章数
pinCount: Number, // 发布沸点数
postedEntriesCount: Number, // 分享文章数
purchasedBookletCount: Number, // 购买小册数
bookletCount: Number, // 撰写小册数
followeesCount: Number, // 关注了多少人
followersCount: Number, // 关注者
level: Number, // 等级
topicCommentCount: Number, // 话题被评论数
viewedEntriesCount: Number, // 猜测是主页浏览数
followees: {type:Array,default: []}, // 存放你关注的列表
follower: {type:Array,default: []} // 存放粉丝列表
})
module.exports = mongoose.model('JueJinUser', jueJinUser)
mongodb/searchSchema.js
const mongoose = require('./config')
const Schema = mongoose.Schema
// 掘金用户查询表: 记录已经查询过的用户,防止重复爬取数据,同时记录爬取状态
let JueJinSearch = new Schema({
uid: {type:String,unique:true,index: true,}, // 用户Id
follower: Boolean, // 是否查询过粉丝
followees: Boolean, // 是否查询过关注用户
followerSpider: String, // 粉丝爬取状态 success 爬取完成 loading 爬取中 none 未爬取
followeesSpider: String // 关注用户爬取状态 success 爬取完成 loading 爬取中 none 未爬取
})
module.exports = mongoose.model('JueJinSearch', JueJinSearch)
настройка маршрутизации коа
В настоящее время предоставляется 5 интерфейсов
-
/api/getUserFlower
: просканировать список поклонников -
/api/getUserFlowees
: сканирование списка отслеживаемых пользователей -
/api/getSpiderStatus
: получить статус сканирования -
/api/getCurrentUserInfo
: получить основную информацию пользователя запроса -
/api/getAnalyzeData
: получить данные аналитики
config/koa.js
const Koa = require("koa")
const Router = require("koa-router")
const path = require('path')
const bodyParser = require('koa-bodyparser')
const koaStatic = require('koa-static')
const ctrl = require("../controller/index")
const app = new Koa()
const router = new Router()
const publicPath = '../public'
app.use(bodyParser())
app.use(koaStatic(
path.join(__dirname, publicPath)
))
router.post('/api/getUserFlower', async(ctx, next) => { // 爬取并写入关注者信息
let body = ctx.request.body;
let res = await ctrl.spiderFlowerList(body);
ctx.response.status = 200;
ctx.body = { code: 200, msg: "ok", data: res.data }
next()
})
router.post('/api/getUserFlowees', async(ctx, next) => { // 爬取并写入关注信息
let body = ctx.request.body;
let res = await ctrl.spiderFloweesList(body);
ctx.response.status = 200;
ctx.body = { code: 200, msg: "ok", data: res.data }
next()
})
router.post('/api/getSpiderStatus', async(ctx, next) => { // 获取爬取状态
let body = ctx.request.body;
let res = await ctrl.spiderStatus(body);
ctx.response.status = 200;
ctx.body = { code: 200, msg: "ok", data: res.data }
next()
})
router.post('/api/getCurrentUserInfo', async(ctx, next) => { // 获取当前用的基本信息
let body = ctx.request.body;
let res = await ctrl.getUserInfo(body)
ctx.response.status = 200;
ctx.body = { code: 200, msg: "ok", data: res }
next()
})
router.post('/api/getAnalyzeData', async(ctx, next) => { // 获取你的关注者分析数据
let body = ctx.request.body;
let res = await ctrl.getAnalyze(body)
ctx.response.status = 200;
ctx.body = { code: 200, msg: "ok", data: res }
next()
})
const handler = async(ctx, next) => {
try {
await next();
} catch (err) {
console.log('服务器错误',err)
ctx.respose.status = 500;
ctx.response.type = 'html';
ctx.response.body = '<p>出错啦</p>';
ctx.app.emit('error', err, ctx);
}
}
app.use(handler)
app.on('error', (err) => {
console.error('server error:', err)
})
app.use(router.routes())
app.use(router.allowedMethods())
app.listen(9080, () => {
console.log('juejinAnalyze is starting at port 9080')
console.log('please Preview at http://localhost:9080')
})
controller
Контроллер является наиболее важной логикой для сканирования и вставки данных.Стандартный серверный проект должен разделить еще один уровень служб для вызова контроллера, но поскольку проект относительно небольшой, здесь нет разделения. Важные логические части прокомментированы в коде. Из-за того, что проект выполняется в выходные и два дня, логика здесь немного раздута, и она будет постепенно оптимизироваться на более позднем этапе.Если вам интересно, вы также можете разветвить его и изменить его до нужного вам эффекта. .
const {request} = require("../config/superagent")
const constant = require("../untils/constant")
const model = require("../mongodb/model")
function getLastTime(arr) {
let obj = arr.pop()
return obj.createdAtString
}
// 爬取用户信息并插入到mongodb
// @ids 用户id @token token @tid 关注者用户id
async function spiderUserInfoAndInsert(ids, token, tid, type) {
let url = constant.get_user_info
let param = {
token: token,
src: constant.src,
ids: ids,
cols: constant.cols
}
try {
let data = await request(url, 'GET', param)
let json = JSON.parse(data.text)
let userInfo = json.d[ids]
let insertData = {
uid: userInfo.uid,
username: userInfo.username,
avatarLarge: userInfo.avatarLarge,
jobTitle: userInfo.jobTitle,
company: userInfo.company,
createdAt: userInfo.createdAt,
rankIndex: userInfo.rankIndex, // 排名,级别
juejinPower: userInfo.juejinPower, // 掘力值
postedPostsCount: userInfo.postedPostsCount, // 发布文章数
totalCollectionsCount: userInfo.totalCollectionsCount, // 获得点赞数
totalCommentsCount: userInfo.totalCommentsCount, // 获得评论总数
totalViewsCount: userInfo.totalViewsCount, // 文章被阅读数
subscribedTagsCount: userInfo.subscribedTagsCount, // 关注标签数
collectionSetCount: userInfo.collectionSetCount, // 收藏集数
likedPinCount: userInfo.likedPinCount, // 点赞的沸点数
collectedEntriesCount: userInfo.collectedEntriesCount, // 点赞的文章数
pinCount: userInfo.pinCount, // 发布沸点数
postedEntriesCount: userInfo.postedEntriesCount, // 分享文章数
purchasedBookletCount: userInfo.purchasedBookletCount, // 购买小册数
bookletCount: userInfo.bookletCount, // 撰写小册数
followeesCount: userInfo.followeesCount, // 关注了多少人
followersCount: userInfo.followersCount, // 关注者
level: userInfo.level, // 等级
topicCommentCount: userInfo.topicCommentCount, // 话题被评论数
viewedEntriesCount: userInfo.viewedEntriesCount, // 猜测是主页浏览数
}
await model.user.insert(insertData)
if (ids !== tid) {
if (type === 'followees') {
updatefollower(ids, tid) // 更新关注你的用户列表
updatefollowees(tid, ids) // 更新你关注用户的列表
} else {
updatefollower(tid, ids) // 更新关注你的用户列表
updatefollowees(ids, tid) // 更新你关注用户的列表
}
}
return 'ok'
} catch (e) {
console.log('用户信息获取失败',ids, e,)
}
}
// 更新用户的关注列表
// @uId 用户id @tId 关注的用户Id
async function updatefollowees(uId, tId) {
let data = {
uid: uId,
followUid: tId
}
model.followees.updatefollowees(data)
}
// 更新用户的被关注列表
// @uId 关注的用户id @tId 被关注的用户Id
async function updatefollower(uId, tId) {
let data = {
uid: uId,
followUid: tId
}
model.follower.updatefollower(data)
}
// 爬取用户的关注者列表
// @uid 用户的id @token token @before 循环获取关注列表的必须参数,取上一组数据中最后一个数据的关注时间
async function getFollower(uid, token, before) {
let param = {
uid: uid,
src: constant.src
}
if (before) {
param.before = before
}
try {
let url = constant.get_follow_list
let list = await request(url, 'GET', param)
let followList = list.body.d
followList.forEach(async function (item) { // 循环获取关注者的信息
await spiderUserInfoAndInsert(item.follower.objectId, token, uid, 'follower')
})
if (followList&&followList.length === 20) { // 获取的数据长度为20继续爬取
let lastTime = getLastTime(followList)
await updateSpider(uid, 'followerSpider', 'loading') // 更新爬取状态为loading
await getFollower(uid, token, lastTime)
} else {
await updateSpider(uid, 'follower', true) // 设置已经爬取标志
await updateSpider(uid, 'followerSpider', 'success') // 更新爬取状态为success
}
} catch (err) {
console.log('获取粉丝列表失败',err)
return {data: err}
}
}
// 更新爬取状态与结果
// @uid 用户id @key 更新的字段 @value 更新的值
async function updateSpider(uid, key, value) {
let condition = {
uid: uid,
key: key,
value: value
}
model.search.update(condition)
}
// 爬取你关注的列表
// @uid 用户的id @token token @before 循环获取关注列表的必须参数,取上一组数据中最后一个数据的关注时间
async function getFollowee(uid, token, before) {
let param = {
uid: uid,
src: constant.src
}
if (before) {
param.before = before
}
try {
let url = constant.get_followee_list
let list = await request(url, 'GET', param)
let followList = list.body.d
followList.forEach(async function (item) { // 循环获取关注者的信息
await spiderUserInfoAndInsert(item.followee.objectId, token, uid, 'followees')
})
if (followList.length === 20) {
let lastTime = getLastTime(followList)
await updateSpider(uid, 'followeesSpider', 'loading') // 更新爬取状态为loading
await getFollowee(uid, token, lastTime)
} else {
await updateSpider(uid, 'followees', true) // 设置已经爬取标志
await updateSpider(uid, 'followeesSpider', 'success') // 更新爬取状态为loading
}
} catch (err) {
console.log('获取关注者列表失败',err)
return {data: err}
}
}
// 用户数据分析
// @uid 用户id @top 可配置选取前多少名 @type 获取数据类型:粉丝 follower 关注的人 followees
async function getTopData(uid, top, type) {
let data = {
uid: uid,
top: parseInt(top),
type: type
}
try {
let article = model.analyze.getTopUser(data, 'postedPostsCount')
let juejinPower = model.analyze.getTopUser(data, 'juejinPower')
let liked = model.analyze.getTopUser(data, 'totalCollectionsCount')
let views = model.analyze.getTopUser(data, 'totalViewsCount')
let follower = model.analyze.getTopUser(data, 'followersCount')
let level = model.analyze.getLevelDistribution(data)
let obj = {
postedPostsCount: await article,
juejinPower: await juejinPower,
totalCollectionsCount: await liked,
totalViewsCount: await views,
followersCount: await follower,
level: await level
}
return obj
} catch (err) {
console.log('err', err)
return err
}
}
module.exports = {
spiderFlowerList: async (body) => { // 获取用户的关注者列表
let uid = body.uid
let token = body.token
let searchStatus = await model.search.findOrInsert({uid: uid})
if (searchStatus.followerSpider == 'success') {
return {data: 'success'}
} else if (searchStatus.followerSpider == 'loading') {
return {data: 'loading'}
} else if (searchStatus.followerSpider == 'none') {
spiderUserInfoAndInsert(uid, token, uid) // 把自己的信息也插入mongodb
getFollower(uid, token)
return {data: 'none'}
}
},
spiderFloweesList: async (body) => { // 获取用户的关注列表
let uid = body.uid
let token = body.token
let searchStatus = await model.search.findOrInsert({uid: uid})
if (searchStatus.followeesSpider == 'success') {
return {data: 'success'}
} else if (searchStatus.followeesSpider == 'loading') {
return {data: 'loading'}
} else if (searchStatus.followeesSpider == 'none') {
spiderUserInfoAndInsert(uid, token, uid) // 把自己的信息也插入mongodb
getFollowee(uid, token)
return {data: 'none'}
}
},
spiderStatus: async (body) => {
let uid = body.uid
let type = body.type + 'Spider'
let spiderStatus = await model.search.getSpiderStatus({uid: uid, type: type})
if (spiderStatus[type] === 'loading' || spiderStatus[type] === 'none') {
return {data: false}
} else if (spiderStatus[type] === 'success') {
return {data: true}
}
},
getUserInfo: async (body) => { // 获取当前用户基本信息
let uid = body.uid
let data = {
uid: uid
}
let result = await model.user.getUserInfo(data)
return result
},
getAnalyze: async (body) => { // 获取关注者数据分析
let uid = body.uid
let top = body.top
let type = body.type
let res = await getTopData(uid, top, type)
return res
}
}
дизайн страницы
Чтобы избавиться от первоначального изображения убийства Мэтта, на этот раз используется более популярное отображение панели больших данных. Тем не менее, дизайн всей страницы в основном связан с наличием подруги, которая занимается дизайном (без какого-либо поведения разбрасывания еды😆, в основном за спасибо), здесь я хотел бы поблагодарить подругу, которая помогла мне 😂, спасибо Вы за то, что пожертвовали временем выходных, чтобы сопровождать меня, чтобы изменить схему дизайна. Кроме того, в связи с рассмотрением различных вопросов экранной адаптации во внешнем коде используются только эффекты пропорционального уменьшения и увеличения, поэтому изображение под некоторыми экранами будет немного деформировано, что является нормальной ситуацией.
Анализ скриншотов
Прочитав свой профиль, я обнаружил, что присоединился к Nuggets на 851 день, опубликовал 12 статей, опубликовал 11 точек кипения и получил 211 подписчиков.Спасибо, что следите за моими пользователями.
наконец
После завершения небольшого проекта самая большая идея состоит в том, чтобы записать весь процесс не только для того, чтобы поделиться им со всеми, но и для обзора проблем, возникающих в процессе всего проекта, и улучшения метода решения проблемы на тот момент. . Я надеюсь, что всем понравится этот проект, а также напомню всем, чтобы не использовать проект, чтобы делать плохие вещи.Этот проект в основном используется для технического обмена и для проверки анализа данных фанатов и подписчиков. Если у вас возникнут какие-либо проблемы во время использования, вы можете оставить сообщение ниже или напрямую добавить WeChat, чтобы связаться со мной, если вы это увидите, я отвечу вовремя.
адрес проекта:
github:GitHub.com/gengchen528…(Если вам понравилось, поставьте звезду)