从 Yahoo! Groups 迁移

许多 Yahoo 群组正受到 Yahoo 决定删除已上传内容的影响,正在寻找替代服务提供商。显而易见的选择是 groups.io,它提供功能对等,并能自动迁移所有群组内容,但存在两个问题:(1) 首次迁移需支付 220 美元(之后若存储需求小于 1 GB 则免费);(2) 仍沿用与 Yahoo 群组相同的以电子邮件为核心的格式。我在另一个主题(https://meta.discourse.org/t/yahoo-groups-to-discourse-migration/131587)中看到有人提到存在迁移脚本,但从我目前所见( admittedly 目前了解尚浅)来看,该脚本仅能迁移消息。

由于 Yahoo 此次变动将促使群组迁移的主要原因是文件与照片存储功能的丧失,因此引发了两个问题:

  • 是否有自动化的方法将已上传内容从 Yahoo 群组迁移到 Discourse 实例?
  • Discourse 中有哪些选项可使这些内容对其他用户可用或可见?我目前看到的唯一方案是让用户将其上传的内容发布到一个或多个主题中,通常是在不同的分类下。对于照片而言,这相对直接;而对于上传到群组的其它文档(主要是 PDF 文件,部分为 .doc 和 .xls 文件),似乎需要进行一些配置调整,但问题并不严重。不过,是否存在其他方案?

有意思……我之前还不知道 Yahoo Groups 要删除用户上传的内容。我还有一个为社区维护的旧 Yahoo 群组,一直想把它们迁移到 Discourse 实例上……也许现在正是时候。

我觉得最简单的迁移方法是:先将订阅者列表导出为 CSV,然后用它来在 Discourse 中创建用户基础——这应该相当 straightforward。

至于内容,您是否有发送到该列表的所有邮件的完整历史记录?如果有,可以使用像 Thunderbird 这样的应用下载所有邮件并保存为 MBOX 格式。之后就有脚本可以导入。我认为这个指南会对你有帮助:Migrate a mailing list to Discourse (mbox, Listserv, Google Groups, etc)

我不太确定您所说的“上传内容”具体指什么——我自己并没有那样使用过 Yahoo Groups。我也不清楚您有哪些选项可以将这些内容从 Yahoo 导出并为 Discourse 做准备。这可能是一个手动过程……也许这也是一个整理资料、清理不再需要内容的良机。

不过没错,Discourse 是以讨论为核心的,所有内容都以主题(topics)形式存在。您可以将主题设置为 Wiki,以便由团队共同维护,包括添加或移除附件。此外还有私信功能,可用于与自己或精心挑选的少数人交流,我想人们也可以在那里保留一些内容。如果您更倾向于文件共享,或许可以考虑其他支持单点登录(SSO)的工具。在我们的社区中,我们使用 WordPress,它有一个支持 SSO 的插件,集成得非常顺畅。如果您涉及大量文件,也可以搭建一个 Nextcloud 实例。

祝您迁移顺利!

更新:哇……看来确实该迁移了。Yahoo Groups 正在采取极端措施限制其可用性,而且很快就要实施。从 10 月 28 日(两天后)起将不再允许发布新内容,而现有内容将在 12 月 14 日被删除。

我没有,因为我并不是我所涉及的任何列表的管理员。不过,迁移消息的问题似乎可以通过以下工具解决:discourse/script/import_scripts/yahoogroup.rb at main · discourse/discourse · GitHubhttps://github.com/jonbartlett/yahoo-groups-export。此外,群组管理员可以导出带有邮箱地址的群组消息(普通用户无法做到)。虽然这看起来并不完全是一键式的简单操作,但消息迁移目前似乎并不是最紧迫的问题。

Yahoo Groups 提供(或者说曾经提供)存储空间,用于存放群组的照片(100 GB)和其他文件(2 GB)。我所加入的群组使用这些空间来存放成员照片、与群组相关的各类物品图片以及其他文件。任何在 Yahoo 群组成员之间私下发送的内容,很可能都是通过电子邮件进行的,而 Yahoo 不会保留这些记录;我认为迁移这部分内容甚至是不可能的,更谈不上是优先事项了。不过,许多群组中确实存储了大量信息,在迁移过程中他们可能希望将这些信息保留下来。

有可能。再次强调,目前据我所知,只有一个(且仅有一个)网站看起来像是现成的替代方案,但它仅对 Yahoo Groups 的旧格式进行了微调。我在想,如果一个群组无论如何都需要迁移,那么迁移到一个更现代化的平台可能更好。Discourse 与电子邮件的配合仍然相当出色(许多其他论坛软件做不到这一点),这意味着像我们这样习惯接收邮件并直接通过邮件回复的“老派”用户,依然可以沿用这种方式。此外,节省一些费用也是不错的。

那么,进一步的进展。这个工具:

在批量下载群组内容方面似乎相当好用——它能获取所有消息、文件、附件等。消息会被下载为两个 .json 文件,一个是“原始”格式,另一个是 HTML 格式。第一个文件看起来像这样:

{
    "userId": 185744666,
    "authorName": "vhsproducts@aol.com",
    "from": "vhsproducts@...",
    "profile": "vhsproducts",
    "replyTo": "LIST",
    "senderId": "fc-T6L4xNaFRDleu_7gutRzgA_WWujKXanij68LOf7iz0WXh-BolDsmiqlo19adwRPTjwe0FpCYycg",
    "spamInfo": {
        "isSpam": false,
        "reason": "0"
    },
    "subject": "Re: [MicroTrak] Mint-Trak300 completed",
    "postDate": "1181013131",
    "msgId": 4,
    "canDelete": false,
    "contentTrasformed": false,
    "systemMessage": false,
    "headers": {
        "messageIdInHeader": "PGM3ZC5lNWZlOTFjLjMzOTYyZThiQGFvbC5jb20+"
    },
    "prevInTopic": 3,
    "nextInTopic": 6,
    "prevInTime": 3,
    "nextInTime": 5,
    "topicId": 3,
    "numMessagesInTopic": 4,
    "msgSnippet": "Outstanding work! I see you have the first gen of the Micro-Trak ( although we still sell them for people with TT3 SMT s) How long will a 9 volt run your GPS? ",
    "rawEmail": "Return-Path: <VHSProducts@...>\r\nX-Sender: VHSProducts@...\r\nX-Apparently-To: MicroTrak@yahoogroups.com\r\nReceived: (qmail 18487 invoked from network); 5 Jun 2007 03:13:19 -0000\r\nReceived: from unknown (66.218.67.36)\n  by m50.grp.scd.yahoo.com with QMQP; 5 Jun 2007 03:13:19 -0000\r\nReceived: from unknown (HELO imo-m23.mx.aol.com) (64.12.137.4)\n  by mta10.grp.scd.yahoo.com with SMTP; 5 Jun 2007 03:13:19 -0000\r\nReceived: from VHSProducts@...\n\tby imo-m23.mx.aol.com (mail_out_v38_r9.2.) id r.c7d.e5fe91c (29679)\n\t for <MicroTrak@yahoogroups.com>; Mon, 4 Jun 2007 23:12:11 -0400 (EDT)\r\nMessage-ID: <c7d.e5fe91c.33962e8b@...>\r\nDate: Mon, 4 Jun 2007 23:12:11 EDT\r\nTo: MicroTrak@yahoogroups.com\r\nMIME-Version: 1.0\r\nContent-Type: multipart/alternative; boundary="-----------------------------1181013131"\r\nX-Mailer: 9.0 Security Edition for Windows sub 5365\r\n(snip)"
}

……而后者看起来像这样:

{
    "userId": 185744666,
    "authorName": "vhsproducts@aol.com",
    "from": "vhsproducts@...",
    "profile": "vhsproducts",
    "replyTo": "LIST",
    "senderId": "oChpSVZSELyeHvFRyDX_nG5dfpdVZTLBKFMDvOg33fSsrDk5l-zpPohl42rhz6OhM9tFfSjAxxGsRg",
    "spamInfo": {
        "isSpam": false,
        "reason": "0"
    },
    "subject": "Re: [MicroTrak] Mint-Trak300 completed",
    "postDate": "1181013131",
    "msgId": 4,
    "canDelete": false,
    "contentTrasformed": false,
    "systemMessage": false,
    "headers": {
        "messageIdInHeader": "PGM3ZC5lNWZlOTFjLjMzOTYyZThiQGFvbC5jb20+"
    },
    "prevInTopic": 3,
    "nextInTopic": 6,
    "prevInTime": 3,
    "nextInTime": 5,
    "topicId": 3,
    "numMessagesInTopic": 4,
    "msgSnippet": "Outstanding work! I see you have the first gen of the Micro-Trak ( although we still sell them for people with TT3 SMT s) How long will a 9 volt run your GPS? ",
    "messageBody": "<div id=\"ygrps-yiv-810547383\">\n<html><head>\n \n</head> \n\n<font id=\"ygrps-yiv-810547383role_document\"\n face=\"Arial\" color=\"#000000\" size=\"2\">\n<div>Outstanding work! I see you have the first gen of the Micro-Trak ( although \nwe still sell them for people with TT3 SMT&#39;s) How long will a 9 volt run your \nGPS?</div>\n(snip)",
    "specialLinks": []
}

根据群组的不同,这些文件的数量可能达到数万甚至数十万。Yahoo 一如既往地屏蔽了普通用户的电子邮件地址——群组所有者可以看到,版主或许也能看到,但其他人则不行。现在需要看看是否有相对简单的方法将这些内容批量导入到 Discourse 实例中,或者是否最好使用我上面提到的工具。

该工具还会下载文件和照片,以及投票、日历和其他我不太在意但其他人可能需要的内容。

还有一点——仔细阅读 Yahoo 的公告后发现,他们不仅会移除文件和照片,还会彻底删除消息存档。这确实会让这些内容对任何用途都变得毫无价值。

请迁移到 Discourse。

我们需要更多独立的网站,减少人们对大型垄断平台的依赖。

我想分享的是,几年前我将本地社区从 Yahoo Group 迁移到了 Discourse,从此再未回头。你可以为共享交流资源添加个性化元素,这本身就物超所值,而额外的功能更是锦上添花。

遗憾的是,我无法提供迁移方面的实用经验,因为我们几乎是从头开始,只保留了邮件列表。为什么不保留旧的 Yahoo Group 站点并提供一个链接呢?你真的需要保留多少附件?只保留最重要的那些吧。

祝你好运,你一定会做得很好!

这并非由我直接决定,但我有此倾向。对于我最关心的群体,我并不认为文件/照片会成为大问题——我现在已将它们全部下载,数量也很少,手动将它们导入到各个主题中应该不会太麻烦。

是的,因为我们现在正目睹其中的一种风险。

因为六周后,那里的所有数据都将消失。

我可以编写一个导入器来读取这些 JSON 文件,但我无法与 200 美元竞争。通常情况下,我收取的费用是 10 倍,用于编写导入器并导入一个中等规模的论坛(几十万条帖子)。

所以听起来我最好使用:

然后使用:

……等我弄清楚它们之后。

(主要编辑如下——第二次尝试)

正在处理消息导入流程,参考了 Migrate from another forum to Discourse 上的说明。据我理解,流程应该是:

  • 按照 Install Discourse on Ubuntu or Debian for Development 的指南设置开发环境
  • 在该系统上安装 MongoDB
  • 在该系统上,使用与运行 Discourse 相同的非特权用户,git clone yahoo-group-export 脚本
  • 以同一用户身份,执行 gem install mechanizegem install mongo。然后编辑 .config.yaml 文件,填入 Yahoo 凭证和群组名称,接着运行 ruby bin/yg-export.rb
  • 喝上一杯(或两杯)你喜欢的饮品。
  • 待 yg-export 完成后,在 Discourse 目录下查看 script/import_scripts/yahoogroup.rb。将其中的 MONGODB_HOST 修改为正确的地址(localhost)。
  • 在 discourse 目录下运行 bundle exec ruby script/import_scripts/yahoogroup.rb
  • 验证导入是否正确
  • 备份并恢复到线上服务器

步骤 2 至 4 是推断得出的。但上述步骤看起来正确吗?我认为是正确的,于是便继续操作。前四步运行顺利——yg-export.rb 运行了大约一小时,报告所有操作均成功,并导入了约 38,000 条消息。此时 syncro 数据库已存在,其中包含约 85MB 的数据。在那之后,我对虚拟机进行了快照。

不过,我在导入脚本上遇到了问题。当我运行 bundle exec ruby script/import_scripts/yahoogroup.rb 时,出现以下错误:

dan@ubuntu:~/discourse$ bundle exec ruby script/import_scripts/yahoogroup.rb
Traceback (most recent call last):
script/import_scripts/yahoogroup.rb: Bootsnap::LoadPathCache::FallbackScan
        7: from script/import_scripts/yahoogroup.rb:4:in `<main>'
        6: from /home/dan/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/zeitwerk-2.1.10/lib/zeitwerk/kernel.rb:23:in `require'
        5: from /home/dan/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:26:in `require'
        4: from /home/dan/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:40:in `rescue in require'
        3: from /home/dan/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21:in `require_with_bootsnap_lfi'
        2: from /home/dan/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/loaded_features_index.rb:89:in `register'
        1: from /home/dan/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `block in require_with_bootsnap_lfi'
/home/dan/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `require': cannot load such file -- mongo (LoadError)

奇怪,我以为我已经安装了 mongo gem。好吧,我再装一次:

dan@ubuntu:~/discourse$ gem install mongo
Successfully installed mongo-2.10.2
Parsing documentation for mongo-2.10.2
Done installing documentation for mongo after 4 seconds
1 gem installed

再次运行导入脚本,结果相同。是否需要在系统级别安装呢?

dan@ubuntu:~/discourse$ sudo apt install ruby-mongo
[sudo] password for dan: 
Reading package lists... Done
Building dependency tree       
Reading state information... Done
ruby-mongo is already the newest version (2.5.1-1).
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.

真是越来越奇怪了……

您需要将 gem 添加到 Gemfile 并运行 bundle install,否则运行 bundle exec ruby script/import_scripts/yahoogroup.rb 时将找不到这些 gem。

这正是我遗漏的部分,现在导入过程顺利进行了。谢谢!

编辑:好的,导入过程运行了 75 分钟,帖子现在已经出现了。太棒了。它还创建了用户,这正是我之前疑惑的地方。不过,我发现了用户方面的几个问题:

  • 看起来所有 Yahoo 用户名都正确导入了(我在该列表的成员身份中认出了许多),但它们却关联到了错误的消息。这种情况非常一致——所有由我发布的帖子现在都显示为另一位用户发布的——但这仍然是一个严重的错误,手动清理会非常麻烦。
  • 所有导入的用户都被禁用了 200 年。

我怀疑这两个问题都源于从 Yahoo 下载的数据中缺乏有效的电子邮件地址。因为我不是群组管理员,所以数据中缺少邮箱地址。官方明确说明这是导致后者问题的原因,但我不确定这是否也会导致前者问题。有什么想法吗?

如果问题确实如此,那我就有方向去排查了,但同时也存在一个潜在问题:我知道该群组有两位现任版主,但群主在过去一年内已经去世。希望有人能访问到相关信息……

你好,

有一个新的插件可以解决这个问题。

我已使用以下脚本,将开发版本中的所有 Yahoo! 消息导出并导入为一个类别:
https://meta.discourse.org/t/topic-and-category-export-import/38930/38

现在出现了重复用户或错误用户的问题。
使用此插件:Merge Users Plugin
您可以轻松地将 Yahoo 用户与您的 Discourse 用户合并。

现在我们只剩下 Yahoo 附件的问题了。

这并不能完全满足我的需求——除了管理员用户外,该实例上现有的用户都是从 Yahoo 导入的。问题在于错误的用户名关联到了错误的帖子:我的帖子(据我所知始终如此)被关联到了他人的用户名下,而他人的帖子则被关联到了我的用户名下。

我现在已获得该群组的管理员权限,这足以下载附带真实电子邮件地址的消息。我将重新构建(虚拟机对此非常有用),重新执行导入操作,看看是否能解决该问题。

嘿,好的,这和我发现的情况不太一样。在我的社区中,所有导入的帖子都没有关联到任何现有社区成员。
如果按照链接主题中描述的方式导入分类,那么用户列表中只会看到重复的用户(或者新用户)。

导入器将帖子分配给了错误的用户,但它的操作方式是正确的。我的意思是,如果我在 Yahoo! 上自己写的帖子被分配给了“Hans”,那么我所有的帖子都会被分配给“Hans”。

在我的社区论坛中,我的用户 ID 是 1,但这与我为开发设置的 Discourse 论坛中的用户名并不相同。因此,我的账户没有被覆盖,而是存在另一个同名账户。但这个账户却关联了错误的帖子。

现在我使用链接的插件,将各个用户单独合并到论坛中对应的正确人员名下。这并不耗时,但很难弄清楚哪些帖子属于哪个用户。

我们可能处于不同的情况——就我而言,并没有“现有社区”。相反,所有内容都是从 Y 群组导入的。

好的,我已经下载了包含完整电子邮件地址的群组消息。在解决了一些开发环境问题后,我重新开始了导入工作。不过,我仍然注意到以下几个问题:

  • 用户名与帖子错误关联的问题依然存在。
  • 可能是上述问题的原因(或其中一个原因),大多数被导入的用户被判定为电子邮件地址无效。在由 yahoo-export 脚本生成的 Mongo 数据库中,From 字段(导入脚本似乎试图从中读取电子邮件地址)对大多数用户的显示格式如下:
First Last &lt;user@domain.com&gt;

……Discourse 将其视为无效的电子邮件地址并予以拒绝。因此,大多数用户被分配了一个类似 5dc3e1b4f4d821bd7de3ce456eaf26d5@email.invalid 的电子邮件地址——例外情况似乎仅限于那些发送邮件时未附带全名的用户。

  • 导入的消息中包含大量 HTML 实体,尤其是引号以及大于号和小于号。

  • 许多(但并非全部)导入的消息的主题行中包含群组名称,例如:Re: [SpareOom] 某个主题。如果能将其移除就太好了。
    针对最后三点,我在想是否可以通过对整个数据库进行简单的查找/替换来解决——如果是的话,具体该如何操作?毕竟我从未接触过 MongoDB。

  • 另一个问题是关于将消息导入到指定分类。yahoogroup.rb 文件顶部的注释提到,可以在运行脚本前执行 export CATEGORY_ID=<CATEGORY_ID> 来实现此功能,但并未说明 <CATEGORY_ID> 具体指代什么。我尝试过使用分类的常规名称以及“分类 slug”(两者仅大小写不同),但在这两种情况下,导入脚本均报错失败:

         1: from /home/dan/discourse/lib/topic_creator.rb:36:in `create'
/home/dan/discourse/lib/topic_creator.rb:115:in `setup_topic_params': category (Discourse::InvalidParameters)

这听起来很像我第一次导入 mbox 的情况,花了我几个月时间。

是的,你大概可以通过一些替换操作修复一些问题。

如果你在分类 URL 的末尾加上 .json,就能找到分类 ID。它是一个整数。

你需要查看用户创建者使用的标识符,以及帖子函数在查找用户时使用的标识符。或者,也许它们根本不匹配。

查看 yahoogroup.rb 文件,它确实期望消息中的 From 字段是纯电子邮件地址。由于大多数用户将邮件客户端配置为同时发送姓名(例如:

Fred Flintstone <fred@flintstone.com>

),这就是问题 #1。一些简单的谷歌搜索表明,可以通过使用 Mail gem 来解决这个问题,这将把导入脚本中的那一行改为:

        email: Mail::ToField.new(user_info["ygData"]["from"]), # 必填

……这样就能只提取电子邮件地址。但如上所述,尖括号是以 HTML 实体形式存储的,这会导致该方法失效。进一步的搜索告诉我,有一个 HTMLEntities gem 可以处理这个问题,于是我尝试了以下代码:

        email: Mail::ToField.new(HTMLEntities.new.decode(user_info["ygData"]["from"])), # 必填

但由于缺少 downcase 方法,这行代码失败了。

编辑:我尝试通过换一种方式避免这个问题;我看到很多关于 Nokogiri 的建议。但无论它多么有用,我找到的建议都没有解码尖括号实体,而这正是(并且仍然是)我最迫切的需求。所以我还是回到了 HTMLEntities。我在 Yahoo 导入脚本的顶部添加了 require 'mail'require 'htmlentities',并将第 75 行(在添加 require 之前是第 73 行)改为如上所示。我仍然收到错误,但我之前忽略了一点:它实际上在失败之前成功解析并导入了一个用户:

dan@ubuntu:~/discourse$ bundle exec ruby script/import_scripts/yahoogroup.rb
正在加载现有群组...
正在加载现有用户...
正在加载现有分类...
正在加载现有帖子...
正在加载现有主题...
(省略)
已连接到数据库....

正在从 Mongodb 导入...

正在导入用户
用户已创建:user@host.tld
Traceback (most recent call last):
        8: from script/import_scripts/yahoogroup.rb:163:in `<main>'
        7: from /home/dan/discourse/script/import_scripts/base.rb:47:in `perform'
        6: from script/import_scripts/yahoogroup.rb:39:in `execute'
        5: from script/import_scripts/yahoogroup.rb:58:in `import_users'
        4: from /home/dan/discourse/script/import_scripts/base.rb:247:in `create_users'
        3: from /home/dan/discourse/script/import_scripts/base.rb:247:in `each'
        2: from /home/dan/discourse/script/import_scripts/base.rb:259:in `block in create_users'
        1: from /home/dan/discourse/script/import_scripts/base.rb:290:in `create_user'
/home/dan/discourse/script/import_scripts/base.rb:385:in `find_existing_user': undefined method `downcase' for #<Mail::ToField:0x00005575597e63b8> (NoMethodError)

(此输出中的电子邮件地址已隐藏,但它在源数据库中确实包含完整姓名以及尖括号的实体编码——因此看来我对脚本的修改确实达到了预期效果)。这让我有些困惑,因为我原本以为 downcase 方法默认应该是可用的。

编辑 2:好吧,它确实解析了用户,但并没有真正将用户导入到 Discourse 实例中。

邮件问题仍然让我一头雾水,但我决定暂时将其搁置,先看看能否将 HTMLEntities 应用于主题标题和消息正文。在 yahoogroup.rb 脚本中,我将第 110 行改为:

        topic_title = HTMLEntities.new.decode(topic_post["ygData"]["subject"])

并将第 116 行改为:

        raw: HTMLEntities.new.decode(topic_post["ygData"]["messageBody"]),

(由于我添加了上面提到的两条 require 语句,这两个行号都比原脚本增加了 2)。效果非常完美。终端输出没有变化(那本该在第 105 行,但我直到脚本开始运行后才注意到),不过导入实例中的主题标题和文本都变得干净利落。

因此,这种方法在清理主题标题和消息正文方面似乎运行完美,但对电子邮件地址却不起作用。关于这方面,大家有什么建议我该查找什么吗?我在这方面也有些束手无策。

使用 Migrate a mailing list to Discourse (mbox, Listserv, Google Groups, etc) 从 Yahoo Groups 导入数据可能会更简单。您可以使用 mbox 文件,或者将您提到的 JSON 文件转换为包含原始邮件文本的独立 MSG 文件。

mbox 导入脚本既能处理 mbox 文件,也能处理存储在独立文件中的邮件,它可能已经解决了您当前遇到的所有问题。