将邮件列表迁移到 Discourse(mbox、Listserv、Google Groups 等)」

我尝试导入来自邮件列表的标准 mbox 转储文件,但遇到了“进程被终止”的问题,通常是在长时间进行“索引 […]mbox”步骤后发生。该文件是一个大型 mbox 文件,来自一个拥有十年帖子的开源项目。

我已尝试以下方法:

  • 将 mbox 文件拆分为多个块。这曾部分奏效,成功导入了许多帖子,但我现在卡在其中一个块的索引阶段。我尝试将该文件再次拆分,第一个块最终成功导入,而第二个块现在似乎已停滞。

  • 增加服务器的可用内存。在索引过程中,内存使用量缓慢上升,目前针对其中一个 80 MB 的 mbox 块进行导入尝试时,内存使用量稳定在约 16 GB(总容量为 32 GB):

在此期间,一个 CPU 核心持续处于满载状态。

非常希望能得到任何建议,特别是关于如何增加调试输出的详细程度,以便确认是否卡在某个特定帖子上。import 文件夹中的 index.db 文件大小约为 800 MB。

我是 Ruby 新手,也不常使用 SQL,因此很难弄清楚问题所在。另外,这台 32 GB 的服务器成本很高,所以我希望能尽快将其规格降级回 4 GB :slight_smile:

感谢任何帮助!

1 个赞

我猜解析器在某个特定的邮件上卡住了。index.db 是一个 SQLite 数据库。请查看 email 表,根据 filename 列中的 mbox 文件名进行筛选,并找出 last_line_number 列中的最大值。解析器很可能在 mbox 文件中该行号之后的下一封邮件处卡住。

3 个赞

非常感谢 @gerhard,我已经成功识别出最后一条成功索引的邮件,以及紧随其后(我假设)导致卡住的那封邮件。不过,在我看来,这两封邮件似乎并没有什么特别之处。我可以通过私信将这两封示例邮件发给您,看看是否有什么异常,这样方便吗?

当然,你可以给我发私信。另外,试着从 mbox 文件中移除那些邮件,然后测试一下索引是否正常工作。

3 个赞

谢谢,已发送。我无法直接私信您,所以通过团队群组发送了,希望这样可以。我还会尝试移除邮箱,看看在再次卡住之前能推进到什么程度。

1 个赞

谢谢您的邮件。我没有发现任何异常情况,在我的测试中也运行正常。

此补丁尚未经过测试,但您可以在运行导入脚本之前尝试应用以下 git 补丁。它为解析邮件添加了 60 秒的超时限制。如果问题仅影响少数几条消息,这或许能帮助您找出原因并继续处理。

From 92efb4fc68724cfa20d5de48ba33b99c126a3a08 Mon Sep 17 00:00:00 2001
From: Gerhard Schlager
Date: Fri, 2 Oct 2020 17:27:39 +0200
Subject: [PATCH] Add timeout for parsing email in mbox importer

---
 script/import_scripts/mbox/support/indexer.rb | 14 +++++++++-----
 1 file changed, 9 insertions(+), 5 deletions(-)

diff --git a/script/import_scripts/mbox/support/indexer.rb b/script/import_scripts/mbox/support/indexer.rb
index dc6e092c29..01523dad13 100644
--- a/script/import_scripts/mbox/support/indexer.rb
+++ b/script/import_scripts/mbox/support/indexer.rb
@@ -65,11 +65,15 @@ module ImportScripts::Mbox
     def index_emails(directory, category_name)
       all_messages(directory, category_name) do |receiver, filename, opts|
         begin
