利用 Facebook 数据和 JavaScript 抛弃无用的朋友
获取数据
从 JavaScript 读取 JSON
解析数据
解读反应表情符号
选择抛弃的朋友
再见
友谊很难维系。为了维持那些可能并不会带来任何实际回报的友谊,我们浪费了太多精力。我发现自己不禁会想:“我当然从幼儿园就认识她了,她把我妻子介绍给我认识,我被赶出去的时候还让我在她家住了六个月,但这样的友谊真的值得吗?”
我需要决定抛弃哪些朋友。但标准是什么?长相?智力?还是金钱?
当然,一个人的价值是主观的。没有办法用经验来衡量,对吧?错了。衡量朋友价值有一个万无一失的方法:Facebook Messenger 上收到的表情符号数量。
笑声越多,说明这个人最搞笑。生气声最多的人最有争议。以此类推。很简单!
手动计数是不可能的;我需要自动执行这项任务。
获取数据
抓取聊天记录太慢了。虽然有 API,但我不知道它是否能解决这个问题。它看起来吓人,文档也太冗长了!我最终找到了一种方法来获取我需要的数据:
Facebook 允许我以易于阅读的 JSON 格式下载他们多年来收集的所有关于我的个人信息。真是太好了!我确保只选择我需要的数据(消息),并选择最低的图像质量,以尽可能缩小存档。生成过程可能需要几个小时甚至几天的时间。
第二天,我收到一封邮件,通知我该压缩包(共8.6 GB)已可在“可用副本”选项卡下下载。该压缩包的结构如下:
messages
├── archived_threads
│ └── [chats]
├── filtered_threads
│ └── [chats]
├── inbox
│ └── [chats]
├── message_requests
│ └── [chats]
└── stickers_used
└── [bunch of PNGs]
我感兴趣的目录是inbox
。[chats]
目录结构如下:
[ChatTitle]_[uniqueid]
├── gifs
│ └── [shared gifs]
├── photos
│ └── [shared photos]
├── videos
│ └── [shared videos]
├── files
│ └── [other shared files]
└── message_1.json
我需要的数据在 中message_1.json
。不知道为什么_1
需要后缀。我的存档里没有这个后缀message_2.json
,也没有任何其他变体。
例如,如果我想使用的聊天名为“裸体排球伙伴”,完整路径将是这样的messages/inbox/NudeVolleyballBuddies_5tujptrnrm/message_1.json
。
这些文件可能会非常大,所以如果你的IDE一看到它就晕倒,也不要感到惊讶。我要分析的聊天记录大约有5年了,包含了超过一百万行的JSON数据。
JSON 文件的结构如下:
{
"participants": [
{ "name": "Ricardo L" },
{ "name": "etc..." }
],
"messages": [
" (list of messages...) "
],
"title": "Nude Volleyball Buddies",
"is_still_participant": true,
"thread_type": "RegularGroup",
"thread_path": "inbox/NudeVolleyballBuddies_5tujptrnrm"
}
我想重点关注一下messages
。每条消息都有以下格式:
{
"sender_name": "Ricardo L",
"timestamp_ms": 1565448249085,
"content": "is it ok if i wear a sock",
"reactions": [
{
"reaction": "\u00f0\u009f\u0098\u00a2",
"actor": "Samuel L"
},
{
"reaction": "\u00f0\u009f\u0098\u00a2",
"actor": "Carmen Franco"
}
],
"type": "Generic"
}
我找到我要找的东西了!所有反应都列在那里。
从 JavaScript 读取 JSON
对于这个任务,我使用FileReader API:
<input type="file" accept=".json" onChange="handleChange(this)">
function handleChange(target) {
const reader = new FileReader();
reader.onload = handleReaderLoad;
reader.readAsText(target.files[0]);
}
function handleReaderLoad (event) {
const parsedObject = JSON.parse(event.target.result);
console.log('parsed object', parsedObject);
}
我在页面上看到了文件输入框,当我选择 JSON 格式时,解析后的 JavaScript 对象会被打印到控制台。由于长度太长,可能需要几秒钟的时间。现在我需要弄清楚如何读取它。
解析数据
让我们从简单的开始。我的第一个目标是将我的messages_1.json
作为输入,并将类似以下内容作为输出:
output = [
{
name: 'Ricardo L',
counts: {
'😂': 10,
'😍': 3,
'😢': 4,
},
},
{
name: 'Samuel L',
counts: {
'😂': 4,
'😍': 5,
'😢': 12,
},
},
// etc for every participant
]
原始 JSON 中的对象participants
已经具有类似的格式。只需添加以下counts
字段:
const output = parsedObject.participants.map(({ name }) => ({
name,
counts: {},
}))
现在我需要迭代整个消息列表,并累积反应计数:
parsedObject.messages.forEach(message => {
// Find the correct participant in the output object
const outputParticipant = output.find(({ name }) => name === message.sender_name)
// Increment the reaction counts for that participant
message.reactions.forEach(({ reaction }) => {
if (!outputParticipant.counts[reaction]) {
outputParticipant.counts[reaction] = 1
} else {
outputParticipant.counts[reaction] += 1
}
})
})
记录的输出如下所示:
我收到了四个奇怪的符号,而不是表情符号。这是怎么回事?
解读反应表情符号
我抓取了一条消息作为示例,它只有一个反应:哭泣的表情符号 (😢)。检查 JSON 文件后,我发现了以下内容:
"reaction": "\u00f0\u009f\u0098\u00a2"
这个角色列车与哭泣表情符号有何关系?
它可能看起来不像,但这个字符串有四个字符长:
\u00f0
\u009f
\u0098
\u00a2
在 JavaScript 中,\u
是表示转义序列的前缀。此特定转义序列以 开头\u
,后跟四个十六进制数字。它表示 UTF-16 格式的 Unicode 字符。注意:实际情况比 稍微复杂一些,但出于本文的目的,我们将所有内容视为 UTF-16 编码。
例如,大写字母 S 的 Unicode 十六进制代码是0053
"\u0053"
。你可以在 JavaScript控制台中输入以下命令来查看其工作原理:
再次查看 Unicode 表,我发现哭泣表情符号的十六进制代码是1F622
。这个代码长度超过四位,所以直接使用\u1F622
是行不通的。有两种方法可以解决这个问题:
-
UFT-16 代理对。这会将较大的十六进制数字拆分为两个较小的 4 位数字。在本例中,哭泣的表情符号将表示为
\ud83d\ude22
。 -
直接使用 Unicode 代码点,使用稍微不同的格式:
\u{1F622}
。请注意包裹代码的花括号。
在 JSON 中,每个反应都使用四个不带花括号的字符代码,并且它们都不能作为代理对,因为它们不在正确的范围内。
那么它们是什么?
让我们来看看这个表情符号的一些可能的编码。这些编码看起来熟悉吗?
非常接近了!原来这是十六进制的 UTF-8 编码。但不知何故,UTF-16 格式的每个字节都写成了 Unicode 字符。
知道了这一点,我该如何从\u00f0\u009f\u0098\u00a2
到\uD83D\uDE22
?
我将每个字符提取为一个字节,然后将这些字节合并为一个 UTF-8 字符串:
function decodeFBEmoji (fbString) {
// Convert String to Array of hex codes
const codeArray = (
fbString // starts as '\u00f0\u009f\u0098\u00a2'
.split('')
.map(char => (
char.charCodeAt(0) // convert '\u00f0' to 0xf0
)
); // result is [0xf0, 0x9f, 0x98, 0xa2]
// Convert plain JavaScript array to Uint8Array
const byteArray = Uint8Array.from(codeArray);
// Decode byte array as a UTF-8 string
return new TextDecoder('utf-8').decode(byteArray); // '😢'
}
现在我已经掌握了正确呈现结果所需的一切:
选择抛弃的朋友
我想根据每种反应的计数来计算得分。我需要一些变量:
- 参与者的消息总数(T)
- 参与者发送的反应总数 ( SR )
- 每位参与者的全局平均消息数(AVG)
对于收到的反馈,我做了一些分类:
- 👍:赞同(A)
- 👎:不赞成(D)
- 😆 和 😍:积极情绪(PE)
- 😢 和 😠:负面情绪(NE)
- 😮:中性,我会扔掉它
最终公式为:
得分越高,说明这个人越优秀。以下是我如何得出这个等式的解释。
在 JavaScript 中它会是这样的:
participants.forEach((participant) => {
const {
reactions,
sentReactionCount,
messageCount,
} = participant
const approval = reactions['👍']
const disapproval = reactions['👎']
const positiveEmotion = reactions['😆'] + reactions['😍']
const negativeEmotions = reactions['😢'] + reactions['😠']
const positiveFactor = (2 * approval + 3 * positiveEmotion + sentReactionCount)
const negativeFactor = (2 * disapproval + 3 * negativeEmotions)
const totalMessageFactor = Math.abs(messageCount - messageCountAverage) / (messageCountAverage)
participant.score = (positiveFactor - negativeFactor) / totalMessageFactor
})
以表格形式显示信息使其更容易解析:
注意:出于隐私考虑,我用朋友的家庭住址替换了他们的真实姓名。
再见
快速浏览一下表格,我最终就能决定需要将谁从我的生活中剔除。
再见了,表哥萨姆。
鏂囩珷鏉ユ簮锛�https://dev.to/raicuparta/ditching-worthless-friends-with-facebook-data-and-javascript-3f2i