Migrar uma lista de emails para Discourse (mbox, Listserv, Google Groups, etc)

Estou tentando importar um arquivo mbox padrão de uma lista de discussão, mas estou enfrentando problemas de “Processo encerrado”, geralmente após um longo período na etapa de “indexação […]mbox”. Isso se trata de um arquivo mbox grande de um projeto de código aberto com dez anos de postagens.

Coisas que já tentei:

  • Dividir o arquivo mbox em partes. Isso funcionou parcialmente, importando com sucesso muitas postagens, mas agora estou travado na indexação de uma dessas partes. Tentei dividir esse arquivo em partes também; a primeira foi importada eventualmente, e a segunda parece estar travada agora.

  • Aumentar a memória disponível no nosso servidor. O uso de memória aumenta lentamente durante a indexação e atualmente estabiliza em cerca de 16 GB (de 32 GB) para uma tentativa de importação de uma dessas partes, um arquivo mbox de 80 MB:

Durante esse tempo, 1 CPU continua no máximo.

Qualquer conselho seria muito apreciado, especialmente aumentar a verbosidade da saída de depuração, caso esteja travado em uma postagem específica. O arquivo index.db na pasta import tem cerca de 800 MB.

Sou novo em Ruby e não uso SQL regularmente, então estou achando difícil entender o que está acontecendo. Além disso, esse servidor de 32 GB é caro, então gostaria de redimensioná-lo de volta para 4 GB em breve :slight_smile:

Obrigado por qualquer ajuda!

1 curtida

Acho que o analisador fica travado em um e-mail específico nesse mbox. O index.db é um banco de dados SQLite. Examine a tabela email, filtre pelo nome do arquivo mbox na coluna filename e encontre o maior valor na coluna last_line_number. É muito provável que o analisador trave no próximo e-mail após esse número de linha dentro do arquivo mbox.

3 curtidas

Muito obrigado, @gerhard. Consegui identificar o último e-mail indexado com sucesso e o e-mail imediatamente seguinte que (presumo) esteja causando a travagem. No entanto, não parece haver nada de excepcional nesses e-mails para mim. Posso enviar-lhe esses dois e-mails de exemplo em uma mensagem privada para ver se algo salta à vista?

Claro, você pode me enviar uma mensagem privada. E tente remover esses e-mails do arquivo mbox e teste se a indexação funciona.

3 curtidas

Obrigado, enviado. Não consegui te enviar uma mensagem privada diretamente, então enviei pelo grupo da equipe. Espero que esteja tudo bem. Vou tentar remover o e-mail também e ver até onde chegamos antes de outro travamento.

1 curtida

Obrigado pelos e-mails. Não vi nada fora do comum e, nos meus testes, funcionou sem problemas.

Isso não foi testado, mas você pode tentar aplicar o seguinte patch do git antes de executar o script de importação. Ele adiciona um tempo limite de 60 segundos para o processamento de um e-mail. Isso pode ajudá-lo a identificar o culpado e seguir em frente, caso afete apenas algumas mensagens.

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 curtidas

Muito obrigado, @gerhard, seu patch está funcionando perfeitamente. Para os meus objetivos, acho que pular as mensagens problemáticas é aceitável, já que há apenas uma quantidade pequena delas. No entanto, agora temos uma saída adicional, caso seja útil para resolver o problema ou para tornar o script de importação mais robusto:

Falha ao indexar mensagem em /shared/import/data/lammps-users/chunk_10.mbox nas linhas 726814-729353
execução expirada
["/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>'"]

Como antes, posso compartilhar a mensagem específica se for útil — desta vez, a mensagem de erro me fornece os números das linhas específicas, então podemos ter pelo menos alta confiança de que identificamos a mensagem correta.

4 curtidas

Claro, por favor, compartilhe as mensagens comigo e eu darei uma olhada. Se houver um problema que possamos corrigir, isso não apenas melhorará o importador, mas também o próprio Discourse, já que ele usa o mesmo analisador para e-mails recebidos.

1 curtida

Tenho executado esse script diariamente nos últimos meses para um site que realmente precisa mudar para a assinatura da categoria ao grupo, mas isso ainda não foi feito. Funciona bem, exceto que, de vez em quando, preciso obter um novo arquivo cookies.txt. Cerca de um mês atrás, algo aconteceu e ele começou a reclamar: “Parece que você não tem permissão para ver endereços de e-mail. Abortando.” Fiz… alguma coisa… e voltou a funcionar. Há pouco mais de uma semana, aconteceu novamente, e eu baixe os cookies novamente com vários navegadores/plugins de cookies, mas continuo recebendo a versão das postagens sem e-mails. Consigo ver os endereços quando faço login pelo navegador web.

Alguém mais teve problemas ultimamente? Alguma ideia sobre o que fazer? Tentei brincar com quais domínios estão na chamada add_cookies no script, mas isso não ajudou.

1 curtida

Bem, estou olhando para isso novamente e parece que links como

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

antes incluíam os endereços de e-mail completos, mas agora não incluem mais. Posso confirmar que, quando estou logado, posso clicar em “sobre” e ver os endereços de e-mail completos na interface web do Google Groups, mas se eu acessar o URL acima que o script de raspagem está acessando no mesmo navegador, obtenho os dados com endereços de e-mail ocultados.

Minha suposição é que eles aumentaram a privacidade ou algo assim.

Aqui está outra pista: posso abrir esse link no meu navegador e ele funciona, mas se eu pegar o “copiar como cURL”, o comando curl não está obtendo os endereços de e-mail. Suspiro. Bem, tentei com outro navegador e o comando curl funcionou. Não consigo entender exatamente por que o script não está obtendo os endereços de e-mail.

