۱۲

عبارات با قاعده (Regex) 🔍

یاد می‌گیریم چطور با الگوهای قدرتمند، متن‌ها رو جستجو و پردازش کنیم!

Regex چیه و چرا مهمه؟

عبارات منظم (Regular Expressions) ابزاری قدرتمند برای جستجو، تطبیق و دستکاری متن‌هاست. مثل یه زبان خاص برای توصیف الگوهای متنی:

🎯 کاربردهای Regex

  • اعتبارسنجی: بررسی فرمت ایمیل، شماره تلفن، کد ملی
  • استخراج داده: پیدا کردن اطلاعات خاص از متن
  • جایگزینی: تغییر الگوهای خاص در متن
  • تقسیم متن: جدا کردن متن بر اساس الگو
اولین تجربه با Regex
import re

# متن نمونه
text = """سلام! من علی هستم. ایمیل من ali@gmail.com است.
شماره تلفن من 09123456789 است.
سایت من https://example.com است."""

print("=== جستجوی ساده ===")
# جستجوی کلمه "علی"
result = re.search(r"علی", text)
if result:
    print(f"کلمه 'علی' در موقعیت {result.start()} پیدا شد")

# پیدا کردن همه اعداد
numbers = re.findall(r"\d+", text)
print(f"اعداد پیدا شده: {numbers}")

# پیدا کردن ایمیل
email_pattern = r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}"
email = re.search(email_pattern, text)
if email:
    print(f"ایمیل پیدا شده: {email.group()}")

# پیدا کردن شماره موبایل
phone_pattern = r"09\d{9}"
phone = re.search(phone_pattern, text)
if phone:
    print(f"شماره موبایل: {phone.group()}")

# پیدا کردن URL
url_pattern = r"https?://[^\s]+"
url = re.search(url_pattern, text)
if url:
    print(f"آدرس سایت: {url.group()}")

# خروجی:
# کلمه 'علی' در موقعیت 11 پیدا شد
# اعداد پیدا شده: ['09123456789']
# ایمیل پیدا شده: ali@gmail.com
# شماره موبایل: 09123456789
# آدرس سایت: https://example.com

الگوهای پایه Regex

بیایید با کاراکترهای خاص و الگوهای پایه آشنا شویم:

📝 کاراکترهای خاص

. - هر کاراکتر (به جز خط جدید)

* - صفر یا بیشتر

+ - یک یا بیشتر

? - صفر یا یک

^ - شروع رشته

$ - پایان رشته

\d - رقم (0-9)

\w - حرف، رقم، _

\s - فاصله، تب، خط جدید

\D - غیر رقم

\W - غیر حرف

\S - غیر فاصله

مثال‌های عملی الگوها
import re

# متن نمونه برای تست
test_text = """نام: احمد رضایی
سن: 25 سال
ایمیل: ahmad.rezaei@example.com
تلفن: 021-12345678
موبایل: 09123456789
کد پستی: 1234567890
تاریخ: 1402/05/15
ساعت: 14:30:25"""

print("=== الگوهای مختلف ===")

# 1. پیدا کردن اعداد
digits = re.findall(r"\d+", test_text)
print(f"اعداد: {digits}")

# 2. پیدا کردن کلمات فارسی
farsi_words = re.findall(r"[آ-ی]+", test_text)
print(f"کلمات فارسی: {farsi_words}")

# 3. پیدا کردن ایمیل با گروه‌بندی
email_pattern = r"([a-zA-Z0-9._%+-]+)@([a-zA-Z0-9.-]+)\.([a-zA-Z]{2,})"
email_match = re.search(email_pattern, test_text)
if email_match:
    print(f"نام کاربری: {email_match.group(1)}")
    print(f"دامنه: {email_match.group(2)}")
    print(f"پسوند: {email_match.group(3)}")

