Popolare un negozio online a mano è la parte più costosa e noiosa del lancio. Quando il fornitore gestisce migliaia di articoli e i prezzi cambiano ogni settimana, senza automazione non si va lontano. Qui entra in scena lo scraper per OpenCart — un programma che raccoglie i dati dei prodotti dal sito di origine (fornitore, distributore, marketplace) e li carica nel tuo negozio: con nomi, descrizioni, foto, caratteristiche e, cosa particolarmente importante, con i prezzi convertiti correttamente.
In questo articolo vedremo in cosa differiscono le versioni di OpenCart dal punto di vista dello scraping, quali soluzioni pronte esistono, e scriveremo un nostro scraper in Python che raccoglie i prodotti dal sito del fornitore e converte la valuta in parallelo.
Perché la versione di OpenCart conta
Alla fine, lo scraper scrive i dati nel database o genera un file di importazione, e la struttura del database e la logica degli URL SEO cambiano da una versione di OpenCart all’altra. Ignorare questo aspetto è la garanzia di ritrovarsi con prodotti «rotti» o errori di importazione.
OpenCart è nato nel lontano 2005 e da allora ha attraversato diverse riscritture importanti: la versione 1.5 (2011) ha portato un nuovo editor di layout e un nuovo pannello di amministrazione; il ramo 2.x (2014), un’architettura modulare e un sistema di estensioni migliorato; la versione 3.0 (2017), la modalità multi-store e il Marketplace; e il ramo 4.x è diventato quello attuale, con la base di codice rinnovata e il ritorno di OCMOD. Tutti questi rami si incontrano ancora in progetti vivi.
Ecco cosa deve considerare lo scraper:
OpenCart 1.5.x — ramo obsoleto, ma ancora in circolazione. Struttura delle tabelle semplice, però con nomi di campo diversi in alcune tabelle. I moduli di importazione pronti ormai quasi non la supportano più.
OpenCart 2.x — il ramo «storico» in produzione più diffuso. Qui vivono molti dei moduli di importazione più collaudati. Gli URL SEO sono salvati nella tabella url_alias.
OpenCart 3.x — la versione più diffusa nei negozi in produzione al momento in cui scriviamo. Struttura vicina alla 2.x; gli URL SEO, sempre in url_alias. La maggior parte degli scraper e dei moduli pronti è pensata proprio per le versioni 2.x–3.x.
OpenCart 4.x — il ramo attuale. Qui c’è un cambiamento di fondo: gli URL SEO sono passati da url_alias alla tabella seo_url (con collegamento a store_id e language_id), ed è cambiata anche una parte dei controller. Uno scraper scritto per OC3, senza ritocchi, su OC4 non salverà URL parlanti corretti.
Fork e distribuzioni basate su OpenCart — sul mercato circolano distribuzioni modificate di OpenCart con template e moduli preinstallati. La struttura dei dati coincide con la versione di OpenCart corrispondente, ma i moduli «di serie» a volte si comportano diversamente, quindi conviene sempre testarli separatamente.
Conclusione pratica: prima di scrivere o comprare uno scraper, verifica la versione esatta del negozio (la vedi in fondo alla pagina del pannello di amministrazione o nell’
index.phpdel core) e il prefisso delle tabelle del database (di defaultoc_, ma spesso viene personalizzato).
Soluzioni pronte: cosa offre il mercato
Se il budget è limitato e il negozio è di taglio standard, forse uno scraper su misura non serve nemmeno. Di strumenti pronti ce n’è in abbondanza, e si dividono in alcune categorie.
Moduli di importazione all’interno di OpenCart
Sono estensioni che di per sé non «girano» per i siti altrui, ma ricevono un file già preparato (CSV / XLS / XLSX / XML) e lo caricano nel catalogo:
- Export/Import Tool — modulo gratuito e molto diffuso; gestisce importazioni fino a qualche migliaio di prodotti (i limiti dipendono dall’hosting). Una buona opzione per iniziare.
- Moduli commerciali di importazione CSV del Marketplace ufficiale di OpenCart — a pagamento, con licenza per dominio, più flessibili nella configurazione della mappatura dei campi.
- Estensioni multiformato — moduli commerciali capaci di importare CSV, XLS, XLSX e XML in un colpo solo, pensati per OC 2–3.
- Moduli di importazione massiva con coda AJAX — caricano e aggiornano cataloghi di grandi dimensioni a lotti, senza scontrarsi con i limiti di tempo di esecuzione del server.
La combinazione «lo scraper genera il file → il modulo di importazione lo carica» è la più affidabile: controlli i dati con i tuoi occhi prima di caricarli e non tocchi nulla direttamente nel database.
Moduli completi: scraper e importatore in uno
Sono soluzioni «tutto in uno» all’interno dell’ecosistema di estensioni di OpenCart: fanno da sole lo scraping dei siti di origine e caricano il risultato nel negozio. Due profili tipici:
- Moduli con scraper integrato — estrazione multithread dai siti di origine, traduzione automatica dei testi, generazione di descrizioni con l’AI, pianificatore di attività e mappatura flessibile dei dati; di norma la licenza è annuale, per singolo dominio.
- Moduli di importazione massiva avanzata — creano l’albero delle categorie, caricano migliaia di prodotti con descrizioni, opzioni, attributi e foto, e sanno standardizzare gli attributi «al volo».
Programmi esterni e servizi cloud
- Octoparse — strumento visuale di scraping, desktop e cloud, con decine di template pronti per i siti più popolari; raccoglie dati da negozi e marketplace e li esporta in CSV/Excel, da passare poi a un modulo di importazione.
- Apify — piattaforma di scraping in cloud con «actor» pronti per i siti più noti, esecuzioni pianificate ed esportazione via API; è una buona scelta quando il catalogo del fornitore va sincronizzato con regolarità.
Quando una soluzione pronta non basta? Quando il sito del fornitore ha un layout poco convenzionale o una protezione anti-scraping, quando serve una logica di ricarico specifica, la conversione di valuta con un proprio tasso di cambio, l’adattamento alla propria struttura di categorie o la sincronizzazione periodica dello stock. In questi casi si scrive uno scraper su misura — ed è quello che faremo adesso.
Esempio: uno scraper personalizzato con importazione e conversione di valuta
Analizziamo uno scraper didattico in Python che:
- scorre il catalogo del fornitore e raccoglie le schede prodotto;
- estrae nome, prezzo, descrizione, codice articolo (SKU) e immagine;
- converte il prezzo dalla valuta del fornitore alla valuta del negozio con il tasso di cambio aggiornato e il ricarico impostato;
- importa i prodotti direttamente nel database di OpenCart.
Il codice ha scopo didattico. Prima di eseguirlo su un negozio in produzione, fai assolutamente un backup del database, verifica che lo scraping del sito di origine non ne violi le condizioni d’uso né la legge, e testa tutto su una copia del negozio.
Stack e preparazione
pip install requests beautifulsoup4 pymysql lxmlrequests+beautifulsoup4— download e parsing dell’HTML;pymysql— scrittura nel database di OpenCart;- il tasso di cambio lo prendiamo da una fonte pubblica (nell’esempio, il feed XML dei tassi di riferimento della Banca Centrale Europea; puoi sostituirlo con qualsiasi altra fonte).
Passo 1. Convertitore di valuta
Per prima cosa, un modulo separato che ottiene il tasso di cambio e ricalcola il prezzo con il ricarico. Il tasso viene messo in cache per non chiamare la fonte a ogni prodotto.
import requests
import xml.etree.ElementTree as ET
from datetime import date
class CurrencyConverter:
"""Ottiene il tasso di riferimento della BCE e converte il prezzo del fornitore nella valuta del negozio."""
ECB_URL = "https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml"
def __init__(self, source_currency="USD", markup=1.20):
# markup=1.20 — ricarico del negozio: +20% sul tasso di cambio
self.source_currency = source_currency
self.markup = markup
self._rate = None
self._rate_date = None
def _fetch_rate(self):
"""Cerca nell'XML della BCE il tasso della valuta rispetto all'euro."""
resp = requests.get(self.ECB_URL, timeout=15)
tree = ET.fromstring(resp.text)
for cube in tree.iter():
if cube.get("currency") == self.source_currency:
return float(cube.get("rate")) # unità di valuta per 1 EUR
raise ValueError(f"Valuta {self.source_currency} non trovata nella risposta della BCE")
@property
def rate(self):
# mettiamo in cache il tasso per la data corrente
if self._rate is None or self._rate_date != date.today():
self._rate = self._fetch_rate()
self._rate_date = date.today()
return self._rate
def convert(self, amount):
"""Prezzo del fornitore -> prezzo del negozio (in euro) con il ricarico."""
if amount is None:
return None
price = float(amount) / self.rate * self.markup
return round(price, 2)Passo 2. Scraper delle schede del fornitore
I selettori (.product-card, .price, ecc.) sono indicativi: si adattano individualmente a ogni sito di origine dopo aver esaminato il codice sorgente della pagina.
import time
import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin
class SupplierParser:
def __init__(self, base_url, converter, delay=1.0):
self.base_url = base_url
self.converter = converter
self.delay = delay # pausa tra le richieste per non sovraccaricare il sito di origine
self.session = requests.Session()
self.session.headers.update({
"User-Agent": "Mozilla/5.0 (compatible; CatalogImporter/1.0)"
})
def _get_soup(self, url):
resp = self.session.get(url, timeout=20)
resp.raise_for_status()
return BeautifulSoup(resp.text, "lxml")
def parse_catalog(self, catalog_path, max_pages=5):
"""Raccoglie i link ai prodotti dalle pagine del catalogo con la paginazione."""
product_urls = []
for page in range(1, max_pages + 1):
url = urljoin(self.base_url, f"{catalog_path}?page={page}")
soup = self._get_soup(url)
cards = soup.select(".product-card a.product-link")
if not cards:
break # non ci sono altre pagine
for a in cards:
product_urls.append(urljoin(self.base_url, a["href"]))
time.sleep(self.delay)
return product_urls
def parse_product(self, url):
"""Elabora una singola scheda prodotto."""
soup = self._get_soup(url)
name = soup.select_one("h1.product-title")
price_raw = soup.select_one(".price .value")
description = soup.select_one(".product-description")
sku = soup.select_one(".sku")
image = soup.select_one(".product-gallery img")
# puliamo il prezzo da spazi e simboli di valuta
price_source = None
if price_raw:
digits = "".join(ch for ch in price_raw.text if ch.isdigit() or ch == ".")
price_source = float(digits) if digits else None
time.sleep(self.delay)
return {
"name": name.text.strip() if name else "Senza nome",
"sku": sku.text.strip() if sku else "",
"description": description.decode_contents().strip() if description else "",
"image_url": urljoin(self.base_url, image["src"]) if image else "",
"price_source": price_source,
# è qui che avviene la conversione di valuta:
"price": self.converter.convert(price_source),
"source_url": url,
}Passo 3. Importazione nel database di OpenCart
Qui applichiamo quanto detto all’inizio: la struttura dipende dalla versione. L’esempio è pensato per OpenCart 3.x; la differenza chiave per OC4 è segnalata in un commento.
import pymysql
from datetime import datetime
from slugify import slugify # pip install python-slugify
class OpenCartImporter:
def __init__(self, db_config, prefix="oc_", store_id=0, language_id=1):
self.conn = pymysql.connect(**db_config, charset="utf8mb4",
cursorclass=pymysql.cursors.DictCursor)
self.prefix = prefix
self.store_id = store_id
self.language_id = language_id
def _seo_keyword(self, name):
return slugify(name) or "product"
def import_product(self, p, category_id=None):
cur = self.conn.cursor()
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
px = self.prefix
# 1) tabella principale del prodotto
cur.execute(f"""
INSERT INTO {px}product
(model, sku, quantity, price, image, status,
date_added, date_modified, date_available)
VALUES (%s, %s, %s, %s, %s, 1, %s, %s, %s)
""", (p["sku"] or p["name"][:64], p["sku"], 100,
p["price"], "", now, now, now))
product_id = cur.lastrowid
# 2) descrizione (nome, testo, meta) — per ogni lingua
cur.execute(f"""
INSERT INTO {px}product_description
(product_id, language_id, name, description,
meta_title, meta_description)
VALUES (%s, %s, %s, %s, %s, %s)
""", (product_id, self.language_id, p["name"],
p["description"], p["name"], p["name"]))
# 3) collegamento al negozio
cur.execute(f"""
INSERT INTO {px}product_to_store (product_id, store_id)
VALUES (%s, %s)
""", (product_id, self.store_id))
# 4) collegamento alla categoria (se indicata)
if category_id:
cur.execute(f"""
INSERT INTO {px}product_to_category (product_id, category_id)
VALUES (%s, %s)
""", (product_id, category_id))
# 5) SEO URL (URL parlante)
keyword = f"{self._seo_keyword(p['name'])}-{product_id}"
# --- OpenCart 3.x: tabella url_alias ---
cur.execute(f"""
INSERT INTO {px}url_alias (query, keyword)
VALUES (%s, %s)
""", (f"product_id={product_id}", keyword))
# --- In OpenCart 4.x sarebbe così (tabella seo_url):
# cur.execute(f'''INSERT INTO {px}seo_url
# (store_id, language_id, key, value, keyword)
# VALUES (%s, %s, "product_id", %s, %s)''',
# (self.store_id, self.language_id, product_id, keyword))
self.conn.commit()
return product_id
def close(self):
self.conn.close()Passo 4. Mettiamo tutto insieme
def main():
converter = CurrencyConverter(source_currency="USD", markup=1.25)
parser = SupplierParser("https://supplier-example.com", converter, delay=1.0)
importer = OpenCartImporter(
db_config={
"host": "localhost",
"user": "db_user",
"password": "db_password",
"database": "opencart_db",
},
prefix="oc_",
store_id=0,
language_id=1,
)
product_urls = parser.parse_catalog("/catalog/category-1", max_pages=3)
print(f"Prodotti trovati: {len(product_urls)}")
imported = 0
for url in product_urls:
try:
product = parser.parse_product(url)
if product["price"] is None:
print(f"Saltato (senza prezzo): {url}")
continue
pid = importer.import_product(product, category_id=20)
imported += 1
print(f"[{imported}] {product['name']} — "
f"{product['price_source']} {converter.source_currency} "
f"-> {product['price']} € (ID {pid})")
except Exception as e:
print(f"Errore su {url}: {e}")
importer.close()
print(f"Fatto. Importati: {imported}")
if __name__ == "__main__":
main()Cosa resta fuori dall’esempio ed è importante in un progetto reale
L’esempio didattico è semplificato di proposito. In uno scraper in produzione bisogna prevedere anche:
- Download e collegamento delle immagini — scaricare le foto in
image/catalog/...e registrare il percorso inproduct.imageeoc_product_image. Spesso i siti di origine rispondono 403 al download diretto, e servono referer e header corretti. - Deduplicazione — verificare tramite
sku/modelse il prodotto esiste già nel database e aggiornarne prezzo e stock invece di moltiplicare i duplicati. È questo che trasforma uno scraping una tantum in una sincronizzazione periodica. - Attributi e opzioni —
oc_product_attributeeoc_product_option, con la loro struttura di relazioni. - Categorie — creazione automatica dell’albero delle categorie in base alla struttura del sito di origine (
oc_category,oc_category_description,oc_category_path). - Pulizia della cache di OpenCart e rigenerazione degli indici SEO dopo un’importazione massiva.
- Tasso di cambio e ricarico — salvarli nella configurazione e registrare nei log con quale tasso è stato ricalcolato ogni lotto, così i prezzi restano riproducibili e trasparenti.
- Etica e legalità — rispettare il
robots.txt, mantenere pause ragionevoli tra le richieste e attenersi alle condizioni d’uso del sito di origine.
Lo stesso scraper in PHP — direttamente sul server del negozio
Python è comodo, ma richiede un ambiente a parte. OpenCart, invece, è scritto in PHP e gira su MySQL — quindi lo scraper si può costruire sullo stesso stack ed eseguire direttamente sull’hosting del negozio, senza installare Python né runtime di terze parti. I vantaggi di questo approccio:
- non devi installare nulla in più — PHP è già sul server;
- puoi riutilizzare il
config.phpdi OpenCart per non duplicare le credenziali del database; - è facile pianificarlo con cron per una sincronizzazione periodica;
- volendo, lo script può agganciarsi al core di OpenCart e scrivere i prodotti tramite i suoi modelli, invece di intervenire direttamente sulle tabelle.
Per il parsing dell’HTML usiamo i nativi DOMDocument + DOMXPath (senza librerie di terze parti); per le richieste, cURL; per il database, PDO.
Passo 1. Convertitore di valuta
<?php
class CurrencyConverter
{
private const ECB_URL = 'https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml';
private ?float $rate = null;
private ?string $rateDate = null;
public function __construct(
private string $sourceCurrency = 'USD',
private float $markup = 1.20 // ricarico del negozio: +20% sul tasso di cambio
) {}
private function fetchRate(): float
{
$xmlRaw = file_get_contents(self::ECB_URL);
$xml = new SimpleXMLElement($xmlRaw);
$xml->registerXPathNamespace('e', 'http://www.ecb.int/vocabulary/2002-08-01/eurofxref');
foreach ($xml->xpath('//e:Cube[@currency]') as $cube) {
if ((string)$cube['currency'] === $this->sourceCurrency) {
return (float)$cube['rate']; // unità di valuta per 1 EUR
}
}
throw new RuntimeException("Valuta {$this->sourceCurrency} non trovata nella risposta della BCE");
}
public function getRate(): float
{
$today = date('Y-m-d');
if ($this->rate === null || $this->rateDate !== $today) {
$this->rate = $this->fetchRate();
$this->rateDate = $today;
}
return $this->rate;
}
public function convert(?float $amount): ?float
{
if ($amount === null) {
return null;
}
return round($amount / $this->getRate() * $this->markup, 2);
}
}Passo 2. Scraper delle schede del fornitore
<?php
class SupplierParser
{
public function __construct(
private string $baseUrl,
private CurrencyConverter $converter,
private float $delay = 1.0 // pausa tra le richieste, in secondi
) {}
private function getHtml(string $url): string
{
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_TIMEOUT => 20,
CURLOPT_USERAGENT => 'Mozilla/5.0 (compatible; CatalogImporter/1.0)',
]);
$html = curl_exec($ch);
if ($html === false) {
throw new RuntimeException('cURL: ' . curl_error($ch));
}
curl_close($ch);
return $html;
}
private function makeXPath(string $html): DOMXPath
{
$doc = new DOMDocument();
libxml_use_internal_errors(true); // silenziamo gli errori dell'HTML «sporco»
$doc->loadHTML('<?xml encoding="UTF-8">' . $html);
libxml_clear_errors();
return new DOMXPath($doc);
}
private function absolute(string $href): string
{
return str_starts_with($href, 'http') ? $href : rtrim($this->baseUrl, '/') . '/' . ltrim($href, '/');
}
/** Raccoglie i link ai prodotti dalle pagine del catalogo con la paginazione. */
public function parseCatalog(string $catalogPath, int $maxPages = 5): array
{
$urls = [];
for ($page = 1; $page <= $maxPages; $page++) {
$html = $this->getHtml($this->absolute("{$catalogPath}?page={$page}"));
$xpath = $this->makeXPath($html);
$links = $xpath->query("//div[contains(@class,'product-card')]//a[contains(@class,'product-link')]");
if ($links->length === 0) {
break; // non ci sono altre pagine
}
foreach ($links as $a) {
$urls[] = $this->absolute($a->getAttribute('href'));
}
usleep((int)($this->delay * 1_000_000));
}
return $urls;
}
/** Elabora una singola scheda prodotto. */
public function parseProduct(string $url): array
{
$xpath = $this->makeXPath($this->getHtml($url));
$text = fn(string $q) => trim($xpath->query($q)->item(0)?->textContent ?? '');
$name = $text("//h1[contains(@class,'product-title')]") ?: 'Senza nome';
$sku = $text("//*[contains(@class,'sku')]");
$description = $text("//*[contains(@class,'product-description')]");
$priceRaw = $text("//*[contains(@class,'price')]//*[contains(@class,'value')]");
$imgNode = $xpath->query("//*[contains(@class,'product-gallery')]//img")->item(0);
$imageUrl = $imgNode ? $this->absolute($imgNode->getAttribute('src')) : '';
// puliamo il prezzo da spazi e simboli di valuta
$priceSource = null;
if ($priceRaw !== '') {
$digits = preg_replace('/[^0-9.]/', '', str_replace(',', '.', $priceRaw));
$priceSource = $digits !== '' ? (float)$digits : null;
}
usleep((int)($this->delay * 1_000_000));
return [
'name' => $name,
'sku' => $sku,
'description' => $description,
'image_url' => $imageUrl,
'price_source' => $priceSource,
// conversione di valuta:
'price' => $this->converter->convert($priceSource),
'source_url' => $url,
];
}
}Passo 3. Importazione nel database di OpenCart
<?php
class OpenCartImporter
{
private PDO $pdo;
public function __construct(
array $dbConfig,
private string $prefix = 'oc_',
private int $storeId = 0,
private int $languageId = 1
) {
$dsn = "mysql:host={$dbConfig['host']};dbname={$dbConfig['database']};charset=utf8mb4";
$this->pdo = new PDO($dsn, $dbConfig['user'], $dbConfig['password'], [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);
}
private function seoKeyword(string $name): string
{
// generazione semplificata dell'URL parlante (senza accenti né caratteri speciali)
$slug = mb_strtolower(trim($name));
$slug = strtr($slug, ['à' => 'a', 'è' => 'e', 'é' => 'e', 'ì' => 'i', 'ò' => 'o', 'ù' => 'u']);
$slug = preg_replace('/[^a-z0-9\s-]/u', '', $slug);
$slug = preg_replace('/\s+/u', '-', $slug);
return $slug !== '' ? $slug : 'product';
}
public function importProduct(array $p, ?int $categoryId = null): int
{
$px = $this->prefix;
$now = date('Y-m-d H:i:s');
// 1) tabella principale del prodotto
$stmt = $this->pdo->prepare("
INSERT INTO {$px}product
(model, sku, quantity, price, image, status,
date_added, date_modified, date_available)
VALUES (:model, :sku, 100, :price, '', 1, :added, :modified, :available)
");
$stmt->execute([
':model' => $p['sku'] ?: mb_substr($p['name'], 0, 64),
':sku' => $p['sku'],
':price' => $p['price'],
':added' => $now,
':modified' => $now,
':available' => $now,
]);
$productId = (int)$this->pdo->lastInsertId();
// 2) descrizione (nome, testo, meta)
$this->pdo->prepare("
INSERT INTO {$px}product_description
(product_id, language_id, name, description, meta_title, meta_description)
VALUES (?, ?, ?, ?, ?, ?)
")->execute([
$productId, $this->languageId, $p['name'],
$p['description'], $p['name'], $p['name'],
]);
// 3) collegamento al negozio
$this->pdo->prepare("
INSERT INTO {$px}product_to_store (product_id, store_id) VALUES (?, ?)
")->execute([$productId, $this->storeId]);
// 4) collegamento alla categoria
if ($categoryId !== null) {
$this->pdo->prepare("
INSERT INTO {$px}product_to_category (product_id, category_id) VALUES (?, ?)
")->execute([$productId, $categoryId]);
}
// 5) SEO URL (URL parlante)
$keyword = $this->seoKeyword($p['name']) . '-' . $productId;
// --- OpenCart 3.x: tabella url_alias ---
$this->pdo->prepare("
INSERT INTO {$px}url_alias (query, keyword) VALUES (?, ?)
")->execute(["product_id={$productId}", $keyword]);
// --- In OpenCart 4.x sarebbe così (tabella seo_url):
// INSERT INTO {$px}seo_url (store_id, language_id, `key`, `value`, keyword)
// VALUES (:store, :lang, 'product_id', :pid, :keyword)
return $productId;
}
}Passo 4. Punto di ingresso ed esecuzione via cron
<?php
require 'CurrencyConverter.php';
require 'SupplierParser.php';
require 'OpenCartImporter.php';
$converter = new CurrencyConverter('USD', 1.25);
$parser = new SupplierParser('https://supplier-example.com', $converter, 1.0);
// le credenziali possono stare in un config a parte o essere prese dal config.php di OpenCart
$importer = new OpenCartImporter([
'host' => 'localhost',
'user' => 'db_user',
'password' => 'db_password',
'database' => 'opencart_db',
], prefix: 'oc_', storeId: 0, languageId: 1);
$urls = $parser->parseCatalog('/catalog/category-1', maxPages: 3);
echo 'Prodotti trovati: ' . count($urls) . PHP_EOL;
$imported = 0;
foreach ($urls as $url) {
try {
$product = $parser->parseProduct($url);
if ($product['price'] === null) {
echo "Saltato (senza prezzo): {$url}" . PHP_EOL;
continue;
}
$pid = $importer->importProduct($product, categoryId: 20);
$imported++;
printf("[%d] %s — %s USD -> %s € (ID %d)%s",
$imported, $product['name'], $product['price_source'], $product['price'], $pid, PHP_EOL);
} catch (Throwable $e) {
echo "Errore su {$url}: {$e->getMessage()}" . PHP_EOL;
}
}
echo "Fatto. Importati: {$imported}" . PHP_EOL;Metterlo in esecuzione automatica ogni giorno è una riga nel crontab:
# ogni giorno alle 4:00 sincronizziamo il catalogo con il fornitore
0 4 * * * /usr/bin/php /var/www/opencart/parser/run.php >> /var/www/opencart/parser/parser.log 2>&1Le credenziali del database si possono prendere direttamente da OpenCart:
require '/path/to/opencart/config.php';— dopo di che diventano disponibili le costantiDB_HOSTNAME,DB_USERNAME,DB_PASSWORD,DB_DATABASEeDB_PREFIX, e non servirà duplicare le password nello scraper.
Tutte le avvertenze della sezione «fuori dall’esempio» (immagini, deduplicazione, attributi, cache, legalità) valgono allo stesso modo per la versione PHP.
Conclusione
Per un negozio standard spesso basta la combinazione «scraper pronto + modulo di importazione»: Export/Import Tool, i moduli commerciali del Marketplace o strumenti come Octoparse e Apify coprono la maggior parte degli scenari. Ma appena compare un sito di origine atipico, una logica di ricarico personalizzata, la conversione di valuta con il tasso di cambio aggiornato o la sincronizzazione periodica dello stock per una specifica versione di OpenCart — commissionare uno scraper su misura è più conveniente e più affidabile.
Ti serve lo scraping di negozi online?
Se devi popolare o sincronizzare un negozio su OpenCart (o su qualsiasi altra piattaforma), raccogliere dati dai siti dei fornitori o configurare l’aggiornamento automatico dei prezzi con conversione di valuta — contattaci. Progetteremo e implementeremo uno scraper per la tua versione di OpenCart e per le tue esigenze.