<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title>نوشته های امیر محمدی</title>
        <link>https://virgool.io/feed/@amiremohamadi</link>
        <description>مطالبی که می‌خوانید حاصل ذهن مغشوش یک دانشجوی کامپیوتر بوده و مسئولیت هرگونه خطای احتمالی به عهده ساکنین سیاره &quot;کپلر ۶۹ سی&quot; می باشد!</description>
        <language>fa</language>
        <pubDate>2026-07-06 12:50:00</pubDate>
        <image>
            <url>https://files.virgool.io/upload/users/19624/avatar/ocMpfj.png?height=120&amp;width=120</url>
            <title>امیر محمدی</title>
            <link>https://virgool.io/@amiremohamadi</link>
        </image>

                    <item>
                <title>حل مساله اول اویلر با اسمبلی و gdb</title>
                <link>https://virgool.io/@amiremohamadi/%D8%AD%D9%84-%D9%85%D8%B3%D8%A7%D9%84%D9%87-%D8%A7%D9%88%D9%84-%D8%A7%D9%88%DB%8C%D9%84%D8%B1-%D8%A8%D8%A7-%D8%A7%D8%B3%D9%85%D8%A8%D9%84%DB%8C-%D9%88-gdb-s35c1spr3ksf</link>
                <description>مدتی قبل با «مرحله ۳» آشنا شدم که ترجمه‌ای فارسی از Project Euler بود. بهانه خوبی به نظرم اومد تا دوباره نگاهی به سوالاتش بندازم.در قدم اول سعی می‌کنم یک برنامه کوچک اسمبلی بنویسم.section .text
section .data
section .bssدر حال حاضر نیازی به دیتا ندارم پس می‌شه از .data و .bss چشم‌پوشی کرد. فایل بالا رو می‌تونم به این شکل اسمبل کنم:$ nasm -felf64 problem1.asm -o problem1.oنگاهی به فایل خروجی بندازیم:$ file problem1.o
problem1.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not strippedبرای اجرای برنامه لازمه فایل بالا لینک بشه. از gnu linker استفاده می‌کنم:$ ld -m elf_x86_64 problem1.o -o problem1
ld: warning: cannot find entry symbol _start; defaulting to 0000000000401000لینکرها برای پیدا کردن آدرس instructionها و اجراشون از لیبل‌های مشخص کمک می‌گیرند (یا بطور پیش‌فرض از آدرسی خاص شروع به اجرا می‌کنند). پس به این شکل:section .text
global _start

_start:
    nopیا بطور مشابه می‌تونستیم با gcc هم خروجی اسمبلر رو لینک کنیم. gcc بطور پیش‌فرض دستورات رو از لیبل main شروع به خوندن می‌کنه. پس به این شکل هم می‌تونستیم فایل زیر رو داشته باشیم:section .text
global main

main:
    nopیک Makefile ساده می‌نویسم تا فرایند اسمبل و لینک کردن رو راحت‌تر کنم:problem1: problem1.o
        ld -m elf_x86_64 problem1.o -o problem1

problem1.o: problem1.asm
        nasm -felf64 problem1.asm -o problem1.oاجرای برنامه منجر به خطای زیر می‌شه:$ make &amp;&amp; ./problem1
[1]    365385 segmentation fault (core dumped)  ./problem1هممم! منطقیه که ما می‌دونیم دستورات‌مون کجا تمام شدن؛ سی‌پی‌یو اما بعد از اجرای خط اول، به روند اجرا ادامه می‌ده و رجیستر instruction pointer (ip) رو جلو می‌بره. پس لازمه در نقطه‌ای از برنامه خارج بشیم.بیایین نگاهی بندازیم به لیست syscallهای لینوکس (اینجا)linux syscalls tableهر syscall با یک عدد مشخص شده که با رجیستر rax بهش اشاره می‌کنیم و آرگومان‌های اون به ترتیب روی rdi، rsi و rdx نوشته می‌شه. سخت شد؟ مثال پایین رو ببینیم:mov rax, 3
mov rdi, 666
syscallبه این شکل sys_close رو صدا زدیم (rax = 3) و فایلی با شناسه ۶۶۶ (rdi = 666) رو بستیم.با ایده‌ای مشابه می‌تونم sys_exit رو صدا بزنم و از برنامه خارج بشم:section .text
global _start

_start:
    mov rax, 60
    mov rdi, 69
    syscallکه اگه اجراش کنیم:$ make &amp;&amp; ./problem1اینطوری می‌تونم exit code دستور قبلی رو چک کنم:$ echo $?
69خب! نگاهی به سوال اول بندازیم:marhale3.github.ioبرای شروع سعی می‌کنم از ۱ تا ۱۰ بشمارم. قبلش ساختار کلی یک رجیستر رو در یک سیستم ۶۴ بیتی x86 ببینیم:برای شمارش از ۱ تا ۱۰ یک رجیستر ۸ بیتی هم کافیه. برای شمارش اعداد کمتر از ۱۰۰۰ چطور؟ نه!پس می‌تونم از ۱۶ بیت رجیستر استفاده کنم:section .text
global _start

_start:
+    mov bx, 1
+    loop:
+        inc bx
+        cmp bx, 10
+        jne loop

    mov rax, 60
    mov rdi, 69
    syscallحالا کافیه ببینیم کدوم اعداد به ۳ یا ۵ بخش‌پذیرن.با نگاهی به instruction setهای x86 متوجه می‌شیم که باقی‌مانده تقسیم جفت‌رجیستر DX:AX به یک رجیستر ۱۶ بیتی داخل DX ذخیره می‌شه. می‌تونیم چنین ماکرویی برای محاسبه باقی‌مانده بنویسیم:%macro mod 2
    mov ax, %1
    mov cx, %2
    mov dx, 0
    div cx
%endmacroو اینطوری استفاده‌ش کنیم:section .text
global _start

_start:
     mov bx, 1
     loop:
+       mov cx, 3
+       mod bx, cx
+       cmp dx, 0
+       je _divisible
+
+       mov cx, 5
+       mod bx, cx
+       cmp dx, 0
+       jne _after
+
+ _divisible:
+ _after:
         inc bx
         cmp bx, 10
         jne loop

+ _exit:
        mov rax, 60
        mov rdi, 69
        syscallحالا می‌تونیم اعدادی که بخش‌پذیر بودن رو داخل یک بافر جمع بزنیم و روی exit code خروجی بدیم:section .text
global _start

_start:
     mov bx, 1
     loop:
        mov cx, 3
        mod bx, cx
        cmp dx, 0
        je _divisible
 
        mov cx, 5
        mov bx, cx
        cmp dx, 0
        jne _after
 
  _divisible:
