使用 Facebook 数据和 JavaScript 抛弃无用的朋友 获取数据 从 JavaScript 读取 JSON 解析数据 解码反应表情符号 选择要抛弃的朋友 再见

2025-06-11

利用 Facebook 数据和 JavaScript 抛弃无用的朋友

获取数据

从 JavaScript 读取 JSON

解析数据

解读反应表情符号

选择抛弃的朋友

再见

友谊很难维系。为了维持那些可能并不会带来任何实际回报的友谊,我们浪费了太多精力。我发现自己不禁会想:“我当然从幼儿园就认识她了,她把我妻子介绍给我认识,我被赶出去的时候还让我在她家住了六个月,但这样的友谊真的值得吗?”

我需要决定抛弃哪些朋友。但标准是什么?长相?智力?还是金钱?

当然,一个人的价值是主观的。没有办法用经验来衡量,对吧?错了。衡量朋友价值有一个万无一失的方法:Facebook Messenger 上收到的表情符号数量。

酒保说“为什么拉长脸”哈哈哈。12 个笑脸表情符号反应。

笑声越多,说明这个人最搞笑。生气声最多的人最有争议。以此类推。很简单!

手动计数是不可能的;我需要自动执行这项任务。

获取数据

抓取聊天记录太慢了。虽然有 API,但我不知道它是否能解决这个问题。它看起来吓人,文档也太冗长了!我最终找到了一种方法来获取我需要的数据:

Facebook数据下载页面

Facebook 允许我以易于阅读的 JSON 格式下载他们多年来收集的所有关于我的个人信息。真是太好了!我确保只选择我需要的数据(消息),并选择最低的图像质量,以尽可能缩小存档。生成过程可能需要几个小时甚至几天的时间。

第二天,我收到一封邮件,通知我该压缩包(共8.6 GB)已可在“可用副本”选项卡下下载。该压缩包的结构如下:

messages
├── archived_threads
│   └── [chats]
├── filtered_threads
│   └── [chats]
├── inbox
│   └── [chats]
├── message_requests
│   └── [chats]
└── stickers_used
    └── [bunch of PNGs]
Enter fullscreen mode Exit fullscreen mode

我感兴趣的目录是inbox[chats]目录结构如下:

[ChatTitle]_[uniqueid]
├── gifs
│   └── [shared gifs]
├── photos
│   └── [shared photos]
├── videos
│   └── [shared videos]
├── files
│   └── [other shared files]
└── message_1.json
Enter fullscreen mode Exit fullscreen mode

我需要的数据在 中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"
}
Enter fullscreen mode Exit fullscreen mode

我想重点关注一下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"
}
Enter fullscreen mode Exit fullscreen mode

我找到我要找的东西了!所有反应都列在那里。

从 JavaScript 读取 JSON

对于这个任务,我使用FileReader API

<input type="file" accept=".json" onChange="handleChange(this)">
Enter fullscreen mode Exit fullscreen mode
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);
}
Enter fullscreen mode Exit fullscreen mode

我在页面上看到了文件输入框,当我选择 JSON 格式时,解析后的 JavaScript 对象会被打印到控制台。由于长度太长,可能需要几秒钟的时间。现在我需要弄清楚如何读取它。

解析数据

让我们从简单的开始。我的第一个目标是将我的messages_1.json作为输入,并将类似以下内容作为输出

output = [
  {
    name: 'Ricardo L',
    counts: {
      '😂': 10,
      '😍': 3,
      '😢': 4,
    },
  },
  {
    name: 'Samuel L',
    counts: {
      '😂': 4,
      '😍': 5,
      '😢': 12,
    },
  },
  // etc for every participant
]
Enter fullscreen mode Exit fullscreen mode

原始 JSON 中的对象participants已经具有类似的格式。只需添加以下counts字段:

const output = parsedObject.participants.map(({ name }) => ({
  name,
  counts: {},
}))
Enter fullscreen mode Exit fullscreen mode