# 4. پیدا کردن تاریخ شمسی
date_pattern = r"(\d{4})/(\d{2})/(\d{2})"
date_match = re.search(date_pattern, test_text)
if date_match:
    year, month, day = date_match.groups()
    print(f"سال: {year}, ماه: {month}, روز: {day}")

# 5. پیدا کردن ساعت
time_pattern = r"(\d{2}):(\d{2}):(\d{2})"
time_match = re.search(time_pattern, test_text)
if time_match:
    hour, minute, second = time_match.groups()
    print(f"ساعت: {hour}, دقیقه: {minute}, ثانیه: {second}")

# 6. استفاده از کلاس کاراکتر
phone_numbers = re.findall(r"[0-9]{3}-[0-9]{8}|09[0-9]{9}", test_text)
print(f"شماره تلفن‌ها: {phone_numbers}")

# 7. استفاده از quantifiers
postal_code = re.search(r"\d{10}", test_text)
if postal_code:
    print(f"کد پستی: {postal_code.group()}")

توابع مهم ماژول re

ماژول re توابع مختلفی برای کار با regex داره:

توابع اصلی ماژول re
import re

# متن نمونه
text = "سلام دوستان! امروز 1402/05/15 است. ایمیل من test@example.com است."

print("=== توابع مختلف re ===")

# 1. re.search() - اولین تطبیق
result = re.search(r"\d{4}/\d{2}/\d{2}", text)
if result:
    print(f"تاریخ پیدا شده: {result.group()}")
    print(f"موقعیت: {result.start()}-{result.end()}")

# 2. re.match() - تطبیق از ابتدای رشته
match_result = re.match(r"سلام", text)
if match_result:
    print(f"شروع با 'سلام': {match_result.group()}")

# 3. re.findall() - همه تطبیق‌ها
all_numbers = re.findall(r"\d+", text)
print(f"همه اعداد: {all_numbers}")

# 4. re.finditer() - iterator برای تطبیق‌ها
for match in re.finditer(r"\w+@\w+\.\w+", text):
    print(f"ایمیل: {match.group()} در موقعیت {match.start()}")

# 5. re.sub() - جایگزینی
new_text = re.sub(r"\d{4}/\d{2}/\d{2}", "[تاریخ مخفی]", text)
print(f"متن جدید: {new_text}")

# 6. re.split() - تقسیم رشته
parts = re.split(r"[.!?]", text)
print(f"بخش‌های جمله: {[part.strip() for part in parts if part.strip()]}")

# 7. re.compile() - کامپایل الگو برای استفاده مکرر
pattern = re.compile(r"\b[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\b")
emails = pattern.findall(text)
print(f"ایمیل‌های پیدا شده: {emails}")

# 8. استفاده از flags
text_mixed = "Hello سلام WORLD دنیا"
words = re.findall(r"[a-z]+", text_mixed, re.IGNORECASE)
print(f"کلمات انگلیسی: {words}")

# 9. گروه‌بندی با نام
phone_pattern = r"(?P\d{3})-(?P\d{8})"
phone_text = "تلفن: 021-12345678"
phone_match = re.search(phone_pattern, phone_text)
if phone_match:
    print(f"کد شهر: {phone_match.group('area')}")
    print(f"شماره: {phone_match.group('number')}")

پروژه عملی: اعتبارسنج داده‌ها

بیایید یک کلاس برای اعتبارسنجی انواع داده‌ها بسازیم:

کلاس DataValidator
import re
from typing import List, Dict, Optional

