هوش مصنوعی جای regex رو میگیره؟!

۱۴۰۵/۰۶/۳۰تپه‌نوردیهوش مصنوعی

چند روز پیش (۲۷ شهریور) یه مدل جدید به اسم Jev از شرکت TypeSafe به OpenRouter اضافه شد. این مدل با ChatGPT و بقیه رفقاش یه فرق اساسی داره. متن نمی‌نویسه! شما یه متن بهش میدید و یه سوال بله/خیر ازش می‌پرسید، اونم فقط یه عدد بین ۰ تا ۱ برمی‌گردونه که یعنی چقدر مطمئنه جواب «بله»ست.

من میخواستم ببینم این مدل میتونه جای regex رو بگیره یا نه. یعنی به جای اینکه یه الگوی عجیب غریب بنویسیم، ازش بپرسیم «این ایمیل درسته؟» و تمام. اومدم یه بنچمارک درست کردم که سرعت، هزینه و دقتش رو هم در حالت تک به تک و هم در حالت بچ (batch) با regex مقایسه کنم.

اول بیاید ببینیم regex چیه؟

regex (عبارت منظم) به زبون آدمیزاد یه الگوئه که میگه یه متن باید چه شکلی باشه. مثلا وقتی توی یه سایت شماره موبایلتون رو وارد می‌کنید و میگه «شماره وارد شده صحیح نیست»، معمولا پشتش یه چیزی شبیه این نشسته :

[1]:
re.match(r'^(\+98|0098|98|0)?9\d{9}$', "09121234567")

یعنی چی؟ یعنی اولش میتونه +98 یا 0098 یا 98 یا 0 باشه (یا هیچی)، بعدش حتما 9 و بعدش دقیقا ۹ تا رقم دیگه. به همین سادگی! این الگو ها رو معمولا کسی از اول نمی‌نویسه و از اینترنت کپی میشن. من هم دقیقا همین regex های رایج رو برداشتم، یعنی همونایی که همه جا کپی شدن.

Jev چه جوری کار میکنه؟

یه درخواست ساده به Jev این شکلیه :

[2]:
requests.post("https://openrouter.ai/api/alpha/decisions", headers=headers, json={
    "model": "~typesafe/jev-latest",
    "state": "[email protected]",
    "questions": {
        "email": {"type": "noul",
                  "instructions": "Is the state exactly one syntactically valid email address?"}
    }
}).json()
Out[2]:
{'model': 'typesafe/jev-1.13-20260917',
 'answers': {'email': {'type': 'noul', 'noul': 0.88}},
 'usage': {'input_tokens': 283, 'output_tokens': 20, 'cost': 1.1886e-05}}

state همون متنیه که میخوایم در موردش سوال بپرسیم و questions هم سوال هامونه (noul یعنی سوال بله/خیر). اون 0.88 یعنی Jev ۸۸ درصد مطمئنه که این یه ایمیل درسته. من هر جا عدد بالای ۰.۵ بود جواب رو «بله» در نظر گرفتم. قیمتش هم اینجوریه که هر ۱ میلیون توکن ورودی ۰.۰۴۲ دلاره و خروجی مجانیه. توکن هم یعنی تیکه های کوچیک متن که مدل ها با اون حساب میکنن، تقریبا اندازه یه کلمه یا کمتر. همین سوال بالا ۲۸۳ توکن شد، یعنی حدود ۰.۰۰۰۰۱۲ دلار.

چی رو با چی مقایسه کردم؟

دو جور آزمون گذاشتم :

