وصف المشكلة/الخطأ/السؤال
مرحباً الجميع، أواجه مشكلة غريبة جداً مع عقدة YouTube أثناء محاولة إنشاء قائمة تشغيل ديناميكية. تعمل بشكل مثالي عندما أنقر يدويّاً على “Execute Step” داخل واجهة n8n، لكن في اللحظة التي يعمل فيها سير العمل في الإنتاج (مشغل مؤتمت/مباشر)، فإنه يفشل تماماً مع خطأ 400 Bad Request.
إعدادات وإعداد العقدة
السلوك
-
في اختبار الواجهة اليدويّ: يتم حل العبارة بشكل مثالي إلى سلسلة نظيفة: "Zedd, Bright Lights - Follow You Down - Keys N Krates Remix" (حوالي 77 حرفاً، أقل بكثير من حد YouTube البالغ 150 حرفاً). تمرر عقدة الإدخال (Match) حقول JSON وبيانات المعلومات الثنائية.
-
في تشغيل الإنتاج: تأتي نفس بنية البيانات بالضبط، يجب أن تكون سلسلة العنوان متطابقة، لكن العقدة ترمي خطأ API التالي.
ما تحققت منه بالفعل/حاولته:
-
تطبيق Google Cloud Console: تم تعيينه على وضع Production (وليس Testing/Sandbox).
-
حالة الخصوصية: حاولت كلاً من Public وPrivate/Unlisted. نفس الخطأ في كليهما في الإنتاج.
-
حدود الأحرف: العنوان يحتوي على حوالي 77 حرفاً فقط.
-
أحرف خاصة: حاولت استخدام تعبير نمطي صارم لإزالة كل شيء باستثناء أحرف أبجدية رقمية أساسية، لكن خطأ 400 يستمر.
-
وجود القناة: حساب Google لديه قناة YouTube نشطة تم إنشاؤها بالكامل، وعقد أخرى (مثل تحميل الفيديو) تعمل بشكل جيد.
لماذا يتصرف n8n بشكل مختلف بين التنفيذ اليدويّ وتشغيل الإنتاج المؤتمت عند تحليل أو إرسال هذا الحمل إلى YouTube؟ أي أفكار حول كيفية تصحيح الأخطاء أو تجاوز هذا؟
يرجى مشاركة سير العمل الخاص بك
شارك المخرجات التي أرجعتها العقدة الأخيرة
معلومات حول إعداد n8n الخاص بك
- إصدار n8n: v2.23.2
- قاعدة البيانات (الافتراضي: SQLite): الافتراضي
- إعداد n8n EXECUTIONS_PROCESS (الافتراضي: own, main): main
- تشغيل n8n عبر (Docker, npm, n8n cloud, سطح المكتب): Docker
- نظام التشغيل: Raspberry Pi OS 64-bit Debian 13 trixie (aarch64)
يمكن لاختبار واجهة المستخدم اليدوي أن يخفي عدم التطابق لأنه يعمل على العنصر المحدد حاليًا في المحرر. في حالة الفشل في الإنتاج، المدخل الوحيد الذي يهم هو لوحة الإدخال مباشرة على عقدة YouTube، وليس عقدة Match الأعلى.
تحقق من تلك اللوحة للعنصر الذي استقبلته عقدة YouTube بالفعل. إذا كان artist_name / album_name مفقودًا هناك أو يأتي من عنصر مقترن، قم بتجميد العنوان في عقدة Set/Edit Fields مباشرة قبل YouTube واربط عنوان قائمة التشغيل من هذا الحقل الجديد. إذا كان الحقل بالفعل نصًا عاديًا هناك، فنشر عنصر الإدخال لعقدة YouTube المحررة بالإضافة إلى نص رد الخطأ 400 كاملًا.
مرحباً، شكراً على إجابتك. أعدت تنظيم سير العمل لاستخدام عقدة Merge في وضع combine (كما هو موضح في المرفق) للتأكد من أن البيانات الثنائية وبيانات JSON الوصفية تبقى متزامنة تماماً طوال خط الأنابيب.
لذلك، بدا أن كل شيء مثالياً حتى التنفيذ هنا. بناءً على إجابتك، اعتقدت أنني يمكنني تطبيق نفس الحالة لتحميل مقاطع الفيديو. ومع ذلك، لم ينجح الأمر. كما غيرت وضع عقدة merge الخاصة بي إلى combine. للأسف، لم ينجح. يمكنك العثور على آخر حالة لسير العمل الخاص بي أدناه.
شكراً. لا تزال لقطات الشاشة لا تظهر إدخال عقدة YouTube الفاشلة، لذا فإن وضع الدمج هو خطوة واحدة في اتجاه المنبع من الشيء الذي يقرر هذا.
افتح تنفيذ الإنتاج الفاشل، انقر على عقدة YouTube نفسها، وانسخ عنصر إدخال JSON واحد محجوب من تلك العقدة بالإضافة إلى نص الاستجابة 400 الكامل. إذا كانت تلك العقدة تتلقى بالفعل سلسلة نص قائمة تشغيل عادية، فإن الفحص التالي هو الحمولة الدقيقة التي ترفضها YouTube؛ وإذا لم تكن، فالإصلاح يكون مباشرة قبل YouTube.
{“json”: {“album_name”: “Follow You Down - Keys N Krates Remix”,“artist_name”: “Zedd, Bright Lights”},قمت بالفعل بالعودة إلى سجلات التنفيذ الفاشلة القديمة للحصول على ما استقبله عقدة YouTube Playlist في لوحة الإدخال الخاصة به بالضبط أثناء الانهيار، فقط لإرضاء تحليل السبب الجذري.
إليك ملف JSON المحرر من لوحة INPUT الخاصة بعقدة YouTube Playlist الفاشلة:
{
"json": {
"album_name": "Follow You Down - Keys N Krates Remix",
"artist_name": "Zedd, Bright Lights"
}
}
والتعبير في حقل العنوان كان:
{{ $json.artist_name }} - {{ $json.album_name }} (Full Release)
على الرغم من أنه تم حله بصرياً بشكل جيد في معاينة واجهة المستخدم، إلا أنه في عملية الإنتاج المحددة تلك، كان n8n يفقد سياق العنصر المقترن لأن تدفق الإدخال كان يأتي مباشرة من عقدة ثنائية متعددة العناصر بدون تثبيت فهرس عنصر صريح (مثل استخدام .item.json). لذلك، من المحتمل أن يكون YouTube قد استقبل سلسلة حمولة فارغة أو مشوهة تحت الغطاء.
الآن، لدي بعض الأسئلة:
1- تم القول أنك:
“يمكن لاختبار واجهة المستخدم اليدوي إخفاء عدم التطابق لأنه يعمل على العنصر المحدد حالياً في المحرر.”
إذاً، كيف يمكنني الذهاب لاختباره؟ هل هو دائماً في الإنتاج أم هناك بيئة خاصة؟
2- عندما بدأت تشغيل سير العمل (منشور)، يبدأ بشكل مثالي، لكن تبدأ بعض الأخطاء تحدث بدون سبب. على سبيل المثال، مشاكل تتعلق بالتعبيرات وما إلى ذلك. لماذا يحدث هذا حتى لو لم تكن هناك مشكلة؟
شكراً لك.
عمل بحثي جيد في البحث عن خلال سجلات التنفيذ القديمة.
بخصوص السؤال 1 — اختبار سلوك الإنتاج بدون تفعيل مباشر:
الطريقة الأنظف هي استخدام زر “Test workflow” (أيقونة التشغيل في أعلى لوحة العمل عندما تكون سير العمل نشطة/منشورة). يقوم بتشغيل سير العمل تماماً كما سيعمل في الإنتاج، باستخدام التفعيل الفعلي — بالنسبة لسير العمل المستند إلى webhook، يضع n8n في حالة استماع لاستدعاء واحد حقيقي قادم. يمكنك أيضاً إرسال payload اختبار يدويّاً عبر POST إلى عنوان webhook الخاص بك من أداة مثل Postman أو curl أثناء وجودك في هذا الوضع. هذا يعطيك تنفيذاً إنتاجياً حقيقياً يمكنك فحصه، وليس واحداً محاكى.
بخصوص السؤال 2 — أخطاء التعبيرات العشوائية بعد النشر:
هذا يكون دائماً تقريباً بسبب سياق العناصر ينقطع في مكان ما في المنبع. عندما يعمل سير العمل مباشرة، كل عقدة ترى فقط العناصر التي مررت إليها من العقدة الأب المباشرة — إذا كان لديك فرع أو عقدة تُرجع شكلاً مختلفاً عما اختبرته به، التعبيرات التي عملت في الوضع اليدوي تبدأ برفع أخطاء. الحل هو إضافة عقدة “Set” (تعديل الحقول) مباشرة قبل أي عقدة تستخدم تعبيرات من السابق في السلسلة، إعادة تعيين صريحة للحقول التي تحتاجها. بهذه الطريقة شكل البيانات يكون دائماً صريحاً وليس معتمداً على ما حدث أن يكون محدداً في المحرر عندما اختبرت.
مرحبا، مجرد تحديث قبل نقل هذا إلى الإنتاج، المشكلة لا تزال مستمرة. عقدة الدمج (Merge node) لم تنجح، وحاولت عدة طرق مختلفة لتمرير البيانات الثنائية عبر عقدة الكود (Code node) لكن لم أتمكن من جعله يعمل بشكل صحيح. شكرا على المساعدة على أي حال!
كود المطابقة:
const jsonData = $("Multi-track splitter").all();
const videoFiles = $input.all();
let matchedItems = [];
const normalize = (str) => str ? str.toLowerCase().replace(/[^a-z0-9]/g, '') : '';
for (const item of jsonData) {
const song = item.json;
const safeSongName = normalize(song.song_name);
const match = videoFiles.find(v => {
const fileName = v.json.fileName || '';
const cleanFileName = fileName.replace('.mp4', '').replace(/_\d+$/, '');
const safeFileName = normalize(cleanFileName);
return safeFileName.includes(safeSongName) || safeSongName.includes(safeFileName);
});
if (match) {
matchedItems.push({
json: {
...song,
yt_description: `Artist: ${song.artist_name}\n\nAlbum: ${song.album_name}\n\nRelease Date: ${song.release_date}\n\n${song.company_name}\n${song.publisher_name}\n\nNOT auto-generated by YouTube.`,
yt_title: `${song.artist_name} - ${song.song_name}`
},
binary: match.binary
});
}
}
return matchedItems;