-          msg_id = receiver.message_id
-          parsed_email = receiver.mail
-          from_email, from_display_name = receiver.parse_from_field(parsed_email)
-          body, elided, format = receiver.select_body
-          reply_message_ids = extract_reply_message_ids(parsed_email)
+          msg_id = parsed_email = from_email = from_display_name = body = elided = format = reply_message_ids = nil
+
+          Timeout.timeout(60) do
+            msg_id = receiver.message_id
+            parsed_email = receiver.mail
+            from_email, from_display_name = receiver.parse_from_field(parsed_email)
+            body, elided, format = receiver.select_body
+            reply_message_ids = extract_reply_message_ids(parsed_email)
+          end
 
           email = {
             msg_id: msg_id,
-- 
2.28.0
3 个赞

非常感谢 @gerhard,你的补丁运行得非常完美。就我的用途而言,跳过这些有问题的消息是可以接受的,因为数量很少。不过,我们现在有了额外的输出,如果这有助于解决问题或使导入脚本更加健壮,那将很有帮助:

Failed to index message in /shared/import/data/lammps-users/chunk_10.mbox at lines 726814-729353
execution expired
["/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/nokogumbo-2.0.2/lib/nokogumbo/html5.rb:243:in `escape_text'",
"/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/nokogumbo-2.0.2/lib/nokogumbo/html5.rb:214:in `serialize_node_internal'",
"/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/nokogumbo-2.0.2/lib/nokogumbo/html5/node.rb:58:in `write_to'",
"/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/nokogiri-1.10.10/lib/nokogiri/xml/node.rb:699:in `serialize'",
"/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/nokogiri-1.10.10/lib/nokogiri/xml/node.rb:855:in `to_format'",
"/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/nokogiri-1.10.10/lib/nokogiri/xml/node.rb:711:in `to_html'",
"/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/nokogumbo-2.0.2/lib/nokogumbo/html5/node.rb:28:in `block in inner_html'",
"/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/nokogiri-1.10.10/lib/nokogiri/xml/node_set.rb:238:in `block in each'",
"/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/nokogiri-1.10.10/lib/nokogiri/xml/node_set.rb:237:in `upto'",
"/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/nokogiri-1.10.10/lib/nokogiri/xml/node_set.rb:237:in `each'",
"/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/nokogumbo-2.0.2/lib/nokogumbo/html5/node.rb:28:in `map'",
"/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/nokogumbo-2.0.2/lib/nokogumbo/html5/node.rb:28:in `inner_html'",
"/var/www/discourse/lib/html_to_markdown.rb:74:in `block (2 levels) in hoist_line_breaks!'",
"/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/nokogiri-1.10.10/lib/nokogiri/xml/node_set.rb:238:in `block in each'",
"/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/nokogiri-1.10.10/lib/nokogiri/xml/node_set.rb:237:in `upto'",
"/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/nokogiri-1.10.10/lib/nokogiri/xml/node_set.rb:237:in `each'",
"/var/www/discourse/lib/html_to_markdown.rb:57:in `block in hoist_line_breaks!'",
"/var/www/discourse/lib/html_to_markdown.rb:54:in `loop'",
"/var/www/discourse/lib/html_to_markdown.rb:54:in `hoist_line_breaks!'",
"/var/www/discourse/lib/html_to_markdown.rb:16:in `initialize'",
"/var/www/discourse/lib/email/receiver.rb:387:in `new'",
"/var/www/discourse/lib/email/receiver.rb:387:in `select_body'",
"/var/www/discourse/script/import_scripts/mbox/support/indexer.rb:74:in `block (2 levels) in index_emails'", 
"/usr/local/lib/ruby/2.6.0/timeout.rb:108:in `timeout'",
"/var/www/discourse/script/import_scripts/mbox/support/indexer.rb:70:in `block in index_emails'", 
"/var/www/discourse/script/import_scripts/mbox/support/indexer.rb:139:in `block (2 levels) in all_messages'",
"/var/www/discourse/script/import_scripts/mbox/support/indexer.rb:171:in `block in each_mail'", 
"/var/www/discourse/script/import_scripts/mbox/support/indexer.rb:190:in `block in each_line'",
 "/var/www/discourse/script/import_scripts/mbox/support/indexer.rb:189:in `each_line'", 
"/var/www/discourse/script/import_scripts/mbox/support/indexer.rb:189:in `each_line'", 
"/var/www/discourse/script/import_scripts/mbox/support/indexer.rb:166:in `each_mail'", 
"/var/www/discourse/script/import_scripts/mbox/support/indexer.rb:132:in `block in all_messages'", 
"/var/www/discourse/script/import_scripts/mbox/support/indexer.rb:125:in `foreach'", 
"/var/www/discourse/script/import_scripts/mbox/support/indexer.rb:125:in `all_messages'", 
"/var/www/discourse/script/import_scripts/mbox/support/indexer.rb:66:in `index_emails'", 
"/var/www/discourse/script/import_scripts/mbox/support/indexer.rb:25:in `block in execute'", 
"/var/www/discourse/script/import_scripts/mbox/support/indexer.rb:22:in `each'", 
"/var/www/discourse/script/import_scripts/mbox/support/indexer.rb:22:in `execute'", 
"/var/www/discourse/script/import_scripts/mbox/importer.rb:43:in `index_messages'", 
"/var/www/discourse/script/import_scripts/mbox/importer.rb:27:in `execute'", 
"/var/www/discourse/script/import_scripts/base.rb:47:in `perform'", 
"script/import_scripts/mbox.rb:12:in `<module:Mbox>'", 
"script/import_scripts/mbox.rb:10:in `<module:ImportScripts>'", 
"script/import_scripts/mbox.rb:9:in `<main>'"]

和之前一样,如果需要的话,我可以提供具体的消息内容——这次错误信息给出了具体的行号,因此我们至少可以高度确信已经定位到了正确的消息。

4 个赞

好的,请把消息发给我,我会查看一下。如果发现问题,我们修复它不仅会改进导入器,也会改进 Discourse 本身,因为它使用相同的解析器来处理传入的邮件。

1 个赞

过去几个月里,我一直在为某个网站每日运行此脚本。该网站确实需要将类别订阅到群组,但尚未完成此设置。脚本运行基本正常,只是每隔一段时间就需要获取新的 cookies.txt 文件。大约一个月前,发生了某些情况,脚本开始报错:“看起来您没有权限查看电子邮件地址。正在中止。”我做了些操作后,它又开始正常工作了。就在上周,同样的问题再次出现。我使用多个浏览器和 Cookie 插件重新下载了 Cookie,但获取到的帖子内容始终是不显示邮箱地址的版本。而在浏览器中登录时,我确实可以看到这些地址。

最近有人也遇到过类似问题吗?有什么解决建议吗?我曾尝试调整脚本中 add_cookies 调用所包含的域名,但并未奏效。

1 个赞

好吧,我又看了一遍,发现像这样的链接:

https://groups.google.com/forum/message/raw?msg=GROUP_NAME/THREAD_ID/POST_ID

以前会包含完整的电子邮件地址,但现在不行了。我可以确认,当我登录时,点击“关于”可以在 Google Groups 网页界面中看到完整的电子邮件地址;但如果我在同一个网页浏览器中访问上述 URL(也就是抓取脚本正在访问的链接),获取到的数据中的电子邮件地址却被隐藏了。

我猜他们可能是增强了隐私保护之类的。

再提供一个线索:我在浏览器中打开该链接可以正常工作,但如果我复制为 cURL 命令并执行,却得不到电子邮件地址。 唉。好吧,我换了一个浏览器测试,cURL 命令确实能正常工作。我还不太明白为什么脚本获取不到电子邮件地址。

所以,也许这是某种特定于浏览器的行为?

我最近没有尝试过,所以有可能出现了一些当前爬虫无法处理的变更。

@riking 注意到 Google Takeout 可以为群组所有者导出 mbox 文件,这或许是一个值得尝试的选项。

5 个赞

谢谢。嗯,一周前它在另一个网站上还能用,后来我再次更新了 cookie 文件,它就下载了第一个网站的数据。但似乎只成功了一两天,现在又不行了,两个网站都是如此。我在浏览器里能看到完整的邮箱地址,也下载了该标签页的 cookie,但毫无效果。

我会去查看 takout。编辑:看来要获取 mbox 文件,需要是超级管理员,而不仅仅是所有者。

4 个赞

这是一个命令行工具,可将 Mailman2 邮件列表(即 config.pck 的内容,包含 选项、成员、版主、私有或公开标志等)转换为 Discourse 分类。该工具可在以下地址获取:Client Challenge

1 个赞

@gerhard 有什么建议可以将这些说明调整为使用 开发环境安装,而不是标准安装吗?我觉得自己已经接近通过几条命令成功完成 listserv 迁移了,但无论使用以下哪种方式,我都无法完成我认为是最后一步的操作:

ruby /src/script/import_scripts/mbox.rb ~/import/settings.yml
bundle exec ruby /src/script/import_scripts/mbox.rb /home/discourse/import/settings.yml

两者都无法拉取所有依赖项。参见此处 以查看我使用的所有命令及错误信息。有什么建议吗?是否缺少了一些 d/bundle 调用?

接下来我打算尝试使用 Ubuntu 虚拟机并在那里进行“标准安装”,但考虑到 dev 安装在其他方面运行得相当不错,这样做似乎有点大材小用。

我完全是 discourse(以及 ruby,还有大部分 docker)的新手,所以如果这个问题显而易见,或者(更糟糕的是)完全不相关,还请见谅!

3 个赞

看起来你已经解决了大部分问题。

我从未尝试过在基于 Docker 的开发环境中进行此操作,但我想你需要在 Gemfile 中添加 gem 'sqlite3',并在容器内执行 apt install -y libsqlite3-dev,然后再运行 bundle install

之后,bundle exec ruby ... 应该就能正常工作了。

3 个赞

@gerhard 感谢你的温和提醒——我从头开始重新运行(从 git clone 开始),并在 /src/Gemfile 末尾添加了 gem 'sqlite3',因为我猜这就是你提到的那个,结果成功了!供参考,以下是我使用的步骤(针对 mne_analysis 邮件列表):

1. 在 Ubuntu 主机上

git clone https://github.com/discourse/discourse.git
cd discourse
d/boot_dev --init
d/rails db:migrate RAILS_ENV=development
d/shell
vim /src/Gemfile  # 在末尾添加 gem 'sqlite3'
exit
d/bundle

2. 在 Docker shell 中

sudo mkdir -p /shared/import/data
sudo chown -R discourse:discourse /shared/import
wget -r -l1 --no-parent --no-directories "https://mail.nmr.mgh.harvard.edu/pipermail//mne_analysis/" -P /shared/import/data/mne_analysis -A "*-*.txt.gz"
rm /shared/import/data/mne_analysis/robots.txt.tmp
gzip -d /shared/import/data/mne_analysis/*.txt.gz
wget https://gist.githubusercontent.com/larsoner/940cd6c7100b87c4c5668cb0bc540afb/raw/9e78513620d11355ad0e10f4a2470996c26ebc8c/mailmanToMBox.py -O ~/mailmanToMBox.py
python3 ~/mailmanToMBox.py /shared/import/data/mne_analysis/
rm /shared/import/data/mne_analysis/*.txt
sudo apt install -y libsqlite3-dev  # 对我而言无操作

# 检查结果
cat /shared/import/data/mne_analysis/*.mbox > ~/all.mbox
sudo apt install -y procmail
mkdir -p ~/split
export FILENO=0000
formail -ds sh -c 'cat > ~/split/msg.$FILENO' < ~/all.mbox
rm -rf ~/split ~/all.mbox

# 设置
wget https://raw.githubusercontent.com/discourse/discourse/master/script/import_scripts/mbox/settings.yml -O /shared/import/settings.yml

# 运行
cd /src
bundle exec ruby script/import_scripts/mbox.rb /shared/import/settings.yml

这输出了很多有用的信息,最后显示:

...
正在更新分类中的特色主题
        5 / 5 (100.0%)  [6890 项/分钟]   ]  
重置主题计数器


完成 (00 小时 06 分 21 秒)

然后退出并在 Ubuntu 主机上执行:

d/unicorn &
google-chrome http://0.0.0.0:9292

完成!

我可能会调整一下设置,去掉 [Mne_analysis] 前缀,但让我非常高兴的是,它已经运行得如此顺利!

4 个赞

@gerhard 您的 mbox 导入器是只能在首次安装 Discourse 时使用,还是可以在其他用户已经开始使用 Discourse 之后使用?如果在 Discourse 已被他人使用时运行导入器,他们是否会受到任何副作用影响?

1 个赞

为了让导入器能够从 Google Groups 抓取消息,我必须在 /script/import_scripts/google_groups.rb 中撤销此更改:
https://review.discourse.org/t/fix-google-groups-import-changed-login-url-9432/10615
我将这一行:

    wait_for_url { |url| url.start_with?("https://accounts.google.com") }

改回:

    wait_for_url { |url| url.start_with?("https://myaccount.google.com") }

否则,每次都会出现以下消息:

正在登录...
登录失败。请检查您的 cookies.txt 文件内容
6 个赞

@gerhard 我注意到导入后,虽然消息看起来正常,但完全没有“待处理用户”(staged users),即使看起来应该有的(我使用了默认的 staged: true)。输出如下:

...
正在索引回复和用户

正在创建分类
        1 / 1 (100.0%)  [13440860 项/分钟]  
正在创建用户

正在创建主题和帖子
     7399 / 7399 (100.0%)  [1421 项/分钟]     
...

这里应该也显示用户计数器吗?

我还尝试使用 staged: false 运行,输出了相同的内容,且没有任何邮件列表用户被分配到任何群组。如果查看实际处理的内容有帮助,以下是正在导入的众多 .mbox 文件之一:

2020-December.zip (49.5 KB)

唯一的非默认设置是添加了:

tags:
  "Mne_analysis": "mne_analysis"

如果能将这些用户显示为“待处理”状态就太好了,这样他们在注册时可以认领旧帖子。任何提示或建议都非常欢迎!

1 个赞

可能应该同时接受这两个选项吧?