هذا الالتزام موجود في:
Abdelsabour
2025-09-03 03:03:42 +03:00
التزام 905ad8f612
10 ملفات معدلة مع 3201 إضافات و0 حذوفات

217
backend/index.js Normal file
عرض الملف

@@ -0,0 +1,217 @@
// استدعاء المكتبات الأساسية والمكتبات الجديدة للتخفي
const express = require('express');
const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
const cors = require('cors');
const path = require('path');
const fs = require('fs').promises;
// تفعيل مكون التخفي لتجنب الحظر
puppeteer.use(StealthPlugin());
const app = express();
app.use(cors());
app.use(express.json());
// خدمة الملفات الثابتة من مجلد frontend
app.use(express.static(path.join(__dirname, '../frontend')));
// route للصفحة الرئيسية
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, '../frontend/index.html'));
});
// دالة مساعدة للتأخير
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
const userAgents = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36',
];
const cache = new Map();
const CACHE_DURATION = 30 * 60 * 1000; // 30 دقيقة
// --- route استخراج البيانات (النسخة الاحترافية) ---
app.post('/scrape', async (req, res) => {
const { url } = req.body;
// ==================================================================
// ## بداية التعديل: جعل الكود يقبل جميع أنواع روابط خرائط جوجل ##
// ==================================================================
// الشرط الجديد يتحقق إذا كان الرابط يحتوي على "google.com/maps" أو "maps.app.goo.gl"
if (!url.includes('google.com/maps') && !url.includes('maps.app.goo.gl')) {
return res.status(400).json({ error: 'الرابط يجب أن يكون من خرائط جوجل (مثل google.com/maps أو maps.app.goo.gl)' });
}
// ==================================================================
// ## نهاية التعديل ##
// ==================================================================
const cachedData = cache.get(url);
if (cachedData && (Date.now() - cachedData.timestamp) < CACHE_DURATION) {
console.log('إرجاع البيانات من الذاكرة المؤقتة:', url);
return res.json(cachedData.data);
}
let browser;
try {
browser = await puppeteer.launch({
headless: true,
args: ['--no-sandbox', '--disable-setuid-sandbox', '--lang=ar-EG,ar'],
});
const page = await browser.newPage();
await page.setViewport({ width: 1366, height: 768 });
await page.setUserAgent(userAgents[Math.floor(Math.random() * userAgents.length)]);
console.log(`\nبدء المحاكاة البشرية الكاملة للرابط: ${url}`);
await page.goto(url, { waitUntil: 'networkidle2', timeout: 90000 });
try {
const consentButtonSelector = 'button[aria-label*="Accept all"], button[aria-label*="قبول الكل"]';
await page.waitForSelector(consentButtonSelector, { timeout: 7000 });
await page.click(consentButtonSelector);
console.log('تم قبول ملفات تعريف الارتباط.');
await delay(1500);
} catch (e) {
console.log('نافذة قبول الكوكيز لم تظهر.');
}
const mainPanelSelector = 'div[role="main"]';
await page.waitForSelector(mainPanelSelector, { timeout: 15000 });
let images = [];
try {
const directImages = await page.evaluate(() =>
Array.from(document.querySelectorAll('img[src*="googleusercontent.com"]'))
.map(img => img.src.replace(/=w\d+-h\d+/, '=s1024'))
);
if (directImages.length > 0) {
images.push(...directImages);
console.log(`تم استخراج ${directImages.length} صورة مباشرة من الـ HTML.`);
}
const mainImageButtonSelector = 'button[jsaction*="hero-header-photo"]';
await page.waitForSelector(mainImageButtonSelector, { timeout: 8000 });
await page.click(mainImageButtonSelector);
console.log('تم النقر على الصورة الرئيسية لفتح المعرض.');
await page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 15000 });
await delay(2000);
const galleryImages = await page.evaluate(() =>
Array.from(document.querySelectorAll('div[role="img"] > img'))
.map(img => img.src.replace(/=w\d+-h\d+/, '=s1024'))
);
if (galleryImages.length > 0) {
images.push(...galleryImages);
console.log(`تم استخراج ${galleryImages.length} صورة إضافية من المعرض.`);
}
await page.goBack({ waitUntil: 'networkidle2' });
await delay(1500);
} catch (e) {
console.warn('لم يتم العثور على معرض الصور أو حدث خطأ، سيتم الاعتماد على الصور المستخرجة مباشرة فقط.');
}
images = [...new Set(images)].slice(0, 10);
console.log(`إجمالي عدد الصور الفريدة التي تم العثور عليها: ${images.length}`);
const finalUrl = page.url();
const coordMatch = finalUrl.match(/@(-?\d+\.\d+),(-?\d+\.\d+)/);
const coordinates = coordMatch ? { latitude: parseFloat(coordMatch[1]), longitude: parseFloat(coordMatch[2]) } : null;
const fullData = await page.evaluate(() => {
const get = (selector, attribute = 'textContent') => {
const element = document.querySelector(selector);
if (!element) return null;
return attribute === 'textContent' ? element.textContent.trim() : element.getAttribute(attribute);
};
const baseData = {
name: get('h1'),
category: get('button[jsaction*="category"]'),
address: get('button[data-item-id="address"]', 'aria-label'),
hours: get('div[data-item-id*="hours"]', 'aria-label'),
phone: get('button[data-item-id*="phone"]', 'aria-label'),
website: get('a[data-item-id="authority"]', 'href'),
};
const reviewButton = document.querySelector('button[jsaction*="reviewchart.click"]');
if (reviewButton) {
const reviewText = reviewButton.getAttribute('aria-label') || '';
const ratingMatch = reviewText.match(/(\d[\.,]\d|\d+)\s+نجوم/);
const reviewsMatch = reviewText.match(/([\d,]+)\s+(تعليقات|مراجعات)/);
baseData.rating = ratingMatch ? ratingMatch[1] : null;
baseData.reviewsCount = reviewsMatch ? reviewsMatch[1] : null;
}
const aboutSections = [];
document.querySelectorAll('div.iP2t7d').forEach(section => {
const title = section.querySelector('h2.b9tNq')?.textContent.trim();
const items = Array.from(section.querySelectorAll('li.hpLkke')).map(li => li.textContent.trim());
if (title && items.length > 0) {
aboutSections.push({ title, items });
}
});
baseData.about = aboutSections;
const reviewsList = [];
document.querySelectorAll('div.jftiEf').forEach(reviewElement => {
const author = reviewElement.querySelector('.d4r55')?.textContent.trim();
const avatar = reviewElement.querySelector('.NBa7we')?.getAttribute('src');
const text = reviewElement.querySelector('.wiI7pd')?.textContent.trim();
const ratingMatch = reviewElement.querySelector('.kvMYJc')?.getAttribute('aria-label')?.match(/(\d+)/);
if (author && text) {
reviewsList.push({
author: author,
avatar: avatar || null,
text: text,
rating: ratingMatch ? ratingMatch[0] : null,
});
}
});
baseData.reviews = reviewsList.slice(0, 5);
return baseData;
});
const finalData = { ...fullData, images, coordinates };
if (!finalData.name) {
throw new Error("فشل استخراج البيانات الأساسية (الاسم).");
}
cache.set(url, { data: finalData, timestamp: Date.now() });
console.log('تم استخراج البيانات الشاملة بنجاح لـ:', finalData.name);
try {
const dataDir = path.join(__dirname, 'scraped_data');
await fs.mkdir(dataDir, { recursive: true });
const safeName = finalData.name.replace(/[^a-z0-9\u0600-\u06FF]/gi, '_').toLowerCase();
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const fileName = `${safeName}_${timestamp}.json`;
const filePath = path.join(dataDir, fileName);
await fs.writeFile(filePath, JSON.stringify(finalData, null, 2), 'utf-8');
console.log(`تم حفظ البيانات بنجاح في ملف: ${fileName}`);
} catch (fileError) {
console.error('حدث خطأ أثناء حفظ الملف:', fileError);
}
res.json(finalData);
} catch (error) {
console.error('حدث خطأ فادح أثناء عملية الاستخراج:', error.message.split('\n')[0]);
res.status(500).json({ error: 'فشل استخراج البيانات. قد يكون الرابط غير صحيح أو أن جوجل تمنع الوصول حاليًا.' });
} finally {
if (browser) {
await browser.close();
}
}
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));

19
backend/proxies.js Normal file
عرض الملف

@@ -0,0 +1,19 @@
const { getRandomProxy } = require('./proxies');
// قائمة proxies مجانية (يجب تحديثها بانتظام)
const freeProxies = [
'103.149.162.194:80',
'45.7.64.106:999',
'167.71.5.83:3128',
'138.68.60.8:3128',
'167.99.131.11:80',
// إضافة المزيد من الـ proxies هنا
];
// دالة للحصول على proxy عشوائي
function getRandomProxy() {
return freeProxies[Math.floor(Math.random() * freeProxies.length)];
}
module.exports = { getRandomProxy };