+       add [result], ebx

  _after:
         inc bx
         cmp bx, 10
         jne loop

 _exit:
        mov rax, 60
+       mov rdi, [result]
        syscall

+ section .data
+       result dd 0با اجرای برنامه به عدد ۲۳ می‌رسیم که خروجی درستیه. روال مشابهی رو برای اعداد ۱ تا ۱۰۰۰ انجام می‌دیم:$ make &amp;&amp; ./problem1
$ echo $?
208به عدد ۲۰۸ می‌رسیم که به نظر خروجی درستی نیست. با کمی جستجو متوجه می‌شیم که exit codeهای استاندارد لینوکس همیشه بین ۰ تا ۲۵۵ هستن.می‌تونیم نتیجه رو مستقیم از مموری بخونیم. با gdb برنامه رو اجرا می‌کنم و روی لِیبل _exit متوقف می‌شم:$ gdb ./problem1
(gdb) break _exit
(gdb) runلِیبل _divisible رو دیس‌اسمبل می‌کنم تا آدرس بافر رو به دست بیارم:(gdb) disass _divisible
Dump of assembler code for function _divisible:
        0x0000000000401032 &lt;+0&gt;:     add    DWORD PTR ds:0x402000,ebxو مقدار داخل بافر رو دامپ می‌کنیم:(gdb) print *0x402000