۱. چک کردن درستی : یعنی یه متن رو بدیم و بپرسیم «این ایمیل درسته؟» یا «این تاریخ درسته؟». ۱۲ تا از الگو های رایج رو انتخاب کردم : ایمیل، آدرس سایت، IP، موبایل، کد ملی، تاریخ میلادی، تاریخ شمسی، کد رنگ (مثل #fff)، UUID، ساعت، رمز عبور قوی و «متن فقط فارسی». برای هر کدوم حدود ۴۴ تا نمونه ساختم که در مجموع شد ۵۳۴ تا. بعضیاش رو دستی نوشتم که عمدا تله داشته باشن (مثل [email protected] که دو تا نقطه پشت هم داره) و بقیه رو به صورت تصادفی ساختم.

۲. پیدا کردن توی پیام : یعنی یه پیام فارسی بدیم و بپرسیم «توش شماره موبایل هست؟»، «ایمیل هست؟» و «لینک هست؟». ۴۴ تا پیام نوشتم که بعضیاش آدم رو میپیچونن. مثلا «شماره‌ام ۰۹۱۲-۱۲۳-۴۵۶۷ است» یا «ali.rezaei ات جیمیل دات کام». ۴۴ تا پیام ضربدر ۳ تا سوال میشه ۱۳۲ تا سوال.

جواب درست نمونه های بخش اول رو با کد معمولی پایتون (نه regex) حساب کردم که مطمئن باشم درسته. جواب پیام ها رو هم دستی زدم. به Jev هم دقیقا همون قانونی که regex چک میکنه رو به زبون انگلیسی گفتم. مثلا برای ایمیل بهش گفتم نقطه نباید اول یا آخر اسم باشه و دو تا نقطه هم نباید پشت هم بیاد.

تک به تک یا بچ؟

تک به تک یعنی هر بار یه نمونه بفرستیم و جوابش رو بگیریم. بچ یعنی یه جا ۱۰ یا ۱۰۰ یا ۱۰۰۰ تا نمونه رو با هم بفرستیم. مثل اینکه به جای اینکه ۱۰۰ بار تا سوپرمارکت برید و هر بار یه چیز بخرید، یه بار برید و ۱۰۰ تا چیز بخرید. چرا این مهمه؟ چون هر درخواست به Jev یه هزینه ثابت داره، درست مثل کرایه رفت و آمد تا سوپرمارکت. حدود ۲۷۰ تا توکن فقط خرج خود درخواست میشه، حالا چه یه نمونه توش باشه چه هزار تا.

نکته‌ای که وجود داره اینه که Jev خودش بچ نداره و هر درخواست فقط یه state (یعنی یه ورودی) میگیره. من اومدم چند تا نمونه رو گذاشتم توی یه JSON و برای هر کدوم یه سوال جدا پرسیدم :

[3]:
state = {
    "rules": {"email": "A valid email address: ..."},
    "items": {"i000": "[email protected]",
              "i001": "[email protected]"}
}
questions = {
    "i000": {"type": "noul", "instructions": "Is items.i000 a valid email address? Apply rules.email strictly."},
    "i001": {"type": "noul", "instructions": "Is items.i001 a valid email address? Apply rules.email strictly."}
}

سرعت و هزینه

فرض کنید یه فایل دارید با ۱ میلیون تا ایمیل یا شماره موبایل و میخواید ببینید کدوماش درسته. جدول زیر نشون میده هر روش چقدر طول میکشه و چقدر خرج داره. البته من ۱ میلیون تا رو واقعا به Jev ندادم. سرعتی که توی آزمون اندازه گرفتم رو برای ۱ میلیون حساب کردم :

Out[4]:
زمان برای ۱ میلیونهزینه برای ۱ میلیون
regex (روی یه هسته CPU)۰.۱۸ ثانیهتقریبا صفر
Jev تک به تک، یکی بعد از دیگری۸ روز۱۶.۶ دلار
Jev تک به تک، ۱۶ تا همزمان۱۴ ساعت۱۶.۶ دلار
Jev، هر درخواست ۵۰۰ تا، ۱۰ درخواست همزمان۲۱ دقیقه۲.۲ دلار

یعنی تو همون ۰.۶ ثانیه‌ای که Jev به یه سوال جواب میده، regex حدود ۴.۷ میلیون تا نمونه رو چک کرده! اگه بخوام یه جور دیگه بگم، اگه regex برای چک کردن یه چیزی ۱ ثانیه وقت بذاره، Jev تک به تک ۵۴ روز وقت میذاره 🙂

البته انصافا از اون ۰.۶ ثانیه، حدود ۰.۵ ثانیه‌ش رفت و برگشت اینترنته. من از ایران و با پروکسی به سرور های Jev توی آمریکا وصل میشدم. ولی حتی اگه شبکه رو کنار بذاریم، باز هم regex صدها هزار برابر سریع‌تره.

بچ چقدر فرق میکنه؟

حالا بریم سراغ اینکه اندازه بچ چه تاثیری داره. هر بار N تا ایمیل تازه رو توی یه درخواست فرستادم (هر اندازه رو ۳ بار) و زمان جواب و هزینه رو نوشتم. جدول زیر نتیجه‌ست :

Out[5]:
تعداد در هر درخواستزمان جواب (ثانیه)هزینه هر ۱۰۰۰ تا (سنت)توکن برای هر نمونه
۱۰.۴۷۱.۸۹۴۵۰
۱۰۰.۸۹۰.۳۹۹۲
۵۰۰.۶۹۰.۲۵۶۰
۱۰۰۱.۴۳۰.۲۳۵۶
۲۵۰۳.۳۵۰.۲۲۵۲
۵۰۰۵.۳۲۰.۲۲۵۱
۱,۰۰۰۸.۹۶۰.۲۱۵۱
۲,۰۰۰خطا داد!--

همونطور که می‌بینید از ۱ تا ۵۰، هزینه حدود ۷.۵ برابر کم میشه ولی زمان جواب تقریبا عوض نمیشه. از ۱۰۰ به بعد هزینه دیگه خیلی کم نمیشه ولی زمان جواب پشت سر هم زیاد میشه، به ازای هر نمونه حدود ۹ میلی‌ثانیه. ۲۰۰۰ تا رو هم کلا قبول نکرد و خطای max_tokens_exceeded داد. پس جای خوبش یه جایی بین ۵۰ تا ۱۰۰ تاست. دقتش هم توی بچ ۱۰۰۰ تایی هنوز ۹۸ درصد بود.

کی درست‌تر جواب داد؟

خب رسیدیم به سوال اصلی. جدول زیر نشون میده هر روش چند درصد جواب ها رو درست داده :

Out[6]:
regex رایجregex با پیش‌پردازشJev با متن سادهJev با ورودی JSONJev بچ ۱۰ تایی
چک کردن درستی ایمیل، تاریخ و … (۵۳۴ تا)۸۵.۰-۷۷.۹۹۱.۸۹۳.۴
پیدا کردن شماره، ایمیل و لینک توی پیام (۱۳۲ تا)۸۲.۶۹۷.۰۱۰۰۱۰۰۹۹.۲

«regex با پیش‌پردازش» یعنی قبل از اینکه regex رو اجرا کنم، متن رو یکدست کردم : ارقام فارسی رو انگلیسی کردم، فاصله و خط تیره وسط عدد ها رو برداشتم و [at] و dot رو به @ و . تبدیل کردم. این کار فقط برای پیام ها معنی داره. دو ستون «Jev با متن ساده» و «Jev با ورودی JSON» هر دو تک به تک هستن و فرقشون رو پایین‌تر توضیح میدم.

نکته جالبش اینه که توی چک کردن درستی، regex رایج از Jev با متن ساده بهتر بود ولی از Jev با ورودی JSON بدتر. توی پیدا کردن شماره و ایمیل و لینک هم Jev هر ۱۳۲ تا سوال رو درست جواب داد.

البته اینم بگم که جواب درست بخش اول رو با کد معمولی پایتون حساب کرده بودم. پس اگه همون کد رو به جای regex استفاده کنید، طبیعتا ۱۰۰ درصد میشه 🙂 و هر بار چک کردن هم زیر ۳ میکروثانیه طول میکشه.

یه ترفند ساده!

اولش متن رو همونجوری که بود به Jev میدادم و قانون رو توی متن سوال می‌نوشتم. یعنی state فقط خود متن بود. بعد دیدم اگه متن و قانون رو کنار هم توی یه JSON بذارم (همون شکلی که بالاتر برای بچ ساختم)، خیلی بهتر جواب میده. حتی وقتی فقط یه نمونه توش باشه! جدول زیر چند تا نمونه رو نشون میده. عدد ها احتمال «بله» هستن :

Out[7]:
متنسوالجواب درستJev با متن سادهJev با ورودی JSON
# fffکد رنگ درسته؟نه۰.۷۴۰.۰۲
9:05ساعت به شکل HH:MM هست؟نه۰.۹۳۰.۰۲
ali [email protected]ایمیل درسته؟نه۰.۹۴۰.۰۱
[email protected]ایمیل درسته؟نه۰.۸۸۰.۰۱
#FFFFFFFFکد رنگ ۳ یا ۶ رقمی هست؟نه۰.۷۰۰.۰۱
1405/06/30تاریخ شمسی واقعیه؟آره۰.۷۱۰.۳۵

همونطور که می‌بینید با JSON خیلی بهتر شد ولی همه جا نه. ۳۰ شهریور ۱۴۰۵ (یعنی امروز!) رو با متن ساده درست گفت ولی با JSON گفت همچین تاریخی وجود نداره :/

regex های رایج کجا سوتی دادن؟

این الگو ها رو هزاران نفر کپی کردن ولی ایراد های جالبی دارن :

Out[8]:
متنregex چی گفت؟چرا غلطه؟
[email protected]ایمیل درستهدو تا نقطه پشت هم مجاز نیست
2026-02-30تاریخ درستهفوریه ۳۰ روز نداره
1405/07/31تاریخ درستهمهر ۳۰ روزه‌ست
09۱۲۱۲۳۴۵۶۷شماره درستهنصفش ارقام فارسیه. توی پایتون \d ارقام فارسی رو هم قبول میکنه!
192.168.1.1\nIP درسته$ یه Enter آخر متن رو نمی‌بینه
می‌خواهم کتاب بخرممتن فارسی نیستنیم‌فاصله توی بازه \u0600-\u06FF نیست
کد پیگیری پرداختم 60379912345678 هستشماره موبایل دارهوسط یه عدد ۱۴ رقمی، 9912345678 رو پیدا کرده

راه حلش سخت نیست. به جای $ از fullmatch استفاده کنید، به جای \d بنویسید [0-9]، نیم‌فاصله (\u200c) رو به بازه حروف فارسی اضافه کنید و برای تاریخ و کد ملی چند خط کد بنویسید. regex به تنهایی نمی‌تونه بفهمه ۳۱ مهر وجود نداره یا رقم کنترل کد ملی درسته یا نه.

Jev کجا سوتی داد؟

حتی با ورودی JSON هم چند جا کم آورد :

کد ملی : فرمول رقم کنترل رو کامل بهش دادم ولی بیشتر جواب هاش دور و بر ۰.۵ بود. یعنی عملا شیر یا خط انداخت. بهترین دقتش ۷۵ درصد بود.

تاریخ شمسی : علاوه بر ۳۰ شهریور، 1399/12/30 رو هم رد کرد، با اینکه بهش گفته بودم ۱۳۹۹ کبیسه‌ست.

موبایل بدون صفر : 9121234567 طبق همون قانون درسته ولی Jev ردش کرد.

نیم‌فاصله : با اینکه صریحا گفته بودم نیم‌فاصله مجازه، چند تا از متن های فارسیِ نیم‌فاصله‌دار رو رد کرد. یعنی دقیقا همون سوتی regex رو داد!

به زبون آدمیزاد، Jev معنی رو خوب میفهمه ولی حساب کتاب و شمردن کاراکتر کارش نیست.

Jev کجا ترکوند؟

اما توی پیدا کردن شماره و ایمیل و لینک توی پیام ها قضیه برعکس شد. جدول زیر چند تا از پیام هایی رو نشون میده که regex نتونست. عدد های ستون Jev احتمال «بله» هستن :

Out[9]:
پیامسوالregex رایجregex با پیش‌پردازشJev
صفر نهصد و دوازده، صد و بیست و سه، چهل و پنج، شصت و هفت شماره منهشماره داره؟✘✘۰.۶۷ ✔
0912 123 4567 واتساپ دارمشماره داره؟✘✔۰.۹۲ ✔
کد پیگیری پرداختم 60379912345678 هست ولی سفارشم نیومدشماره داره؟✘✔۰.۰۴ ✔
ali.rezaei ات جیمیل دات کامایمیل داره؟✘✘۰.۹۸ ✔
ali.rezaei [at] gmail [dot] comایمیل داره؟✘✔۰.۹۹ ✔
سایت کتاب‌یار دات کام رو سرچ کنلینک داره؟✘✘۰.۹۵ ✔
sitename[.]irلینک داره؟✘✔۰.۹۷ ✔

یعنی اگه کاربر شماره‌ش رو با حروف بنویسه یا به جای نقطه بنویسه «دات»، regex هیچ جوره نمی‌بینه ولی Jev می‌بینه. البته همه این پیام ها رو خودم نوشتم و ۴۴ تا پیام هم عدد بزرگی نیست. پس ۱۰۰ درصد اینجا به این معنی نیست که روی پیام های واقعی هم همینقدر خوبه.

راستی کل این آزمایش با ۲,۲۴۷ تا درخواست و ۲۲,۰۶۱ تا سوال، ۷ سنت خرج برداشت.

خلاصه اینکه اگه میخواید چک کنید یه ایمیل یا شماره درست وارد شده یا نه، همون regex (با چند خط کد) رو نگه دارید. هم میلیون ها بار سریع‌تره، هم تقریبا مجانیه، هم دقیق‌تره. ولی اگه میخواید بفهمید کسی تو پیامش شماره یا لینک قایم کرده یا نه، Jev می‌ارزه 🙂

آپدیت دوشنبه، ۳۰ شهریور ۱۴۰۵ :

Laya چی؟

یه مدل دیگه هم هست به اسم Laya از شرکت Convai Innovations که دقیقا همون کار Jev رو میکنه. همون شکل سوال ها (بله/خیر، چند گزینه‌ای و نمره) و همون جواب عددی بین ۰ تا ۱. فرقش اینه که متن‌بازه و میشه روی کامپیوتر خودتون اجراش کنید. یعنی بعد از اینکه دانلودش کردید نه اینترنت میخواد نه پول 🙂 توی صفحه‌ش هم ادعا کرده ۶ تا ۸ برابر از Jev سریع‌تره و روی چند تا بنچمارک هم دقیق‌تره.

گفتم خب اینم بذارم کنار بقیه! سه تا نسخه داره : نسخه انگلیسی، نسخه چندزبانه و یه نسخه که روی چند تا کار مشخص (مثل پشتیبانی مشتری و بررسی فاکتور) آموزش دیده. من دو تای اول رو تست کردم. یه ابزار کوچیک هم کنارش داده که خودش تشخیص میده متن انگلیسیه یا نه و میفرستش سمت نسخه مناسب. من همون ۶۶۶ تا نمونه رو با همون سوال های انگلیسی که به Jev داده بودم، تک به تک بهش دادم. پس باید با ستون «Jev با متن ساده» مقایسه‌ش کرد :

Out[10]:
Jev با متن سادهLaya
چک کردن درستی ایمیل، تاریخ و … (۵۳۴ تا)۷۷.۹۵۰.۶
پیدا کردن شماره، ایمیل و لینک توی پیام (۱۳۲ تا)۱۰۰۳۰.۳

یعنی چی؟ یعنی توی چک کردن درستی عملا شیر یا خط انداخت. توی پیام ها هم از شیر یا خط بدتر شد! چرا؟ چون تقریبا به هر سوالی گفت «بله». از اون ۱۳۲ تا سوال فقط جواب ۳۱ تاش «بله» بود ولی Laya به ۱۱۷ تاش گفت «بله». جدول زیر چند تا نمونه رو نشون میده. عدد ها احتمال «بله» هستن :

Out[11]:
متنسوالجواب درستJevLaya
[email protected]ایمیل درسته؟آره۰.۹۴۰.۱۰
9:05ساعت به شکل HH:MM هست؟نه۰.۹۳۰.۳۶
0912 123 4567 واتساپ دارمایمیل داره؟نه۰.۰۱۱.۰۰
صفر نهصد و دوازده، صد و بیست و سه، چهل و پنج، شصت و هفت شماره منهلینک داره؟نه۰.۰۱۱.۰۰
ali.rezaei ات جیمیل دات کامشماره داره؟نه۰.۰۲۱.۰۰
ali.rezaei [at] gmail [dot] comایمیل داره؟آره۰.۹۹۰.۳۰

همونطور که می‌بینید یه جا که Jev سوتی داده بود رو درست گفت، ولی یه ایمیل کاملا سالم رو رد کرد و به پیامی که فقط شماره موبایل توشه با اطمینان کامل گفت ایمیل داره :/

نکنه من اشتباه تنظیمش کرده بودم؟

اول باید مطمئن میشدم که مشکل از تنظیمات من نیست. برای همین مثال خود صفحه‌ش رو اجرا کردم (یه ایمیل که مشتری میگه دو بار ازش پول کم شده و باید بگه مال کدوم بخشه) و درست جواب داد. بعد ۸ تا تیتر خبری ساده بهش دادم، ۴ تا انگلیسی و ۴ تا فارسی، که بگه ورزشیه یا اقتصادی یا فناوری یا بین‌الملل. هر ۸ تا رو درست گفت. پس مدل سالمه و موضوع کلی یه متن رو می‌فهمه، ولی وقتی پای یه قانون دقیق وسط میاد (مثل همین آزمون ها) کم میاره.

نکته جالبش اینه که خود صفحه‌ش هم یه جا صادقانه نوشته که نسخه پایه، بدون اینکه روی داده خودتون آموزش ببینه، نزدیک شیر یا خط جواب میده. اون دقت بالایی که اعلام کرده مال نسخه‌ایه که روی همون بنچمارک آموزش دیده. عدد های Jev توی جدول مقایسه‌شون رو هم خودشون اندازه نگرفتن و از جای دیگه آوردن.

سرعتش چی؟

اینجا دیگه Laya برنده‌ست. هر سوال روی کارت گرافیک (RTX 3090) حدود ۲۱ میلی‌ثانیه طول کشید و روی CPU (Ryzen 9 7900X) حدود ۲۲۰ میلی‌ثانیه. اگه همون حساب ۱ میلیون تا رو بکنیم :

Out[12]:
زمان برای ۱ میلیونهزینه برای ۱ میلیون
regex (روی یه هسته CPU)۰.۱۸ ثانیهتقریبا صفر
Jev تک به تک، یکی بعد از دیگری۸ روز۱۶.۶ دلار
Laya تک به تک روی کارت گرافیک۶ ساعتمجانی
Laya تک به تک روی CPU۲.۵ روزمجانی

البته Jev رو از ایران و با پروکسی صدا میزدم و حدود ۰.۵ ثانیه‌ش فقط رفت و برگشت اینترنت بود، پس بیشتر این فرق مال شبکه‌ست. ولی خب سرعت به چه دردی میخوره وقتی جواب درست نیست؟ 🙂

خلاصه اینکه Laya سریعه و مجانیه، ولی بدون اینکه روی داده خودتون آموزشش بدید، حداقل برای این کار ها به درد نمی‌خوره. پس حرف اول پست هنوز سر جاشه : برای چک کردن درستی ایمیل و شماره همون regex با چند خط کد، برای پیدا کردن شماره و لینکی که توی پیام قایم شده Jev 🙂

ضمیمه : regex هایی که استفاده کردم

اینم regex هایی که توی آزمون استفاده کردم. همشون با ماژول re پایتون و تنظیمات پیش‌فرضش اجرا شدن. فقط UUID رو با re.I اجرا کردم که حروف بزرگ و کوچیک فرقی نکنن. برای چک کردن درستی از re.match استفاده کردم و همه الگو ها با ^ شروع و با $ تموم میشن. یعنی کل متن باید با الگو جور باشه :

[13]:
validate = {
    "email":    r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$',
    "url":      r'^https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&\/=]*)$',
    "ipv4":     r'^((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$',
    "mobile":   r'^(\+98|0098|98|0)?9\d{9}$',
    "nid":      r'^\d{10}$',
    "iso_date": r'^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$',
    "jalali":   r'^1[34]\d{2}/(0?[1-9]|1[0-2])/(0?[1-9]|[12]\d|3[01])$',
    "hex":      r'^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$',
    "uuid":     r'^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$',  # re.I
    "time":     r'^([01]\d|2[0-3]):([0-5]\d)$',
    "password": r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$',
    "persian":  r'^[\u0600-\u06FF\s]+$',
}

جدول زیر نشون میده هر کدوم چی رو چک میکنه، از نمونه هاش چند تا رو درست جواب داد و کجا اشتباه کرد :

Out[14]:
چی رو چک میکنه؟درست جواب دادکجا اشتباه کرد؟
ایمیل۳۱ از ۴۶نقطه اول یا آخر اسم، دو تا نقطه پشت هم، خط تیره اول دامنه، Enter آخر متن
آدرس سایت۳۴ از ۴۶دو تا نقطه پشت هم، _ توی دامنه، خط تیره اول دامنه، پورت بزرگ‌تر از ۶۵۵۳۵
IP۴۴ از ۴۶Enter آخر متن، ارقام فارسی
شماره موبایل۴۳ از ۴۶ارقام فارسی، Enter آخر متن
کد ملی۲۶ از ۴۰رقم کنترل رو چک نمیکنه، ۱۰ تا رقم یکسان (مثل 1111111111)، ارقام فارسی، Enter آخر متن
تاریخ میلادی۳۸ از ۴۶روز هایی که وجود ندارن، مثل ۳۰ فوریه یا ۳۱ آوریل
تاریخ شمسی۳۸ از ۴۶روز ۳۱ برای مهر تا اسفند، ۳۰ اسفند توی سال غیر کبیسه
کد رنگ۴۳ از ۴۴Enter آخر متن
UUID۴۱ از ۴۲Enter آخر متن
ساعت (HH:MM)۴۳ از ۴۴Enter آخر متن
رمز عبور قوی۴۱ از ۴۴ارقام فارسی، Enter آخر متن
متن فقط فارسی۳۲ از ۴۴هر متنی که نیم‌فاصله داشت

برای پیدا کردن توی پیام هم از re.search استفاده کردم، یعنی کافیه یه جای پیام با الگو جور باشه. الگو ها هم تقریبا همونان، فقط ^ و $ ندارن :

[15]:
detect = {
    "mobile": r'(?:\+98|0098|0)?9\d{9}',
    "email":  r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}',
    "link":   r'https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&\/=]*)',
}