class DataValidator:
    """کلاس اعتبارسنجی داده‌ها با Regex"""
    
    def __init__(self):
        # الگوهای مختلف
        self.patterns = {
            'email': r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$',
            'iranian_mobile': r'^09[0-9]{9}$',
            'iranian_phone': r'^0[1-9][0-9]{1,2}-?[0-9]{8}$',
            'iranian_postal_code': r'^[0-9]{10}$',
            'iranian_national_id': r'^[0-9]{10}$',
            'url': r'^https?://[^\s/$.?#].[^\s]*$',
            'ip_address': r'^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$',
            'persian_text': r'^[آ-ی\s]+$',
            'english_text': r'^[a-zA-Z\s]+$',
            'strong_password': r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$'
        }
        
        self.validation_history = []
    
    def validate_email(self, email: str) -> bool:
        """اعتبارسنجی ایمیل"""
        is_valid = bool(re.match(self.patterns['email'], email))
        self.validation_history.append({
            'type': 'email',
            'value': email,
            'valid': is_valid
        })
        return is_valid
    
    def validate_iranian_mobile(self, mobile: str) -> bool:
        """اعتبارسنجی شماره موبایل ایرانی"""
        is_valid = bool(re.match(self.patterns['iranian_mobile'], mobile))
        self.validation_history.append({
            'type': 'iranian_mobile',
            'value': mobile,
            'valid': is_valid
        })
        return is_valid
    
    def validate_iranian_national_id(self, national_id: str) -> bool:
        """اعتبارسنجی کد ملی ایرانی"""
        # ابتدا فرمت را بررسی می‌کنیم
        if not re.match(self.patterns['iranian_national_id'], national_id):
            return False
        
        # الگوریتم محاسبه رقم کنترل
        check_sum = sum(int(national_id[i]) * (10 - i) for i in range(9))
        remainder = check_sum % 11
        
        if remainder < 2:
            expected_check_digit = remainder
        else:
            expected_check_digit = 11 - remainder
        
        is_valid = int(national_id[9]) == expected_check_digit
        
        self.validation_history.append({
            'type': 'iranian_national_id',
            'value': national_id,
            'valid': is_valid
        })
        return is_valid
    
    def extract_data_from_text(self, text: str) -> Dict[str, List[str]]:
        """استخراج انواع داده از متن"""
        extracted = {
            'emails': re.findall(self.patterns['email'], text),
            'mobiles': re.findall(self.patterns['iranian_mobile'], text),
            'urls': re.findall(self.patterns['url'], text),
            'numbers': re.findall(r'\d+', text)
        }
        return extracted
    
    def clean_phone_number(self, phone: str) -> str:
        """تمیز کردن شماره تلفن"""
        # حذف کاراکترهای غیرضروری
        cleaned = re.sub(r'[^0-9]', '', phone)
        
        # فرمت استاندارد
        if len(cleaned) == 11 and cleaned.startswith('09'):
            return cleaned
        elif len(cleaned) == 10 and cleaned.startswith('9'):
            return '0' + cleaned
        else:
            return cleaned
    
    def mask_sensitive_data(self, text: str) -> str:
        """پنهان کردن اطلاعات حساس"""
        # پنهان کردن ایمیل
        text = re.sub(r'([a-zA-Z0-9._%+-]+)@([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})', 
                     r'***@\2', text)
        
        # پنهان کردن شماره موبایل
        text = re.sub(r'(09)(\d{2})(\d{3})(\d{4})', 
                     r'\1**\3****', text)
        
        # پنهان کردن کد ملی
        text = re.sub(r'\b(\d{3})(\d{4})(\d{3})\b', 
                     r'\1****\3', text)
        
        return text
    
    def get_validation_report(self) -> Dict:
        """گزارش اعتبارسنجی‌ها"""
        total = len(self.validation_history)
        valid_count = sum(1 for item in self.validation_history if item['valid'])
        
        return {
            'total_validations': total,
            'valid_count': valid_count,
            'invalid_count': total - valid_count,
            'success_rate': (valid_count / total * 100) if total > 0 else 0,
            'history': self.validation_history
        }