$1 = 233168</description>
                <category>امیر محمدی</category>
                <author>امیر محمدی</author>
                <pubDate>Tue, 23 Apr 2024 18:53:36 +0330</pubDate>
            </item>
                    <item>
                <title>چگونه با حذف حافظه مشترک، صدها هزار درخواست در ثانیه را پردازش کردیم</title>
                <link>https://tech.arvancloud.ir/حذف-حافظه-مشترک-صدها-هزار-درخواست-g0dfi2bbc7m9</link>
                <description>محصول CDN ابر آروان در هر ثانیه صدها هزار درخواستِ متعلق به ده‌ها هزار دامنه مختلف را دریافت می‌کند و هر کدام از این درخواست‌ها براساس تنظیماتِ دامنه توسط یک یا چند ماژول مختلف پردازش می‌شوند (برای مثال ماژول فایروال، Rate Limit و ...). پردازش این حجم بالا از اطلاعات نیازمند معماری مناسب و راهکارهایی برای بهبود کارایی است.معماری فعلیدر معماری کنونی به تعداد هسته‌های منطقی CPU در سیستم، Nginx Worker داریم که برای استفاده بهینه از منابع و دستیابی به بیشترین بهره‌وری سعی می‌کنیم تا حد امکان مستقل از یکدیگر عمل کنند. متناسب با هر worker یک accept queue مستقل داریم و پردازش هر درخواست از ابتدای برقراری کانکشن تا ارسال جواب به درخواست‌دهنده و بسته شدن، در یک worker صورت می‌گیرد.مکانیزم فعلی nginx به گونه‌ای است که workerها سوکت‌های جداگانه‌ای در حالت SO_REUSEPORT باز می‌کنند؛ به صورت پیش‌فرض در کرنل لینوکس برای هر چهارتایی از IP و Port مبدا و مقصد مقدار Hash محاسبه شده و بر اساس آن پکت‌ها به یکی از این workerها هدایت می‌شوند.چالشماژول‌ها برای پردازش هر درخواست به داده‌هایی نیاز دارند که باید بین تمام یا بخشی از workerها به اشتراک گذاشته شوند. بخش زیادی از این داده‌ها (مانند تنظیمات دامنه‌ها) read-intensive هستند و می‌توان با مکانیزم RCU (Read - Copy - Update) آن‌ها را به‌صورت non-blocking به اشتراک گذاشت. بخش دیگر، داده‌های تغییر پذیرند (مثل شمارنده‌ی ماژول Rate Limit) که در زمان کوتاه بارها و بارها ممکن است مقدارشان تغییر پیدا کند.حافظه مشترک و داده‌های تغییرپذیربرای مثال، فرض کنید کلاینتی با آدرس 192.51.100.1 درخواستی ارسال کند؛ درخواست‌هایی با پورت‌های مختلف (رفتار مرورگر) منجر به تولید hashهای متفاوت و درنتیجه هدایت آن درخواست به worker جداگانه‌ای می‌شود. از طرف دیگر، همانطور که بالاتر اشاره شد هر worker یک پراسس جداگانه و شامل تمام ماژول‌ها ست. از آنجایی که هر درخواست به worker جداگانه‌ای هدایت شده نیاز به مکانیزمی برای به اشتراک گذاشتن داده‌های آن‌ها داشتیم (برای مثال در ماژول Rate Limit شمارنده‌هایی بر اساس IP هر درخواست را به اشتراک بگذاریم).تلاش اول؛ اشتراک گذاریِ داده‌هادر ابتدا استفاده از Redis و ابزارهای مشابه برای نگهداری این داده‌ها بررسی شد که با کارایی مورد نظر خیلی فاصله داشت. در واقع نیاز بود تا متناسب با هر درخواست، از Redis سوال بپرسیم و داده مناسب را بگیریم؛ در بهترین حالت با فرض اینکه هر کانکشن تنها یک درخواست داشته باشد، با توجه به ترافیک بالای CDN (۱۰۰ تا ۲۰۰ هزار کانکشن هم‌زمان)، درخواست به نودِ Redis تاخیری در حدود ده‌ها تا صد میلی‌ثانیه به وجود می‌آورد.در قدم بعدی این مساله را با استفاده از یک حافظه مشترک (Shared Memory) حل کردیم؛ نتیجه بسیار بهتر شد اما به اندازه‌ی کافی بهینه نبود. درواقع در لحظات اولیه یک حمله DDoS این داده‌های تغییر پذیر با نرخ بسیار بالایی تغییر می‌کنند، استفاده از lock روی بخشی از مموری در این زمان درخواست‌های سالم را هم تحت تاثیر قرار می‌دهد، که مستقل از شیوه طراحیِ حافظه مشترک (blocking / lock-free) باعث افت کارایی می‌شد.تلاش دوم؛ تغییر معماریاستراتژی کرنل برای توزیع بار  (محاسبه hash از چهارتایی IP و Port مبدا و مقصد) باعث می‌شد در حملات، درخواست‌های مخرب به workerهای مختلف هدایت شوند و lock شدن حافظه مشترک اتفاق بیفتد. پس ایده بعدی تغییر نحوه توزیع بار بود به صورتی که نیاز به هرگونه حافظه مشترک از بین برود و از سخت‌افزار حداکثر بهره را ببریم. امکان تغییر شیوه این توزیع بار با نوشتن یک برنامه eBPF از ورژن ۴.۵ کرنل برای UDP و ۴.۶ برای TCP  ارائه شده.تصمیم گرفتیم پکت‌ها را بر اساس fingerprint دیگری بین workerها توزیع کنیم تا هر کلاینت مشخص همیشه به یکی از آن‌ها متصل شود. حافظه مشترک را در تمام ماژول‌های امنیتی که باید با حملات مقابله کنند حذف و همینطور برای اینکه از سخت‌افزار بیشترین بهره را ببریم، تسک‌های پردازشی را از تسک‌های منطقی جدا کردیم.در nginx تغییراتی انجام دادیم تا برنامه eBPF که برای این کار نوشته بودیم را به سوکت‌های با آپشن SO_REUSEPORT متصل کند.بخشی از برنامه eBPF برای تغییر رفتار پیشفرض کرنل در توزیع ترافیک بین سوکت‌هایک آرایه از سوکت‌های listen شده در reuse_sockmap نگهداری می‌کنیم که هنگام بالا آمدن nginx مقداردهی می‌شود؛ هر درخواست در مرحله socket lookup به این برنامه eBPF می‌رسد و به کمک یک تابع hash به یکی از سوکت‌های آرایه هدایت می‌شود.مقایسه بنچمارک: ۱.داشتن حافظه مشترک ۲.استفاده از لودبالانسر eBPFبا توجه به نمودار بالا که مقایسه‌ای از بنچمارک در دو حالت: ۱. استفاده از لودبالانسر پیش‌فرض کرنل و داشتن حافظه مشترک و ۲. استفاده از لودبالانسر eBPF و تغییر شیوه توزیع بار است، در حالت دوم با افزایش تعداد درخواست‌ها تفاوت محسوسی در Response Time رخ نمی‌دهد.جمع‌بندیبه‌طور کلی اگر سرویس‌دهنده‌ای در ابعاد یک CDN هستید باید در حالت عادی هم استراتژی‌هایی برای توزیع یکنواختِ load روی هسته‌های CPU داشته باشید و موضوع را به طور دائم مونیتور کنید. به این دلیل که سشن‌های درازمدتِ TCP ممکن است بار را از حالت تعادل بسیار دور کنند یا اگر لودبالانسرِ socket یا مدل polling کرنل را patch می‌کنید به شکل مضاعف باید نگران یکنواختی این توزیع باشید و برای سناریوهای مختلف راه‌حل داشته باشید. همچنان که هر کدام از این تغییرات نباید سشن‌های TCP یا پروتکلی مثل QUIC را در آینده مختل کنند.</description>
                <category>امیر محمدی</category>
                <author>امیر محمدی</author>
                <pubDate>Wed, 03 May 2023 15:37:47 +0330</pubDate>
            </item>
                    <item>
                <title>مهندسی معکوس ویروس کرونا (SARS-COV2) | تلاش اول</title>
                <link>https://virgool.io/@amiremohamadi/%D9%85%D9%87%D9%86%D8%AF%D8%B3%DB%8C-%D9%85%D8%B9%DA%A9%D9%88%D8%B3-%D9%88%DB%8C%D8%B1%D9%88%D8%B3-%DA%A9%D8%B1%D9%88%D9%86%D8%A7-sars-cov2-%D8%AA%D9%84%D8%A7%D8%B4-%D8%A7%D9%88%D9%84-pdnesjsemnub</link>
                <description>نکته اولیه:مرکز ملی اطلاعات زیست‌فناوری (NCBI) شروع کرده به منتشر کردن دیتابیس‌هایی مربوط به ژنوم ویروس SARS-COV-2 (مثلا این) و این مطلب فقط مجموعه مطالعاتم برای شناخت بهتر این ویروس در این روزها ست؛ یک جور مستندسازی از مجموعه فعالیت‌هایی که برای شناختش دارم انجام می‌دم؛نه داده کاوی بلدم و نه شناخت قبلی از ساختار ویروس‌ها دارم؛ به پیروی از این پروژه جورج هاتز که کار مشابهی کرده با داده‌ها ور می‌ریم.داده‌های ژنومی:بالاتر گفتم سایت NCBI دیتابیس‌های اطلاعاتی کرونا رو منتشر کرده؛ بذارین یه نمونه از اون‌ها رو ببینیم:https://www.ncbi.nlm.nih.gov/nuccore/MN908947اطلاعات بالا، ژنوم یک نمونه در ووهان چین رو نشون می‌ده.ما توی تئوری اطلاعات، مفهومی داریم به اسم پیچیدگی کولموگروف؛ پیچیدگی کولموگروفِ یک متن، طول کوتاه‌ترین برنامه کامپیوتری هست که اون رو به عنوان خروجی تولید می‌کنه؛ به عبارت ساده‌تر: یک متن، چقدر اطلاعات داره!یک روش ساده برای به دست آوردن تقریبی این مقدار، استفاده از الگوریتم‌های فشرده‌سازیه؛ مثلا اینکه متن رو zip کنیم که حجم نهایی می‌شه میزان اطلاعات خالص.قبل از هر چیزی لازمه که اطلاعاتی که داریم رو به فرم معقول‌تری دربیاریم؛ مثلا یک رشته‌ی کاراکتریِ پشت سر هم (بدون عدد و رقم). من از پایتون استفاده می‌کنم:import re
corona = &#039;&#039;&#039;
         1 attaaaggtt tataccttcc caggtaacaa accaaccaac tttcgatctc ttgtagatct
       61 gttctctaaa cgaactttaa aatctgtgtg gctgtcactc ggctgcatgc ttagtgcact
      121 cacgcagtat aattaataac taattactgt cgttgacagg acacgagtaa ctcgtctatc
      181 ttctgcaggc tgcttacggt ttcgtccgtg ttgcagccga tcatcagcac atctaggttt
      241 cgtccgggtg tgaccgaaag gtaagatgga gagccttgtc cctggtttca acgagaaaac
      301 acacgtccaa ctcagtttgc ctgttttaca ggttcgcgac gtgctcgtac gtggctttgg
      361 agactccgtg gaggaggtct tatcagaggc acgtcaacat cttaaagatg gcacttgtgg
      421 cttagtagaa gttgaaaaag gcgttttgcc tcaacttgaa cagccctatg tgttcatcaa
      ....
&#039;&#039;&#039;

