better logging, fixing code smells

This commit is contained in:
Christian Kellner
2025-01-07 12:25:19 +01:00
parent 2aaf63c253
commit 90a4ee5dcf
5 changed files with 232 additions and 231 deletions

View File

@@ -1,5 +1,5 @@
import {NoNewListingsWarning} from './errors.js'; import { NoNewListingsWarning } from './errors.js';
import {setKnownListings, getKnownListings} from './services/storage/listingsStorage.js'; import { setKnownListings, getKnownListings } from './services/storage/listingsStorage.js';
import * as notify from './notification/notify.js'; import * as notify from './notification/notify.js';
import Extractor from './services/extractor/extractor.js'; import Extractor from './services/extractor/extractor.js';
import urlModifier from './services/queryStringMutator.js'; import urlModifier from './services/queryStringMutator.js';
@@ -47,11 +47,17 @@ class FredyRuntime {
_getListings(url) { _getListings(url) {
const extractor = new Extractor(); const extractor = new Extractor();
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
extractor.execute(url,this._providerConfig.waitForSelector) extractor
.execute(url, this._providerConfig.waitForSelector)
.then(() => { .then(() => {
const listings = extractor.parseResponseText(this._providerConfig.crawlContainer, this._providerConfig.crawlFields); const listings = extractor.parseResponseText(
this._providerConfig.crawlContainer,
this._providerConfig.crawlFields,
url,
);
resolve(listings == null ? [] : listings); resolve(listings == null ? [] : listings);
}).catch(err => { })
.catch((err) => {
reject(err); reject(err);
/* eslint-disable no-console */ /* eslint-disable no-console */
console.error(err); console.error(err);

View File

@@ -1,11 +1,9 @@
import restana from 'restana'; import restana from 'restana';
import fetch from 'node-fetch';
import * as jobStorage from '../../services/storage/jobStorage.js'; import * as jobStorage from '../../services/storage/jobStorage.js';
import * as userStorage from '../../services/storage/userStorage.js'; import * as userStorage from '../../services/storage/userStorage.js';
import * as immoscoutProvider from '../../provider/immoscout.js';
import { config } from '../../utils.js'; import { config } from '../../utils.js';
import { isAdmin } from '../security.js'; import { isAdmin } from '../security.js';
import {trackDemoJobCreated} from '../../services/tracking/Tracker.js'; import { trackDemoJobCreated } from '../../services/tracking/Tracker.js';
const service = restana(); const service = restana();
const jobRouter = service.newRouter(); const jobRouter = service.newRouter();
function doesJobBelongsToUser(job, req) { function doesJobBelongsToUser(job, req) {
@@ -28,7 +26,7 @@ jobRouter.get('/', async (req, res) => {
jobRouter.get('/processingTimes', async (req, res) => { jobRouter.get('/processingTimes', async (req, res) => {
res.body = { res.body = {
interval: config.interval, interval: config.interval,
lastRun: config.lastRun || null lastRun: config.lastRun || null,
}; };
res.send(); res.send();
}); });
@@ -51,7 +49,7 @@ jobRouter.post('/', async (req, res) => {
trackDemoJobCreated({ trackDemoJobCreated({
name, name,
provider, provider,
adapter: notificationAdapter adapter: notificationAdapter,
}); });
res.send(); res.send();
}); });

View File

@@ -1,19 +1,18 @@
import {setDebug} from './utils.js'; import { setDebug } from './utils.js';
import puppeteerExtractor from './puppeteerExtractor.js'; import puppeteerExtractor from './puppeteerExtractor.js';
import {loadParser, parse} from './parser/parser.js'; import { loadParser, parse } from './parser/parser.js';
const DEFAULT_OPTIONS = { const DEFAULT_OPTIONS = {
debug: false, debug: false,
puppeteerTimeout: 20_000, puppeteerTimeout: 60_000,
puppeteerHeadless: true puppeteerHeadless: true,
}; };
export default class Extractor { export default class Extractor {
constructor(options) { constructor(options) {
this.options = { this.options = {
...DEFAULT_OPTIONS, ...DEFAULT_OPTIONS,
...options ...options,
}; };
this.responseText = null; this.responseText = null;
setDebug(this.options); setDebug(this.options);
@@ -29,7 +28,7 @@ export default class Extractor {
this.responseText = null; this.responseText = null;
try { try {
this.responseText = await puppeteerExtractor(url, waitForSelector, this.options); this.responseText = await puppeteerExtractor(url, waitForSelector, this.options);
if(this.responseText != null) { if (this.responseText != null) {
loadParser(this.responseText); loadParser(this.responseText);
} }
} catch (error) { } catch (error) {
@@ -38,8 +37,7 @@ export default class Extractor {
return this; return this;
}; };
parseResponseText = (crawlContainer, crawlFields, url) => {
parseResponseText = (crawlContainer, crawlFields) => { return parse(crawlContainer, crawlFields, this.responseText, url);
return parse(crawlContainer, crawlFields, this.responseText);
}; };
} }

View File

@@ -6,21 +6,21 @@ export function loadParser(text) {
$ = cheerio.load(text); $ = cheerio.load(text);
} }
export function parse(crawlContainer, crawlFields, text) { export function parse(crawlContainer, crawlFields, text, url) {
if (!text) { if (!text) {
console.warn('Cannot parse, text was empty.'); console.warn('Cannot parse, text was empty for url ', url);
return null; return null;
} }
if (!crawlContainer || !crawlFields) { if (!crawlContainer || !crawlFields) {
console.warn('Cannot parse, selector was empty.'); console.warn('Cannot parse, selector was empty for url ', url);
return null; return null;
} }
const result = []; const result = [];
if ($(crawlContainer).length === 0) { if ($(crawlContainer).length === 0) {
console.error('No elements in crawl container found!'); console.error('No elements in crawl container found for url ', url);
} }
$(crawlContainer).each((_, element) => { $(crawlContainer).each((_, element) => {
@@ -32,8 +32,9 @@ export function parse(crawlContainer, crawlFields, text) {
let value; let value;
try { try {
const selector = fieldSelector.includes('|')
const selector = fieldSelector.includes('|') ? fieldSelector.substring(0, fieldSelector.indexOf('|')).trim() : fieldSelector; ? fieldSelector.substring(0, fieldSelector.indexOf('|')).trim()
: fieldSelector;
if (selector.includes('@')) { if (selector.includes('@')) {
const [sel, attr] = selector.split('@'); const [sel, attr] = selector.split('@');
@@ -48,7 +49,9 @@ export function parse(crawlContainer, crawlFields, text) {
// Apply modifiers if specified // Apply modifiers if specified
if (fieldSelector.includes('|')) { if (fieldSelector.includes('|')) {
const [_, ...modifiers] = fieldSelector.split('|').map(s => s.trim()); /* eslint-disable no-unused-vars */
const [_, ...modifiers] = fieldSelector.split('|').map((s) => s.trim());
/* eslint-disable no-unused-vars */
value = applyModifiers(value, modifiers); value = applyModifiers(value, modifiers);
} }
@@ -73,7 +76,7 @@ export function parse(crawlContainer, crawlFields, text) {
function applyModifiers(value, modifiers) { function applyModifiers(value, modifiers) {
if (!value) return value; if (!value) return value;
modifiers.forEach(modifier => { modifiers.forEach((modifier) => {
switch (modifier) { switch (modifier) {
case 'int': case 'int':
value = parseInt(value, 10); value = parseInt(value, 10);
@@ -91,4 +94,3 @@ function applyModifiers(value, modifiers) {
return value; return value;
} }

View File

@@ -1,34 +1,31 @@
let debuggingOn = false; let debuggingOn = false;
export const DEFAULT_HEADER = { export const DEFAULT_HEADER = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.5', 'Accept-Language': 'en-US,en;q=0.5',
'Connection': 'keep-alive', Connection: 'keep-alive',
'Upgrade-Insecure-Requests': '1', 'Upgrade-Insecure-Requests': '1',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36' 'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36',
}; };
export const setDebug = options => { export const setDebug = (options) => {
debuggingOn = !!options?.debug; debuggingOn = !!options?.debug;
}; };
export const debug = (message) => { export const debug = (message) => {
if(debuggingOn) { if (debuggingOn) {
/* eslint-disable no-console */
console.debug(message); console.debug(message);
/* eslint-enable no-console */
} }
}; };
export const botDetected = (pageSource, statusCode) => { export const botDetected = (pageSource, statusCode) => {
const suspiciousStatusCodes = [ const suspiciousStatusCodes = [403, 429];
403, 429 const botDetectionPatterns = [/verify you are human/i, /access denied/i, /x-amz-cf-id/i];
];
const botDetectionPatterns = [
/verify you are human/i,
/access denied/i,
/x-amz-cf-id/i,
];
const detectedInSource = botDetectionPatterns.some(pattern => pattern.test(pageSource)); const detectedInSource = botDetectionPatterns.some((pattern) => pattern.test(pageSource));
const detectedByStatus = suspiciousStatusCodes.includes(statusCode); const detectedByStatus = suspiciousStatusCodes.includes(statusCode);
return detectedInSource || detectedByStatus; return detectedInSource || detectedByStatus;