现在我需要迭代整个消息列表,并累积反应计数:

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
    }
  })
})
Enter fullscreen mode Exit fullscreen mode

记录的输出如下所示:

JavaScript 控制台输出。两个参与者,带有反应计数,但用奇怪的字符代替了表情符号

我收到了四个奇怪的符号,而不是表情符号。这是怎么回事?

解读反应表情符号

我抓取了一条消息作为示例,它只有一个反应:哭泣的表情符号 (😢)。检查 JSON 文件后,我发现了以下内容:

"reaction": "\u00f0\u009f\u0098\u00a2"
Enter fullscreen mode Exit fullscreen mode

这个角色列车与哭泣表情符号有何关系?

它可能看起来不像,但这个字符串有四个字符长:

  • \u00f0
  • \u009f
  • \u0098
  • \u00a2

在 JavaScript 中,\u是表示转义序列的前缀。此特定转义序列以 开头\u,后跟四个十六进制数字。它表示 UTF-16 格式的 Unicode 字符。注意:实际情况比 稍微复杂一些,但出于本文的目的,我们将所有内容视为 UTF-16 编码。

例如,大写字母 S 的 Unicode 十六进制代码是0053"\u0053"。你可以在 JavaScript控制台中输入以下命令来查看其工作原理:

JavaScript 控制台。“\u0053”作为输入,“S”作为输出

再次查看 Unicode 表,我发现哭泣表情符号的十六进制代码是1F622。这个代码长度超过四位,所以直接使用\u1F622是行不通的。有两种方法可以解决这个问题:

  • UFT-16 代理对。这会将较大的十六进制数字拆分为两个较小的 4 位数字。在本例中,哭泣的表情符号将表示为\ud83d\ude22

  • 直接使用 Unicode 代码点,使用稍微不同的格式:\u{1F622}。请注意包裹代码的花括号。

在 JSON 中,每个反应都使用四个不带花括号的字符代码,并且它们都不能作为代理对,因为它们不在正确的范围内

那么它们是什么?

让我们来看看这个表情符号的一些可能的编码。这些编码看起来熟悉吗?

graphemica.com 关于哭泣表情符号的页面。UTF-8(十六进制)编码为“0xF0 0x9F 0x98 0xA2”。

非常接近了!原来这是十六进制的 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);  // '😢'
}
Enter fullscreen mode Exit fullscreen mode

现在我已经掌握了正确呈现结果所需的一切:

JavaScript 控制台输出。两位参与者,带有反应计数,现在显示正确的表情符号

选择抛弃的朋友

我想根据每种反应的计数来计算得分。我需要一些变量:

  • 参与者的消息总数(T
  • 参与者发送的反应总数 ( SR )
  • 每位参与者的全局平均消息数(AVG

对于收到的反馈,我做了一些分类:

  • 👍:赞同(A
  • 👎:不赞成(D
  • 😆 和 😍:积极情绪(PE
  • 😢 和 😠:负面情绪(NE
  • 😮:中性,我会扔掉它

最终公式为:

方程: (2A + 3PE + SR) - (2D + 3NE)/(abs(T - AVG) / AVG)

得分越高,说明这个人越优秀。以下是我如何得出这个等式的解释。

在 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
})
Enter fullscreen mode Exit fullscreen mode

以表格形式显示信息使其更容易解析:

结果表

注意:出于隐私考虑,我用朋友的家庭住址替换了他们的真实姓名。

再见

快速浏览一下表格,我最终就能决定需要将谁从我的生活中剔除。

从 Facebook 上删除 Samuel L

再见了,表哥萨姆。

鏂囩珷鏉ユ簮锛�https://dev.to/raicuparta/ditching-worthless-friends-with-facebook-data-and-javascript-3f2i
PREV
Data visualization: Creating charts using REST API's in React.js Modifying the application Takeaway Conclusion References
NEXT
纯 CSS 灯:一步一步