Проектът представлява API fetcher за събиране на текстови файлове от Chitanka (https://chitanka.info). Данните се нормализират и записват в PostgreSQL база данни, а файловете се изтеглят, разархивират и съхраняват локално като минават обработка, която предоставя статистика за тях и я записва в базата данни. Той също така притежава web interface за преглед на съхранените данни.
Основни компоненти:
- API fetcher за текстове чрез
/texts/search.xml - API fetcher за автори чрез
/persons/search.xml - Изтегляне на
.txt.zipфайлове - Кеширане на автори, за да се избягват повторни заявки
- Семафори, които ограничават конкурентността до 1 операция, което гарантира сигурно поведение (ограничаване на броя заявки в секунда, всеки файл се пише от една "нишка", не се надвишава лимити зададен за нови свалени файлове)
- Node.js HTTP сървър за
/statsи/import, които са публични endpoints за преглед на статистиките и сваляне на нови файлове - Статичен фронт-енд в
public/
/project-root
/src
scraper.js
fileProcessor.js
routes/router.js
server.js
db.js
util/file.js
/public
scripts/index.js
styles/index.css
index.html
/test/src
fileProcessor.test.js
scraper.test.js
utils/file.test.js
README.md
-
xml2js - Парсира XML от Chitanka в JavaScript обекти.
-
mime-types - Определя правилния
Content-Typeпри сервиране на статични файлове. -
pg - PostgreSQL клиент. Използва се за връзка с базата данни.
-
fs / path / url - За файлови операции, пътища, работа с директории.
-
async-sema - Контролира броя на едновременните заявки.
-
fetch - За извличане на данни от читанка. Вграден в новите версии на Node.js. Използвам го в browserFetch, който добавя защитни HTTP header-и, имитиращи браузър, които ChatGpt каза, че ще помогнат за 429 Too Many Requests error.
Изпраща заявка към:
https://chitanka.info/texts/search.xml?q={query}
Парсира само нужните полета:
{
textId: id,
textTitle: title,
textSubtitle: subtitle или title
authorId: author?.id,
year: trans-year или null,
}Игнорира езикови полета (<lang>, <orig-lang>), коментари, slug, рейтинг и т.н.
- Проверява кеша
- Ако няма —
semaphore.acquire() - Изпраща заявка към:
https://chitanka.info/persons/search.xml?q={id}&by=id&match=exact
- Парсира:
authorId
authorName
authorOriginalName
authorCountry- Записва автора в база
- Слага автора в cache
semaphore.release()
Chitanka ме блокира, когато правя много паралелни заявки. (Направих първата си DDoS атака и сървърът им беше долу за 3 дни, а тъкмо се зарадвах, че приложението ми сваляше много бързо файловете)
Затова семафорът е конфигуриран така:
const authorSemaphore = new Semaphore(1);
const textSemaphore = new Semaphore(1);По този начин и лесно се следи да не се надхвърли лимита за записани нови файлове. Такова поведение може да се дефинира и с mutex или с atomic типове, които съществуват в JS. Опитът ми за имплементация с тях беше успешен, но не успях да намеря достатъно материали, които да ми покажат правилната им имплементация в езика и се наложи да използвам, AI, за да напиша кода с тях, затова сега не ги използвам.
Само една заявка се изпълнява в даден момент, независимо че JavaScript е асинхронен. Това елиминира:
- 429 Too Many Requests
- 404 от блокиране
- временно забавяне от страна на сървъра
Ако увеличиш капацитета → scraper-ът става конкурентен.
Генерира URL:
https://m3.chitanka.info/text/textId=X.txt.zip
Изтегля файла и го записва в:
./texts/{country}/{id_title}!.txt
В условието се искаше само да се добави '!', но промените бяха нужни, за да се подсигури, че файлове с еднакви имена (каквито има) няма да се презаписват един върху друг.
Също така:
- преброява уникални думи
- изчислява средна дължина на изречение
- обновява статистики в базата
Връща статистики от базата.
- Стартира скрейпър за N текста.
- Стартира file processor.
- Връща JSON с резултатите.
- Сервира статични файлове от
/public - Обработва CORS
- Управлява router-а
- Fallback към index.html
-
/import?count=qсе извиква -
API fetcher-ът генерира тригъри (
ааа,ааб, …) -
За всеки намерен текст:
- парсира XML
- извлича authorId
- извиква fetchAuthorByIdCached (синхронно чрез семафор)
- записва в база
-
File processor тегли текстовете
-
Информацията генерирана от свалянето и обработката на файловете се записва в log файлове
-
UI показва напредъка чрез
/stats, като забранява кликането на import многократно
- Всяка една версия на scraper.js или т.н. API fetcher добавя инфо за автори, макар да няма техни произведения, които да са свалени. Предполагам, че това е заради неуспешно сваляне на някои от файловете.
- Настоящата версия сваля по-малко файлове от изискваните, но след получаването на 429 грешката от chitanka и виждането на съобщението за временен бан съм приел, че ще свалям по-малко файлове.
- Не съм сигурен как би се държала програмта при import-ването на данни от много инстанции едновременно. Евентуални решения биха били да пиша файлове само синхронно или да ползвам нещо като Redis.