Então, talvez haja alguma outra coisa específica do navegador que ele esteja fazendo?

Eu não testei recentemente, então é possível que tenham ocorrido alterações que o scraper não consegue lidar no momento.

@riking notou que o Google Takeout exporta arquivos mbox para proprietários de grupos, então isso pode ser uma opção para verificar.

5 curtidas

Obrigado. Bem, há uma semana funcionou para um segundo site e, em seguida, atualizei meu arquivo de cookies mais uma vez e ele baixou os dados do primeiro site. Parece que só funcionou por um ou dois dias, e agora novamente não está funcionando. Para nenhum dos dois sites. Vejo o endereço de e-mail completo no meu navegador, baixo os cookies para aquela aba e nada acontece.

Vou verificar o takout. EDIÇÃO: Bem, para obter o arquivo mbox, parece que você precisa ser um superadministrador, não apenas um proprietário.

4 curtidas

Uma ferramenta de linha de comando para converter uma lista de discussão do Mailman2 (ou seja, o conteúdo do config.pck com opções, membros, moderadores, flags privadas ou públicas etc.) em uma categoria do Discourse está disponível aqui: Client Challenge

1 curtida

@gerhard alguma ideia de como adaptar essas instruções para usar uma instalação de desenvolvimento em vez da instalação padrão? Sinto que cheguei perto de fazer uma migração de listserv funcionar usando apenas alguns comandos, mas não consigo fazer o que imagino ser o último passo funcionar com nenhum dos seguintes:

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

Ambos falham ao carregar todas as dependências. Veja aqui o conjunto completo de comandos que usei e os erros. Alguma ideia? Estaria faltando alguma chamada de d/bundle talvez?

A próxima coisa que vou tentar é usar uma VM Ubuntu e fazer uma “instalação padrão” lá, mas isso parece um pouco exagerado, já que a instalação dev funciona muito bem de outro modo.

Sou um total iniciante em discourse (e ruby, e principalmente docker), então desculpe se isso for óbvio ou (pior) irrelevante!

3 curtidas

Parece que você já descobriu a maior parte.

Nunca tentei isso com a instalação de desenvolvimento baseada no Docker, mas acho que você precisa adicionar "gem 'sqlite3'" ao Gemfile e executar apt install -y libsqlite3-dev dentro do container antes de rodar d/bundle install.

Depois disso, bundle exec ruby ... deve funcionar.

3 curtidas

@gerhard obrigado pelo empurrãozinho suave – executei tudo do zero absoluto (git clone em diante) adicionando gem 'sqlite3' ao final de /src/Gemfile, já que assumi que era esse o que você mencionou, e funcionou! Para registro, aqui estão as instruções que usei (para a lista de e-mails mne_analysis):

1. No host 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  # adicione gem 'sqlite3' ao final
exit
d/bundle

2. No shell do Docker

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  # sem efeito para mim

# verificar resultados
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

# configurações
wget https://raw.githubusercontent.com/discourse/discourse/master/script/import_scripts/mbox/settings.yml -O /shared/import/settings.yml

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

Isso gerou bastante saída informativa e, no final:

...
Atualizando tópicos em destaque nas categorias
        5 / 5 (100.0%)  [6890 itens/min]   ]  
Redefinindo contadores de tópicos


Concluído (00h 06min 21s)

Em seguida, saindo e no host Ubuntu:

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

Pronto!

Provavelmente vou ajustar as configurações para remover o prefixo [Mne_analysis], mas estou muito feliz por já estar funcionando tão bem!

4 curtidas

@gerhard O seu importador de mbox pode ser usado apenas na instalação inicial do Discourse ou também pode ser utilizado posteriormente, quando outros usuários já estiverem usando o Discourse? Se o importador for utilizado enquanto o Discourse já está em uso por outras pessoas, elas notarão algum efeito colateral?

1 curtida

Para fazer o importador coletar mensagens do Google Groups, precisei reverter essa alteração em /script/import_scripts/google_groups.rb
https://review.discourse.org/t/fix-google-groups-import-changed-login-url-9432/10615
Coloquei a linha

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

de volta para

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

Caso contrário, eu receberia a seguinte mensagem toda vez:

Logando...
Falha no login. Verifique o conteúdo do seu cookies.txt
6 curtidas

@gerhard Notei que, após a importação, embora as mensagens pareçam corretas, não há nenhum usuário em estágio, mesmo que pareça que deveria haver (usei o padrão staged: true). As saídas são assim:

...
indexando respostas e usuários

criando categorias
        1 / 1 (100.0%)  [13440860 itens/min]  
criando usuários

criando tópicos e postagens
     7399 / 7399 (100.0%)  [1421 itens/min]     
...

Deveria haver também um contador de usuários exibido?

Também tentei executar com staged: false e a mesma saída foi mostrada, e nenhum dos usuários da lista de e-mails está em algum grupo. Caso ajude ver o que está sendo realmente processado, aqui está um dos muitos arquivos .mbox que está sendo importado:

2020-December.zip (49,5 KB)

A única configuração não padrão foi adicionar:

tags:
  "Mne_analysis": "mne_analysis"

Seria ótimo ter esses usuários aparecendo como em estágio, para que possam reivindicar suas postagens antigas ao se cadastrar. Então, qualquer dica ou ideia é muito bem-vinda!

1 curtida

Provavelmente deveria aceitar apenas ambos?