اینم نسخه «با پیش‌پردازش». اول پیام از یکی از این سه تا تابع رد میشه و بعد regex روش اجرا میشه. تابع ها و regex های این بخش رو خودم برای همین آزمون نوشتم :

[16]:
def normalize_phone(s):
    s = s.translate(str.maketrans('۰۱۲۳۴۵۶۷۸۹٠١٢٣٤٥٦٧٨٩', '01234567890123456789'))
    return re.sub(r'(?<=\d)[\s\-.\u200c()]+(?=\d)', '', s)

def normalize_email(s):
    s = re.sub(r'\s*[\[({]\s*at\s*[\])}]\s*|\s+at\s+', '@', s, flags=re.I)
    return re.sub(r'\s*[\[({]\s*dot\s*[\])}]\s*|\s+dot\s+', '.', s, flags=re.I)

def normalize_link(s):
    s = normalize_email(s)
    return re.sub(r'\s*[\[({]\s*(?:dot|\.)\s*[\])}]\s*|\s+dot\s+', '.', s, flags=re.I)

detect_norm = {
    "mobile": (normalize_phone, r'(?<!\d)(?:\+98|0098|0)?9[0-9]{9}(?!\d)'),
    "email":  (normalize_email, r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}'),
    "link":   (normalize_link,  r'(?i)(?:https?://|www\.)\S+|(?<![@\w.])[a-z0-9-]+(?:\.[a-z0-9-]+)*\.(?:com|ir|net|org|me|io|ly|al|co|app|info|xyz)\b(?:/\S*)?'),
}

