Feat/parapharmacy api #38
@@ -28,12 +28,12 @@ const SOURCES = {
|
|||||||
},
|
},
|
||||||
primor: {
|
primor: {
|
||||||
name: 'Primor',
|
name: 'Primor',
|
||||||
searchUrl: (q) => `https://www.primor.eu/search?s=${encodeURIComponent(q)}`,
|
searchUrl: (q) => `https://www.primor.eu/catalogsearch/result/?q=${encodeURIComponent(q)}`,
|
||||||
selectors: {
|
selectors: {
|
||||||
product: '.product-item, .product-miniature',
|
product: 'form.product-item',
|
||||||
name: '.product-title, h3',
|
name: 'form.product-item',
|
||||||
price: '.price',
|
price: 'form.product-item',
|
||||||
link: 'a.product-title',
|
link: 'a',
|
||||||
image: 'img'
|
image: 'img'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -74,7 +74,10 @@ async function scrapeSource(browser, source, query) {
|
|||||||
'a[href*="/p/"]',
|
'a[href*="/p/"]',
|
||||||
'[class*="Product"]',
|
'[class*="Product"]',
|
||||||
'article',
|
'article',
|
||||||
'[role="listitem"]'
|
'[role="listitem"]',
|
||||||
|
'.product-item',
|
||||||
|
'.product',
|
||||||
|
'[data-product]'
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const sel of selectors) {
|
for (const sel of selectors) {
|
||||||
@@ -85,8 +88,8 @@ async function scrapeSource(browser, source, query) {
|
|||||||
count: els.length,
|
count: els.length,
|
||||||
samples: Array.from(els).slice(0, 3).map(el => ({
|
samples: Array.from(els).slice(0, 3).map(el => ({
|
||||||
tag: el.tagName,
|
tag: el.tagName,
|
||||||
class: el.className?.substring(0, 100),
|
class: el.className?.substring(0, 150),
|
||||||
html: el.outerHTML?.substring(0, 300)
|
html: el.outerHTML?.substring(0, 500)
|
||||||
}))
|
}))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -110,8 +113,19 @@ async function scrapeSource(browser, source, query) {
|
|||||||
|
|
||||||
cards.forEach(card => {
|
cards.forEach(card => {
|
||||||
// Try data attributes first (Promofarma style)
|
// Try data attributes first (Promofarma style)
|
||||||
const name = card.getAttribute('data-name') || card.querySelector(selectors.name)?.innerText?.trim();
|
let name = card.getAttribute('data-name');
|
||||||
const priceStr = card.getAttribute('data-pvp') || card.querySelector(selectors.price)?.innerText?.trim();
|
let priceStr = card.getAttribute('data-pvp');
|
||||||
|
|
||||||
|
// If no data attributes, try to extract from content
|
||||||
|
if (!name) {
|
||||||
|
const nameEl = card.querySelector('[class*="name"], h3, h2, .product-name');
|
||||||
|
name = nameEl?.innerText?.trim();
|
||||||
|
}
|
||||||
|
if (!priceStr) {
|
||||||
|
const priceEl = card.querySelector('[class*="price"], .price');
|
||||||
|
priceStr = priceEl?.innerText?.trim();
|
||||||
|
}
|
||||||
|
|
||||||
const linkEl = card.querySelector(selectors.link);
|
const linkEl = card.querySelector(selectors.link);
|
||||||
const imgEl = card.querySelector(selectors.image);
|
const imgEl = card.querySelector(selectors.image);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import { workflow, node, trigger } from '@n8n/workflow-sdk';
|
||||||
|
|
||||||
|
const webhook = trigger({
|
||||||
|
type: 'n8n-nodes-base.webhook',
|
||||||
|
version: 2,
|
||||||
|
config: {
|
||||||
|
name: 'Webhook',
|
||||||
|
position: [240, 300],
|
||||||
|
parameters: {
|
||||||
|
httpMethod: 'POST',
|
||||||
|
path: 'scrape',
|
||||||
|
responseMode: 'lastNode'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
output: [{}]
|
||||||
|
});
|
||||||
|
|
||||||
|
const callApi = node({
|
||||||
|
type: 'n8n-nodes-base.httpRequest',
|
||||||
|
version: 4.2,
|
||||||
|
config: {
|
||||||
|
name: 'Call Scraper API',
|
||||||
|
position: [440, 300],
|
||||||
|
parameters: {
|
||||||
|
method: 'POST',
|
||||||
|
url: 'http://parapharmacy-api:3002/api/scrape',
|
||||||
|
sendBody: true,
|
||||||
|
specifyBody: 'json',
|
||||||
|
jsonBody: '={{ JSON.stringify($json.body || { queries: ["capricare"], sources: ["promofarma"] }) }}'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
output: [{}]
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = node({
|
||||||
|
type: 'n8n-nodes-base.code',
|
||||||
|
version: 2,
|
||||||
|
config: {
|
||||||
|
name: 'Response',
|
||||||
|
position: [640, 300],
|
||||||
|
parameters: {
|
||||||
|
jsCode: `return [{ json: $input.first().json }];`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
output: [{}]
|
||||||
|
});
|
||||||
|
|
||||||
|
export default workflow('scrape-simple', 'Parapharmacy Scraper Simple')
|
||||||
|
.add(webhook)
|
||||||
|
.to(callApi)
|
||||||
|
.to(response);
|
||||||
Reference in New Issue
Block a user