Найти дубликаты файлов PDF по содержанию

9

Некоторые журналы генерируют разные PDF для каждой загрузки. APS, например, хранит время и IP-адрес в PDF.

Или есть бумажная версия с гиперссылками и одна с текстовыми ссылками.

Как можно найти повторяющиеся загрузки документов с равным содержанием на 90% в системе Linux с помощью программного обеспечения с открытым исходным кодом?

Я думал о преобразовании файлов PDF в обычный текст во временном каталоге с pdf2txt. Тогда я мог бы отфильтровать все имена файлов, что diff a bприводит к более чем х строк. Но это совсем не элегантно и потерпит неудачу с отсканированными публикациями. Журналы часто не предоставляют текст OCR для старых публикаций.

Я также пытался compareв комплекте ImageMagick, но я не мог обрабатывать многостраничные файлы PDF с этим инструментом.

diffpdf 2.1.1 хорошо работает в графическом интерфейсе для двух файлов, но я не мог понять, как применить его ко многим файлам, и последние версии не доступны ни по одной лицензии с открытым исходным кодом.

Джонас Стейн
источник
1
Поскольку среди ответов очень разные подходы, было бы хорошо быть более конкретным и прояснить вопрос. Ищете ли вы надежный способ сравнения различных PDF-файлов, включая научные статьи, или вы пытаетесь найти эффективное и элегантное решение для сравнения статей в журнале, где достаточно просто проверить, совпадают ли заголовок или DOI.
inVader
Я ищу подобное решение - теперь я использую md5, который проблематичен, когда каждая загрузка записывает время и IP в формате PDF. Я работаю над решением с imagemagick со скриптом-оберткой для циклического перемещения по страницам (и, возможно, пытаюсь пропустить первую страницу в случае, если это заголовок, добавленный журналом). Я очень уверен, что это самое надежное решение . Вы знаете, что это будет работать очень хорошо, потому что это тот же метод, который используется человеком при визуальном сравнении двух документов. Он также полностью независим от способа генерации документа, только от его внешнего вида.
Орион
Я также сказал бы, что сравнения одной страницы, вероятно, достаточно - вряд ли два документа различаются, если одна страница одинакова. Запись blah.pdf[1]будет вызывать нужную страницу из документа.
Орион
Если вам действительно нужно сравнить PDF-файлы, где один или оба основаны на сканировании, я думаю, что вы не можете избежать использования OCR. Поэтому многие из предложенных подходов не решают проблему.
gogoud

Ответы:

4

Поскольку разные издатели используют разные методы «маркировки» PDF-файлов, вам необходимо убедиться, что вы сравниваете их без учета маркировки.

Вам также нужен эффективный метод для сравнения нового PDF со всеми уже загруженными PDF-файлами в случае, если вы повторно загружаете один и тот же PDF-файл, и, например, он помечен IP-адресом и / или отметкой даты и времени, как вы предлагаете. Вы не хотите использовать трудоемкий механизм сравнения, который сравнивает каждый новый PDF со многими уже загруженными PDF

Вам нужна утилита, которая удаляет каждую из возможных меток и генерирует хэш оставшихся данных. Вам нужно будет сохранить карту хэша → имя файла, которая может быть в простом файле, и если вычисленный хэш уже есть в файле, у вас есть дубликат (и удалить его или сделать все необходимое), и если хеш еще не существует там вы добавляете хеш и имя файла. Файл будет выглядеть примерно так:

6fcb6969835d2db7742e81267437c432  /home/anthon/Downloads/explanation.pdf
fa24fed8ca824976673a51803934d6b9  /home/anthon/orders/your_order_20150320.pdf

Этот файл небрежно мал по сравнению с оригинальными PDF-файлами. Если у вас есть миллионы PDF-файлов, вы можете сохранить эти данные в базе данных. Для эффективности вы можете включить размер файла и количество страниц ( pdfinfo | egrep -E '^Pages:' | grep -Eo '[0-9]*').


