Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

readme.md

Документация на проекта

Общо описание

Проектът представлява 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

Използвани npm пакети

  • 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.


API fetcher за текстове

fetchTexts(query)

Изпраща заявка към:

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, рейтинг и т.н.


Скрейпър за автори

fetchAuthorByIdCached(authorId, cache, semaphore)

  1. Проверява кеша
  2. Ако няма — semaphore.acquire()
  3. Изпраща заявка към:
https://chitanka.info/persons/search.xml?q={id}&by=id&match=exact
  1. Парсира:
authorId
authorName
authorOriginalName
authorCountry
  1. Записва автора в база
  2. Слага автора в cache
  3. semaphore.release()

Как работят семафорите

Chitanka ме блокира, когато правя много паралелни заявки. (Направих първата си DDoS атака и сървърът им беше долу за 3 дни, а тъкмо се зарадвах, че приложението ми сваляше много бързо файловете)

Затова семафорът е конфигуриран така:

const authorSemaphore = new Semaphore(1);
const textSemaphore = new Semaphore(1);

Капацитет 1 = синхронно изпълнение

По този начин и лесно се следи да не се надхвърли лимита за записани нови файлове. Такова поведение може да се дефинира и с mutex или с atomic типове, които съществуват в JS. Опитът ми за имплементация с тях беше успешен, но не успях да намеря достатъно материали, които да ми покажат правилната им имплементация в езика и се наложи да използвам, AI, за да напиша кода с тях, затова сега не ги използвам.

Само една заявка се изпълнява в даден момент, независимо че JavaScript е асинхронен. Това елиминира:

  • 429 Too Many Requests
  • 404 от блокиране
  • временно забавяне от страна на сървъра

Ако увеличиш капацитета → scraper-ът става конкурентен.


File Processor

Генерира URL:

https://m3.chitanka.info/text/textId=X.txt.zip

Изтегля файла и го записва в:

./texts/{country}/{id_title}!.txt

В условието се искаше само да се добави '!', но промените бяха нужни, за да се подсигури, че файлове с еднакви имена (каквито има) няма да се презаписват един върху друг.

Също така:

  • преброява уникални думи
  • изчислява средна дължина на изречение
  • обновява статистики в базата

Router

GET /stats

Връща статистики от базата.

POST /import?count=N

  1. Стартира скрейпър за N текста.
  2. Стартира file processor.
  3. Връща JSON с резултатите.

Server

  • Сервира статични файлове от /public
  • Обработва CORS
  • Управлява router-а
  • Fallback към index.html

Обобщен работен поток

  1. /import?count=q се извиква

  2. API fetcher-ът генерира тригъри (ааа, ааб, …)

  3. За всеки намерен текст:

    • парсира XML
    • извлича authorId
    • извиква fetchAuthorByIdCached (синхронно чрез семафор)
    • записва в база
  4. File processor тегли текстовете

  5. Информацията генерирана от свалянето и обработката на файловете се записва в log файлове

  6. UI показва напредъка чрез /stats, като забранява кликането на import многократно

Проблеми

  1. Всяка една версия на scraper.js или т.н. API fetcher добавя инфо за автори, макар да няма техни произведения, които да са свалени. Предполагам, че това е заради неуспешно сваляне на някои от файловете.
  2. Настоящата версия сваля по-малко файлове от изискваните, но след получаването на 429 грешката от chitanka и виждането на съобщението за временен бан съм приел, че ще свалям по-малко файлове.
  3. Не съм сигурен как би се държала програмта при import-ването на данни от много инстанции едновременно. Евентуални решения биха били да пиша файлове само синхронно или да ползвам нещо като Redis.