Remplir un catalogue à la main coûte cher et prend du temps, surtout lorsque le fournisseur propose des milliers de références et que les prix et les stocks changent chaque semaine. La solution : un scraper pour 1C-Bitrix, un programme qui collecte les produits sur le site du fournisseur (ou depuis sa grille tarifaire) et les charge dans votre boutique avec les noms, descriptions, photos, propriétés et, point critique, des prix correctement recalculés.
Dans cet article, nous verrons en quoi les éditions de « 1C-Bitrix » diffèrent du point de vue du web scraping, quelles solutions prêtes à l'emploi existent, et nous écrirons notre propre scraper qui collecte les données sur le site du fournisseur et convertit les devises au passage — en s'appuyant sur le module de devises intégré à Bitrix.
Pourquoi l'édition et l'architecture de Bitrix sont importantes
Contrairement à de nombreux CMS, il est fortement déconseillé d'écrire les produits directement en base dans Bitrix : la structure de stockage est complexe (infoblocks, catalogue commercial, prix, entrepôts, offres commerciales), et un INSERT direct cassera presque à coup sûr les index et la logique métier. La bonne approche consiste à passer par l'API de Bitrix. Pour un scraper, ce qui compte n'est donc pas tant la « version du noyau » que l'édition du produit et les modules qu'elle inclut.
« 1C-Bitrix : Gestion de site » est proposé en cinq éditions principales — « Start », « Standard », « Small Business », « Business » et « Enterprise ». Une boutique en ligne complète et les modules nécessaires au scraper n'apparaissent qu'à partir de l'édition « Small Business ».
« Start » et « Standard » — pour les sites de contenu. Le module « Catalogue commercial » est absent de la configuration de base : impossible de monter une vraie boutique sans développements supplémentaires. Il n'y a donc nulle part où importer des produits en tant qu'articles de catalogue.
« Small Business » — la première édition « e-commerce ». Elle contient les modules « Boutique en ligne » et « Catalogue commercial », et prend en charge l'intégration avec « 1C » et Yandex.Market. Elle fonctionne avec un seul type de prix et un seul entrepôt — suffisant pour le scraper d'une boutique de taille moyenne.
« Business » — la boutique étendue : plusieurs types de prix (gros/détail), multi-entrepôts, remises cumulatives, lots et kits. Si vous collectez des prix de gros et de détail ou répartissez les stocks entre entrepôts, le scraper doit savoir enregistrer tout cela — il vous faut donc précisément l'édition « Business ».
« Enterprise » — pour les grands projets à forte charge. La logique du scraper est la même que pour « Business », mais s'y ajoutent des exigences de performance lors des imports massifs.
À part se trouve « 1C-Bitrix24 : Boutique en ligne + CRM » — c'est un autre produit (la boutique est liée aux entités CRM et au catalogue CRM). Un scraper pour ce produit s'écrit différemment de celui pour « Gestion de site » — gardez-le à l'esprit si vous utilisez précisément Bitrix24.
Encore quelques notions que le scraper doit impérativement maîtriser à propos de Bitrix :
- Infoblocks — les produits sont stockés comme éléments de l'infoblock du catalogue, les sections de l'infoblock correspondent aux catégories.
- Offres commerciales (SKU / offers) — les variantes d'un produit (couleur, taille) résident dans un infoblock lié distinct. Si le fournisseur propose des variantes, le scraper doit créer à la fois le produit et ses offres commerciales.
- Module de devises — Bitrix dispose d'un module de devises intégré capable de récupérer automatiquement les taux de la Banque centrale de Russie via un agent. C'est pratique : la conversion peut se faire avec les moyens de Bitrix lui-même, sans inventer sa propre source de taux.
- CommerceML — le format XML standard d'échange avec « 1C ». C'est aussi par lui que fonctionne l'import de catalogue natif.
Conclusion pratique : avant le développement, fixez l'édition, l'ID de l'infoblock du catalogue, le schéma des propriétés, et déterminez si les offres commerciales et plusieurs types de prix sont utilisés. Toute la logique d'import en dépend.
Solutions prêtes à l'emploi : ce qui existe sur le marché
Si la boutique est standard, il n'est peut-être même pas nécessaire de développer quoi que ce soit.
Les outils natifs de Bitrix
Les éditions « Small Business » et « Business » incluent d'origine un import de catalogue :
- Import CommerceML — le canal principal d'échange avec « 1C » : produits, prix, stocks, propriétés. Si le fournisseur sait exporter du CommerceML, c'est la méthode la plus « native ».
- Import CSV — « Catalogue commercial → Export/Import ». Il accepte un CSV préparé avec produits, prix et propriétés.
À eux seuls, ces outils ne « parcourent » pas les sites tiers — il leur faut un fichier prêt. On les associe donc généralement à un scraper : le scraper produit le CSV/XML, l'import natif le charge. C'est l'option la plus sûre pour le noyau.
Les modules de la Marketplace 1C-Bitrix
Ce sont des extensions qui scrapent et chargent le résultat dans les infoblocks directement depuis l'administration :
- « Chargement de produits, scraper de sites, import » (
kitnet.import) — scrape les sites directement depuis l'administration et ajoute les produits dans l'infoblock : chargement depuis toutes les catégories, création automatique des sections et propriétés, multithreading, import CSV. Les développeurs proposent également une connexion via l'API du fournisseur. - « Sotbit : Scraper de contenu » (
shs.parser) — module de Sotbit : intégration de la nomenclature des fournisseurs avec la boutique depuis un site-catalogue, Excel, XML, YML, CSV ; chargement dans les infoblocks et les highload-blocks, fonctionnement via proxy, journalisation, mode démo avec limitations.
Conseil : la plupart de ces modules disposent d'un mode démo (généralement quelques produits / pages). Testez-le impérativement sur votre site source concret avant l'achat — tous les sites, loin de là, ne se scrapent pas « clé en main ».
Les programmes externes
- Datacol — un logiciel de bureau avec des dizaines de configurations prêtes pour les boutiques populaires. Il collecte les données depuis les sites, les grilles tarifaires et les marketplaces, les traite (traduction, réécriture, marge) et les charge dans Bitrix de manière semi-automatique (via Excel + import natif) ou automatique.
Quand les solutions toutes faites ne conviennent-elles pas ? Mise en page non standard ou protection du site source, logique de marge spécifique, conversion de devises selon votre propre taux, adaptation à votre schéma de propriétés et d'offres commerciales, synchronisation régulière des stocks. On écrit alors un scraper sur mesure — passons-y justement.
Exemple 1 : un scraper en PHP via l'API Bitrix (avec conversion de devises)
Puisque Bitrix est écrit en PHP, le scraper le plus pertinent est un script PHP qui charge le noyau de Bitrix et écrit les produits via son API. Nous ferons la conversion de devises avec le module de devises intégré, et appliquerons nous-mêmes la marge.
Ce code est pédagogique. Avant de le lancer sur une boutique en production, faites une sauvegarde, assurez-vous que le scraping du site source n'enfreint ni ses règles ni la loi, et testez sur une copie.
Étape 0. Chargement du noyau et des modules
Le script se lance depuis la console ou via cron. Il initialise Bitrix et charge les modules nécessaires.
<?php
// pour que le script fonctionne depuis la console/cron, et pas seulement en contexte web
$_SERVER['DOCUMENT_ROOT'] = '/var/www/bitrix';
define('NO_KEEP_STATISTIC', true);
define('NOT_CHECK_PERMISSIONS', true);
define('BX_NO_ACCELERATOR_RESET', true);
require $_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/prolog_before.php';
use Bitrix\Main\Loader;
Loader::includeModule('iblock'); // infoblocks (catalogue)
Loader::includeModule('catalog'); // catalogue commercial (prix, stocks)
Loader::includeModule('currency'); // module de devises (taux de la banque centrale)
Étape 1. Convertisseur de devises basé sur le module intégré
Bitrix stocke déjà et met à jour (via un agent) les taux de la Banque centrale de Russie. Nous prenons le taux du module de devises via CCurrencyRates::ConvertCurrency, et ajoutons la marge séparément.
<?php
class CurrencyConverter
{
public function __construct(
private string $from = 'USD',
private string $to = 'RUB',
private float $markup = 1.20 // marge de la boutique : +20%
) {}
/** Prix fournisseur -> prix boutique au taux Bitrix + marge. */
public function convert(?float $amount): ?float
{
if ($amount === null) {
return null;
}
// ConvertCurrency récupère lui-même le taux actuel depuis le module de devises
$converted = \CCurrencyRates::ConvertCurrency($amount, $this->from, $this->to);
return round($converted * $this->markup, 2);
}
}
Étape 2. Scraper des fiches produits du fournisseur
Pour analyser le HTML, nous utilisons les classes natives DOMDocument + DOMXPath et cURL — sans bibliothèques tierces. Les sélecteurs sont donnés à titre d'exemple : ils s'ajustent individuellement pour chaque site source.
<?php
class SupplierParser
{
public function __construct(
private string $baseUrl,
private CurrencyConverter $converter,
private float $delay = 1.0
) {}
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 xpath(string $html): DOMXPath
{
$doc = new DOMDocument();
libxml_use_internal_errors(true);
$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, '/');
}
/** Liens produits depuis les pages du catalogue avec pagination. */
public function parseCatalog(string $path, int $maxPages = 5): array
{
$urls = [];
for ($page = 1; $page <= $maxPages; $page++) {
$xp = $this->xpath($this->getHtml($this->absolute("{$path}?page={$page}")));
$links = $xp->query("//div[contains(@class,'product-card')]//a[contains(@class,'product-link')]");
if ($links->length === 0) {
break;
}
foreach ($links as $a) {
$urls[] = $this->absolute($a->getAttribute('href'));
}
usleep((int)($this->delay * 1_000_000));
}
return $urls;
}
/** Analyse d'une fiche produit. */
public function parseProduct(string $url): array
{
$xp = $this->xpath($this->getHtml($url));
$text = fn(string $q) => trim($xp->query($q)->item(0)?->textContent ?? '');
$name = $text("//h1[contains(@class,'product-title')]") ?: 'Sans nom';
$sku = $text("//*[contains(@class,'sku')]");
$descr = $text("//*[contains(@class,'product-description')]");
$priceRaw = $text("//*[contains(@class,'price')]//*[contains(@class,'value')]");
$imgNode = $xp->query("//*[contains(@class,'product-gallery')]//img")->item(0);
$imageUrl = $imgNode ? $this->absolute($imgNode->getAttribute('src')) : '';
$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' => $descr,
'image_url' => $imageUrl,
'price_source' => $priceSource,
'price' => $this->converter->convert($priceSource), // conversion
'source_url' => $url,
];
}
}
Étape 3. Import dans le catalogue via l'API Bitrix
C'est ici que réside la principale différence avec les autres CMS : nous n'écrivons pas directement en base, mais via CIBlockElement (élément d'infoblock), CCatalogProduct (fiche du catalogue commercial) et CPrice (prix). L'image est téléchargée via CFile::MakeFileArray. Les doublons sont détectés par le code externe (XML_ID).
<?php
class BitrixImporter
{
public function __construct(
private int $iblockId, // ID de l'infoblock du catalogue
private int $sectionId, // section (catégorie) par défaut
private string $priceCurrency = 'RUB'
) {}
/** Recherche du produit par code externe pour éviter les doublons. */
private function findByXmlId(string $xmlId): ?int
{
$res = \CIBlockElement::GetList(
[],
['IBLOCK_ID' => $this->iblockId, 'XML_ID' => $xmlId],
false, false, ['ID']
);
$row = $res->Fetch();
return $row ? (int)$row['ID'] : null;
}
public function import(array $p): int
{
$el = new \CIBlockElement();
$xmlId = $p['sku'] !== '' ? $p['sku'] : md5($p['source_url']);
$fields = [
'IBLOCK_ID' => $this->iblockId,
'IBLOCK_SECTION_ID' => $this->sectionId,
'NAME' => $p['name'],
'XML_ID' => $xmlId, // code externe = référence du site source
'DETAIL_TEXT' => $p['description'],
'DETAIL_TEXT_TYPE' => 'html',
'ACTIVE' => 'Y',
];
// on télécharge et attache l'image (si le site source la fournit)
if ($p['image_url'] !== '') {
$file = \CFile::MakeFileArray($p['image_url']);
if ($file) {
$fields['DETAIL_PICTURE'] = $file;
}
}
$existingId = $this->findByXmlId($xmlId);
if ($existingId) {
// le produit existe déjà — mise à jour (sans écraser l'image)
unset($fields['DETAIL_PICTURE'], $fields['XML_ID']);
$el->Update($existingId, $fields);
$productId = $existingId;
} else {
$productId = $el->Add($fields);
if (!$productId) {
throw new RuntimeException('CIBlockElement: ' . $el->LAST_ERROR);
}
}
// fiche du catalogue commercial (stock)
\CCatalogProduct::Add([
'ID' => $productId,
'QUANTITY' => 100,
'AVAILABLE' => 'Y',
]);
// prix de base (déjà converti et avec marge)
if ($p['price'] !== null) {
\CPrice::SetBasePrice($productId, $p['price'], $this->priceCurrency);
}
return (int)$productId;
}
}
Étape 4. Point d'entrée et lancement via cron
<?php
// après le bloc de chargement du noyau (Étape 0) et les classes ci-dessus :
$converter = new CurrencyConverter('USD', 'RUB', 1.25);
$parser = new SupplierParser('https://supplier-example.com', $converter, 1.0);
$importer = new BitrixImporter(iblockId: 12, sectionId: 47, priceCurrency: 'RUB');
$urls = $parser->parseCatalog('/catalog/category-1', maxPages: 3);
echo 'Produits trouvés : ' . count($urls) . PHP_EOL;
$imported = 0;
foreach ($urls as $url) {
try {
$product = $parser->parseProduct($url);
if ($product['price'] === null) {
echo "Ignoré (pas de prix) : {$url}" . PHP_EOL;
continue;
}
$id = $importer->import($product);
$imported++;
printf("[%d] %s — %s USD -> %s RUB (ID %d)%s",
$imported, $product['name'], $product['price_source'], $product['price'], $id, PHP_EOL);
} catch (Throwable $e) {
echo "Erreur sur {$url} : {$e->getMessage()}" . PHP_EOL;
}
}
echo "Terminé. Importés/mis à jour : {$imported}" . PHP_EOL;
Pour un lancement automatique quotidien, une ligne dans crontab :
# chaque jour à 4h00, synchronisation du catalogue avec le fournisseur
0 4 * * * /usr/bin/php /var/www/bitrix/local/parser/run.php >> /var/www/bitrix/local/parser/parser.log 2>&1
L'alternative à cron : l'agent Bitrix (
CAgent::AddAgent) : il s'exécute lors des visites du site et se révèle pratique lorsque le planificateur système n'est pas accessible. Mais pour un scraping lourd, cron est plus fiable, afin de ne pas pénaliser les visiteurs.
Exemple 2 : collecte en Python → CSV → import natif
Si vous ne souhaitez pas charger le noyau ni toucher au code de production, le scraping peut être découplé de l'import : collecter les données avec l'outil de votre choix (par exemple en Python), les déposer dans un CSV, et confier le chargement à l'import natif « Catalogue commercial → Export/Import ». Le scraper ne dépend alors plus du tout de Bitrix ni de ses mises à jour.
Ici, nous effectuons nous-mêmes la conversion de devises : nous récupérons le taux de la Banque centrale de Russie et appliquons la marge (comme dans la variante PHP, mais côté collecteur).
import csv
import time
import requests
import xml.etree.ElementTree as ET
from bs4 import BeautifulSoup
from urllib.parse import urljoin
from datetime import date
class CbrRate:
"""Taux de la Banque centrale de Russie avec cache journalier."""
URL = "https://www.cbr.ru/scripts/XML_daily.asp"
def __init__(self, currency="USD"):
self.currency = currency
self._rate = None
self._date = None
def rate(self):
if self._rate is None or self._date != date.today():
r = requests.get(self.URL, timeout=15)
r.encoding = "windows-1251"
tree = ET.fromstring(r.text)
for v in tree.findall("Valute"):
if v.find("CharCode").text == self.currency:
nominal = int(v.find("Nominal").text)
value = float(v.find("Value").text.replace(",", "."))
self._rate = value / nominal
self._date = date.today()
break
return self._rate
def parse_and_export(base_url, catalog_path, out_csv,
currency="USD", markup=1.25, max_pages=3, delay=1.0):
cbr = CbrRate(currency)
session = requests.Session()
session.headers["User-Agent"] = "Mozilla/5.0 (compatible; CatalogImporter/1.0)"
rows = []
for page in range(1, max_pages + 1):
soup = BeautifulSoup(
session.get(f"{base_url}{catalog_path}?page={page}", timeout=20).text, "lxml"
)
cards = soup.select(".product-card a.product-link")
if not cards:
break
for a in cards:
url = urljoin(base_url, a["href"])
ps = BeautifulSoup(session.get(url, timeout=20).text, "lxml")
name = ps.select_one("h1.product-title")
sku = ps.select_one(".sku")
descr = ps.select_one(".product-description")
price_el = ps.select_one(".price .value")
img = ps.select_one(".product-gallery img")
price_src = None
if price_el:
digits = "".join(c for c in price_el.text if c.isdigit() or c == ".")
price_src = float(digits) if digits else None
# conversion de devises : taux de la banque centrale * marge
price_rub = round(price_src * cbr.rate() * markup, 2) if price_src else ""
rows.append({
"XML_ID": sku.text.strip() if sku else url,
"NAME": name.text.strip() if name else "Sans nom",
"DETAIL_TEXT": descr.decode_contents().strip() if descr else "",
"PRICE": price_rub,
"CURRENCY": "RUB",
"PICTURE": urljoin(base_url, img["src"]) if img else "",
})
time.sleep(delay)
time.sleep(delay)
# CSV pour l'import natif du Catalogue commercial (séparateur ;)
with open(out_csv, "w", newline="", encoding="utf-8-sig") as f:
writer = csv.DictWriter(f, fieldnames=rows[0].keys(), delimiter=";")
writer.writeheader()
writer.writerows(rows)
print(f"Terminé. Lignes écrites : {len(rows)} -> {out_csv}")
if __name__ == "__main__":
parse_and_export(
base_url="https://supplier-example.com",
catalog_path="/catalog/category-1",
out_csv="import.csv",
)
Le CSV se charge ensuite dans l'administration : Boutique → Paramètres → Paramètres des modules → Catalogue commercial → Export/Import → Import CSV, où les colonnes sont mises en correspondance avec les champs du produit (code externe, nom, description, prix, devise, image). L'avantage de cette approche : le scraper ne dépend pas du noyau de Bitrix ; l'inconvénient : le chargement ne se fait pas « à la volée », mais en étape séparée.
Ce qui compte dans un projet réel
Les exemples pédagogiques sont volontairement simplifiés. Dans un scraper de production pour Bitrix, il faut en plus prévoir :
- Les offres commerciales (SKU) — si le produit a des variantes, créer les éléments dans l'infoblock des offres et les lier au produit, en rattachant prix/stock aux offres et non au produit.
- Les propriétés du catalogue — faire correspondre les caractéristiques du site source aux propriétés de l'infoblock (
CIBlockElement::SetPropertyValues), avec au besoin la création automatique des valeurs de listes. - Les sections — création automatique de l'arborescence des catégories d'après la structure du site source via
CIBlockSection. - Plusieurs types de prix et d'entrepôts — pour les éditions « Business »/« Enterprise » : prix de gros/détail, stocks par entrepôt.
- La déduplication et la synchronisation — mettre à jour prix/stock par
XML_IDplutôt que de multiplier les doublons ; un scraping ponctuel devient ainsi une synchronisation régulière. - Le taux et la marge — les stocker en configuration et journaliser à quel taux chaque lot a été recalculé, pour que les prix soient reproductibles.
- L'éthique et la légalité — respecter le
robots.txt, faire des pauses entre les requêtes et tenir compte des conditions d'utilisation du site source.
Conclusion
Pour une boutique standard, la combinaison « le scraper produit un fichier + import natif CommerceML/CSV » ou un module prêt à l'emploi de la Marketplace (kitnet.import, « Sotbit : Scraper de contenu », Datacol) suffit souvent. Mais dès qu'apparaissent un site source atypique, un schéma spécifique de propriétés et d'offres commerciales, plusieurs types de prix, une conversion de devises au taux du jour ou une synchronisation régulière des stocks adaptée à votre édition de Bitrix — il est plus fiable de commander un scraper sur mesure, écrit via l'API Bitrix.
Besoin d'une solution de scraping de boutiques en ligne ?
Si vous devez remplir ou synchroniser une boutique sur 1C-Bitrix (ou toute autre plateforme), collecter des données sur les sites de fournisseurs, ou mettre en place une mise à jour automatique des prix avec conversion de devises — contactez-nous. Nous concevrons et déploierons un scraper adapté à votre édition de Bitrix et à vos besoins.