Вышесказанное выдвигает проблему удаления меток и создания хэша. Если вы знаете, откуда берется PDF, когда вызываете подпрограмму генерации хеша (то есть, если вы загружаете программно), вы можете точно настроить генерацию хеша, основываясь на этом. Но даже без этого есть несколько возможностей для генерации хеша:

  1. если метаданные заголовка и автора не пустые и не включают в себя неспецифические строки, такие как «Acrobat» или «PDF», вы можете сгенерировать хеш на основе только информации об авторе и заголовке. Используйте, pdfinfo -E file.pdf | grep -E '^(Author:)|(Title:) | md5sumчтобы получить хэш. Вы можете включить количество страниц в расчет хэша (' Pages:' в pdfinfoвыводе).
  2. Если предыдущее правило не работает и PDF-файл содержит изображения, извлеките изображения и сгенерируйте хэш для объединенных данных изображений. Если изображения когда-либо содержат текст в нижнем колонтитуле или верхнем колонтитуле, например «Лицензия для пользователя Joe», перед вычислением хэша удалите количество строк X сверху или снизу. Если эта маркировка выделена каким-то большим серым фоновым текстом с буквами, это, конечно, не сработает, если только вы не отфильтруете пиксели, которые не являются полностью черными (для этого вы можете использовать imagemagick). Вы можете использовать pdfimagesдля извлечения информации изображения во временный файл.
  3. если предыдущие правила не работают (потому что нет изображений), вы можете использовать, pdftextчтобы извлечь текст, отфильтровать маркировку (если вы отфильтровываете немного, это не проблема), а затем сгенерировать хеш на основе который.

Кроме того, вы можете сравнить размер файла старого файла, найденного с помощью хэша, и посмотреть, находится ли он в пределах определенных полей с новым файлом. Сжатие и ifference в строках (IP / date-time-stamp) должны приводить только к разнице менее одного процента.

Если вам известен метод, используемый издателем при определении хэша, вы можете напрямую применить «правильный» метод из вышеприведенного, но даже без этого вы можете проверить метаданные и применить некоторые эвристические методы или определить количество изображений в файле. и сравните это с количеством страниц (если они близко, у вас, вероятно, есть документ, состоящий из сканов). pdftextна отсканированных изображениях PDF также имеет узнаваемый вывод.


В качестве основы для работы я создал пакет python, который находится на bitbucket и / или может быть установлен с использованием PyPIpip install ruamel.pdfdouble . Это предоставляет вам pdfdblкоманду, которая выполняет сканирование, как описано выше, для метаданных, извлеченных изображений или текста. Он не выполняет никакой фильтрации меток (пока) , но в файле readme описано, какие (два) метода для улучшения добавить.

Включенный файл readme:

ruamel.pdfdouble

этот пакет предоставляет pdfdblкоманду:

pdfdbl scan dir1 dir2

Это позволит перейти к каталогам, указанным в качестве аргумента, и для найденных PDF-файлов создать хеш на основе (по порядку):

  • метаданные, если они уникальны
  • изображения, если количество изображений
  • текст