# تست کلاس
if __name__ == "__main__":
    validator = DataValidator()
    
    print("=== تست اعتبارسنجی ===")
    
    # تست ایمیل
    emails = ['test@example.com', 'invalid-email', 'user@domain.co.uk']
    for email in emails:
        result = validator.validate_email(email)
        print(f"ایمیل {email}: {'معتبر' if result else 'نامعتبر'}")
    
    # تست موبایل
    mobiles = ['09123456789', '9123456789', '021123456']
    for mobile in mobiles:
        result = validator.validate_iranian_mobile(mobile)
        print(f"موبایل {mobile}: {'معتبر' if result else 'نامعتبر'}")
    
    # تست کد ملی
    national_ids = ['0123456789', '1234567890']
    for nid in national_ids:
        result = validator.validate_iranian_national_id(nid)
        print(f"کد ملی {nid}: {'معتبر' if result else 'نامعتبر'}")
    
    # استخراج داده از متن
    sample_text = """سلام! ایمیل من ali@example.com است.
    شماره موبایل: 09123456789
    سایت: https://example.com
    """
    
    extracted = validator.extract_data_from_text(sample_text)
    print(f"\nداده‌های استخراج شده: {extracted}")
    
    # پنهان کردن اطلاعات حساس
    masked = validator.mask_sensitive_data(sample_text)
    print(f"\nمتن با اطلاعات پنهان:\n{masked}")
    
    # گزارش
    report = validator.get_validation_report()
    print(f"\nگزارش: {report['valid_count']} از {report['total_validations']} معتبر")

تمرین عملی

یک برنامه بنویسید که:

  1. از کاربر یک متن دریافت کند
  2. همه ایمیل‌ها، شماره تلفن‌ها و URL های موجود در متن را پیدا کند
  3. اعتبار هر کدام را بررسی کند
  4. گزارش کاملی از یافته‌ها ارائه دهد
راه حل تمرین
import re

class TextAnalyzer:
    def __init__(self):
        self.patterns = {
            'email': r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b',
            'phone': r'\b(?:09\d{9}|0[1-9]\d{1,2}-?\d{8})\b',
            'url': r'https?://[^\s]+'
        }
    
    def analyze_text(self, text):
        results = {
            'emails': [],
            'phones': [],
            'urls': []
        }
        
        # پیدا کردن ایمیل‌ها
        emails = re.findall(self.patterns['email'], text)
        for email in emails:
            results['emails'].append({
                'value': email,
                'valid': self.validate_email(email)
            })
        
        # پیدا کردن شماره تلفن‌ها
        phones = re.findall(self.patterns['phone'], text)
        for phone in phones:
            results['phones'].append({
                'value': phone,
                'valid': self.validate_phone(phone)
            })
        
        # پیدا کردن URL ها
        urls = re.findall(self.patterns['url'], text)
        for url in urls:
            results['urls'].append({
                'value': url,
                'valid': self.validate_url(url)
            })
        
        return results
    
    def validate_email(self, email):
        pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
        return bool(re.match(pattern, email))
    
    def validate_phone(self, phone):
        # حذف خط تیره
        clean_phone = phone.replace('-', '')
        return len(clean_phone) >= 10
    
    def validate_url(self, url):
        return url.startswith(('http://', 'https://'))
    
    def generate_report(self, results):
        print("=== گزارش تحلیل متن ===")
        
        for data_type, items in results.items():
            print(f"\n{data_type.upper()}:")
            if not items:
                print("  هیچ موردی پیدا نشد")
            else:
                for item in items:
                    status = "✅ معتبر" if item['valid'] else "❌ نامعتبر"
                    print(f"  {item['value']} - {status}")

# استفاده
analyzer = TextAnalyzer()

sample_text = input("متن خود را وارد کنید: ")
results = analyzer.analyze_text(sample_text)
analyzer.generate_report(results)

چالش پیشرفته

یک سیستم پردازش لاگ بسازید که:

  • لاگ‌های وب سرور را تجزیه کند
  • IP آدرس‌ها، تاریخ/زمان، HTTP method و status code را استخراج کند
  • آمار کاملی از درخواست‌ها ارائه دهد
  • IP های مشکوک (تعداد درخواست بالا) را شناسایی کند

این چالش نیاز به ترکیب regex با ساختارهای داده پیشرفته دارد. سعی کنید خودتان حل کنید!