دو تا نکته ریز داره. توی موبایل، (?<!\d) و (?!\d) یعنی درست قبل و بعد شماره نباید رقم دیگه‌ای باشه. همین باعث شد دیگه وسط کد پیگیری ۱۴ رقمی شماره پیدا نکنه. لینک رو هم اینجوری عوض کردم که لینک بدون http:// رو هم پیدا کنه، ولی فقط اگه آخرش یکی از پسوند های این لیست باشه (مثل .com و .ir). جدول زیر نشون میده هر کدوم از ۴۴ تا پیام، چند تا رو درست گفتن :

Out[17]:
regex رایجregex با پیش‌پردازش
شماره موبایل۳۲ از ۴۴۴۲ از ۴۴
ایمیل۴۰ از ۴۴۴۳ از ۴۴
لینک۳۷ از ۴۴۴۳ از ۴۴

اون ۴ تایی هم که با پیش‌پردازش باز درست نشد، همونایی بودن که شماره یا ایمیل یا لینک رو با حروف نوشته بودن. مثل «صفر نهصد و دوازده...» یا «ات جیمیل دات کام». اینا رو دیگه regex به این سادگی نمی‌بینه 🙂

[18]:
nb.neighbours()
Out[18]:
titledate
prevیک چالش وب‌اسکرپینگ که خودش خودش را می‌شکند۱۴۰۵/۰۵
nextبلندگوی مدرسه تا کجا صداش میاد؟!۱۴۰۵/۰۷