Popular uma loja virtual manualmente é a parte mais cara e tediosa do lançamento. Quando o fornecedor trabalha com milhares de itens e os preços mudam toda semana, não há como se virar sem automação. É aqui que entra em cena o scraper para OpenCart: um programa que coleta os dados de produtos de um site de origem (fornecedor, distribuidor, marketplace) e os grava na loja com nomes, descrições, fotos, características e — o que é especialmente importante — com os preços corretamente convertidos.
Neste artigo, vamos ver em que as versões do OpenCart diferem do ponto de vista do scraping, que soluções prontas existem e escrever nosso próprio scraper em Python, que coleta produtos do site do fornecedor e converte a moeda no processo.
Por que a versão do OpenCart importa
No fim das contas, o scraper grava os dados no banco ou gera um arquivo de importação — e a estrutura do banco e a lógica das URLs SEO variam entre as versões do OpenCart. Ignorar isso é garantia de terminar com produtos «quebrados» ou erros de importação.
O OpenCart surgiu lá em 2005 e, desde então, passou por várias reescritas de peso: a versão 1.5 (2011) trouxe um novo editor de layouts e um novo painel administrativo; a linha 2.x (2014), uma arquitetura modular e um sistema de extensões aprimorado; a versão 3.0 (2017), o modo multiloja e o Marketplace; e a linha 4.x virou a atual, com a base de código renovada e o retorno do OCMOD. Todas essas linhas continuam aparecendo em projetos vivos.
Eis o que o scraper precisa levar em conta:
OpenCart 1.5.x: linha obsoleta, mas ainda encontrada por aí. Estrutura de tabelas simples, embora com nomes de campos diferentes em várias tabelas. Os módulos de importação prontos praticamente já não recebem manutenção para ela.
OpenCart 2.x: a linha «antiga» mais difundida em produção. É aqui que vivem muitos dos módulos de importação mais testados. As URLs SEO ficam na tabela url_alias.
OpenCart 3.x: a versão mais popular em lojas em produção no momento em que este texto é escrito. Estrutura próxima da 2.x; as URLs SEO, também em url_alias. A maioria dos scrapers e módulos prontos é pensada justamente para 2.x--3.x.
OpenCart 4.x: a linha atual. Traz uma mudança de fundo: as URLs SEO migraram de url_alias para a tabela seo_url (vinculadas a store_id e language_id), e parte dos controladores também mudou. Um scraper escrito para o OC3 não deixará URLs amigáveis corretas no OC4 sem ajustes.
Forks e distribuições baseadas no OpenCart: circulam no mercado distribuições modificadas do OpenCart com templates e módulos pré-instalados. A estrutura de dados delas coincide com a da versão correspondente do OpenCart, mas os módulos «de fábrica» às vezes se comportam de outro jeito, então vale sempre testá-los separadamente.
Conclusão prática: antes de escrever ou comprar um scraper, confirme a versão exata da loja (visível no rodapé do painel administrativo ou no
index.phpdo núcleo) e o prefixo das tabelas do banco (por padrãooc_, embora muitas vezes seja trocado por outro).
Soluções prontas: o que o mercado oferece
Se o orçamento é apertado e a loja segue um perfil padrão, talvez nem seja preciso escrever um scraper próprio. Ferramentas prontas não faltam, e elas se dividem em algumas categorias.
Módulos de importação dentro do OpenCart
São extensões que não «varrem» sites de terceiros por conta própria: elas recebem um arquivo já preparado (CSV / XLS / XLSX / XML) e o carregam no catálogo:
- Export/Import Tool — módulo gratuito e muito difundido; dá conta de importações de até alguns milhares de produtos (os limites dependem da hospedagem). Uma boa opção para começar.
- Módulos comerciais de importação CSV do Marketplace oficial do OpenCart — pagos, com licença por domínio e mais flexíveis na hora de configurar a correspondência de campos.
- Extensões multiformato — módulos comerciais capazes de importar CSV, XLS, XLSX e XML de uma só vez, voltados ao OC 2--3.
- Módulos de importação em massa por fila AJAX — carregam e atualizam catálogos grandes em lotes, sem esbarrar nos limites de tempo de execução do servidor.
A combinação «o scraper gera o arquivo → o módulo de importação faz o upload» é a mais confiável: dá para conferir os dados com os próprios olhos antes de carregá-los, sem mexer em nada diretamente no banco.
Módulos integrados: scraper e importador em um só
São soluções «tudo em um» dentro do ecossistema de extensões do OpenCart: elas mesmas fazem o scraping dos sites de origem e carregam o resultado na loja. Dois perfis típicos:
- Módulos com scraper integrado — extração multithread dos sites de origem, tradução automática de textos, geração de descrições com IA, agendador de tarefas e mapeamento flexível de dados; o habitual é uma licença anual por domínio.
- Módulos de importação em massa avançada — criam a árvore de categorias, carregam milhares de produtos com descrições, opções, atributos e fotos e sabem padronizar os atributos «na hora».
Programas externos e serviços na nuvem
- Octoparse — ferramenta visual de scraping, para desktop e na nuvem, com dezenas de templates prontos para sites populares; coleta dados de lojas e marketplaces e os exporta para CSV/Excel, para depois passá-los por um módulo de importação.
- Apify — plataforma de scraping na nuvem com «actors» prontos para sites conhecidos, execuções agendadas e exportação via API; encaixa bem quando o catálogo do fornecedor precisa ser sincronizado com regularidade.
Quando uma solução pronta não basta? Quando o site do fornecedor tem um layout fora do trivial ou proteção anti-scraping, quando é preciso uma lógica de margens específica, a conversão de moedas com taxa de câmbio própria, o encaixe na sua própria estrutura de categorias ou a sincronização periódica de estoque. Nesses casos, escreve-se um scraper sob medida — e é para lá que vamos agora.
Exemplo: um scraper próprio com importação e conversão de moedas
Vamos analisar um scraper didático em Python que:
- percorre o catálogo do fornecedor e coleta as fichas de produto;
- extrai o nome, o preço, a descrição, o código de referência (SKU) e a imagem;
- converte o preço da moeda do fornecedor para a moeda da loja com a taxa de câmbio atual e a margem configurada;
- importa os produtos diretamente no banco de dados do OpenCart.
O código tem fins didáticos. Antes de executá-lo em uma loja em produção, faça sem falta um backup do banco, verifique se o scraping do site de origem não viola os termos de uso nem a lei e teste tudo em uma cópia da loja.
Stack e preparação
pip install requests beautifulsoup4 pymysql lxmlrequests+beautifulsoup4— download e parsing do HTML;pymysql— gravação no banco do OpenCart;- a taxa de câmbio vem de uma fonte pública (no exemplo, a API aberta de cotações PTAX do Banco Central do Brasil; dá para substituí-la por qualquer outra).
Passo 1. Conversor de moedas
Primeiro, um módulo à parte que obtém a taxa de câmbio e recalcula o preço com a margem. A cotação fica em cache para não chamar a fonte a cada produto.
import requests
from datetime import date
class CurrencyConverter:
"""Obtém a cotação PTAX do Banco Central do Brasil e converte o preço do fornecedor para a moeda da loja."""
# dólar comercial; para outras moedas, o serviço PTAX oferece o recurso CotacaoMoedaDia
PTAX_URL = ("https://olinda.bcb.gov.br/olinda/servico/PTAX/versao/v1"
"/odata/CotacaoDolarDia(dataCotacao=@dataCotacao)")
def __init__(self, source_currency="USD", markup=1.20):
# markup=1.20 — margem da loja: +20% sobre a taxa de câmbio
self.source_currency = source_currency
self.markup = markup
self._rate = None
self._rate_date = None
def _fetch_rate(self):
"""Busca na resposta do Banco Central a cotação de venda do dólar em reais."""
today = date.today().strftime("%m-%d-%Y")
url = f"{self.PTAX_URL}?@dataCotacao='{today}'&$format=json"
resp = requests.get(url, timeout=15)
quotes = resp.json().get("value", [])
if not quotes:
# a PTAX só é publicada em dias úteis:
# em produção, consulte a cotação do último dia útil
raise ValueError(f"Cotação {self.source_currency} não encontrada na resposta do Banco Central")
return float(quotes[-1]["cotacaoVenda"]) # reais por 1 USD
@property
def rate(self):
# guardamos a cotação em cache para a data atual
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):
"""Preço do fornecedor -> preço da loja (em reais) com a margem."""
if amount is None:
return None
price = float(amount) * self.rate * self.markup
return round(price, 2)Passo 2. Scraper de fichas do fornecedor
Os seletores (.product-card, .price etc.) são ilustrativos: eles são ajustados individualmente para cada site de origem depois de examinar o código-fonte da página.
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 entre requisições para não «derrubar» o site de origem
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):
"""Coleta os links de produtos das páginas do catálogo com paginação."""
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 # não há mais páginas
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):
"""Processa uma ficha de produto."""
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")
# limpamos o preço de espaços e símbolos de moeda
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 "Sem 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,
# é aqui que acontece a conversão de moedas:
"price": self.converter.convert(price_source),
"source_url": url,
}Passo 3. Importação para o banco de dados do OpenCart
Aqui aplicamos o que foi dito no início: a estrutura depende da versão. O exemplo é pensado para o OpenCart 3.x; a diferença-chave para o OC4 vai indicada em um comentário.
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) tabela principal do produto
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) descrição (nome, texto, metadados) — para cada idioma
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) vínculo com a loja
cur.execute(f"""
INSERT INTO {px}product_to_store (product_id, store_id)
VALUES (%s, %s)
""", (product_id, self.store_id))
# 4) vínculo com a categoria (se informada)
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 amigável)
keyword = f"{self._seo_keyword(p['name'])}-{product_id}"
# — OpenCart 3.x: tabela url_alias ---
cur.execute(f"""
INSERT INTO {px}url_alias (query, keyword)
VALUES (%s, %s)
""", (f"product_id={product_id}", keyword))
# — No OpenCart 4.x seria assim (tabela 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. Juntando tudo
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"Produtos encontrados: {len(product_urls)}")
imported = 0
for url in product_urls:
try:
product = parser.parse_product(url)
if product["price"] is None:
print(f"Ignorado (sem preço): {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"-> R$ {product['price']} (ID {pid})")
except Exception as e:
print(f"Erro em {url}: {e}")
importer.close()
print(f"Concluído. Importados: {imported}")
if __name__ == "__main__":
main()O que fica fora do exemplo e importa em um projeto real
O exemplo didático é simplificado de propósito. Em um scraper em produção, é preciso prever, além disso:
- O download e o vínculo das imagens — baixar as fotos para
image/catalog/...e registrar o caminho emproduct.imageeoc_product_image. Muitas vezes os sites de origem devolvem 403 no download direto, e são necessários um referer e cabeçalhos corretos. - A deduplicação — verificar por
sku/modelse o produto já existe no banco e atualizar preço e estoque em vez de multiplicar duplicados. É isso que transforma um scraping pontual em uma sincronização periódica. - Atributos e opções —
oc_product_attributeeoc_product_option, com estrutura própria de relações. - Categorias — criação automática da árvore de categorias conforme a estrutura do site de origem (
oc_category,oc_category_description,oc_category_path). - A limpeza do cache do OpenCart e a regeneração dos índices SEO após uma importação em massa.
- A taxa de câmbio e a margem — guardá-las na configuração e registrar nos logs com qual cotação cada lote foi recalculado, para que os preços sejam reproduzíveis e transparentes.
- Ética e legalidade — respeitar o
robots.txt, manter pausas razoáveis entre requisições e cumprir os termos de uso do site de origem.
O mesmo scraper em PHP — direto no servidor da loja
Python é prático, mas exige um ambiente à parte. Já o OpenCart é escrito em PHP e roda sobre MySQL, então o scraper pode ser construído com esse mesmo stack e executado diretamente na hospedagem da loja, sem instalar Python nem runtimes de terceiros. Vantagens dessa abordagem:
- não é preciso instalar nada extra: o PHP já está no servidor;
- dá para reutilizar o
config.phpdo OpenCart e não duplicar as credenciais do banco; - é fácil agendá-lo com cron para uma sincronização periódica;
- se quiser, o script pode se acoplar ao núcleo do OpenCart e gravar os produtos por meio dos modelos dele, em vez de atacar as tabelas diretamente.
Para o parsing do HTML usamos os nativos DOMDocument + DOMXPath (sem bibliotecas de terceiros); para as requisições, cURL; para o banco, PDO.
Passo 1. Conversor de moedas
<?php
class CurrencyConverter
{
// dólar comercial; para outras moedas, o serviço PTAX oferece o recurso CotacaoMoedaDia
private const PTAX_URL = 'https://olinda.bcb.gov.br/olinda/servico/PTAX/versao/v1/odata/CotacaoDolarDia(dataCotacao=@dataCotacao)';
private ?float $rate = null;
private ?string $rateDate = null;
public function __construct(
private string $sourceCurrency = 'USD',
private float $markup = 1.20 // margem da loja: +20% sobre a taxa de câmbio
) {}
private function fetchRate(): float
{
$url = self::PTAX_URL . "?@dataCotacao='" . date('m-d-Y') . "'&\$format=json";
$json = file_get_contents($url);
$data = json_decode($json, true);
$quotes = $data['value'] ?? [];
if ($quotes === []) {
// a PTAX só é publicada em dias úteis:
// em produção, consulte a cotação do último dia útil
throw new RuntimeException("Cotação {$this->sourceCurrency} não encontrada na resposta do Banco Central");
}
$last = end($quotes);
return (float)$last['cotacaoVenda']; // reais por 1 USD
}
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 de fichas do fornecedor
<?php
class SupplierParser
{
public function __construct(
private string $baseUrl,
private CurrencyConverter $converter,
private float $delay = 1.0 // pausa entre requisições, em segundos
) {}
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); // silenciamos os erros do HTML «sujo»
$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, '/');
}
/** Coleta os links de produtos das páginas do catálogo com paginação. */
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; // não há mais páginas
}
foreach ($links as $a) {
$urls[] = $this->absolute($a->getAttribute('href'));
}
usleep((int)($this->delay * 1_000_000));
}
return $urls;
}
/** Processa uma ficha de produto. */
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')]") ?: 'Sem 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')) : '';
// limpamos o preço de espaços e símbolos de moeda
$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,
// conversão de moedas:
'price' => $this->converter->convert($priceSource),
'source_url' => $url,
];
}
}Passo 3. Importação para o banco de dados do 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
{
// geração simplificada da URL amigável (sem acentos nem caracteres especiais)
$slug = mb_strtolower(trim($name));
$slug = strtr($slug, ['á' => 'a', 'à' => 'a', 'â' => 'a', 'ã' => 'a', 'é' => 'e', 'ê' => 'e', 'í' => 'i', 'ó' => 'o', 'ô' => 'o', 'õ' => 'o', 'ú' => 'u', 'ü' => 'u', 'ç' => 'c']);
$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) tabela principal do produto
$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) descrição (nome, texto, metadados)
$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) vínculo com a loja
$this->pdo->prepare("
INSERT INTO {$px}product_to_store (product_id, store_id) VALUES (?, ?)
")->execute([$productId, $this->storeId]);
// 4) vínculo com a 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 amigável)
$keyword = $this->seoKeyword($p['name']) . '-' . $productId;
// — OpenCart 3.x: tabela url_alias ---
$this->pdo->prepare("
INSERT INTO {$px}url_alias (query, keyword) VALUES (?, ?)
")->execute(["product_id={$productId}", $keyword]);
// — No OpenCart 4.x seria assim (tabela 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. Ponto de entrada e execução 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);
// as credenciais podem ficar em um arquivo à parte ou vir do config.php do 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 'Produtos encontrados: ' . count($urls) . PHP_EOL;
$imported = 0;
foreach ($urls as $url) {
try {
$product = $parser->parseProduct($url);
if ($product['price'] === null) {
echo "Ignorado (sem preço): {$url}" . PHP_EOL;
continue;
}
$pid = $importer->importProduct($product, categoryId: 20);
$imported++;
printf("[%d] %s — %s USD -> R$ %s (ID %d)%s",
$imported, $product['name'], $product['price_source'], $product['price'], $pid, PHP_EOL);
} catch (Throwable $e) {
echo "Erro em {$url}: {$e->getMessage()}" . PHP_EOL;
}
}
echo "Concluído. Importados: {$imported}" . PHP_EOL;Agendar a execução automática diária é uma linha no crontab:
# todo dia às 4:00 sincronizamos o catálogo com o fornecedor
0 4 * * * /usr/bin/php /var/www/opencart/parser/run.php >> /var/www/opencart/parser/parser.log 2>&1As credenciais do banco podem vir diretamente do OpenCart:
require '/path/to/opencart/config.php';— depois disso ficam disponíveis as constantesDB_HOSTNAME,DB_USERNAME,DB_PASSWORD,DB_DATABASEeDB_PREFIX, e não será preciso duplicar senhas no scraper.
Todas as ressalvas da seção «fora do exemplo» (imagens, deduplicação, atributos, cache, legalidade) continuam igualmente válidas para a versão em PHP.
Conclusão
Para uma loja padrão, costuma bastar a combinação «scraper pronto + módulo de importação»: o Export/Import Tool, os módulos comerciais do Marketplace ou ferramentas como Octoparse e Apify cobrem a maioria dos cenários. Mas, assim que aparece um site de origem atípico, uma lógica de margens própria, a conversão de moedas com a taxa de câmbio atual ou a sincronização periódica de estoque para uma versão específica do OpenCart, sai mais rentável e confiável encomendar um scraper sob medida.
Precisa de scraping de lojas online?
Se for preciso popular ou sincronizar uma loja no OpenCart (ou em qualquer outra plataforma), coletar dados dos sites dos seus fornecedores ou configurar a atualização automática de preços com conversão de moedas, entre em contato conosco. Vamos projetar e implantar um scraper para a sua versão do OpenCart e as suas tarefas específicas.