Это предполагает, что pdfinfo, pdfimages и pdftotext` из пакета poppler-utils доступны.

Создается «база данных», по ~/.config/pdfdbl/pdf.lstкоторой проверяются дальнейшие проверки.

Удаление маркировки

В ruamel/pdfdouble/pdfdouble.pyЕсть два способа , которые могут быть усилены , чтобы отфильтровать маркировки в формате PDF , которые делают их менее уникальным и сделать практически одни и те же файлы , чтобы иметь различные хэши.

Для текста метод PdfData.filter_for_markingдолжен быть расширен для удаления и разметки строки, являющейся его аргументами, и возврата результата.

Для отсканированных изображений этот метод PdfData.process_image_and_updateнеобходимо усовершенствовать, например, обрезая нижнюю и верхнюю X-строчки изображений и удаляя любой серый фоновый текст, устанавливая все черные пиксели в белый цвет. Эта функция должна обновить хеш, переданный с использованием .update()метода, передающего отфильтрованные данные.

ограничения

Текущая «база данных» не может обрабатывать пути, содержащие символы новой строки

Эта утилита в настоящее время только на Python 2.7.


Соответствующие IP строки могут быть заменены reмодулем Python :

import re
IPre = re.compile("(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}"
              "([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])")

x = IPre.sub(' ', 'abcd 132.234.0.2 ghi')
assert x == 'abcd   ghi'
Энтон
источник
В прошлом я использовал пакет python pdfrwдля извлечения метаданных, но он не может обрабатывать зашифрованные PDF-файлы, где это pdfinfoвозможно.
Anthon
2

Я бы дал pdftotextеще один шанс, по крайней мере для PDF-файлов в вашей коллекции, которые на самом деле содержат текст (в противном случае вам потребуется запустить OCR), используя лучший инструмент для обработки вывода.

Как только у вас есть (грязный) текстовый вывод, запустите его через программу, предназначенную для определения сходства (а не diffпострочных различий, которые могли бы стать быстрым путем к безумию).

Рассмотрим что-то вроде Perl's String :: Similarity или программу simhash (которая доступна в Debian, но не в Fedora / RHEL).

Адам Кац
источник
2

PDF-файлы содержат метаданные, и я только что проверил ряд статей по физике от разных издателей, и все они имеют как минимум атрибут «Заголовок». Для некоторых название является фактическим названием публикации, для некоторых оно содержит DOI или аналогичные идентификаторы. Во всяком случае, каждая статья, которую я проверил, содержит название, и это всегда что-то уникальное для данной публикации.

Вы можете использовать pdftkдля доступа к метаданным PDF-файлов и сравнить их. Для вашей цели этого должно быть достаточно, и это намного быстрее, чем pdftotextесли бы производительность была проблемой. В случае, если в документе действительно не должно быть метаданных заголовка, вы все равно можете вернуться к нему pdftotext.

Для выгрузки всех метаданных в текстовый файл (или стандартный вывод) для дальнейшей обработки используйте

pdftk <PDF> dump_data output <TEXTFILE>

или обратитесь к руководству для получения дополнительной информации.

Если вы хотите попробовать ImageMagick , compareно проблемы возникают с несколькими страницами, вы также можете использовать их pdftkдля извлечения отдельных страниц и сравнения их всех по отдельности (хотя, возможно, достаточно просто сравнить одну страницу).

Вот фрагмент кода, который использует этот подход для создания diffPDF-вывода, похожего на многостраничный PDF: https://gist.github.com/mpg/3894692

захватчик
источник
1

Вы смотрели в PDF Content Comparer ? Есть параметры командной строки, которые должны позволить вам автоматизировать процесс.

Вы можете запустить некую логику в журнале различий, который он создает, чтобы увидеть, насколько они похожи.

В противном случае вы можете попытаться временно разбить PDF-файлы на несколько файлов и сравнить их таким образом. У вас, вероятно, все еще будут дубликаты таким образом. Один PDF-файл может содержать дополнительную пустую страницу или что-то такое, что может привести к тому, что все последующие страницы будут сравниваться как совершенно разные.

Bratchley
источник
Может быть, две самые дорогие версии этой программы с закрытым исходным кодом могут сделать эту работу. Я бы предпочел решение с открытым исходным кодом, хотя оно не должно быть бесплатным.
Джонас Стейн
1

После скромного вклада в обсуждение (частичный ответ):

После преобразования в текст я использовал бы следующее для вычисления (на основе разности слов) улыбки файла:

wdiff -s -123 file1.txt file2.txt |    ## word difference statistics (1)
     grep -Po '(\d+)(?=% common)' |    ## 
     awk '{a+=$1}END{print a/2}'       ## (2)

(1) производит результат как

file1.txt: 36 words  33 92% common  3 8% deleted  0 0% changed
file2.txt: 35 words  33 94% common  2 6% inserted  0 0% changed

(2) = 93

JJoao
источник
1

У меня есть скрипт, который просматривает PDF и сначала пытается извлечь текст, используя pdftotext, но если это не удается (как это будет с отсканированным документом), он использует ghostscript, чтобы превратить многостраничный отсканированный PDF в серию файлов PNG, а затем использует tesseract для преобразования этой серии в один текстовый файл. Если сканирование имеет достаточное качество, оно делает довольно хорошую работу. Было бы просто добавить код, сравнивающий текст между файлами, но у меня не было этого требования.

ghostscript и tesseract имеют открытый исходный код и работают из командной строки.

gogoud
источник
Вы можете напрямую извлекать отсканированные изображения, используя pdfimagesпакет poppler, без дополнительной потери качества, которую вы можете получить при рендеринге через ghostscript (что негативно влияет на любое распознавание текста, которое вы хотите сделать).
Anthon
@Anthon спасибо за то, что указал на это, но, конечно pdfimages, просто делает то же самое, что ghostscript ( gs) здесь, то есть извлекает изображения из pdf в jpg / png. Почему это лучше, чем gs?
Гогуд
Рендеринг, который выполняет ghostscript, искажает пиксели изображений, если только все сканы не имеют одинакового разрешения (не в случае, например, если пропущены края пробелов), и только в том случае, если вы рендерите с точно таким же разрешением, которое используют изображения
Anthon
@Anthon Интересно, я провел небольшое тестирование. Результаты очень похожи, но кажется, что gs/ tesseract(промежуточный формат png) работает немного лучше, чем pdfimages/ tesseract(промежуточный формат pbm). pdfimagesвсе же быстрее.
gogoud
0

Я бы предложил Perl в качестве решения. Есть модуль, CAM::PDFкоторый позволяет вам извлекать ... содержимое PDF.

Это работает примерно так:

#!/usr/bin/perl

use strict;
use warnings;

use CAM::PDF;

my $file = 'sample.pdf';

my $pdf = CAM::PDF->new($file);

my $word_count = 0;
for my $pagenum ( 1 .. $pdf->numPages ) {
    my $page_text = $pdf->getPageText($pagenum) );
    print $page_text; 
}

Вы можете извлечь текст и сравнить это.

Только для отсканированных документов - это намного сложнее, но при условии , что они используют одни и те же базовые изображения (например, не сканировали их отдельно), вы, вероятно, можете использовать:

#!/usr/bin/perl

use strict;
use warnings;

use CAM::PDF;
use CAM::PDF::Renderer::Images;
use Data::Dumper; 

my $file = 'sample.pdf';

my $pdf = CAM::PDF->new($file);

my $word_count = 0;
for my $pagenum ( 1 .. $pdf->numPages ) {
    my $content =  $pdf->getPageText($pagenum);
    my $page = $pdf->getPageContentTree($pagenum);
    my $gs = $page->findImages();
    my @imageNodes = @{$gs->{images}};
    print Dumper \@imageNodes;

    print Dumper \$gs;
}

Я не проверял это особенно хорошо, потому что у меня нет ваших исходных документов. Я думаю, что этот подход должен сработать - вы не сравниваете фактическое содержание изображения, потому что… ну, это действительно сложно. Но вы должны быть в состоянии распознать похожие изображения из метаданных.

За идентичных PDF-файлов с разными метаданными, что-то простое, например, хэширование текстового содержимого и метаданных изображения, должно помочь.

Sobrique
источник
-1

Существует приложение для Linux, которое называется recoll . Он может выполнить задачу, но только для PDF-файлов с текстовым слоем.

annndrey
источник
2
Мне recollкажется, это настольная поисковая система. Я не мог видеть, как использовать это, чтобы найти дубликаты.
Джонас Стейн
1
recollиспользует pdftotextдля обработки PDF-файлов, что ОП пытается здесь избежать.
Джон У. С. Смит,