vitus_wagner: My photo 2005 (Default)
[personal profile] vitus_wagner

Скачавши очередное (декабрьское) обновление флибусты, решил подумать о том, как бы себе сделать правильную искалку по ее метаинформации. На основании того, что я знаю о XML-схеме fb2-файлов и о том как устроены индексные (inpx) файлы флибусты. (ну и о том что флибустьеры не правят метаинфомрацию в файлах, поэтому корректная метаинформация содержится (если содержится) только в inp-файлах.

Ну то есть я знаю что у MyHomeLib схема базы кривая, ее какой-то девопс дизайнил, который не то что Дейта - Граббера не читал.

Ну для начала взял pgmodeller и нарисовал ER-диаграмму каталога библиотеки fb2-файлов. Из чисто флибустовских сущностей добавил туда только понятие архива. В смысле того zip-файла в котором в скачанном из торрентов мирроре лежит нужный f2.

Получилось 10 таблиц. Сущностей в принципе не так уж много

  1. book
  2. author (он же translator)
  3. sequence
  4. lang (классификатор для полей lang и srclang в таблице book)
  5. genre

Но везде же отношения m:n и нужны развязывающие таблички. А в табличке устанавливающей соответствие между книгой и серией еще и содержательное поле number лежит. И ведь на него даже NOT NULL не повесишь, я же знаю что полно есть в архиве книг у которых серия без номера.

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

Вот теперь думаю - писать парсилку inp-файлов в эту схему в sqlite, или уж сразу постгрес использовать. (и импортить туда весь текст fb2 на предмет организации полнотекстового поиска).

Для интересующихся сдизайненную схему положу под кат:

CREATE TABLE lang (
    lang_code char(3) NOT NULL,
    lang_name text NOT NULL,
    CONSTRAINT lang_pk PRIMARY KEY (lang_code)

);

CREATE TABLE archive (
    archive_id serial NOT NULL,
    archive_name text NOT NULL,
    CONSTRAINT archive_pk PRIMARY KEY (archive_id)

);

CREATE TABLE book (
    book_id int8 NOT NULL,
    title text NOT NULL,
    file text NOT NULL,
    deleted boolean NOT NULL DEFAULT false,
    writedate datetime,
    lang char(3),
    srclang char(3),
    archive smallint NOT NULL,
    CONSTRAINT book_pk PRIMARY KEY (book_id),
    FOREIGN KEY (lang) REFERENCES lang(lang_code),
    FOREIGN KEY (srclang) REFERENCES lang(lang_code),
    FOREIGN KEY (archive) REFERENCES archive(archive_id)
);

CREATE TABLE author (
    author_id int4 NOT NULL,
    first_name text,
    middle_name text,
    last_name text,
    nickname text,
    CONSTRAINT author_pk PRIMARY KEY (author_id)

);

CREATE TABLE seq (
    sequence_id int4 NOT NULL,
    name text NOT NULL,
    CONSTRAINT seq_pk PRIMARY KEY (sequence_id)

);

CREATE TABLE replaces (
    old_book_id int8 NOT NULL,
    new_book_id int8 NOT NULL,
    CONSTRAINT replaces_pk PRIMARY KEY (old_book_id,new_book_id),
    FOREIGN KEY (old_book_id) REFERENCES book(book_id),
    FOREIGN KEY (new_book_id) REFERENCES book(book_id)
);

CREATE TABLE book_author (
    book_id int8 NOT NULL,
    author_id int4 NOT NULL,
    CONSTRAINT book_author_pk PRIMARY KEY (book_id,author_id),
    FOREIGN KEY (book_id) REFERENCES book(book_id),
    FOREIGN KEY (author_id) REFERENCES author(author_id)
);

CREATE TABLE book_translator (
    book_id int8 NOT NULL,
    translator_id int4 NOT NULL,
    CONSTRAINT book_translator_pk PRIMARY KEY (book_id,translator_id),
    FOREIGN KEY (book_id) REFERENCES book(book_id),
    FOREIGN KEY (translator_id) REFERENCES author(author_id)

);

CREATE TABLE book_seq (
    book_id int8 NOT NULL,
    sequence_id int4 NOT NULL,
    sequence_number int4,
    CONSTRAINT book_seq_pk PRIMARY KEY (book_id,sequence_id),
    FOREIGN KEY (book_id) REFERENCES book(book_id),
    FOREIGN KEY (sequence_id) REFERENCES seq(sequence_id)

);



CREATE TABLE genre (
    genre_id varchar(20) NOT NULL,
    genre_name text NOT NULL,
    CONSTRAINT genre_pk PRIMARY KEY (genre_id)

);

CREATE TABLE book_genre (
    book_id int8 NOT NULL,
    genre_id varchar(20) NOT NULL,
    CONSTRAINT book_genre_pk PRIMARY KEY (book_id,genre_id),
    FOREIGN KEY (book_id) REFERENCES book(book_id),
    FOREIGN KEY (genre_id) REFERENCES genre(genre_id)
);


Что характерно, пока я причесывал этот SQL скрипт так, чтобы его сожрал sqlite, его объем сократился в 2.5 раза, а из содержательной информации потерялись только имена констрейнтов внешних ключей (и то можно было бы сохранить).

Date: 2022-12-10 04:25 pm (UTC)
From: [personal profile] vikarti_anantra
А postgres на домашнем сервере с разумными требованиями к ресурсам сможет работать с полнотекстовым поиском по загруженой туда полной базе fb2шек с флибусты?

У меня вот полнотекстовый поиск для себя есть через спецплагин к calibre и recoll (штатный поиск в Calibre 6 - неудобный) но у меня объемы базы Calibre целиком - около 100 Gb и там не только fb2 а объем fb2 насколько помню где то 300-400 Gb архивов.

Date: 2022-12-10 05:32 pm (UTC)
From: [personal profile] vikarti_anantra
Для меня поиск на смартфоне - это мечта. Которую непонятно как реализовать
(пока выкручиваюсь кое как поиском на компе и развитой системой тегов)ю

Было бы интересно посмотреть что у вас в итоге получится если это хоть как то публично в итоге будет.

Date: 2022-12-10 06:02 pm (UTC)
2e8: (Default)
From: [personal profile] 2e8
Только художка это неинтересно. Я объединяю флибусту с либгеном, и при этом главная идея, что разные экземпляры базы должны быть избирательно синхронизируемыми. Само собой sqlite.

Date: 2022-12-11 10:06 am (UTC)
2e8: (Default)
From: [personal profile] 2e8
Где-то 50 наверное, но много дубликатов. Там сейчас все сложно, либгенов сейчас несколько и совместимость постепенно падает.

Основная метаинформация там конечно есть, но она частенько неправильная. В файлах она содержаться в принципе не может, т.к. там могут попадаться самые разные форматы. Разве что sidecar

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

Date: 2022-12-11 11:28 am (UTC)
2e8: (Default)
From: [personal profile] 2e8
Вы не поняли, там встечаются абсолютно любые форматы, причем не только текстово-книжные, начиная с плайн текста и лексикона, заканчивая исо-образами и архивами неизвестных мне форматов с самым разнообразным содержимым, включая исходники и бинарники.

Под связью между файлами я имел в виду что-то вида: "этот файл является улучшенной копией того файла", серии-же конструкция искусственная и весьма спорно, нужно ли для их поддержки городить отдельный огород.

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

Даже тематический каталог сильно подробный не нужен. Это в бумажной библиотеке нужно с помощью каталога отобрать считанные единицы книг, т.к. ни один библиотекарь не потащит вам для посмотреть пару сотен томов. В электронной-же библиотеке это совершенно не проблема.

Date: 2022-12-11 03:45 pm (UTC)
2e8: (Default)
From: [personal profile] 2e8
Но ведь информация о замене относится к метаинформации файла. Зачем отдельная таблица?

Date: 2022-12-11 06:13 pm (UTC)
fau74: (Default)
From: [personal profile] fau74
В момент загрузки файла наличествует информация лишь о файлах, загруженных до него. Следовательно, replaces инфоблок (если он вообще есть в заголовке fb2) может отнестись лишь к этим файлам.

Более поздний файл совершенно не обязательно более новый файл, поскольку акт загрузки файла и акт генерации fb2 никак в общем случае не связаны с актом творения текста. В базе либрусека мне попадались и менее очевидные сочетания, нежели "кто-то загрузил юболее старую версию с более нововй датой". Акт простановки связи replaces в общем случае творческая интерпретация информации в файле и сопутствующей (напр. внешнего относительно файла знания о сущности обновлений текста автором).

Date: 2022-12-11 06:15 pm (UTC)
2e8: (Default)
From: [personal profile] 2e8
А разве с этим кто-то спорит? В общем случае любая метаинформация является творческой интерпретацией, если это не однозначно измеримая велечина, производная от самого файла.

Date: 2022-12-12 06:30 pm (UTC)
fau74: (Default)
From: [personal profile] fau74
Ну так это и объясняет, зачем отдельная таблица. Данная информация не может быть однозначно построена на основе данных самого файла, она межфайловая, а в общем случае может быть и внефайловая. Поэтому хранить ее нужно не в файле, а в отдельном информационном блоке.

Date: 2022-12-11 07:24 pm (UTC)
2e8: (Default)
From: [personal profile] 2e8
Вы, я вижу, оптимист. В одном из последних баз флибусти на 673 тысячи записей 120 тысяч заменены. В либгене на 3500 тысяч тоже порядка 100 тысяч замен, но это исключительно из-за бардака в базе. При беглом поиске любой книги находятся десятки экземпляров. Так что это очень важная информация, нужная для наведения порядка и существенного сокращения архива. И записей там будет, в лучшем случае, всего раз в десять меньше (считаем что флибуста более-менее причесана).

Date: 2022-12-11 02:06 pm (UTC)
From: [identity profile] justy-tylor.livejournal.com
Чтобы миррор делать - придётся не только .inp, но и SQL dump флибустовский парсить. Причём, итеративно и с кучей особых случаев вроде "есть только новые .inp, дамп пока не обновлялся" или "дамп оказался на пару книжек впереди паровоза".

Date: 2022-12-11 03:42 pm (UTC)
From: [identity profile] justy-tylor.livejournal.com
Краткие описания авторов весьма полезны, на мой взгляд. Учитывая совпадающие имена. Прочее, включая комментарии пользователей - не критично.

Date: 2022-12-12 07:24 am (UTC)
2e8: (Default)
From: [personal profile] 2e8
http://flibusta.is/sql/

Здесь должно быть все, но наличие описаний авторов я не проверял.

Date: 2022-12-12 08:18 am (UTC)
2e8: (Default)
From: [personal profile] 2e8
Может так, а может и не так. Я сопоставляю файлы с записями в базе по md5. Это самый надежный метод.

Date: 2022-12-12 09:05 am (UTC)
stanislavvv: (Default)
From: [personal profile] stanislavvv
Когда начинал свою NIH-вебморду с opds - сделал примерно такую же схему на sqlite, написал во фласке всякое и обломался - на микрокомпе Orange PI 3 LTS оно работало медленно даже при максимальной частоте процессора, т.е. в некоторых случаях не дожидалось таймаута nginx.
Переделал на создание индексов в виде файлов (json и jsonl) и тупое чтение без сложных разборов, чтобы не грузить процессор. Правда, пришлось ещё делать многопроходное создание индексов по авторам, чтобы не упереться в память до oom, но это уже детали.

У меня парсится каждый fb2 (точнее, первые десятки кб, а не весь), а .inp использовался только для замены всякого, ибо рядом может лежать архив с файлами из samlib, к примеру. Плюс есть файлы замены метаданных (скажем, для автора "Луиза-Франсуаза" они понадобились, так как ник я ещё могу вспомнить, а фио - уже нет). В новом варианте индексация занимает примерно ночь, в старом - около суток (sqlite не самая быстрая штука на запись).

Надо будет выложить в доступное место, а то сейчас только в моём личном гите.
Edited Date: 2022-12-12 09:08 am (UTC)

Date: 2022-12-13 07:04 am (UTC)
stanislavvv: (Default)
From: [personal profile] stanislavvv

Когда делал разбор - наступил не только на проблему с метаинформацией (её в нужной мне части действительно проще взять из .inp), но и на то, что часть fb2 либо является некорректным xml (слава BeautifulSoap!), либо не соответствует формату FictionBook вплоть до того, что понадобилось писать такое:

def get_struct_by_key(key, struct):
    if key in struct:
        return struct[key]
    if isinstance(struct, list):
        for k in struct:
            r = get_struct_by_key(key, k)
            if r is not None:
                return r
    if isinstance(struct, dict):
        for k, v in struct.items():
            r = get_struct_by_key(key, v)
            if r is not None:
                return r
    return None

и потом вызывать:

fb2data = get_struct_by_key('FictionBook', data)  # data['FictionBook']

Date: 2022-12-13 08:57 am (UTC)
jno: (Default)
From: [personal profile] jno
Однако, нормализация пробелов там - задача насущная.
Так как народ копипастит буквы откуда ни попадя, то неотображаемого мусора там хватает.
\u00A0 (nbsp) - это уж просто повсеместно.

Date: 2022-12-13 03:18 pm (UTC)
jno: (Default)
From: [personal profile] jno
Я не говорю, что оно сложно, но - нужно.

Происхождение довольно очевидное: копипаста из PDF и прочих вордов (особенно из таблиц).

И я бы сделал что-то вроде
line=" ".join(line.split()) # обрезать ведущие/висящие + сведение к одному
Edited Date: 2022-12-13 03:20 pm (UTC)

Date: 2022-12-13 08:32 pm (UTC)
From: [personal profile] legolegs
Хорошо, если в художественном произведении нет кода на питоне :)

Date: 2022-12-14 06:04 am (UTC)
yurikhan: (Default)
From: [personal profile] yurikhan

Вот Бобби Тейблз вырастет и напишет книгу с значащими сериями пробелов/табов/nbsp в названии ;)

Date: 2022-12-14 07:23 pm (UTC)
From: [personal profile] legolegs
Пускай Бобби лучше судится с кем попало, а айти оставит в покое! :)

Зачем inp-файлы?

Date: 2022-12-16 10:26 pm (UTC)
From: [personal profile] incognitus7
Насколько я понимаю, inp(x) файлы делает кто-то третий на основе, скаченного дампа sql-файлов той же Флибусты. По-моему, логичнее всего, может немного лишнего повозиться, но получать информацию напрямую из них. Там вся информация, включая комментарии к книгам, которые в 90% случаев наверное бесполезны, но иногда кое-что любопытное есть. Хотя у самого руки до sql так и не дошли, хех.

Profile

vitus_wagner: My photo 2005 (Default)
vitus_wagner

June 2025

S M T W T F S
1 23 4 56 7
89 1011121314
15161718192021
22232425262728
2930     

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Jun. 11th, 2025 01:56 pm
Powered by Dreamwidth Studios