Token Korumalı XML ile Ürün Çekme ve Upsert Rehberi
Bu rehber; xShopum ürün verisini token ile korunan XML üzerinden çekip,
ister WooCommerce/WordPress, ister OpenCart, ister hazır e-ticaret altyapıları (ikas / Ticimax / IdeaSoft vb.),
ister pazaryeri entegrasyon servisleri (StockMount tarzı) üzerinden
doğru şekilde güncellemen (insert / update, yani upsert) için hazırlanmıştır.
Bu rehber kimler için hazırlanmıştır?
Bu doküman; XML entegrasyonu konusunda temel teknik bilgisi olan, kendi altyapısını geliştiren veya mevcut sistemine özel entegrasyon yazan yazılımcılar, ajanslar ve entegrasyon firmaları için hazırlanmıştır. İçerik, teknik detaylar ve örnek senaryolar içerir.
Önemli not:
Eğer bir entegrasyon firması, hazır yazılım veya pazaryeri entegratörü (ör. StockMount, T-Soft vb.) ile çalışıyorsanız, bu dokümandaki teknik detaylara ihtiyaç duymazsınız. Bu durumda entegrasyon sağlayıcınıza sadece XML bağlantısını (token’lı link) iletmeniz yeterlidir.
Benzer şekilde, WooCommerce veya OpenCart üzerinde bir eklenti / modül kullanıyorsanız; XML alanları, parse mantığı veya upsert senaryoları ile manuel olarak ilgilenmenize gerek yoktur. Gerekli tüm yapılandırmalar ilgili eklenti/modül arayüzü üzerinden yapılmaktadır.
Bu rehber, hazır çözümler dışında kendi entegrasyonunu yazmak isteyen geliştiricilere yol göstermek amacıyla hazırlanmış bir teknik dokümantasyon niteliğindedir.
1) Mantık & kapsam • 2) Token güvenliği • 3) XML alanları • 4) Mimari • 5) Stream parse •
6) Alan eşleme • 7) Upsert stratejisi • 8) WooCommerce/OpenCart pratik yol •
9) Hazır altyapılar checklist • 10) Pazaryerlerine listeleme (Hepsiburada/Trendyol/N11/Çiçeksepeti) • 11) Sık yapılan hatalar
1) XML’i DOM ile RAM’e almak • 2) SKU yerine ID ile eşlemek • 3) Timeout/retry koymamak •
4) Her sync’te görseli sil-yaz yapmak • 5) Content’i yanlışlıkla overwrite etmek •
6) Log tutmamak • 7) Token’ı repo/ekran görüntüsünde paylaşmak •
8) Pazaryerinde GTIN/marka eşlemesini ihmal etmek • 9) Kategori mapping yapmadan “otomatik” beklemek
kritik olan SKU eşlemesi, stok doğruluğu ve overwrite stratejisidir.
Kod örneklerini ve hazır kısayolları aşağıdaki alanda sunuyoruz:
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
public class XshopumPostFeedSync
{
private readonly HttpClient _http;
public XshopumPostFeedSync(HttpClient httpClient)
{
_http = httpClient;
_http.Timeout = TimeSpan.FromSeconds(90);
}
public async Task SyncAsync(string xmlUrlWithToken, CancellationToken ct)
{
using var resp = await _http.GetAsync(xmlUrlWithToken, HttpCompletionOption.ResponseHeadersRead, ct);
resp.EnsureSuccessStatusCode();
await using var stream = await resp.Content.ReadAsStreamAsync(ct);
var settings = new XmlReaderSettings
{
Async = true,
IgnoreComments = true,
IgnoreWhitespace = true,
DtdProcessing = DtdProcessing.Prohibit
};
using var reader = XmlReader.Create(stream, settings);
while (await reader.ReadAsync())
{
if (reader.NodeType == XmlNodeType.Element && reader.Name == "post")
{
var model = await ReadPostAsync(reader, ct);
if (string.IsNullOrWhiteSpace(model.SKU))
continue;
await UpsertProductAsync(model, ct);
await UpsertImagesAsync(model, ct);
}
}
}
private static async Task<PostProduct> ReadPostAsync(XmlReader reader, CancellationToken ct)
{
var m = new PostProduct();
var images = new List<string>();
if (reader.IsEmptyElement) return m;
while (await reader.ReadAsync())
{
ct.ThrowIfCancellationRequested();
if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "post")
break;
if (reader.NodeType != XmlNodeType.Element)
continue;
var name = reader.Name;
string value = await reader.ReadElementContentAsStringAsync();
switch (name)
{
case "ID": m.SourceID = TryLong(value); break;
case "SKU": m.SKU = value?.Trim() ?? ""; break;
case "Title": m.Title = value?.Trim() ?? ""; break;
case "Stock": m.Stock = TryInt(value); break;
case "Wholesale_Excl_Tax_TL": m.WholesaleExclTaxTL = TryDec(value); break;
case "Wholesale_Incl_Tax_TL": m.WholesaleInclTaxTL = TryDec(value); break;
case "Wholesale_Incl_Tax_USD": m.WholesaleInclTaxUSD = TryDec(value); break;
case "Sale_Price_TL": m.SalePriceTL = TryDec(value); break;
case "Tax_Rate": m.TaxRate = TryInt(value); break;
case "Product_Category": m.CategoryName = value?.Trim() ?? ""; break;
case "Original_Brand": m.OriginalBrand = value?.Trim() ?? ""; break;
case "Marketplace_Brand": m.MarketplaceBrand = value?.Trim() ?? ""; break;
case "GlobalTradeItemNumber": m.GTIN = value?.Trim() ?? ""; break;
case "Permalink": m.Permalink = value?.Trim() ?? ""; break;
case "Product_Video_URL": m.VideoUrl = value?.Trim() ?? ""; break;
case "Content": m.ContentHtml = value ?? ""; break;
default:
if (name.StartsWith("Image", StringComparison.OrdinalIgnoreCase))
{
var url = value?.Trim();
if (!string.IsNullOrWhiteSpace(url))
images.Add(url);
}
break;
}
}
m.ImageUrls = images;
return m;
}
private static long? TryLong(string s) => long.TryParse(s, out var v) ? v : null;
private static int? TryInt(string s) => int.TryParse(s, out var v) ? v : null;
private static decimal? TryDec(string s)
{
return decimal.TryParse(s, NumberStyles.Any, CultureInfo.InvariantCulture, out var v) ? v : null;
}
private Task UpsertProductAsync(PostProduct m, CancellationToken ct) => Task.CompletedTask;
private Task UpsertImagesAsync(PostProduct m, CancellationToken ct) => Task.CompletedTask;
}
public class PostProduct
{
public long? SourceID { get; set; }
public string SKU { get; set; } = "";
public string Title { get; set; } = "";
public string ContentHtml { get; set; } = "";
public int? Stock { get; set; }
public decimal? WholesaleExclTaxTL { get; set; }
public decimal? WholesaleInclTaxTL { get; set; }
public decimal? WholesaleInclTaxUSD { get; set; }
public decimal? SalePriceTL { get; set; }
public int? TaxRate { get; set; }
public string CategoryName { get; set; } = "";
public string OriginalBrand { get; set; } = "";
public string MarketplaceBrand { get; set; } = "";
public string GTIN { get; set; } = "";
public string Permalink { get; set; } = "";
public string VideoUrl { get; set; } = "";
public List<string> ImageUrls { get; set; } = new List<string>();
}
<?php
declare(strict_types=1);
$xmlUrl = "https://www.xshopum.com/xml-feed?token=YOUR_TOKEN";
$tmpFile = sys_get_temp_dir() . "/xshopum_feed.xml";
download_xml_to_file($xmlUrl, $tmpFile);
$reader = new XMLReader();
if (!$reader->open($tmpFile, null, LIBXML_NONET | LIBXML_NOERROR | LIBXML_NOWARNING)) {
throw new Exception("XMLReader open failed");
}
while ($reader->read()) {
if ($reader->nodeType === XMLReader::ELEMENT && $reader->name === 'post') {
$post = read_post_node($reader);
if (empty($post['SKU'])) {
continue;
}
upsert_product($post);
upsert_images($post['SKU'], $post['Images']);
}
}
$reader->close();
@unlink($tmpFile);
// -------------------- HELPERS --------------------
function download_xml_to_file(string $url, string $filePath): void {
$fp = fopen($filePath, "w");
if (!$fp) {
throw new Exception("Cannot open tmp file");
}
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_FILE => $fp,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_TIMEOUT => 90,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_FAILONERROR => true,
]);
$ok = curl_exec($ch);
if (!$ok) {
$err = curl_error($ch);
curl_close($ch);
fclose($fp);
throw new Exception("XML download failed: " . $err);
}
curl_close($ch);
fclose($fp);
}
function read_post_node(XMLReader $reader): array {
$data = [
"ID" => null,
"SKU" => "",
"Title" => "",
"Content" => "",
"Stock" => null,
"Wholesale_Excl_Tax_TL" => null,
"Wholesale_Incl_Tax_TL" => null,
"Wholesale_Incl_Tax_USD" => null,
"Sale_Price_TL" => null,
"Tax_Rate" => null,
"Product_Category" => "",
"Original_Brand" => "",
"Marketplace_Brand" => "",
"GlobalTradeItemNumber" => "",
"Permalink" => "",
"Product_Video_URL" => "",
"Images" => [],
];
if ($reader->isEmptyElement) {
return $data;
}
$depth = $reader->depth;
while ($reader->read()) {
if (
$reader->nodeType === XMLReader::END_ELEMENT &&
$reader->name === 'post' &&
$reader->depth === $depth
) {
break;
}
if ($reader->nodeType !== XMLReader::ELEMENT) continue;
$name = $reader->name;
$value = $reader->readString();
if (stripos($name, 'Image') === 0) {
$url = trim($value);
if ($url !== "") {
$data["Images"][] = $url;
}
continue;
}
switch ($name) {
case "ID":
$data["ID"] = (int)trim($value);
break;
case "SKU":
$data["SKU"] = trim($value);
break;
case "Title":
$data["Title"] = trim($value);
break;
case "Content":
$data["Content"] = $value;
break;
case "Stock":
$data["Stock"] = (int)trim($value);
break;
case "Wholesale_Excl_Tax_TL":
case "Wholesale_Incl_Tax_TL":
case "Wholesale_Incl_Tax_USD":
case "Sale_Price_TL":
$data[$name] = (float)trim($value);
break;
case "Tax_Rate":
$data["Tax_Rate"] = (int)trim($value);
break;
case "Product_Category":
case "Original_Brand":
case "Marketplace_Brand":
case "GlobalTradeItemNumber":
case "Permalink":
case "Product_Video_URL":
$data[$name] = trim($value);
break;
}
}
return $data;
}
// -------------------- DB (PSEUDO) --------------------
function upsert_product(array $p): void {
// PSEUDO:
// 1) SELECT id FROM products WHERE sku = ?
// 2) varsa UPDATE
// 3) yoksa INSERT
// 4) last_sync_at = NOW()
}
function upsert_images(string $sku, array $images): void {
// PSEUDO:
// - Önce sku için görselleri sil
// - Sonra image_url + sort_order ile yeniden ekle
}
/**
* Node.js stream + SAX parse örneği (xShopum <post> feed'i için)
* Paketler:
* npm i sax node-fetch
*
* DB katmanı burada pseudo. (mysql2 / pg / prisma / sequelize fark etmez)
*/
import fetch from "node-fetch";
import sax from "sax";
const XML_URL = "https://www.xshopum.com/xml-feed?token=YOUR_TOKEN";
async function run() {
const res = await fetch(XML_URL, { timeout: 90_000 });
if (!res.ok) throw new Error("XML download failed: " + res.status);
const parser = sax.createStream(true, { trim: true });
let inPost = false;
let bufferText = "";
/** @type {any} */
let post = null;
parser.on("opentag", (node) => {
bufferText = "";
if (node.name === "post") {
inPost = true;
post = {
ID: null,
SKU: "",
Title: "",
Content: "",
Stock: null,
Wholesale_Excl_Tax_TL: null,
Wholesale_Incl_Tax_TL: null,
Wholesale_Incl_Tax_USD: null,
Sale_Price_TL: null,
Tax_Rate: null,
Product_Category: "",
Original_Brand: "",
Marketplace_Brand: "",
GlobalTradeItemNumber: "",
Permalink: "",
Product_Video_URL: "",
Images: []
};
}
});
parser.on("text", (txt) => {
if (!inPost) return;
bufferText += txt;
});
parser.on("cdata", (txt) => {
if (!inPost) return;
bufferText += txt;
});
parser.on("closetag", async (tagName) => {
if (!inPost) return;
const value = (bufferText || "").trim();
if (post && tagName !== "post") {
if (tagName.startsWith("Image")) {
if (value) post.Images.push(value);
} else {
switch (tagName) {
case "ID": post.ID = toInt(value); break;
case "SKU": post.SKU = value; break;
case "Title": post.Title = value; break;
case "Content": post.Content = value; break;
case "Stock": post.Stock = toInt(value); break;
case "Wholesale_Excl_Tax_TL": post.Wholesale_Excl_Tax_TL = toFloat(value); break;
case "Wholesale_Incl_Tax_TL": post.Wholesale_Incl_Tax_TL = toFloat(value); break;
case "Wholesale_Incl_Tax_USD": post.Wholesale_Incl_Tax_USD = toFloat(value); break;
case "Sale_Price_TL": post.Sale_Price_TL = toFloat(value); break;
case "Tax_Rate": post.Tax_Rate = toInt(value); break;
case "Product_Category": post.Product_Category = value; break;
case "Original_Brand": post.Original_Brand = value; break;
case "Marketplace_Brand": post.Marketplace_Brand = value; break;
case "GlobalTradeItemNumber": post.GlobalTradeItemNumber = value; break;
case "Permalink": post.Permalink = value; break;
case "Product_Video_URL": post.Product_Video_URL = value; break;
}
}
}
if (tagName === "post") {
inPost = false;
if (!post || !post.SKU) {
post = null;
return;
}
try {
await upsertProduct(post);
await upsertImages(post.SKU, post.Images);
} catch (e) {
console.error("Upsert error SKU:", post.SKU, e);
} finally {
post = null;
}
}
bufferText = "";
});
parser.on("error", (err) => {
console.error("SAX parse error:", err);
parser._parser.error = null;
parser._parser.resume();
});
await new Promise((resolve, reject) => {
res.body.on("error", reject);
parser.on("end", resolve);
res.body.pipe(parser);
});
console.log("Sync completed.");
}
function toInt(s) {
const n = parseInt(s, 10);
return Number.isFinite(n) ? n : null;
}
function toFloat(s) {
const n = parseFloat(s);
return Number.isFinite(n) ? n : null;
}
// -------------------- DB (PSEUDO) --------------------
async function upsertProduct(p) {
// PSEUDO:
// 1) SELECT id FROM products WHERE sku=?
// 2) varsa UPDATE; yoksa INSERT
// 3) last_sync_at = NOW()
}
async function upsertImages(sku, images) {
// En basit yöntem:
// - DELETE FROM product_images WHERE sku = ?
// - sonra images listesini sort_order ile INSERT et
}
run().catch(console.error);
"""
Python iterparse örneği (xShopum <post> feed'i için)
pip install requests
DB katmanı pseudo. İstersen:
- MySQL: mysql-connector-python / pymysql
- Postgres: psycopg2
- ORM: SQLAlchemy
"""
import os
import requests
import xml.etree.ElementTree as ET
XML_URL = "https://www.xshopum.com/xml-feed?token=YOUR_TOKEN"
TMP_FILE = "/tmp/xshopum_feed.xml"
def download_xml(url: str, out_path: str) -> None:
with requests.get(url, stream=True, timeout=90) as r:
r.raise_for_status()
with open(out_path, "wb") as f:
for chunk in r.iter_content(chunk_size=1024 * 64):
if chunk:
f.write(chunk)
def safe_text(el):
return (el.text or "").strip()
def parse_post(post_el):
# post_el: <post> element
data = {
"ID": None,
"SKU": "",
"Title": "",
"Content": "",
"Stock": None,
"Wholesale_Excl_Tax_TL": None,
"Wholesale_Incl_Tax_TL": None,
"Wholesale_Incl_Tax_USD": None,
"Sale_Price_TL": None,
"Tax_Rate": None,
"Product_Category": "",
"Original_Brand": "",
"Marketplace_Brand": "",
"GlobalTradeItemNumber": "",
"Permalink": "",
"Product_Video_URL": "",
"Images": []
}
for child in list(post_el):
tag = child.tag
val = safe_text(child)
if tag.startswith("Image"):
if val:
data["Images"].append(val)
continue
if tag == "ID":
data["ID"] = to_int(val)
elif tag == "SKU":
data["SKU"] = val
elif tag == "Title":
data["Title"] = val
elif tag == "Content":
# CDATA içindeki HTML genelde .text içine gelir
data["Content"] = child.text or ""
elif tag == "Stock":
data["Stock"] = to_int(val)
elif tag in ("Wholesale_Excl_Tax_TL", "Wholesale_Incl_Tax_TL", "Wholesale_Incl_Tax_USD", "Sale_Price_TL"):
data[tag] = to_float(val)
elif tag == "Tax_Rate":
data["Tax_Rate"] = to_int(val)
elif tag in ("Product_Category", "Original_Brand", "Marketplace_Brand", "GlobalTradeItemNumber", "Permalink", "Product_Video_URL"):
data[tag] = val
return data
def to_int(s: str):
try:
return int(s)
except:
return None
def to_float(s: str):
try:
# "305.00" gibi değerler için float OK
return float(s)
except:
return None
# -------------------- DB (PSEUDO) --------------------
def upsert_product(p: dict) -> None:
# 1) SELECT id FROM products WHERE sku=%s
# 2) varsa UPDATE (stock, prices, title, content, vb.)
# 3) yoksa INSERT
# 4) last_sync_at = NOW()
pass
def upsert_images(sku: str, images: list[str]) -> None:
# Basit:
# - DELETE FROM product_images WHERE sku=%s
# - sonra (sku, url, sort_order) INSERT
pass
def sync():
download_xml(XML_URL, TMP_FILE)
# iterparse: end eventinde <post> tamamlanınca işliyoruz
context = ET.iterparse(TMP_FILE, events=("end",))
for event, elem in context:
if elem.tag == "post":
p = parse_post(elem)
if p.get("SKU"):
try:
upsert_product(p)
upsert_images(p["SKU"], p["Images"])
except Exception as e:
print("Upsert error SKU:", p.get("SKU"), e)
# Çok önemli: belleği temizle
elem.clear()
try:
os.remove(TMP_FILE)
except:
pass
if __name__ == "__main__":
sync()
import javax.xml.stream.*;
import java.io.InputStream;
import java.net.URL;
import java.util.*;
public class XshopumXmlSync {
private static final String XML_URL =
"https://www.xshopum.com/xml-feed?token=YOUR_TOKEN";
public static void main(String[] args) throws Exception {
new XshopumXmlSync().sync();
}
public void sync() throws Exception {
InputStream stream = new URL(XML_URL).openStream();
XMLInputFactory factory = XMLInputFactory.newInstance();
factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
factory.setProperty(XMLInputFactory.SUPPORT_DTD, false);
XMLStreamReader reader = factory.createXMLStreamReader(stream);
Map<String, Object> post = null;
String currentTag = null;
StringBuilder buffer = new StringBuilder();
List<String> images = null;
while (reader.hasNext()) {
int event = reader.next();
switch (event) {
case XMLStreamConstants.START_ELEMENT:
currentTag = reader.getLocalName();
buffer.setLength(0);
if ("post".equals(currentTag)) {
post = new HashMap<>();
images = new ArrayList<>();
}
break;
case XMLStreamConstants.CHARACTERS:
case XMLStreamConstants.CDATA:
if (post != null) buffer.append(reader.getText());
break;
case XMLStreamConstants.END_ELEMENT:
String tag = reader.getLocalName();
String val = buffer.toString().trim();
if (post != null && !"post".equals(tag)) {
if (tag.startsWith("Image")) {
if (!val.isEmpty()) images.add(val);
} else {
post.put(tag, val);
}
}
if ("post".equals(tag) && post != null) {
post.put("Images", images);
String sku = (String) post.get("SKU");
if (sku != null && !sku.isEmpty()) {
upsertProduct(post);
upsertImages(sku, images);
}
post = null;
images = null;
}
buffer.setLength(0);
currentTag = null;
break;
}
}
reader.close();
stream.close();
}
private void upsertProduct(Map<String, Object> p) {
// PSEUDO:
// SELECT id FROM products WHERE sku=?
// varsa UPDATE (Stock, Sale_Price_TL, Wholesale_Incl_Tax_TL, vb.)
// yoksa INSERT
// last_sync_at = NOW()
}
private void upsertImages(String sku, List<String> images) {
// DELETE FROM product_images WHERE sku=?
// sonra (sku, image_url, sort_order) INSERT
}
}


Realistik Penisler
Titreşimli Penisler
Hareketli Penisler
Dev Penisler
Boşalabilir Penisler
Çift Başlı Penisler
Rabbit Vibratörler
G-Spot Vibratörler
Mini Vibratörler
Hareketli Vibratörler
Giyilebilir Vibratörler
Emiş Güçlü Vibratörler
Parmak Vibratörler
Masaj Aletleri
Telefon Kontrollü Vibratörler



















