Skip to content
This repository has been archived by the owner on Oct 2, 2023. It is now read-only.

FileNotFoundError: [Errno 2] No such file or directory: '/code/data/tmp/5368pmfscp0012021.rar' #311

Open
sentry-io bot opened this issue Mar 28, 2021 · 12 comments
Assignees
Labels
bug Something isn't working good first issue Good for newcomers

Comments

@sentry-io
Copy link

sentry-io bot commented Mar 28, 2021

Essa exceção acontece porque o Tika não consegue extrair o rar 5 (formato proprietário). Temos que pegar a exceção e cancelar o retry nesse caso.

Sentry Issue: MARIA-QUITERIA-4V

FileNotFoundError: [Errno 2] No such file or directory: '/code/data/tmp/5368pmfscp0012021.rar'
  File "dramatiq/worker.py", line 476, in process_message
    res = actor(*message.args, **message.kwargs)
  File "dramatiq/actor.py", line 145, in __call__
    return self.fn(*args, **kwargs)
  File "web/datasets/tasks.py", line 99, in content_from_file
    Path(path).unlink()
  File "pathlib.py", line 1324, in unlink
    self._accessor.unlink(self)

Failed to process message <dramatiq.brokers.rabbitmq._RabbitmqMessage object at 0x7fa0cb6d4a90> with unhandled exception.
@anapaulagomes anapaulagomes added the good first issue Good for newcomers label Mar 28, 2021
@anapaulagomes anapaulagomes added the bug Something isn't working label Jul 4, 2021
@exageraldo
Copy link
Member

Eu pensei em verificar a extensão antes de tentar executar o unlink. Cê acha que faz sentido?

Algo mais ou menos assim (nesse arquivo):

    has_rar_extension = path.endswith('.rar')
    raw = parser.from_file(path)

    if not has_rar_extension and not keep_file:
        Path(path).unlink()

@exageraldo
Copy link
Member

    has_rar_extension = path.endswith('.rar')

    if has_rar_extension:
        return

    raw = parser.from_file(path)

    if not keep_file:
        Path(path).unlink()

    if a_file:
        a_file.content = raw["content"]
        a_file.save()

    return raw["content"]

Ou algo assim, dependendo do que seja retornado pelo parser.from_file quando o arquivo é RAR.

@anapaulagomes
Copy link
Contributor

Na verdade, o melhor seria evitar pular os arquivos .rar porque eles são maioria nos arquivos compactados - se não forem todos. Perdão pela descrição da issue porque ela não reflete nada disso.

Uma alternativa seria usar alguma outra biblioteca para descompactar os arquivos e aí sim salvá-los de maneira individual. Isso traria alguns problemas, como nós salvando arquivos de corel draw etc. Mas só testando pra saber hehe

Pesquisando sobre .rar e Apache Tika vi isso aqui:

Tika uses the Commons Compress library to support various compression and packaging formats. The CompressorParser class handles parsing of the top level compression formats, then PackageParser class and its subclasses parse the packaging formats and then pass the unpacked document streams to a second parsing stage using the parser instance specified in the parse context. Formats supported include Tar, AR, ARJ, CPIO, Dump, Zip, 7Zip, Gzip, BZip2, XZ, LZMA, Z and Pack200.

Additionally, the RarParser class supports the RAR archive format, which isn't supported by Commons Compress.
https://tika.apache.org/1.17/formats.html

Talvez só tenhamos que modificar algo na configuração.

@exageraldo
Copy link
Member

@anapaulagomes tenho algumas dúvidas, cê poderia me ajudar?

  • Qual é a responsabilidade do tika no projeto? Quando rodamos o raw = parser.from_file(path), qual o conteúdo que esperamos de lá?
  • Cê acha que tem alguma outro biblioteca/pacote/serviço que podíamos dar uma olhada? É mais pra olhar/avaliar as opções e entender melhor o contexto das soluções.

Procurei um pouco na documentação da tika-pythone não encontrei muita coisa para usar outro parser (o RarParser). Nem no google achei muitos resultados. Não sei se estou procurando no canto e/ou do jeito certo. Foquei mais na biblioteca, acha que devo voltar o olhar mais na API?

