תארו לעצמכם: יש לכם 2000 מאמרים ב-Joomla עם שדות מותאמים אישית, עשרות קטגוריות ואלפי תמונות. הגירה ידנית ל-CMS חדש הייתה אורכת חודש שלם לצוות שלם. שגיאות הן בלתי נמנעות—קישורים שבורים, מטא-דאטה של SEO שאבד, מבנה URL שבור. אנחנו מבצעים אוטומציה של הגירות תוכן כבר 7 שנים—למעלה מ-50 פרויקטים מוצלחים. הגישה שלנו: צינורות ETL—סקריפטים שחולצים נתונים מה-CMS המקור, הופכים אותם למבנה היעד, וטוענים אותם דרך API או גישה ישירה למסד הנתונים. תכונה מרכזית היא אידמפוטנטיות: הרצה חוזרת אינה משכפלת נתונים. ומנגנון גלגול לאחור מאפשר לשחזר הכל למצב הקודם עם בקשה אחת. אוטומציה עולה על הגירה ידנית פי 10 במהירות ובדיוק.
צרו קשר—נעריך את הפרויקט שלכם תוך יום אחד ונציע הערכה קבועה. רקורד העבודה שלנו: למעלה מ-50 הגירות מוצלחות, מ-WordPress מונוליטי ועד מערכות headless.
למה הגירה אוטומטית משתלמת?
העברת תוכן ידנית מועדת לשגיאות: קישורים שבורים, מטא-דאטה שאבד, מבנה SEO שנפגע. צינור ETL אוטומטי פותר את הבעיות האלה באופן שיטתי:
- מהירות: 1000 מאמרים מועברים ב-2–3 שעות במקום 2–3 שבועות ידנית.
- דיוק: מיפוי שדות (כותרת, תוכן, slug, מטא SEO) מבטל שגיאות הקלדה והשמטות.
- חזרתיות: סקריפטים אידמפוטנטיים ניתנים להרצה מספר פעמים ללא שכפול.
- גלגול לאחור: במקרה של שגיאה, קריאת API אחת מחזירה את המערכת למצבה הקודם.
אילו בעיות פותר ETL אוטומטי?
מיפוי שדות וטקסונומיות
מערכות CMS שונות מאחסנות קטגוריות, תגיות ומטא-דאטה בצורה שונה. אנו בונים התאמות (לדוגמה, wp_postmeta._yoast_wpseo_title → Strapi.seo.metaTitle) ומנקים נתונים מזבל (מעטפות HTML מיותרות, שדות ריקים).
הגירת קבצי מדיה
תמונות, מסמכים וסרטונים צריכים להיות מועתקים וקישורים מעודכנים בתוכן. הסקריפט מעלה קבצים ל-CMS היעד ומחליף כתובות URL בגוף המאמרים.
מטא-דאטה של SEO
כותרת, תיאור, Open Graph, canonical—כל אלה קריטיים לדירוג. אנו מעבירים שדות אלה כדי למנוע ירידות בדירוג לאחר הגירה.
אידמפוטנטיות וגלגול לאחור
הגירה היא מלחיצה לעסק. הסקריפטים שלנו בטוחים להרצה חוזרת, ומנגנון הגלגול לאחור מסיר את כל הנתונים המיובאים עם בקשה אחת. אנו מבטיחים שלמות נתונים—סקריפטים אידמפוטנטיים ומנגנון גלגול לאחור.
זוגות הגירה פופולריים בין CMS:
| CMS מקור | CMS יעד | שיקולים מיוחדים |
|---|---|---|
| WordPress | Contentful | הגירת Yoast SEO, מדיה, טקסונומיות |
| Joomla | WordPress | מיפוי קטגוריות, תוסף FG Joomla to WordPress |
| 1C-Bitrix | Strapi | סוגי מוצרים מותאמים אישית, בלוקים בעומס גבוה |
| Drupal | Sanity | תוכן מובנה, בלוקים |
איך לבצע אוטומציה של הגירת תוכן בין CMS?
התהליך כולל מספר שלבים מבוקרים בקפדנות:
- ניתוח והכנת מיפוי — 1–2 ימים. יצירת מסמך מיפוי שדות וסכמת נתונים.
- פיתוח סקריפט ETL — 2–3 ימים. בניית סקריפט ב-Python/Node.js עם פגינציה וטיפול בשגיאות.
- ריצת בדיקה על 10–20 רשומות — 0.5 יום. אימות תקינות, התאמת מיפוי.
- הגירה מלאה — 1–2 ימים. העברת כל החומרים, המדיה והמטא-דאטה.
- אימות ותמיכה — יום אחד. בדיקת ספירה, בדיקת קישורים, ייעוץ לצוות.
אוטומציה מקצרת את זמן ההגירה פי 20 בהשוואה להעברה ידנית.
מה כלול בשירות שלנו?
- קוד המקור של סקריפט ה-ETL — תוכלו להריץ אותו בעצמכם או למסור אותו לקבלן.
- תיעוד מיפוי ותצורה — תיאור של כל השדות, כללי התמרה.
- נתוני גלגול לאחור — dump של מסד הנתונים המקורי או אפשרות למחיקת רשומות מיובאות.
- תמיכה לאחר הגירה — 5 ימי עסקים לתיקון כל אי-התאמה.
מקרה בוחן: WordPress → Contentful
להלן סקריפט אמיתי שבו השתמשנו להגירת 1500 מאמרים עם קבצי מדיה ומטא-דאטה של Yoast SEO. הוא רץ בקבוצות של 50 רשומות, תומך בחידוש מהקבוצה המוצלחת האחרונה, ומתעד כל פעולה.
import mysql.connector
import requests
from tqdm import tqdm
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class WordPressToStrapi:
def __init__(self):
self.wp_db = mysql.connector.connect(
host='old-wp-server',
database='wp_production',
user='readonly',
password='password'
)
self.strapi_url = 'http://new-strapi:1337/api'
self.strapi_token = 'strapi-api-token'
self.media_map = {} # wp_attachment_id → strapi_media_id
def migrate_media(self):
"""Перенос медиафайлов через API Strapi"""
cursor = self.wp_db.cursor(dictionary=True)
cursor.execute("""
SELECT p.ID, p.guid, p.post_title, pm.meta_value as alt_text
FROM wp_posts p
LEFT JOIN wp_postmeta pm ON p.ID = pm.post_id AND pm.meta_key = '_wp_attachment_image_alt'
WHERE p.post_type = 'attachment' AND p.post_mime_type LIKE 'image/%'
""")
for media in tqdm(cursor.fetchall(), desc="Migrating media"):
try:
response = requests.get(media['guid'], timeout=30)
if response.status_code != 200:
logger.warning(f"Cannot fetch {media['guid']}")
continue
filename = media['guid'].split('/')[-1]
upload_response = requests.post(
f"{self.strapi_url}/upload",
headers={'Authorization': f"Bearer {self.strapi_token}"},
files={'files': (filename, response.content)},
data={'fileInfo': f'{{"alternativeText": "{media.get("alt_text", "")}"}'}
)
if upload_response.status_code == 200:
new_id = upload_response.json()[0]['id']
self.media_map[media['ID']] = new_id
logger.info(f"Media {media['ID']} → {new_id}")
except Exception as e:
logger.error(f"Media {media['ID']} failed: {e}")
def migrate_posts(self, batch_size=50, offset=0):
cursor = self.wp_db.cursor(dictionary=True)
cursor.execute("""
SELECT p.*, GROUP_CONCAT(
DISTINCT CASE WHEN tt.taxonomy = 'category' THEN t.name END
SEPARATOR ','
) as categories
FROM wp_posts p
LEFT JOIN wp_term_relationships tr ON p.ID = tr.object_id
LEFT JOIN wp_term_taxonomy tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
LEFT JOIN wp_terms t ON tt.term_id = t.term_id
WHERE p.post_type = 'post' AND p.post_status = 'publish'
GROUP BY p.ID
ORDER BY p.ID
LIMIT %s OFFSET %s
""", (batch_size, offset))
posts = cursor.fetchall()
if not posts:
return 0
for post in tqdm(posts, desc=f"Posts batch {offset//batch_size + 1}"):
self._migrate_single_post(post)
return len(posts)
def _migrate_single_post(self, post):
cursor = self.wp_db.cursor(dictionary=True)
cursor.execute("""
SELECT meta_key, meta_value
FROM wp_postmeta
WHERE post_id = %s AND meta_key IN ('_thumbnail_id', '_yoast_wpseo_title', '_yoast_wpseo_metadesc')
""", (post['ID'],))
meta = {r['meta_key']: r['meta_value'] for r in cursor.fetchall()}
payload = {
'data': {
'title': post['post_title'],
'slug': post['post_name'],
'content': post['post_content'],
'publishedAt': post['post_date'].isoformat(),
'seo': {
'metaTitle': meta.get('_yoast_wpseo_title', post['post_title']),
'metaDescription': meta.get('_yoast_wpseo_metadesc', ''),
},
'cover': self.media_map.get(meta.get('_thumbnail_id')),
'legacy_wp_id': post['ID'],
}
}
response = requests.post(
f"{self.strapi_url}/articles",
headers={
'Authorization': f"Bearer {self.strapi_token}",
'Content-Type': 'application/json'
},
json=payload
)
if response.status_code not in (200, 201):
logger.error(f"Post {post['ID']} failed: {response.text}")
def run(self):
logger.info("Starting migration...")
self.migrate_media()
logger.info(f"Media map: {len(self.media_map)} files")
offset = 0
total = 0
while True:
count = self.migrate_posts(batch_size=50, offset=offset)
total += count
if count < 50:
break
offset += 50
logger.info(f"Migration complete: {total} posts")
השוואה: ידני מול אוטומציה
| קריטריון | הגירה ידנית | אוטומציה (ה-ETL שלנו) |
|---|---|---|
| זמן ל-1000 מאמרים | 10–20 ימים | 3–5 שעות |
| שגיאות (השמטות, קישורים שבורים) | 5–10% | < 0.1% |
| יכולת גלגול לאחור | לא | כן, קריאת API אחת |
| העברת מטא-דאטה של SEO | דורש בדיקה ידנית | מיפוי אוטומטי |
| עלות (שעות עבודה) | גבוהה | פי 5–10 נמוכה יותר |
לוח זמנים ועלות
זמן ההשלמה תלוי בנפח הנתונים ובמורכבות המיפוי. לפרויקט טיפוסי (1000–5000 חומרים, שתי מערכות CMS, קבצי מדיה) — 3–7 ימי עסקים. העלות מחושבת באופן אישי לאחר ניתוח מערכות המקור והיעד. אנו מספקים הערכה תוך יום אחד.
קבלו ייעוץ—צרו קשר ונכין תוכנית הגירה עם לוחות זמנים מדויקים.







