From 21af607e380278d4dcfb07c359faaf1af0e96dcd Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 19 Feb 2026 20:47:33 +0000 Subject: [PATCH] perf: Optimize JSON-LD parsing in MovieScraper Parse JSON-LD string once in `MovieScraper.movie()` and pass the object to helpers, avoiding redundant `JSON.parse` calls in `getMovieYear` and `getMovieDuration`. This reduces the overhead of parsing large JSON strings multiple times per movie request. - Define `MovieJsonLd` interface. - Update `getMovieYear` and `getMovieDuration` to accept `MovieJsonLd` object. - Update `MovieScraper.movie` to parse JSON-LD once. - Update tests to reflect signature changes. Co-authored-by: bartholomej <5861310+bartholomej@users.noreply.github.com> --- src/dto/movie.ts | 6 ++++++ src/helpers/movie.helper.ts | 31 +++++++++++++++++-------------- src/services/movie.service.ts | 12 +++++++++--- tests/movie.test.ts | 19 +++++++++++++++---- 4 files changed, 47 insertions(+), 21 deletions(-) diff --git a/src/dto/movie.ts b/src/dto/movie.ts index fe088686..b3d4e062 100644 --- a/src/dto/movie.ts +++ b/src/dto/movie.ts @@ -181,3 +181,9 @@ export interface CSFDPremiere { } export type CSFDBoxContent = 'Související' | 'Podobné'; + +export interface MovieJsonLd { + dateCreated: string; + duration: string; + [key: string]: any; +} diff --git a/src/helpers/movie.helper.ts b/src/helpers/movie.helper.ts index 230b4004..9772ab78 100644 --- a/src/helpers/movie.helper.ts +++ b/src/helpers/movie.helper.ts @@ -11,7 +11,8 @@ import { CSFDPremiere, CSFDTitlesOther, CSFDVod, - CSFDVodService + CSFDVodService, + MovieJsonLd } from '../dto/movie'; import { addProtocol, getColor, parseISO8601Duration, parseIdFromUrl } from './global.helper'; @@ -123,23 +124,23 @@ export const getMovieRatingCount = (el: HTMLElement): number => { } }; -export const getMovieYear = (el: string): number => { - try { - const jsonLd = JSON.parse(el); +export const getMovieYear = (jsonLd: MovieJsonLd | null): number => { + if (jsonLd) { return +jsonLd.dateCreated; - } catch (error) { - console.error('node-csfd-api: Error parsing JSON-LD', error); - return null; } + return null; }; -export const getMovieDuration = (jsonLdRaw: string, el: HTMLElement): number => { - let duration = null; +export const getMovieDuration = (jsonLd: MovieJsonLd | null, el: HTMLElement): number => { + try { + if (jsonLd && jsonLd.duration) { + return parseISO8601Duration(jsonLd.duration); + } + } catch (e) { + // Ignore error + } + try { - const jsonLd = JSON.parse(jsonLdRaw); - duration = jsonLd.duration; - return parseISO8601Duration(duration); - } catch (error) { const origin = el.querySelector('.origin').innerText; const timeString = origin.split(','); if (timeString.length > 2) { @@ -151,11 +152,13 @@ export const getMovieDuration = (jsonLdRaw: string, el: HTMLElement): number => const hoursMinsRaw = timeRaw.split('min')[0]; const hoursMins = hoursMinsRaw.split('h'); // Resolve hours + minutes format - duration = hoursMins.length > 1 ? +hoursMins[0] * 60 + +hoursMins[1] : +hoursMins[0]; + const duration = hoursMins.length > 1 ? +hoursMins[0] * 60 + +hoursMins[1] : +hoursMins[0]; return duration; } else { return null; } + } catch (e) { + return null; } }; diff --git a/src/services/movie.service.ts b/src/services/movie.service.ts index e30470ac..497dd5d9 100644 --- a/src/services/movie.service.ts +++ b/src/services/movie.service.ts @@ -1,6 +1,6 @@ import { HTMLElement, parse } from 'node-html-parser'; import { CSFDFilmTypes } from '../dto/global'; -import { CSFDMovie } from '../dto/movie'; +import { CSFDMovie, MovieJsonLd } from '../dto/movie'; import { fetchPage } from '../fetchers'; import { getLocalizedCreatorLabel, @@ -41,7 +41,13 @@ export class MovieScraper { const pageClasses = movieHtml.querySelector('.page-content').classNames.split(' '); const asideNode = movieHtml.querySelector('.aside-movie-profile'); const movieNode = movieHtml.querySelector('.main-movie-profile'); - const jsonLd = movieHtml.querySelector('script[type="application/ld+json"]').innerText; + const jsonLdString = movieHtml.querySelector('script[type="application/ld+json"]').innerText; + let jsonLd: MovieJsonLd | null = null; + try { + jsonLd = JSON.parse(jsonLdString); + } catch (e) { + console.error('node-csfd-api: Error parsing JSON-LD', e); + } return this.buildMovie(+movieId, movieNode, asideNode, pageClasses, jsonLd, options); } @@ -50,7 +56,7 @@ export class MovieScraper { el: HTMLElement, asideEl: HTMLElement, pageClasses: string[], - jsonLd: string, + jsonLd: MovieJsonLd | null, options: CSFDOptions ): CSFDMovie { return { diff --git a/tests/movie.test.ts b/tests/movie.test.ts index 1cf9f189..84951bec 100644 --- a/tests/movie.test.ts +++ b/tests/movie.test.ts @@ -6,7 +6,8 @@ import { CSFDMovieListItem, CSFDPremiere, CSFDTitlesOther, - CSFDVod + CSFDVod, + MovieJsonLd } from '../src/dto/movie'; import { getColor } from '../src/helpers/global.helper'; import { @@ -48,13 +49,23 @@ const getNode = (node: HTMLElement): HTMLElement => { return node.querySelector('.main-movie-profile') as HTMLElement; }; -const getJsonLd = (node: HTMLElement): string => { - return node.querySelector('script[type="application/ld+json"]')?.innerText ?? '{}'; +const getJsonLd = (node: HTMLElement): MovieJsonLd | null => { + const text = node.querySelector('script[type="application/ld+json"]')?.innerText ?? '{}'; + try { + return JSON.parse(text); + } catch (e) { + return null; + } }; const getMovie = ( node: HTMLElement -): { pClasses: string[]; aside: HTMLElement; pNode: HTMLElement; jsonLd: string } => { +): { + pClasses: string[]; + aside: HTMLElement; + pNode: HTMLElement; + jsonLd: MovieJsonLd | null; +} => { return { pClasses: getPageClasses(node), aside: getAsideNode(node),