@anapaulagomes
Copy link
Contributor

Claro! :)

  • O Tika é o serviço que extrai de arquivos os seus textos. Esperamos uma grande string com todo o conteúdo.
  • Atualmente o tika-python é apenas uma interface com a API REST do Tika. Acredito que seja melhor olhar na documentação do Tika como fazer e depois checaro tika-python. No pior dos casos, podemos interagir diretamente com a API.

@exageraldo
Copy link
Member

Show! Daí, quando manipulamos arquivos compactados com o Tika, ele vai saber como manipular todos os tipos de arquivos lá dentro (PDF, corel draw, txt) e nos trazer uma string com todo o conteúdo, confere?

Eu procurei dentro da documentação to tika-python, procurei no código também, mas não consegui muita coisa ainda, infelizmente. Encontrei uma issue (link) sobre uma dúvidas parecida, porem a resposta apenas sugere a leitura da documentação (de forma bem geral). ): A função from_file tem um argumento chamado requestOptions, busquei se havia alguma forma de mudar algo no header, ou em outro canto, para configurar o parser, mas também sem muito sucesso até o momento.

Encontrei o RarParser na documentação, mas não faço ideia de como fazemos para usa-lo. Tambem achei uma resposta no stackoverflow relacionado a essa questão do RAR5 (link) que comenta sobre "não haver solução disponível para descompactar arquivos RAR5 (no java)".

Estava pensando que se esse for realmente o caso, podíamos tentar descompactar usando algum pacote python (como a rarfile que suporta o RAR5, por exemplo) e mudarmos a função de processamento de parser.from_file(path) para parser.from_buffer(blob_file). Faz sentido essa abordagem? Teríamos que ver qual seria o impacto dessa mudança (tempo e memória principalmente eu acredito).

@anapaulagomes
Copy link
Contributor

Show! Daí, quando manipulamos arquivos compactados com o Tika, ele vai saber como manipular todos os tipos de arquivos lá dentro (PDF, corel draw, txt) e nos trazer uma string com todo o conteúdo, confere?

Isso mesmo.

Estava pensando que se esse for realmente o caso, podíamos tentar descompactar usando algum pacote python (como a rarfile que suporta o RAR5, por exemplo) e mudarmos a função de processamento de parser.from_file(path) para parser.from_buffer(blob_file). Faz sentido essa abordagem? Teríamos que ver qual seria o impacto dessa mudança (tempo e memória principalmente eu acredito).

Eu gosto dessa ideia! 🏆 Mas, sim, temos que ver o impacto. Pensando rápido aqui eu acredito que não seria um problema.
Obrigada, Geraldo!

@exageraldo
Copy link
Member

Feshow! Vou fazer uns testes/profilings e trago mais informações sobre a mudança.

@exageraldo
Copy link
Member

exageraldo commented Sep 16, 2021

Fazendo alguns experimentos com o tika e com bibliotecas que descompactam arquivos RAR no python (como patoolib e rarfile) pude perceber alguns pontos (interessantes talvez):

  • Acredito que a maioria das ferramentas que fazem a descompactação dos arquivos RAR são apenas wrappers/interfaces para interagir com ferramentas externas via CLI (como rar, unrar e 7z por exemplo), dai dessa forma não conseguimos pegar o conteúdo de arquivo em memória/buffer; geralmente são comandos para descompactar, listar, ler alguma coisa dentro do arquivo. Acredito que isso também tenha relação do formato RAR ser proprietário...
  • Ao enviar um arquivo RAR para o tika, não gerou nenhum erro. E quando fui ver os dados retornados, mostra que os parses utilizados, e ele configura sozinho o parser correto: ['org.apache.tika.parser.DefaultParser', 'org.apache.tika.parser.pkg.RarParser']; não precisamos configurar isso! 🥳
  • O conteúdo (content) que vem no retorno do tika depois de processar o arquivo RAR vem vazio. O parser está correto, não apresenta nenhum erro, mas aparentemente não processa nada. Fiz um teste descompactando os arquivos e enviando apenas um PDF e o conteúdo foi processado e retornado lindamente.

cenário