corona = &#039;&#039;.join(re.findall(&#039;[a-zA-Z]+&#039;, corona))خروجی درسته:attaaaggtttataccttcccaggtaacaaaccaaccaactttcgatctcttgtagatctgttctctaaacgaactttaaaatctgtgtggctgtcactcggctgcatgcttagtgcactcacgcagtataattaataactaattactgtcgttgacaggacacgagtaactcgtctatcttctgcaggctgcttacggtttcgtccgtgttgcagccgatcatcagcacatctaggtttcgtccgggtgtgaccgaaaggtaag...بیایید سایزش رو با چیزی که ویکیپدیا گفته مقایسه کنیم:۲۹۹۰۳۲۹۹۰۳پس ادامه بدیم؛ پیچیدگی کولموگروف رو حساب کنیم:import zlib
comp = zlib.compress(corona)
print(len(comp))اررررررور!یه بار دیگه؛ اطلاعات قبل از فشرده‌سازی باید encode می‌شدن:comp = zlib.compress(corona.encode(&#039;utf-8&#039;))
print(len(comp))خروجی می‌گه: 8858یعنی ویروس کرونا حدود ۸ کیلوبایت اطلاعات داره؛ نکته بامزه اینکه sql slammer (کرم کامپیوتری) فقط حدود ۳۷۶ بایت حجم داشت!یک مقایسه:ظاهرا می‌تونستیم دیتای مرتب شده رو هم از NCBI دانلود کنیم؛ بهرحال!اینجا نوشته که شباهت زیادی بین Bat Coronavirus و SARS-COV-2 وجود داره؛ از این لینک می‌شه رشته‌های ژنومی مختلف رو سرچ کرد.https://www.ncbi.nlm.nih.gov/labs/virusدنبال اطلاعات کرونا ویروس‌هایی که بین خفاش، مرغ، سگ و شتر دیده شده بگردیم و با نمونه‌ای که داریم مقایسه کنیم.لازمه یک سری اطلاعات (virus و host) رو به سایت بالاییه بدیم تا دیتا رو پیدا کنه و دانلودش کنیم؛ کرونا ویروس‌ها عضو خانواده Coronaviridae حساب می‌شند. فیلد Virus رو Coronaviridae می‌ذاریم و Host رو bat، chicken، dog و camel.چجوری مقایسه‌شون کنیم؟! |:فاصله لون‌اشتاین:بله بله! روش‌های بهتری هم برای مقایسه رشته‌های ژنتیکی وجود داره، بذارین فعلا از همین استفاده کنم.فرض کنید دوتا رشته داشته باشیم؛ مثلاs1 = &#039;amir&#039;
s2 = &#039;mina&#039;کمترین تعداد تغییراتی که می‌تونه s1 رو به s2 تبدیل کنه، فاصله لون‌اشتاین اونهاست. می‌شه گفت: اندازه‌گیری فرق بین دو تا رشته؛فاصله لون‌اشتاین، با حضور خودم در پس‌زمینه D:من چهار تا فایل دارم با یک عالمه رشته ژنتیکی از یک سری جونوَر؛ یه نگاه به این فایل بندازیم:هر فایل، یک سری رشته داره که خط اولِ هر کدوم اینجوری شروع شده:&gt; blah blah blah |blah blah blah blah blah| blah blah blahتوی قدم اول، فایل رو باز می‌کنیم و نمونه‌ها رو توی لیست نگه می‌داریم:file = open(&#039;bat.fasta&#039;, &#039;r&#039;)
lines = file.read().split(&#039;\n&#039;)

current_sequence = &#039;&#039;
genomes = []
for line in lines[1:]:
    # ignore first lines
    if line[0] == &#039;&gt;&#039;:
        genomes.append(current_sequence.lower())
        current_sequence = &#039;&#039;
        continue
    # append lines to current sequence until end of the sequence
    current_sequence += lineحالا هر کدوم از اونها رو با رشته‌ای که داشتیم مقایسه می‌کنیم و میانگین می‌گیریم:from editdistance import distance # install with pip install editdistance

avg = 0
for genome in genomes:
    avg += distance(corona, genome)

print(avg / len(genomes))همین روند رو برای هر چهار تا فایلی که داریم انجام می‌دیم و نتیجه:ظاهرا شبیه‌ترین کرونا ویروسی که بین این حیوون‌ها وجود داشته نسبت به نمونه‌ای که داریم، bat coronavirus هست؛ همین مقایسه با انفولانزای A و B هم نشون می‌ده نمونه‌ای که داریم از نظر ساختاری با آنفولانزا کلی تفاوت داره؛ کرونا، آنفولانزا نیست.کمی عمیق‌تر:ویکی‌پدیا می‌گه: بیان ژن (gene expression) فرایندی است که در آن اطلاعات درون ژن استفاده می‌شود تا یک محصول کاربردی از آن بدست آید. محصول ژن‌ها عمدتاً آمینو اسیدی هستند.مراحل مختلفی را می‌توان برای فرایند بیان ژن در نظر گرفت که عموماً شامل رونویسی، اتصال RNA، ترجمه و تغییرات بعد از ترجمه یک پروتئین می‌باشد.از آنجا که تمام سلول‌های بدن ما از یک سلول مشتق شده‌اند تفاوت‌ها و تمایزات بین سلول‌ها حاصل از بیان شدن یا نشدن قسمت‌هایی از ژن است.تا جایی که متوجه شدم یک رشته DNA توی دو مرحله به پروتئین تبدیل می‌شه:۱- رونویسی‌ (transcription): اطلاعات خاصی از DNA یک ژن به RNA کپی میشه۲- ترجمه (translation): که دنباله نوکلئیدهای mRNA به دنباله amino acid پروتئین تبدیل می‌شهtaken from www.khanacademy.orgاینجا گفته که:و ظاهرا فقط لازمه که مرحله ۲ (ترجمه یا translation) رو انجام بدیم.بذارین یه خورده دقیق‌تر ببینیم که ترجمه چجوری انجام می‌شه:در واقع یک سری ساختار به اسم ریبوزوم مسئول ساخت پروتئین هستند که mRNA ها رو چک می‌کنه، کدونِ (codon) شروع رو پیدا می‌کنه و کل اون mRNA رو چک می‌کنه (یک کدون در هر لحظه چک می‌شه).یادم رفت بگم! mRNA ساختارش این شکلیه:taken from www.khanacademy.org اگه یک رشته mRNA رو به بخش‌های سه تایی تقسیم کنیم، به هر کدوم از این بخش‌ها می‌گن یک کدون (codon)؛ یک کدون شروعِ رشته رو مشخص می‌کنه و یکی هم آخر رشته رو.ریبوزوم از کدون اول شروع می‌کنه به ترجمه کردن؛ از کجا می‌دونه که هر کُدون رو به چه آمینو اسیدی ترجمه کنه؟! اطراف سلول، یک سری tRNA وجود داره و هر tRNA یک نوع آمینو اسید رو مشخص می‌کنه؛ ریبوزوم کدون‌هایی که می‌خونه رو با هر کدوم از این tRNAها مقایسه می‌کنه و اگه match بود انتخابش می‌کنه؛ دقیقا  مثل اینکه دستورات سه بایتیِ زبان ماشین داشته باشیم!taken from www.khanacademy.org  جدول کدون‌ها رو داریم:https://en.wikipedia.org/wiki/DNA_codon_tableبیایید یک جور دیس‌اسمبلر کوچیک بنویسیم:import re

# genetic code
codon_table = {
    &#039;ttt&#039;: &#039;F&#039;, 
    &#039;ttc&#039;: &#039;F&#039;,
    &#039;tta&#039;: &#039;L&#039;,
    &#039;ttg&#039;: &#039;L&#039;,
    &#039;ctt&#039;: &#039;L&#039;, 
    &#039;ctc&#039;: &#039;L&#039;,
    &#039;cta&#039;: &#039;L&#039;,
    &#039;ctg&#039;: &#039;L&#039;,
    &#039;att&#039;: &#039;I&#039;,
    &#039;atc&#039;: &#039;I&#039;,
    &#039;ata&#039;: &#039;I&#039;,
    &#039;atg&#039;: &#039;M&#039;,
    &#039;gtt&#039;: &#039;V&#039;,
    &#039;gtc&#039;: &#039;V&#039;,
    &#039;gta&#039;: &#039;V&#039;,
    &#039;gtg&#039;: &#039;V&#039;,
    &#039;tct&#039;: &#039;S&#039;,
    &#039;tcc&#039;: &#039;S&#039;,
    &#039;tca&#039;: &#039;S&#039;,
    &#039;tcg&#039;: &#039;S&#039;,
    &#039;cct&#039;: &#039;P&#039;,
    &#039;ccc&#039;: &#039;P&#039;,
    &#039;cca&#039;: &#039;P&#039;,
    &#039;ccg&#039;: &#039;P&#039;,
    &#039;act&#039;: &#039;T&#039;,
    &#039;acc&#039;: &#039;T&#039;,
    &#039;aca&#039;: &#039;T&#039;,
    &#039;acg&#039;: &#039;T&#039;,
    &#039;gct&#039;: &#039;A&#039;,
    &#039;gcc&#039;: &#039;A&#039;,
    &#039;gca&#039;: &#039;A&#039;,
    &#039;gcg&#039;: &#039;A&#039;,
    &#039;tat&#039;: &#039;Y&#039;,
    &#039;tac&#039;: &#039;Y&#039;,
    &#039;taa&#039;: &#039;$&#039;,
    &#039;tag&#039;: &#039;$&#039;,
    &#039;cat&#039;: &#039;H&#039;,
    &#039;cac&#039;: &#039;H&#039;,
    &#039;caa&#039;: &#039;Q&#039;,
    &#039;cag&#039;: &#039;Q&#039;,
    &#039;aat&#039;: &#039;N&#039;,
    &#039;aac&#039;: &#039;N&#039;,
    &#039;aaa&#039;: &#039;K&#039;,
    &#039;aag&#039;: &#039;K&#039;,
    &#039;gat&#039;: &#039;D&#039;,
    &#039;gac&#039;: &#039;D&#039;,
    &#039;gaa&#039;: &#039;E&#039;,
    &#039;gag&#039;: &#039;E&#039;,
    &#039;tgt&#039;: &#039;C&#039;,
    &#039;tgc&#039;: &#039;C&#039;,
    &#039;tga&#039;: &#039;$&#039;,
    &#039;tgg&#039;: &#039;W&#039;,
    &#039;cgt&#039;: &#039;R&#039;,
    &#039;cgc&#039;: &#039;R&#039;,
    &#039;cga&#039;: &#039;R&#039;,
    &#039;cgg&#039;: &#039;R&#039;,
    &#039;agt&#039;: &#039;S&#039;,
    &#039;agc&#039;: &#039;S&#039;,
    &#039;aga&#039;: &#039;R&#039;,
    &#039;agg&#039;: &#039;R&#039;,
    &#039;ggt&#039;: &#039;G&#039;,
    &#039;ggc&#039;: &#039;G&#039;,
    &#039;gga&#039;: &#039;G&#039;,
    &#039;ggg&#039;: &#039;G&#039;,
}

def disassemble(genome):
    global codon_table
    codons = re.findall(&#039;.{1,3}&#039;, genome) # split genome to triplets (codons)
    pteins = &#039;&#039;.join(map(lambda x: codon_table[x], codons))
    return pteinsبعدا این باید بهش اضافه بشه؛ فعلا کار راه اندازه.یه نگاه به این بندازین:اینجا هم اطلاعات کامل‌تر نمونه‌ای که داریم اومده؛ بیایید مثل یک سری فانکشن مدلش کنیم:sars_cov_2 = {}

sars_cov_2[&#039;5utr&#039;] = corona[0:265]
sars_cov_2[&#039;orf1a&#039;] = disassemble(corona[265:13483])
sars_cov_2[&#039;orf1b&#039;] = disassemble(corona[13467:21555])
sars_cov_2[&#039;S&#039;] = disassemble(corona[21562:25384])
sars_cov_2[&#039;orf3a&#039;] = disassemble(corona[25392:26220])
sars_cov_2[&#039;E&#039;] = disassemble(corona[26244:26472])
sars_cov_2[&#039;M&#039;] = disassemble(corona[26522:27191])
sars_cov_2[&#039;orf6&#039;] = disassemble(corona[27201:27387])
sars_cov_2[&#039;orf7a&#039;] = disassemble(corona[27393:27759])
sars_cov_2[&#039;orf7b&#039;] = disassemble(corona[27755:27887])
sars_cov_2[&#039;orf8&#039;] = disassemble(corona[27893:28259])
sars_cov_2[&#039;nucleocapsid_phosphoprotein&#039;] = disassemble(corona[28273:29533])
sars_cov_2[&#039;orf10&#039;] = disassemble(corona[29557:29674])خوبه! حالا ببینیم هر کدوم چیکار می‌کنند.هممم.. طولانی شد... نفس بگیرید، ادامه بدیم D:relax, relax, relaxاگه کرونا رو مثل یک ویروس کامپیوتری تصور کنیم:دوتا orf اول یعنی orf1a و orf1b مثل ماشین کپی یا quine عمل می‌کنندپروتئین S همون چیزی هست که کرونا برای چسبیدن به غشای سلول و نفوذ بهش استفاده می‌کنه؛ مثل اکسپلویتِ کامپیوتری! می‌گن ACE2، نقطه ورود بعضی از ویروس‌های کرونا به سلول‌ و همون چیزیه که S بهش می‌چسبه؛ علت اینکه می‌گفتن داروهای قلبی یا فشار خون روی ویروس تاثیر داره هم ظاهرا کم و زیاد شدن همین ACE2 هست.پوشش ویروسی (M و E) هم یک لایه بیرونی هست که وقتی ویروس وارد سلول شده، اون رو پوشش می‌ده؛ تا جایی که متوجه شدم هم باعث می‌شه که ویروس از دید دستگاه ایمنی بدن مخفی بمونه و هم وظیفه شناسایی و چسبیدن به گیرنده‌های اون سلول رو دارهبا این شرایط، بد نیست به این دو موضوع فکر کنیم:چجوری اکسپلویت رو از کار بندازیم؛ یا اینکه سلول رو در برابرش ایمن کنیمچجوری ماشین کپی رو متوقف کنیمقدم‌های بعدی:لازمه درک بهتر این ویروس، شناخت دقیقتر اجزای تشکیل دهنده‌ اونه؛ تلاش بعدی می‌تونه شناخت ساختار و عملکرد پروتئین‌های سازنده، استفاده از فانکشن‌هایی که نوشتیم و رسم نمودارهاشون باشه.همونطور که گفتم، این‌ها صرفا تلاشم برای درک بهتر ویروس کرونا ست و هدف دیگه‌ای نداره؛ اگه حس کنم چیزی که نوشتم مفید بوده ادامه‌ اون رو هم اینجا منتشر می‌کنم (:مرتبط:https://en.wikipedia.org/wiki/Severe_acute_respiratory_syndrome_coronavirus_2https://siasky.net/bACLKGmcmX4NCp47WwOOJf0lU666VLeT5HRWpWVtqZPjEAhttps://www.chaibio.com/coronavirushttps://en.wikipedia.org/wiki/Angiotensin-converting_enzyme_2https://en.wikipedia.org/wiki/Protein_structure_prediction#Secondary_structurehttps://www.ncbi.nlm.nih.gov/pmc/articles/PMC2095096/pdf/JIDMM17330.pdfhttp://www.cmi.ustc.edu.cn/1/3/193.pdf</description>
                <category>امیر محمدی</category>
                <author>امیر محمدی</author>
                <pubDate>Sun, 29 Mar 2020 17:38:41 +0430</pubDate>
            </item>
                    <item>
                <title>دور زدن کپچای سایت سایپا بدون پردازش تصویر</title>
                <link>https://virgool.io/@amiremohamadi/%D8%AF%D9%88%D8%B1-%D8%B2%D8%AF%D9%86-%DA%A9%D9%BE%DA%86%D8%A7%DB%8C-%D8%B3%D8%A7%DB%8C%D8%AA-%D8%B3%D8%A7%DB%8C%D9%BE%D8%A7-%D8%A8%D8%AF%D9%88%D9%86-%D9%BE%D8%B1%D8%AF%D8%A7%D8%B2%D8%B4-%D8%AA%D8%B5%D9%88%DB%8C%D8%B1-sjdy9wfvz3au</link>
                <description>‌‌دور زدن کپچای saipa.irancar.comپیامی داشتم که دوستی می‌گفت دنبال یک اسکریپت ساده هست تا بتونه فیلدهای یک سری فرم، مثل فرم سامانه فروش سایپا رو اتوماتیک پر کنه.خب! راه حل، سرراست و ساده به نظر می‌رسه، به جز کپچای پایین صفحه که همیشه دردسر ساز بوده!توی این پست قصد دارم قدم به قدم، مراحلی که برای دور زدن کپچای سایت https://saipa.iranecar.com انجام دادم رو توضیح بدم.DON&#039;T TRY THIS AT HOME!!! :))تلاش اولبرای شروع کنجکاوی روی کپچا راست کلیک میکنم و inspect element رو میزنم تا ببینم اون پشت چه خبره.You can use Inspect Element to have fun!اتفاق عجیب و شوک کننده اینکه دیتاهای کپچا به صورت inline svg داخل متن html ذخیره شدند!! با کمی دقت متوجه می‌شیم که کل تصویر کپچا به صورت یک سری داده داخل تگهای path هستند.The Path Is Everywhereدر قدم اول، همه داده های مربوط به کد کپچا رو با چند خط کد استخراج می‌کنم. انتخاب من برای این‌کار، پایتون و سلنیوم:from selenium import webdriver
driver = webdriver.Chrome(&#039;/path/to/your/webdriver&#039;)
driver.get(&#039;https://saipa.iranecar.com/registration&#039;)
paths = driver.find_elements_by_tag_name(&#039;path&#039;)مشخصه که در خط دوم، سایت سایپا رو باز می‌کنم و بعد تمام elementهایی که تگ path دارند رو داخل یک لیست نگه می‌دارم.یک مشکل! با رفتن به لینک https://saipa.iranecar.com، مستقیماً صفحه ای که کپچا وجود داره باز نمی‌شه و نیازه تا یک سری کلیک و انتخاب داشته باشیم تا به صفحه مورد نظرمون برسیم.برای جلوگیری از مشکل ساز شدنش، یک ورودی به کد بالا اضافه می‌کنم تا بعد از اینکه به کد کپچا رسیدم و داخل کنسول ENTER زدم برنامه ادامه پیدا کنه:from selenium import webdriver
driver = webdriver.Chrome(&#039;/path/to/your/webdriver&#039;)
driver.get(&#039;https://saipa.iranecar.com/registration&#039;)

# wait for input -&gt; after captcha loaded we press enter to continue
input(&#039;press &amp;quotENTER&amp;quot to see the result&#039;)

paths = driver.find_elements_by_tag_name(&#039;path&#039;)یک نگاه به مقادیر attribute d از هر عنصر paths بندازیم:from selenium import webdriver
driver = webdriver.Chrome(&#039;/path/to/your/webdriver&#039;)
driver.get(&#039;https://saipa.iranecar.com/registration&#039;)

# wait for input -&gt; after captcha loaded we press enter to continue
input(&#039;press &amp;quotENTER&amp;quot to see the result&#039;)

paths = driver.find_elements_by_tag_name(&#039;path&#039;)

for path in paths:
    print(path.get_attribute(&#039;d&#039;)) اوه اوه! شد این:WoOwاین عددهای عجیب و غریب، تصویر svg که به عنوان کپچا میبینیم رو می‌سازند.هنوز بهم ریخته و بدرد نخوره و لیستی که داریم خط خطی های اضافی روی کد کپچا رو هم شامل میشه؛ قدم بعدی اینه که اونها رو هم حذف کنم تا فقط لیستی از چهار رقم اصلی رو داشته باشم. برای اینکار کافیه element هایی که fill=none است رو پردازش نکنم!from selenium import webdriver
driver = webdriver.Chrome(&#039;/path/to/your/webdriver&#039;)
driver.get(&#039;https://saipa.iranecar.com/registration&#039;)

# wait for input -&gt; after captcha loaded we press enter to continue
input(&#039;press &amp;quotENTER&amp;quot to see the result&#039;)

paths = driver.find_elements_by_tag_name(&#039;path&#039;)

for path in paths:
    if path.get_attribute(&#039;fill&#039;) != &#039;none&#039;:
        print(path.get_attribute(&#039;d&#039;))ظاهرا همه چیز روبراهه و فقط باید از لیستی که به دست آوردیم، رقم ها رو یکی یکی تشخیص بدیم؛ ایده من اینه که ظاهرا تمام ۵ ها داده های مشابه دارند، تمام ۲ ها داده های مشابه دارند و …یعنی اگه اطلاعات attribute d از هر رقم (از ۰ تا ۹) رو یک جا ذخیر داشته باشم، با مقایسه attribute d عنصرهای داخل لیستم می‌تونم عدد مربوط بهش رو تشخیص بدم.برای اینکه مطمئن بشم، دریافت تصویر جدید رو اونقدر می‌زنم تا به تصویری برسم که رقم تکراری داشته باشه:7317اینجا دو تا هفت داریم و انتظار من اینه که داده های attribute d شون یکسان باشه؛ دیتای مربوط به اولین هفت:&lt;path fill=&amp;quot#333&amp;quot d=&amp;quotM60.09 27.15L60.12 27.18L60.21 27.26Q60.87 27.13 62.28 26.97L62.38 27.08L62.30 27.00Q62.26 27.64 62.26 28.29L62.30 28.33L62.22 29.50L62.35 29.64Q61.45 29.57 60.61 29.65L60.58 29.62L60.66 29.69Q59.75 29.66 58.92 29.63L58.90 29.61L58.90 29.61Q56.16 35.89 52.74 40.50L52.60 40.37L52.56 40.33Q50.07 40.99 48.77 41.60L48.84 41.67L48.86 41.69Q52.99 36.00 56.04 29.72L55.91 29.59L53.47 29.78L53.31 29.62Q53.44 28.41 53.32 27.08L53.24 27.00L53.19 26.94Q55.13 27.14 57.19 27.14L57.23 27.17L59.08 23.43L59.11 23.46Q60.07 21.53 61.28 19.93L61.39 20.03L61.36 20.01Q59.73 20.09 58.13 20.09L58.12 20.07L58.09 20.04Q52.09 20.14 48.33 17.93L48.25 17.86L47.68 16.22L47.65 16.19Q47.33 15.38 46.95 14.51L46.86 14.41L46.87 14.42Q51.20 17.03 56.91 17.26L56.92 17.28L56.95 17.30Q62.07 17.51 67.21 15.53L67.33 15.66L67.18 15.51Q67.16 16.09 66.66 16.97L66.62 16.92L66.62 16.93Q63.00 21.68 60.15 27.20ZM68.17 18.25L68.21 18.29L69.26 16.33L69.18 16.25Q68.27 16.79 66.60 17.51L66.64 17.55L66.82 17.24L66.78 17.20Q66.86 17.01 66.97 16.89L66.92 16.84L66.93 16.86Q67.43 16.29 68.16 14.96L68.13 14.93L68.07 14.88Q62.75 17.17 57.00 16.94L57.09 17.03L56.90 16.83Q50.91 16.59 46.15 13.62L46.23 13.71L46.24 13.72Q47.23 15.54 48.07 18.21L48.04 18.18L48.14 18.28Q49.12 18.80 49.92 19.10L49.90 19.09L49.91 19.10Q50.12 19.50 50.58 21.37L50.56 21.35L50.63 21.41Q53.67 22.56 59.19 22.41L59.20 22.41L59.37 22.58Q58.88 22.97 56.90 26.74L57.04 26.88L56.99 26.82Q54.92 26.81 52.94 26.62L52.86 26.54L52.88 26.56Q53.02 27.46 53.02 28.33L53.08 28.40L53.13 30.16L54.69 30.05L54.79 31.56L54.67 31.43Q50.70 38.77 47.92 42.35L47.91 42.34L48.05 42.48Q49.63 41.70 51.27 41.20L51.21 41.14L51.21 41.14Q50.63 42.16 49.26 43.88L49.20 43.82L49.25 43.87Q52.37 42.72 54.80 42.49L54.82 42.51L54.79 42.48Q57.67 38.66 60.79 31.88L60.76 31.86L64.36 32.21L64.25 32.11Q64.26 31.28 64.26 30.36L64.21 30.31L64.18 28.49L64.24 28.56Q63.99 28.57 63.44 28.61L63.27 28.44L63.41 28.59Q62.87 28.63 62.60 28.63L62.55 28.57L62.55 28.58Q62.59 28.50 62.63 28.35L62.66 28.39L62.61 28.06L62.60 28.06Q65.09 22.90 68.17 18.25Z&amp;quot&gt;&lt;/path&gt;و دیتای مربوط به دومین هفت:&lt;path fill=&amp;quot#222&amp;quot d=&amp;quotM89.65 27.11L89.77 27.22L89.62 27.07Q90.51 27.17 91.92 27.01L91.96 27.05L91.94 27.04Q91.96 27.75 91.96 28.39L91.80 28.22L91.81 29.49L91.89 29.58Q91.14 29.67 90.31 29.74L90.19 29.63L90.18 29.61Q89.31 29.63 88.48 29.59L88.48 29.59L88.55 29.66Q85.77 35.91 82.35 40.51L82.32 40.48L82.25 40.42Q79.67 41.00 78.38 41.61L78.53 41.76L78.43 41.66Q82.61 36.02 85.66 29.74L85.67 29.75L82.98 29.69L82.96 29.67Q83.06 28.43 82.95 27.10L82.81 26.96L82.90 27.06Q84.69 27.10 86.75 27.10L86.77 27.12L88.68 23.43L88.66 23.41Q89.63 21.49 90.85 19.89L90.91 19.96L90.97 20.01Q89.43 20.19 87.83 20.19L87.68 20.04L87.76 20.11Q81.72 20.16 77.95 17.96L77.97 17.98L77.29 16.24L77.35 16.29Q76.82 15.27 76.44 14.39L76.55 14.50L76.48 14.43Q80.83 17.06 86.54 17.29L86.65 17.41L86.60 17.35Q91.81 17.65 96.95 15.67L96.78 15.50L96.82 15.55Q96.68 16.02 96.19 16.89L96.26 16.97L96.20 16.91Q92.50 21.58 89.64 27.10ZM97.85 18.33L97.85 18.32L98.85 16.32L98.83 16.30Q97.87 16.79 96.20 17.51L96.12 17.44L96.35 17.17L96.44 17.26Q96.38 16.93 96.49 16.82L96.59 16.92L96.62 16.95Q97.02 16.28 97.74 14.95L97.73 14.94L97.60 14.80Q92.41 17.23 86.67 17.00L86.69 17.02L86.64 16.97Q80.53 16.61 75.77 13.64L75.83 13.70L75.83 13.71Q76.91 15.62 77.74 18.28L77.59 18.13L77.77 18.31Q78.72 18.80 79.52 19.10L79.59 19.18L79.50 19.09Q79.70 19.48 80.16 21.35L80.12 21.30L80.12 21.30Q83.33 22.62 88.85 22.46L88.86 22.47L88.78 22.39Q88.49 22.98 86.51 26.75L86.63 26.86L86.63 26.86Q84.45 26.74 82.47 26.55L82.59 26.67L82.46 26.54Q82.68 27.53 82.68 28.40L82.69 28.41L82.64 30.07L84.26 30.02L84.23 31.39L84.41 31.57Q80.38 38.85 77.60 42.43L77.67 42.50L77.54 42.37Q79.29 41.76 80.93 41.27L80.81 41.15L80.90 41.23Q80.12 42.05 78.75 43.77L78.79 43.81L78.78 43.80Q81.92 42.68 84.36 42.45L84.32 42.41L84.42 42.51Q87.35 38.74 90.47 31.96L90.44 31.93L93.83 32.09L93.88 32.14Q93.80 31.22 93.80 30.31L93.84 30.35L93.74 28.46L93.76 28.48Q93.48 28.46 92.93 28.50L93.00 28.58L92.88 28.45Q92.49 28.66 92.23 28.66L92.07 28.50L92.23 28.66Q92.17 28.48 92.20 28.33L92.12 28.25L92.12 27.97L92.19 28.05Q94.77 22.97 97.85 18.33Z&amp;quot&gt;&lt;/path&gt;با یک مقایسه چشمی متوجه می‌شیم که دو مقدار بالا با هم فرق دارند و مقادیرشون یکسان نیست؛ شکست خوردیم!!!!تلاش دومبعد از کمی وَر رفتن با اعداد، نکته عجیبی خودنمایی میکنه! با کد زیر، attribute d از تمام رقم های کپچایی که واسم اومده رو میخونم، اعدادش رو بر اساس اسپیس از هم جدا میکنم، داخل یک لیست می‌ریزم و طول هر لیست رو محاسبه میکنم ( خودم هم نفهمیدم چی گفتم! (: )from selenium import webdriver
driver = webdriver.Chrome(&#039;/path/to/your/webdriver&#039;)
driver.get(&#039;https://saipa.iranecar.com/registration&#039;)

# wait for input -&gt; after captcha loaded we press enter to continue
input(&#039;press &amp;quotENTER&amp;quot to see the result&#039;)

paths = driver.find_elements_by_tag_name(&#039;path&#039;)

for path in paths:
    if path.get_attribute(&#039;fill&#039;) != &#039;none&#039;:
        data = path.get_attribute(&#039;d&#039;).split(&#039; &#039;)
        print(len(data))برای کپچای بالا (7317) نتیجه میشه:210
401
104
210حله (: ظاهرا با وجود اینکه داده های ۱ ها، ۲ ها یا ۳ های مختلف با هم فرق دارند اما همه رقم های مشابه طول یکسان دارند. (توی خروجی بالا برای هر دو تا ۷ طولِ ۲۱۰ به دست اومد)با چند خط کد مشابه و آزمون و خطا یک دیکشنری از ارقام ۰ تا ۹ میسازم که بر اساس len(data) بهم رقم مربوط رو بده:digit = {
    246: 0,
    104: 1,
    264: 2,
    401: 3,
    221: 4,
    269: 5,
    273: 6,
    210: 7,
    352: 8,
    290: 9
}حالا کافیه لیست paths رو پیمایش کنم، attribute d رو براساس اسپیس از هم جدا کنم و سایزش رو به دست بیارم؛ سایز رو به دیکشنری بالا می‌دم و تمام!برای تصویر پایین نتیجه میشه:8927290
352
210
264خب خب خب (: رقم ها رو درست تشخیص دادیم ولی ترتیب‌شون رو نه! (ترتیب اعداد بالا می‌شه: ۹۸۷۲)تلاش سومعبارت اول داخل attribute d از هر رقم، شامل یک M هست به اضافه یک عدد اعشاری! (مثلا: M89.65)M10نکته بامزه اینه که هر چی عدد کوچکتر باشه نشون دهنده این هست که اون رقم زودتر توی کپچا ظاهر شده! به عبارت دیگه اگه لیست رو  بر اساس این عدد مرتب کنم، ترتیب رقم های کپچا درست می‌شه:from selenium import webdriver

digit = {
    246: 0,
    104: 1,
    264: 2,
    401: 3,
    221: 4,
    269: 5,
    273: 6,
    210: 7,
    352: 8,
    290: 9
}

driver = webdriver.Chrome(&#039;/path/to/your/webdriver&#039;)
driver.get(&#039;https://saipa.iranecar.com/registration&#039;)

# wait for input -&gt; after captcha loaded we press enter to continue
input(&#039;press &amp;quotENTER&amp;quot to see the result&#039;)

paths = driver.find_elements_by_tag_name(&#039;path&#039;)

code = []
for path in paths:
    if path.get_attribute(&#039;fill&#039;) != &#039;none&#039;:
        data = path.get_attribute(&#039;d&#039;).split(&#039; &#039;)
        
        location = float(data[0][1:]) # get the number after M
        d = digit[len(data)]
        
        code.append([d, location])
        
real_code = sorted(code, key=lambda x: x[1])
real_code = &#039;&#039;.join(str(x[0]) for x in real_code)
print(real_code)و نتیجه کد با کمی تغییر: https://aparat.com/v/J2v8T هدف از این مطلب، نشون دادن یک مشکل در کپچای سایت سایپا ست که انتظار می‌ره بتونند با یک روش بهتر جایگزینش کنند.</description>
                <category>امیر محمدی</category>
                <author>امیر محمدی</author>
                <pubDate>Mon, 02 Dec 2019 23:12:25 +0330</pubDate>
            </item>
            </channel>
</rss>