In [1]: from tika import parser
In [2]: rar_file = '5152pmfscp0272020.rar'
In [3]: pdf_file = '5152pmfscp0272020/teste.pdf'
In [4]: rar_parser = parser.from_file(rar_file)
In [5]: pdf_parser = parser.from_file(pdf_file)
In [6]: pdf_parser['metadata']['X-Parsed-By']
Out[6]: 
['org.apache.tika.parser.DefaultParser',
 'org.apache.tika.parser.pdf.PDFParser']
In [7]: rar_parser['metadata']['X-Parsed-By']
Out[7]: 
['org.apache.tika.parser.DefaultParser',
 'org.apache.tika.parser.pkg.RarParser']
In [8]: len(rar_parser['content'] or '')
Out[8]: 0
In [9]: len(pdf_parser['content'] or '')
Out[9]: 225739

Baixei um arquivo RAR para testar (link para o arquivo).

Uma outra ideia que tive foi:
Descompactarmos todos os arquivos em um arquivo (temporário), criarmos um novo arquivo compactado (formato ZIP), enviamos o arquivo pra processar e depois de recebermos o retorno deletarmos tudo! Fiz alguns testes e notei que se enviarmos um arquivo ZIP, até o parser é diferente, mostrando que pegou o arquivo ZIP e os PDF dentro. Tentei criar o arquivo compactado apenas em memória (com a biblioteca zipfile) mas não é identificado como válido para processar... ):

A ideia de descompactar e compactar pra enviar é pra que todos os arquivos permaneçam juntos para retirarmos um conteúdo único.

In [10]: import zipfile
    ...: 
    ...: zip_file = zipfile.ZipFile('teste.zip', 'w', zipfile.ZIP_DEFLATED)
    ...: for file_name in [f'5152pmfscp0272020/teste{num}.pdf' for num in range(1, 6)]:
    ...:     zip_file.write(file_name, file_name)
    ...: 

In [11]: zip_parser = parser.from_file('teste.zip')

In [12]: zip_parser['metadata']['X-Parsed-By']
Out[12]: 
['org.apache.tika.parser.DefaultParser',
 'org.apache.tika.parser.pkg.PackageParser',
 ['org.apache.tika.parser.DefaultParser',
  'org.apache.tika.parser.pdf.PDFParser'],
 ['org.apache.tika.parser.DefaultParser',
  'org.apache.tika.parser.pdf.PDFParser'],
 ['org.apache.tika.parser.DefaultParser',
  'org.apache.tika.parser.pdf.PDFParser'],
 ['org.apache.tika.parser.DefaultParser',
  'org.apache.tika.parser.pdf.PDFParser'],
 ['org.apache.tika.parser.DefaultParser',
  'org.apache.tika.parser.pdf.PDFParser']]

In [13]: len(zip_parser['content'] or '')
Out[13]: 574902

Os aquivos de testes foram retirados do mesmo arquivo RAR.

@exageraldo
Copy link
Member

Encontrei uma abordagem interessante no stackoverflow para converter arquivos RAR para ZIP (link).

Podemos usar a biblioteca tempfile para criar um diretorio temporario com os arquivos descompactados e depois compactarmos para ZIP e enviarmos para o tika. O que acham?

>>> import tempfile

# create a temporary directory using the context manager
>>> with tempfile.TemporaryDirectory() as tmpdirname:
...     print('created temporary directory', tmpdirname)
>>>
# directory and contents have been removed

Dessa forma, acredito que conseguimos até fazer uma lista de formatos permitidos, ou a serem ignorados, e criamos o arquivo ZIP apenas com aquilo que queremos que o tika processe.

@anapaulagomes
Copy link
Contributor

Massa, @exageraldo! Por ora, podemos converter em zip e mandar tudo mesmo. Não são muitos os que tem arquivos corel draw e outros. hahaha Obrigada pela investigação! 🥇 Taca lhe pau!

@anapaulagomes
Copy link
Contributor

Curiosidade: dos 260 mil arquivos que temos, 500 deles são .rar. Embora seja um número baixo, as licitações que tem mais arquivos tem mais itens ou são mais caras (pela minha experiência haha).

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
bug Something isn't working good first issue Good for newcomers
Projects
None yet
Development

No branches or pull requests

2 participants