پروژه: ساخت یک ابزار جستجوی ژن در NCBI با Biopython

فهرست مطالب

دنیای زیست‌شناسی مدرن در حال غرق شدن در سیل عظیمی از داده‌هاست. با پیشرفت تکنیک‌های توالی‌یابی نسل جدید (NGS) و سایر روش‌های omics، پایگاه‌های داده عمومی مانند NCBI (National Center for Biotechnology Information) به مخازنی بی‌بدیل از اطلاعات بیولوژیکی تبدیل شده‌اند. این پایگاه‌ها حاوی میلیاردها رکورد از توالی‌های ژنی و پروتئینی، مقالات علمی، ساختارهای سه بعدی و اطلاعات مربوط به بیماری‌ها هستند. اما با این حجم عظیم از داده، چالش اصلی در دسترسی، سازماندهی و استخراج اطلاعات مرتبط نهفته است. محققان و متخصصان بیوانفورماتیک غالباً نیاز دارند تا به سرعت و به طور موثر ژن‌های خاصی را بر اساس معیارهای مختلف جستجو کنند، جزئیات آن‌ها را بازیابی نمایند، و حتی ارتباط آن‌ها را با سایر موجودیت‌ها بررسی کنند.

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

Biopython مجموعه‌ای قدرتمند از ابزارها و کتابخانه‌های پایتون برای محاسبات بیولوژیکی است. این کتابخانه به طور خاص برای ساده‌سازی تعامل با فرمت‌های رایج فایل‌های بیولوژیکی، دسترسی به پایگاه‌های داده آنلاین مانند NCBI و EBI، و اجرای تحلیل‌های بیوانفورماتیکی پیچیده طراحی شده است. یکی از مهم‌ترین ماژول‌های Biopython، Bio.Entrez است که رابطی برنامه‌نویسی برای سیستم Entrez NCBI فراهم می‌کند. Entrez در واقع سیستم جامع جستجو و بازیابی داده‌های NCBI است که دسترسی به پایگاه‌های داده مختلف از جمله PubMed، GenBank، Protein، Nucleotide، Gene و بسیاری دیگر را امکان‌پذیر می‌سازد.

هدف از این پروژه، گام به گام ساخت یک ابزار جستجوی ژن سفارشی با استفاده از Biopython و ماژول Bio.Entrez است. ما به شما نشان خواهیم داد که چگونه می‌توانید با استفاده از پایتون، جستجوهای خودکار و هدفمندی را در پایگاه داده Gene در NCBI انجام دهید، اطلاعات تفصیلی ژن‌ها را بازیابی کنید، و خروجی‌ها را برای تحلیل‌های بعدی پردازش نمایید. این ابزار به شما این امکان را می‌دهد که از محدودیت‌های رابط وب فراتر رفته و گردش کار خود را به طور قابل توجهی تسریع و بهینه سازید. این مقاله برای متخصصان بیوانفورماتیک، زیست‌شناسان محاسباتی، و هر کسی که به دنبال خودکارسازی وظایف جستجوی داده‌های ژنی است، طراحی شده است و فرض بر این است که خواننده آشنایی اولیه با زبان برنامه‌نویسی پایتون دارد.

آشنایی با Biopython و API های Entrez در NCBI

پیش از غواصی در کدنویسی، لازم است درک عمیقی از ابزارهایی که قرار است استفاده کنیم، داشته باشیم. Biopython به عنوان ستون فقرات برنامه‌نویسی ما و سیستم Entrez در NCBI به عنوان منبع داده‌هایمان عمل خواهد کرد.

Biopython: کتابخانه مرجع برای زیست‌شناسان محاسباتی

Biopython یک پروژه جامعه‌محور است که هدف آن ارائه یک مجموعه جامع از ابزارهای پایتون برای بیوانفورماتیک است. این کتابخانه مجموعه‌ای از ماژول‌ها را برای کار با موارد زیر ارائه می‌دهد:

  • توالی‌ها (Sequences): شیء Seq برای کار با توالی‌های DNA، RNA و پروتئین، همراه با ابزارهایی برای دستکاری، ترجمه و رونویسی آن‌ها.
  • فایل‌ها (File Formats): خواندن و نوشتن فرمت‌های رایج بیولوژیکی مانند FASTA، GenBank، PDB و Clustal.
  • رابط با پایگاه‌های داده (Database Interfaces): ماژول‌هایی مانند Bio.Entrez برای NCBI، Bio.ExPASy برای ExPASy و Bio.PDB برای بانک داده پروتئین.
  • الگوریتم‌های بیوانفورماتیک (Bioinformatics Algorithms): ابزارهایی برای هم‌ترازی توالی‌ها (Sequence Alignment)، فیلوژنتیک (Phylogenetics)، مدل‌سازی ساختاری (Structural Modeling) و غیره.

در این پروژه، تمرکز اصلی ما بر روی ماژول Bio.Entrez خواهد بود.

سیستم Entrez و API های NCBI

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

  • PubMed: مقالات علمی و چکیده‌ها.
  • Gene: اطلاعات جامع در مورد ژن‌ها.
  • Nucleotide: توالی‌های DNA و RNA (GenBank، RefSeq، PDB).
  • Protein: توالی‌های پروتئینی.
  • SNP: پلی‌مورفیسم‌های تک‌نوکلئوتیدی.
  • Structure: ساختارهای سه بعدی ماکرومولکول‌ها.
  • و بسیاری دیگر.

Biopython از طریق ماژول Bio.Entrez، توابعی را برای تعامل با Entrez API در اختیار ما قرار می‌دهد. این توابع عبارتند از:

  • EInfo: برای دریافت اطلاعات کلی درباره یک پایگاه داده (تعداد رکوردها، تاریخ آخرین به‌روزرسانی، فیلدهای قابل جستجو).
  • ESearch: برای انجام یک جستجو و دریافت لیستی از شناسه‌های (UIDs) مرتبط با کوئری شما.
  • EFetch: برای بازیابی رکورد کامل بر اساس شناسه‌های به دست آمده از ESearch.
  • ELink: برای یافتن موجودیت‌های مرتبط در پایگاه‌های داده دیگر. به عنوان مثال، یافتن مقالات PubMed مرتبط با یک ژن خاص.
  • EPost: برای ارسال لیستی از شناسه‌ها به سرور Entrez برای استفاده در عملیات بعدی (مثلاً برای EFetch دسته جمعی).
  • ESummary: برای دریافت خلاصه‌ای از اطلاعات برای لیستی از شناسه‌ها، بدون بازیابی رکورد کامل.
  • EGquery: برای اجرای یک کوئری جهانی در چندین پایگاه داده Entrez.

هر درخواست به Entrez API باید شامل یک آدرس ایمیل باشد تا NCBI بتواند در صورت بروز مشکل با شما تماس بگیرد و همچنین برای نظارت بر استفاده از API. استفاده از کلید API (در صورت وجود) نیز توصیه می‌شود تا محدودیت‌های نرخ (rate limits) کاهش یابد و به NCBI در مدیریت ترافیک کمک کند.

پیشنیازها و تنظیمات اولیه: آماده‌سازی محیط توسعه

قبل از اینکه بتوانیم شروع به کدنویسی کنیم، باید مطمئن شویم که محیط توسعه ما به درستی پیکربندی شده است. این بخش به نصب پایتون، Biopython و تنظیمات اولیه برای تعامل با NCBI می‌پردازد.

نصب پایتون

اگر پایتون (نسخه 3.6 یا بالاتر) را روی سیستم خود نصب ندارید، می‌توانید آن را از وب‌سایت رسمی پایتون (python.org) دانلود و نصب کنید. توصیه می‌شود از آخرین نسخه پایدار پایتون 3 استفاده کنید.

پس از نصب، می‌توانید با اجرای دستور زیر در ترمینال یا Command Prompt، از نصب صحیح و نسخه پایتون اطمینان حاصل کنید:

python --version
# یا در برخی سیستم‌ها:
python3 --version

نصب Biopython

ساده‌ترین راه برای نصب Biopython استفاده از pip، مدیر بسته پایتون است:

pip install biopython
# یا در برخی سیستم‌ها:
pip3 install biopython

پس از اتمام نصب، می‌توانید با باز کردن یک مفسر پایتون و تلاش برای وارد کردن ماژول Bio، صحت نصب را بررسی کنید:

import Bio
print(Bio.__version__)

اگر هیچ خطایی دریافت نکردید و شماره نسخه Biopython نمایش داده شد، Biopython با موفقیت نصب شده است.

تنظیم ایمیل و کلید API برای Entrez

همانطور که قبلاً ذکر شد، هر درخواست به Entrez API باید شامل یک آدرس ایمیل باشد. این کار برای کمک به NCBI در ردیابی استفاده از API و تماس با کاربران در صورت نیاز است. Biopython این امکان را فراهم می‌کند که ایمیل خود را به صورت سراسری برای ماژول Bio.Entrez تنظیم کنید.

from Bio import Entrez

# آدرس ایمیل خود را اینجا وارد کنید
# این ایمیل برای NCBI ارسال می شود تا در صورت نیاز با شما تماس بگیرند
Entrez.email = "Your.Email@example.com" 

# در صورت داشتن کلید API از NCBI، آن را نیز تنظیم کنید. این کار به افزایش محدودیت نرخ کمک می کند.
# برای دریافت کلید API، باید یک حساب کاربری در NCBI ایجاد کنید.
# Entrez.api_key = "YOUR_NCBI_API_KEY"

توصیه می‌شود ایمیل واقعی خود را جایگزین “Your.Email@example.com” کنید. برای دریافت کلید API، می‌توانید یک حساب کاربری در NCBI ایجاد کرده و آن را از بخش “API Keys” در داشبورد کاربری خود دریافت کنید. استفاده از کلید API به NCBI کمک می‌کند تا درخواست‌های شما را بهتر مدیریت کند و ممکن است محدودیت‌های نرخ شما را افزایش دهد، به خصوص اگر قصد دارید درخواست‌های زیادی را در مدت زمان کوتاه ارسال کنید.

شروع به کار: جستجوی پایه ژن با ESearch

اکنون که محیط توسعه ما آماده است، می‌توانیم اولین جستجوی ژن خود را با استفاده از Bio.Entrez.ESearch انجام دهیم. هدف این بخش، جستجو برای ژن‌ها بر اساس یک کلمه کلیدی در پایگاه داده Gene NCBI و بازیابی شناسه‌های منحصر به فرد (UIDs) آن‌هاست.

ساختار ESearch

تابع Entrez.esearch() پارامترهای اصلی زیر را می‌پذیرد:

  • db: نام پایگاه داده‌ای که می‌خواهید در آن جستجو کنید. برای این پروژه، ما از "gene" استفاده خواهیم کرد.
  • term: عبارت جستجوی شما. این می‌تواند یک کلمه کلیدی، نام ژن، نماد ژن، یا حتی یک عبارت پیچیده‌تر با عملگرهای بولي (AND, OR, NOT) باشد.
  • retmax: حداکثر تعداد رکوردهایی که می‌خواهید بازیابی کنید. پیش‌فرض معمولاً 20 است. اگر می‌خواهید تعداد بیشتری رکورد دریافت کنید، حتماً این مقدار را تنظیم کنید.
  • retstart: نقطه شروع برای بازیابی رکوردها (برای صفحه‌بندی). پیش‌فرض 0 است.
  • field: برای محدود کردن جستجو به یک فیلد خاص (مثلاً "organism" برای جستجو بر اساس موجود زنده).
  • usehistory: اگر True باشد، نتایج جستجو در یک history سرور NCBI ذخیره می‌شوند و می‌توانند با EFetch بعدی بازیابی شوند. این برای جستجوهای بزرگ و پردازش دسته‌ای مفید است.

اولین جستجوی ما: ژن p53

بیایید با یک جستجوی ساده برای ژن p53 در انسان شروع کنیم. ما از پایگاه داده “gene” استفاده می‌کنیم و به دنبال “p53 human” می‌گردیم.

from Bio import Entrez
import time

# تنظیم ایمیل (همانطور که در بخش قبلی توضیح داده شد)
Entrez.email = "Your.Email@example.com"
# Entrez.api_key = "YOUR_NCBI_API_KEY" # اگر دارید، فعال کنید

def search_gene(query, retmax=10):
    """
    جستجو برای ژن ها در پایگاه داده NCBI Gene.
    :param query: عبارت جستجو (مثلاً "p53 human").
    :param retmax: حداکثر تعداد نتایج برای بازگرداندن.
    :return: لیستی از UIDs (شناسه های NCBI) ژن های یافت شده.
    """
    try:
        # انجام جستجو با ESearch
        handle = Entrez.esearch(db="gene", term=query, retmax=retmax)
        record = Entrez.read(handle) # خواندن XML خروجی و تبدیل به دیکشنری پایتون
        handle.close()

        # استخراج UIDs از رکورد
        gene_ids = record["IdList"]
        
        print(f"جستجو برای '{query}' انجام شد. تعداد نتایج یافت شده: {record['Count']}")
        print(f"تعداد ID های بازیابی شده: {len(gene_ids)}")
        print(f"ID های یافت شده: {gene_ids}")
        
        return gene_ids

    except Exception as e:
        print(f"خطا در هنگام جستجو: {e}")
        return []

# مثال استفاده: جستجو برای ژن p53 در انسان
p53_ids = search_gene("p53 human", retmax=5)
# برای رعایت محدودیت نرخ NCBI، بین درخواست ها مکث کنید
time.sleep(1)

# مثال دیگر: جستجو برای یک ژن دیگر
brca1_ids = search_gene("BRCA1 human", retmax=3)
time.sleep(1)

تحلیل خروجی ESearch

هنگامی که Entrez.esearch() فراخوانی می‌شود، یک “handle” (شیء فایل‌مانند) برمی‌گرداند که شامل پاسخ XML از سرور NCBI است. تابع Entrez.read(handle) این XML را تجزیه کرده و آن را به یک دیکشنری پایتون قابل فهم تبدیل می‌کند. خروجی نمونه از Entrez.read() برای یک جستجوی ESearch به شکل زیر خواهد بود:

 {
    "Count": "25",
    "RetMax": "5",
    "RetStart": "0",
    "IdList": [
        "7157",
        "100289381",
        "100311394",
        "100340326",
        "100860530"
    ],
    "TranslationSet": [],
    "TranslationStack": [
        {
            "Term": "p53[All Fields]",
            "Field": "All Fields",
            "Count": "42217",
            "Explode": "N"
        },
        {
            "Term": "human[All Fields]",
            "Field": "All Fields",
            "Count": "19943236",
            "Explode": "N"
        },
        "AND"
    ],
    "QueryTranslation": "p53[All Fields] AND human[All Fields]"
}

اجزای مهم این دیکشنری عبارتند از:

  • Count: کل تعداد نتایجی که با عبارت جستجوی شما مطابقت دارند (حتی اگر retmax کمتر از این باشد).
  • RetMax: حداکثر تعداد رکوردهای درخواستی.
  • RetStart: نقطه شروع نتایج درخواستی.
  • IdList: مهم‌ترین بخش، لیستی از شناسه‌های منحصر به فرد (UIDs) مربوط به ژن‌های یافت شده است. این شناسه‌ها برای بازیابی اطلاعات تفصیلی ژن‌ها در مرحله بعدی (با EFetch) استفاده خواهند شد.
  • QueryTranslation: عبارت جستجوی ترجمه شده توسط Entrez، که می‌تواند برای اشکال‌زدایی مفید باشد.

با موفقیت در این مرحله، ما لیستی از شناسه‌های ژن داریم که اکنون می‌توانیم از آن‌ها برای بازیابی اطلاعات غنی‌تر استفاده کنیم.

بازیابی اطلاعات تفصیلی ژن با EFetch و تحلیل خروجی XML

پس از به دست آوردن شناسه‌های ژن با ESearch، گام بعدی بازیابی اطلاعات کامل و تفصیلی برای هر یک از این ژن‌هاست. این کار با استفاده از Bio.Entrez.EFetch انجام می‌شود. EFetch به شما امکان می‌دهد تا رکورد کامل را در فرمت‌های مختلف (مانند XML، GenBank یا FASTA) بازیابی کنید.

ساختار EFetch

تابع Entrez.efetch() پارامترهای اصلی زیر را می‌پذیرد:

  • db: نام پایگاه داده (همان "gene").
  • id: یک رشته یا لیستی از رشته‌ها (UIDs) که می‌خواهید اطلاعاتشان را بازیابی کنید. اینها همان UIDs هستند که از ESearch به دست آوردیم.
  • rettype: نوع رکورد برای بازگرداندن. برای پایگاه داده Gene، معمولاً "xml" برای اطلاعات ساختاریافته، یا "gene_pubmed" برای اطلاعات خلاصه‌شده ژن و PubMed مرتبط استفاده می‌شود.
  • retmode: فرمت داده‌های بازگردانده شده. برای rettype="xml"، این معمولاً "xml" است. برای توالی‌ها ممکن است "text" برای FASTA یا GenBank باشد.

بازیابی و تحلیل اطلاعات XML ژن

بیایید از UIDs ژن p53 که قبلاً پیدا کردیم، برای بازیابی اطلاعات تفصیلی آن‌ها استفاده کنیم. ما اطلاعات را در فرمت XML بازیابی می‌کنیم و سپس به صورت برنامه‌نویسی آن را تجزیه (parse) می‌کنیم تا فیلدهای مورد نظر را استخراج نماییم.

from Bio import Entrez
import time

Entrez.email = "Your.Email@example.com"
# Entrez.api_key = "YOUR_NCBI_API_KEY"

def fetch_gene_details(gene_ids):
    """
    بازیابی جزئیات ژن برای لیستی از UIDs از پایگاه داده NCBI Gene.
    :param gene_ids: لیستی از UIDs ژن ها.
    :return: لیستی از دیکشنری ها که هر کدام شامل جزئیات یک ژن هستند.
    """
    if not gene_ids:
        print("لیست شناسه‌های ژن خالی است.")
        return []

    # تبدیل لیست UIDs به یک رشته با کاما برای EFetch
    id_string = ",".join(gene_ids)
    
    try:
        # انجام EFetch برای بازیابی رکوردهای کامل در فرمت XML
        handle = Entrez.efetch(db="gene", id=id_string, rettype="xml", retmode="xml")
        records = Entrez.read(handle) # خواندن و تجزیه XML
        handle.close()

        # رکوردهای Gene در یک لیست به نام "Entrezgene_Set" قرار دارند
        gene_records = records["Entrezgene_Set"]
        
        extracted_details = []
        for gene in gene_records:
            details = {}
            # استخراج اطلاعات کلیدی
            details["GeneID"] = gene["Entrezgene_trackinfo"]["Entrezgene_trackinfo_geneid"]
            
            # نماد ژن و نام
            for gene_name in gene["Entrezgene_gene"]["Gene-ref"]["Gene-ref_locus"]:
                if gene_name:
                    details["Symbol"] = gene_name
                    break
            else:
                 details["Symbol"] = "N/A" # در صورت عدم یافتن نماد

            for gene_desc in gene["Entrezgene_gene"]["Gene-ref"]["Gene-ref_desc"]:
                 if gene_desc:
                    details["Description"] = gene_desc
                    break
            else:
                 details["Description"] = "N/A"

            # نام موجود زنده
            organism_info = gene["Entrezgene_source"]["BioSource"]["BioSource_org"]["Org-ref"]
            details["Organism"] = organism_info["Org-ref_taxname"]
            
            # سایر اسامی (aliases)
            aliases = []
            if "Org-ref_syn" in organism_info:
                aliases.extend(organism_info["Org-ref_syn"])
            details["Aliases"] = aliases

            # کروموزوم و موقعیت (باید با دقت بیشتری بررسی شود زیرا ساختار پیچیده است)
            # این قسمت بسته به ساختار XML می تواند متفاوت باشد و نیاز به تحلیل عمیق تری دارد.
            # به عنوان مثال، برای Chromosome:
            map_location = "N/A"
            for locus in gene["Entrezgene_locus"]:
                if "Gene-commentary_seqs" in locus and locus["Gene-commentary_seqs"]:
                    for seq in locus["Gene-commentary_seqs"]:
                        if "Seq-loc_int" in seq["Seq-loc"]:
                            seq_int = seq["Seq-loc"]["Seq-loc_int"]
                            if "Seq-interval_accession" in seq_int and "Seq-interval_from" in seq_int and "Seq-interval_to" in seq_int:
                                accession = seq_int["Seq-interval_accession"]
                                start = seq_int["Seq-interval_from"]
                                end = seq_int["Seq-interval_to"]
                                map_location = f"{accession}:{start}-{end}" # این یک مثال ساده است
                                break
                    if map_location != "N/A":
                        break
            details["MapLocation"] = map_location


            # لینک به صفحات NCBI
            details["NCBI_Gene_Link"] = f"https://www.ncbi.nlm.nih.gov/gene/{details['GeneID']}"
            
            extracted_details.append(details)
            
        return extracted_details

    except Exception as e:
        print(f"خطا در هنگام بازیابی جزئیات ژن: {e}")
        return []

# مثال استفاده:
# ابتدا با ESearch شناسه‌ها را بدست آورید
p53_ids = search_gene("p53 human", retmax=2)
time.sleep(1) # مکث برای رعایت محدودیت نرخ

# سپس با EFetch جزئیات را استخراج کنید
if p53_ids:
    p53_details = fetch_gene_details(p53_ids)
    for detail in p53_details:
        print("\n--- جزئیات ژن ---")
        for key, value in detail.items():
            print(f"{key}: {value}")
    time.sleep(1)

brca1_ids = search_gene("BRCA1 human", retmax=1)
time.sleep(1)
if brca1_ids:
    brca1_details = fetch_gene_details(brca1_ids)
    for detail in brca1_details:
        print("\n--- جزئیات ژن ---")
        for key, value in detail.items():
            print(f"{key}: {value}")
    time.sleep(1)

تحلیل ساختار XML و استخراج داده‌ها

خروجی EFetch در فرمت XML، به خصوص برای پایگاه داده Gene، می‌تواند بسیار پیچیده و تودرتو باشد. Entrez.read() این XML را به یک ساختار دیکشنری/لیست پایتون تبدیل می‌کند که کار با آن را آسان‌تر می‌کند. برای استخراج اطلاعات خاص، باید با ساختار این دیکشنری آشنا باشید. بخش‌هایی مانند Entrezgene_gene، Entrezgene_source، Entrezgene_summary و Entrezgene_locus حاوی اطلاعات کلیدی هستند.

نکات مهم در مورد ساختار XML و تجزیه آن:

  • لیست‌ها و دیکشنری‌ها: Entrez.read() عناصر تکراری XML را به لیست و عناصر منفرد را به دیکشنری تبدیل می‌کند. باید با دقت ساختار را دنبال کنید.
  • بررسی وجود کلیدها: همیشه قبل از دسترسی به یک کلید در دیکشنری، وجود آن را بررسی کنید تا از خطاهای KeyError جلوگیری شود. برای مثال، if "Org-ref_syn" in organism_info:.
  • تودرتویی: ساختار XML می‌تواند بسیار تودرتو باشد. باید مسیر صحیح را برای رسیدن به داده‌های مورد نظر خود دنبال کنید.
  • انعطاف‌پذیری: ساختار XML NCBI ممکن است در طول زمان تغییر کند. کد شما باید به اندازه کافی انعطاف‌پذیر باشد تا با تغییرات جزئی سازگار شود.

مثال بالا یک نمونه از چگونگی استخراج اطلاعات پایه است. برای استخراج اطلاعات پیچیده‌تر مانند جزئیات عملکردی، مسیرهای بیولوژیکی، یا جزئیات ساختاری، نیاز به کاوش عمیق‌تری در ساختار رکورد XML خواهید داشت.

پیچیده‌تر کردن جستجوها: فیلترینگ و پارامترهای پیشرفته

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

استفاده از عملگرهای بولي (Boolean Operators)

Entrez از عملگرهای بولي استاندارد (AND, OR, NOT) برای ترکیب عبارات جستجو پشتیبانی می‌کند. این عملگرها باید با حروف بزرگ نوشته شوند.

  • AND: نتایجی را برمی‌گرداند که شامل هر دو عبارت باشند.
  • OR: نتایجی را برمی‌گرداند که شامل حداقل یکی از عبارات باشند.
  • NOT: نتایجی را برمی‌گرداند که شامل عبارت اول باشند اما شامل عبارت دوم نباشند.
# جستجو برای ژن های مرتبط با سرطان در انسان
cancer_genes_human = search_gene("cancer AND human", retmax=5)
print(f"ژن های مرتبط با سرطان در انسان: {cancer_genes_human}")
time.sleep(1)

# جستجو برای ژن های مرتبط با p53 یا p63 در موس (mouse)
p53_or_p63_mouse = search_gene("(p53 OR p63) AND mouse", retmax=5)
print(f"ژن های p53 یا p63 در موس: {p53_or_p63_mouse}")
time.sleep(1)

# جستجو برای ژن های p53 که در انسان نیستند
p53_not_human = search_gene("p53 NOT human", retmax=5)
print(f"ژن های p53 (غیر انسانی): {p53_not_human}")
time.sleep(1)

فیلدهای جستجوی خاص

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

  • [Gene Name]: نام کامل ژن.
  • [Gene Symbol]: نماد ژن.
  • [Organism]: نام علمی یا رایج موجود زنده.
  • [Chromosome]: شماره کروموزوم.
  • [Summary]: خلاصه عملکرد ژن.
  • [Filter]: فیلترهای از پیش تعریف شده.
  • [Properties]: ویژگی‌های ژن.

برای استفاده از فیلدها، عبارت جستجوی خود را با نام فیلد در کروشه ([]) دنبال کنید.

# جستجو برای ژن BRCA1 در موجود زنده Homo sapiens
brca1_homo_sapiens = search_gene("BRCA1[Gene Symbol] AND Homo sapiens[Organism]", retmax=1)
print(f"BRCA1 در Homo sapiens: {brca1_homo_sapiens}")
time.sleep(1)

# جستجو برای ژن های روی کروموزوم 17 در انسان
chr17_human_genes = search_gene("Homo sapiens[Organism] AND 17[Chromosome]", retmax=5)
print(f"ژن های کروموزوم 17 در انسان: {chr17_human_genes}")
time.sleep(1)

فیلتر کردن بر اساس تاریخ و سایر پارامترها

Entrez همچنین امکان فیلتر کردن نتایج بر اساس تاریخ انتشار یا اصلاح را فراهم می‌کند. این کار با پارامترهای mindate، maxdate و datetype در ESearch انجام می‌شود. برای پایگاه داده Gene، معمولاً از فیلد PDAT (Publication Date) استفاده می‌شود.

  • mindate: حداقل تاریخ (فرمت YYYY/MM/DD).
  • maxdate: حداکثر تاریخ (فرمت YYYY/MM/DD).
  • datetype: نوع تاریخ برای فیلتر (مثلاً "pdat" برای تاریخ انتشار).
# جستجو برای ژن های جدید مرتبط با دیابت در انسان که پس از 2020 منتشر شده اند
diabetes_genes_recent = Entrez.esearch(
    db="gene",
    term="diabetes AND human",
    mindate="2020/01/01",
    datetype="pdat", # pdat برای تاریخ انتشار است
    retmax=5
)
record_recent = Entrez.read(diabetes_genes_recent)
diabetes_genes_recent.close()
print(f"ژن های دیابت (بعد از 2020): {record_recent['IdList']}")
time.sleep(1)

علاوه بر این، می‌توانید از sort برای مرتب‌سازی نتایج استفاده کنید (مثلاً "relevance"، "publication date").

استفاده از History (تاریخچه جستجو) برای EFetch دسته‌ای

برای جستجوهایی با تعداد نتایج زیاد، به جای اینکه تمام UIDs را در یک رشته طولانی برای EFetch ارسال کنید، می‌توانید از قابلیت history در Entrez استفاده کنید. با تنظیم usehistory=True در ESearch، نتایج جستجو در یک Session در سرور NCBI ذخیره می‌شوند و ESearch به جای UIDs، یک webenv و query_key برمی‌گرداند. سپس می‌توانید از این اطلاعات برای بازیابی نتایج دسته‌ای با EFetch استفاده کنید.

def search_and_fetch_large_set(query, retmax_search=100, batch_size=20):
    """
    جستجو برای تعداد زیادی ژن و بازیابی جزئیات آنها به صورت دسته‌ای با استفاده از history.
    :param query: عبارت جستجو.
    :param retmax_search: حداکثر تعداد نتایج برای جستجو.
    :param batch_size: تعداد رکوردها برای بازیابی در هر EFetch.
    :return: لیستی از دیکشنری ها شامل جزئیات ژن ها.
    """
    try:
        # گام 1: انجام جستجو با usehistory=True
        print(f"در حال جستجو برای '{query}' با استفاده از تاریخچه...")
        search_handle = Entrez.esearch(
            db="gene", 
            term=query, 
            retmax=retmax_search, 
            usehistory="Y" # استفاده از تاریخچه
        )
        search_record = Entrez.read(search_handle)
        search_handle.close()

        count = int(search_record["Count"])
        webenv = search_record["WebEnv"]
        query_key = search_record["QueryKey"]

        print(f"تعداد کل نتایج یافت شده: {count}")
        if count == 0:
            return []

        all_gene_details = []
        for start in range(0, count, batch_size):
            end = min(count, start + batch_size)
            print(f"در حال بازیابی رکوردها از {start + 1} تا {end}...")

            # گام 2: بازیابی جزئیات به صورت دسته‌ای با EFetch و استفاده از webenv و query_key
            fetch_handle = Entrez.efetch(
                db="gene",
                rettype="xml",
                retmode="xml",
                retstart=start,
                retmax=batch_size,
                webenv=webenv,
                query_key=query_key
            )
            records = Entrez.read(fetch_handle)
            fetch_handle.close()

            gene_records = records["Entrezgene_Set"]
            for gene in gene_records:
                details = {}
                details["GeneID"] = gene["Entrezgene_trackinfo"]["Entrezgene_trackinfo_geneid"]
                # افزودن سایر جزئیات مانند Symbol, Description, Organism...
                for gene_name in gene["Entrezgene_gene"]["Gene-ref"]["Gene-ref_locus"]:
                    if gene_name:
                        details["Symbol"] = gene_name
                        break
                else:
                    details["Symbol"] = "N/A"
                
                details["Organism"] = gene["Entrezgene_source"]["BioSource"]["BioSource_org"]["Org-ref"]["Org-ref_taxname"]
                
                all_gene_details.append(details)
            
            time.sleep(0.5) # مکث کوتاه بین درخواست های دسته‌ای

        return all_gene_details

    except Exception as e:
        print(f"خطا در هنگام جستجوی دسته‌ای: {e}")
        return []

# مثال استفاده از جستجوی دسته‌ای
large_query_results = search_and_fetch_large_set("apoptosis human", retmax_search=50, batch_size=10)
print(f"\nتعداد کل ژن های یافت شده و جزئیات استخراج شده: {len(large_query_results)}")
for i, gene in enumerate(large_query_results[:3]): # نمایش 3 تای اول برای نمونه
    print(f"نمونه {i+1}: GeneID={gene['GeneID']}, Symbol={gene['Symbol']}, Organism={gene['Organism']}")
time.sleep(1)

استفاده از history برای جستجوهای بزرگ بسیار کارآمدتر است زیرا به جای ارسال لیست طولانی UIDs در هر درخواست EFetch، فقط به webenv و query_key ارجاع داده می‌شود که سربار (overhead) شبکه را کاهش می‌دهد و احتمال خطاهای مربوط به URLهای طولانی را از بین می‌برد.

یکپارچه‌سازی و توسعه: فراتر از جستجوی پایه

ابزار جستجوی ژن ما تا اینجا قابلیت جستجو و بازیابی جزئیات پایه را دارد. اما Biopython و Entrez API قابلیت‌های بیشتری را ارائه می‌دهند که می‌توانند ابزار ما را بسیار قدرتمندتر کنند. در این بخش، به بررسی امکانات پیشرفته‌تر مانند لینک کردن موجودیت‌ها، بازیابی توالی‌ها، مدیریت خطاها و بهترین روش‌ها می‌پردازیم.

لینک کردن موجودیت‌ها با ELink

یکی از قابلیت‌های قدرتمند Entrez، توانایی لینک کردن موجودیت‌ها بین پایگاه‌های داده مختلف است. Bio.Entrez.ELink به شما امکان می‌دهد تا موجودیت‌های مرتبط را بر اساس یک UID در یک پایگاه داده، در پایگاه داده دیگری پیدا کنید. به عنوان مثال، می‌توانید مقالات PubMed مرتبط با یک ژن خاص را پیدا کنید یا توالی‌های نوکلئوتیدی/پروتئینی مربوط به آن ژن را بازیابی کنید.

تابع Entrez.elink() پارامترهای اصلی زیر را می‌پذیرد:

  • dbfrom: نام پایگاه داده مبدا (مانند "gene").
  • db: نام پایگاه داده مقصد (مانند "pubmed"، "nuccore" برای Nucleotide، "protein").
  • id: یک یا چند UID از پایگاه داده مبدا.
  • term: عبارت جستجوی اضافی (اختیاری).
  • linkname: نوع لینک مورد نظر (مثلاً "gene_pubmed").
def get_related_pubmed_ids(gene_id):
    """
    بازیابی شناسه‌های PubMed مرتبط با یک GeneID.
    :param gene_id: شناسه Gene.
    :return: لیستی از شناسه‌های PubMed.
    """
    try:
        # استفاده از ELink برای یافتن لینک ها از Gene به PubMed
        handle = Entrez.elink(dbfrom="gene", db="pubmed", id=gene_id, linkname="gene_pubmed")
        record = Entrez.read(handle)
        handle.close()

        pubmed_ids = []
        # ساختار خروجی ELink می تواند کمی پیچیده باشد
        # باید به دنبال لیست لینک ها در IdLinkSet باشیم
        if record and record[0]["LinkSetDb"]:
            for link_set_db in record[0]["LinkSetDb"]:
                if link_set_db["DbName"] == "pubmed":
                    pubmed_ids.extend([link["Id"] for link in link_set_db["Link"]])
        return pubmed_ids

    except Exception as e:
        print(f"خطا در هنگام بازیابی مقالات PubMed مرتبط: {e}")
        return []

def get_gene_sequence_accessions(gene_id, seq_type="nuccore"):
    """
    بازیابی شناسه‌های توالی نوکلئوتیدی یا پروتئینی مرتبط با یک GeneID.
    :param gene_id: شناسه Gene.
    :param seq_type: نوع توالی ('nuccore' برای نوکلئوتید، 'protein' برای پروتئین).
    :return: لیستی از شناسه‌های توالی.
    """
    try:
        link_name = f"gene_{seq_type}"
        handle = Entrez.elink(dbfrom="gene", db=seq_type, id=gene_id, linkname=link_name)
        record = Entrez.read(handle)
        handle.close()

        accession_ids = []
        if record and record[0]["LinkSetDb"]:
            for link_set_db in record[0]["LinkSetDb"]:
                if link_set_db["DbName"] == seq_type:
                    accession_ids.extend([link["Id"] for link in link_set_db["Link"]])
        return accession_ids

    except Exception as e:
        print(f"خطا در هنگام بازیابی توالی های مرتبط: {e}")
        return []

# مثال استفاده:
p53_id_example = "7157" # GeneID برای TP53 در انسان

print(f"\nمقالات PubMed مرتبط با ژن {p53_id_example}:")
pubmed_results = get_related_pubmed_ids(p53_id_example)
print(pubmed_results[:5]) # نمایش 5 مورد اول

print(f"\nتوالی های نوکلئوتیدی مرتبط با ژن {p53_id_example}:")
nuccore_accessions = get_gene_sequence_accessions(p53_id_example, seq_type="nuccore")
print(nuccore_accessions[:5]) # نمایش 5 مورد اول

print(f"\nتوالی های پروتئینی مرتبط با ژن {p53_id_example}:")
protein_accessions = get_gene_sequence_accessions(p53_id_example, seq_type="protein")
print(protein_accessions[:5]) # نمایش 5 مورد اول

time.sleep(1)

بازیابی توالی‌ها با EFetch (در فرمت FASTA یا GenBank)

پس از به دست آوردن شناسه‌های توالی (accessions) با ELink، می‌توانید از EFetch برای بازیابی خود توالی‌ها استفاده کنید. برای این کار، db را روی "nuccore" یا "protein" تنظیم کرده و rettype و retmode را به ترتیب روی "fasta" یا "gb" (GenBank) و "text" تنظیم کنید.

from Bio import SeqIO # برای خواندن فرمت FASTA/GenBank
from io import StringIO # برای کار با رشته به عنوان فایل

def fetch_sequence(accession_id, db="nuccore", rettype="fasta"):
    """
    بازیابی یک توالی (نوکلئوتیدی یا پروتئینی) بر اساس شناسه.
    :param accession_id: شناسه توالی (مثلاً RefSeq ID).
    :param db: پایگاه داده ('nuccore' یا 'protein').
    :param rettype: فرمت بازگشتی ('fasta' یا 'gb' برای GenBank).
    :return: شیء SeqRecord Biopython (برای FASTA/GenBank) یا رشته متن.
    """
    try:
        handle = Entrez.efetch(db=db, id=accession_id, rettype=rettype, retmode="text")
        sequence_data = handle.read()
        handle.close()
        
        if rettype == "fasta":
            # استفاده از SeqIO برای تجزیه FASTA
            return list(SeqIO.parse(StringIO(sequence_data), "fasta"))
        elif rettype == "gb":
            # استفاده از SeqIO برای تجزیه GenBank
            return list(SeqIO.parse(StringIO(sequence_data), "genbank"))
        else:
            return sequence_data # برای فرمت های دیگر به صورت رشته

    except Exception as e:
        print(f"خطا در هنگام بازیابی توالی {accession_id}: {e}")
        return None

# مثال استفاده:
if nuccore_accessions:
    first_nuccore_accession = nuccore_accessions[0]
    print(f"\nبازیابی توالی نوکلئوتیدی (FASTA) برای {first_nuccore_accession}:")
    nuccore_seq_records = fetch_sequence(first_nuccore_accession, db="nuccore", rettype="fasta")
    if nuccore_seq_records:
        seq_record = nuccore_seq_records[0]
        print(f"ID: {seq_record.id}")
        print(f"Description: {seq_record.description}")
        print(f"Sequence (first 50 bases): {seq_record.seq[:50]}...")
    time.sleep(1)

if protein_accessions:
    first_protein_accession = protein_accessions[0]
    print(f"\nبازیابی توالی پروتئینی (FASTA) برای {first_protein_accession}:")
    protein_seq_records = fetch_sequence(first_protein_accession, db="protein", rettype="fasta")
    if protein_seq_records:
        seq_record = protein_seq_records[0]
        print(f"ID: {seq_record.id}")
        print(f"Description: {seq_record.description}")
        print(f"Sequence (first 50 amino acids): {seq_record.seq[:50]}...")
    time.sleep(1)

مدیریت خطا و محدودیت‌های نرخ

هنگام تعامل با APIهای خارجی، مدیریت خطا و رعایت محدودیت‌های نرخ (rate limits) بسیار مهم است. NCBI محدودیت‌هایی را برای تعداد درخواست‌هایی که می‌توانید در هر ثانیه ارسال کنید، اعمال می‌کند (معمولاً 3 درخواست در ثانیه بدون کلید API، و 10 درخواست در ثانیه با کلید API). عدم رعایت این محدودیت‌ها می‌تواند منجر به مسدود شدن موقت یا دائم IP شما شود.

  • time.sleep(): همانطور که در مثال‌ها دیدید، استفاده از time.sleep(X) بین درخواست‌ها برای مکث و رعایت محدودیت نرخ ضروری است.
  • try-except: تمام فراخوانی‌های Entrez باید در بلوک try-except قرار گیرند تا خطاهای شبکه، مشکلات سرور یا داده‌های نامعتبر به درستی مدیریت شوند.
  • خطاهای خاص: Entrez ممکن است خطاهای خاصی مانند HTTP Error 429: Too Many Requests را برگرداند. می‌توانید منطق خود را برای مدیریت این خطاها (مثلاً با مکث طولانی‌تر و تلاش مجدد) گسترش دهید.
  • Entrez.email و Entrez.api_key: همیشه این موارد را تنظیم کنید. این کار به NCBI کمک می‌کند تا استفاده شما را مدیریت کند و در صورت لزوم با شما تماس بگیرد.

ذخیره نتایج

پس از بازیابی و پردازش داده‌ها، معمولاً می‌خواهید آن‌ها را ذخیره کنید. می‌توانید داده‌ها را در فرمت‌های مختلفی مانند CSV، JSON یا حتی پایگاه داده‌های رابطه‌ای ذخیره کنید.

import csv
import json

def save_to_csv(data, filename="gene_details.csv"):
    """
    ذخیره لیستی از دیکشنری ها در یک فایل CSV.
    :param data: لیستی از دیکشنری ها.
    :param filename: نام فایل CSV.
    """
    if not data:
        print("داده‌ای برای ذخیره سازی وجود ندارد.")
        return

    # استخراج تمام کلیدها برای سربرگ CSV
    keys = list(data[0].keys())
    with open(filename, 'w', newline='', encoding='utf-8') as output_file:
        dict_writer = csv.DictWriter(output_file, fieldnames=keys)
        dict_writer.writeheader()
        dict_writer.writerows(data)
    print(f"داده ها با موفقیت در {filename} ذخیره شدند.")

def save_to_json(data, filename="gene_details.json"):
    """
    ذخیره لیستی از دیکشنری ها در یک فایل JSON.
    :param data: لیستی از دیکشنری ها.
    :param filename: نام فایل JSON.
    """
    with open(filename, 'w', encoding='utf-8') as output_file:
        json.dump(data, output_file, indent=4, ensure_ascii=False)
    print(f"داده ها با موفقیت در {filename} ذخیره شدند.")

# مثال استفاده (پس از اجرای search_and_fetch_large_set)
# فرض کنید large_query_results پر شده است
if large_query_results:
    save_to_csv(large_query_results, "apoptosis_human_genes.csv")
    save_to_json(large_query_results, "apoptosis_human_genes.json")

ساخت تابع اصلی ابزار و ارائه رابط کاربری ساده

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

طراحی تابع اصلی

تابع اصلی ما باید قابلیت‌های زیر را ارائه دهد:

  1. دریافت عبارت جستجو از کاربر.
  2. اجرای جستجو در Entrez Gene.
  3. بازیابی جزئیات کامل برای ژن‌های یافت شده.
  4. نمایش نتایج به کاربر.
  5. گزینه ای برای ذخیره نتایج.
  6. گزینه ای برای جستجوی مقالات PubMed مرتبط.
  7. گزینه ای برای بازیابی توالی ها.
import time
import csv
import json
from io import StringIO
from Bio import Entrez, SeqIO

# تنظیمات اولیه Entrez
Entrez.email = "Your.Email@example.com"
# Entrez.api_key = "YOUR_NCBI_API_KEY"

def search_gene_ids(query, retmax=10):
    """جستجو برای ژن ها و بازگرداندن UIDs."""
    try:
        handle = Entrez.esearch(db="gene", term=query, retmax=retmax)
        record = Entrez.read(handle)
        handle.close()
        print(f"تعداد کل نتایج یافت شده برای '{query}': {record['Count']}")
        return record["IdList"]
    except Exception as e:
        print(f"خطا در جستجوی ژن: {e}")
        return []

def fetch_gene_details(gene_ids):
    """بازیابی و تجزیه جزئیات کامل ژن."""
    if not gene_ids:
        return []

    id_string = ",".join(gene_ids)
    try:
        handle = Entrez.efetch(db="gene", id=id_string, rettype="xml", retmode="xml")
        records = Entrez.read(handle)
        handle.close()
        
        extracted_details = []
        for gene in records["Entrezgene_Set"]:
            details = {}
            details["GeneID"] = gene["Entrezgene_trackinfo"]["Entrezgene_trackinfo_geneid"]
            
            # استخراج نماد ژن
            for gene_name in gene["Entrezgene_gene"]["Gene-ref"]["Gene-ref_locus"]:
                if gene_name:
                    details["Symbol"] = gene_name
                    break
            else:
                 details["Symbol"] = "N/A"

            # استخراج نام کامل ژن
            for gene_desc in gene["Entrezgene_gene"]["Gene-ref"]["Gene-ref_desc"]:
                 if gene_desc:
                    details["Description"] = gene_desc
                    break
            else:
                 details["Description"] = "N/A"

            # موجود زنده
            details["Organism"] = gene["Entrezgene_source"]["BioSource"]["BioSource_org"]["Org-ref"]["Org-ref_taxname"]
            
            # کروموزوم و موقعیت (ساده سازی شده برای مثال)
            map_location = "N/A"
            for locus in gene.get("Entrezgene_locus", []):
                for seq_commentary in locus.get("Gene-commentary_seqs", []):
                    if "Seq-loc_int" in seq_commentary["Seq-loc"]:
                        seq_int = seq_commentary["Seq-loc"]["Seq-loc_int"]
                        if "Seq-interval_accession" in seq_int and "Seq-interval_from" in seq_int:
                            map_location = f"{seq_int.get('Seq-interval_accession', '')}:{seq_int.get('Seq-interval_from', '')}-{seq_int.get('Seq-interval_to', '')}"
                            break
                if map_location != "N/A":
                    break
            details["MapLocation"] = map_location

            # خلاصه عملکردی
            details["Summary"] = gene.get("Entrezgene_summary", "N/A")

            details["NCBI_Gene_Link"] = f"https://www.ncbi.nlm.nih.gov/gene/{details['GeneID']}"
            
            extracted_details.append(details)
        return extracted_details
    except Exception as e:
        print(f"خطا در بازیابی جزئیات: {e}")
        return []

def get_pubmed_links(gene_id):
    """بازیابی و نمایش لینک های PubMed مرتبط."""
    try:
        handle = Entrez.elink(dbfrom="gene", db="pubmed", id=gene_id, linkname="gene_pubmed")
        record = Entrez.read(handle)
        handle.close()
        pubmed_ids = []
        if record and record[0]["LinkSetDb"]:
            for link_set_db in record[0]["LinkSetDb"]:
                if link_set_db["DbName"] == "pubmed":
                    pubmed_ids.extend([link["Id"] for link in link_set_db["Link"]])
        if pubmed_ids:
            print(f"  -- مقالات PubMed مرتبط ({len(pubmed_ids)}):")
            for i, pub_id in enumerate(pubmed_ids[:5]): # نمایش 5 مورد اول
                print(f"    - https://pubmed.ncbi.nlm.nih.gov/{pub_id}/")
        else:
            print("  -- مقاله PubMed مرتبطی یافت نشد.")
    except Exception as e:
        print(f"خطا در بازیابی لینک های PubMed: {e}")

def get_sequence_info(gene_id, db_type="nuccore"):
    """بازیابی و نمایش اطلاعات توالی (RefSeq ID)."""
    try:
        link_name = f"gene_{db_type}"
        handle = Entrez.elink(dbfrom="gene", db=db_type, id=gene_id, linkname=link_name)
        record = Entrez.read(handle)
        handle.close()

        accession_ids = []
        if record and record[0]["LinkSetDb"]:
            for link_set_db in record[0]["LinkSetDb"]:
                if link_set_db["DbName"] == db_type:
                    accession_ids.extend([link["Id"] for link in link_set_db["Link"]])
        
        if accession_ids:
            print(f"  -- توالی های {db_type.capitalize()} مرتبط ({len(accession_ids)}):")
            for i, acc_id in enumerate(accession_ids[:3]): # نمایش 3 مورد اول
                print(f"    - Accession ID: {acc_id}")
                # اگر بخواهیم خود توالی را نیز دریافت کنیم، باید efetch دیگری بزنیم
                # seq_record = fetch_sequence(acc_id, db=db_type) 
                # if seq_record: print(f"      Sequence (first 20): {str(seq_record[0].seq)[:20]}...")
        else:
            print(f"  -- توالی {db_type.capitalize()} مرتبطی یافت نشد.")
    except Exception as e:
        print(f"خطا در بازیابی توالی های {db_type.capitalize()}: {e}")

def run_gene_search_tool():
    """تابع اصلی برای اجرای ابزار جستجوی ژن."""
    print("--- ابزار جستجوی ژن NCBI با Biopython ---")
    
    query = input("لطفاً عبارت جستجو (مثلاً 'p53 human' یا 'BRCA1 AND mouse') را وارد کنید: ")
    num_results_str = input("حداکثر تعداد نتایج برای نمایش (پیش‌فرض: 10): ")
    num_results = int(num_results_str) if num_results_str.isdigit() else 10

    print(f"\nدر حال جستجو برای '{query}'...")
    gene_ids = search_gene_ids(query, retmax=num_results)
    time.sleep(1) # رعایت محدودیت نرخ

    if not gene_ids:
        print("هیچ ژنی با عبارت جستجوی شما یافت نشد.")
        return

    print(f"\nدر حال بازیابی جزئیات برای {len(gene_ids)} ژن...")
    gene_details_list = fetch_gene_details(gene_ids)
    time.sleep(1)

    if not gene_details_list:
        print("خطا در بازیابی جزئیات ژن ها.")
        return

    print("\n--- نتایج جستجو ---")
    for i, detail in enumerate(gene_details_list):
        print(f"\n{i+1}. GeneID: {detail['GeneID']}")
        print(f"   Symbol: {detail['Symbol']}")
        print(f"   Description: {detail['Description']}")
        print(f"   Organism: {detail['Organism']}")
        print(f"   Map Location: {detail['MapLocation']}")
        print(f"   Summary: {detail['Summary'][:100]}...") # نمایش 100 کاراکتر اول
        print(f"   NCBI Link: {detail['NCBI_Gene_Link']}")
        
        # گزینه های اضافی
        get_pubmed_links(detail['GeneID'])
        time.sleep(0.5)
        get_sequence_info(detail['GeneID'], db_type="nuccore")
        time.sleep(0.5)
        get_sequence_info(detail['GeneID'], db_type="protein")
        time.sleep(0.5)
    
    save_option = input("\nآیا مایلید نتایج را ذخیره کنید (csv/json/no)؟ ").lower()
    if save_option == "csv":
        save_to_csv(gene_details_list, "search_results.csv")
    elif save_option == "json":
        save_to_json(gene_details_list, "search_results.json")
    
    print("\nعملیات جستجو به پایان رسید. از توجه شما متشکریم!")

# برای اجرای ابزار
if __name__ == "__main__":
    run_gene_search_tool()

این تابع run_gene_search_tool() به عنوان نقطه ورودی اصلی ابزار ما عمل می‌کند. این تابع از توابعی که در بخش‌های قبلی توسعه دادیم (مانند search_gene_ids و fetch_gene_details) استفاده می‌کند و آن‌ها را به ترتیبی منطقی فراخوانی می‌کند تا گردش کار جستجوی ژن را برای کاربر نهایی فراهم کند.

توسعه و بهبود رابط کاربری

رابط کاربری ارائه شده در بالا یک رابط خط فرمان (CLI) بسیار ساده است. برای یک ابزار تولیدی، می‌توانید آن را به روش‌های زیر بهبود بخشید:

  • استفاده از کتابخانه‌های CLI: کتابخانه‌هایی مانند argparse برای مدیریت آرگومان‌های خط فرمان پیچیده‌تر، یا Click و Typer برای ساخت CLIهای قدرتمندتر.
  • رابط کاربری گرافیکی (GUI): برای کاربرانی که با خط فرمان راحت نیستند، می‌توان یک رابط کاربری گرافیکی با استفاده از Tkinter، PyQt، Kivy یا فریم‌ورک‌های وب مانند Flask یا Streamlit ایجاد کرد.
  • اعتبارسنجی ورودی: اعتبارسنجی دقیق‌تر ورودی‌های کاربر برای جلوگیری از خطاهای ناخواسته.
  • نمایش پیشرفت: برای جستجوهای بزرگ، نمایش نوار پیشرفت می‌تواند تجربه کاربری را بهبود بخشد.

چالش‌ها، بهترین روش‌ها و ملاحظات آینده

ساخت ابزارهای بیوانفورماتیکی با Biopython و APIهای NCBI نه تنها کارآمد است بلکه با چالش‌هایی نیز همراه است. درک این چالش‌ها و پیاده‌سازی بهترین روش‌ها برای اطمینان از پایداری و کارایی ابزار شما ضروری است.

مدیریت محدودیت‌های نرخ و سرور

  • مکث مناسب: همیشه از time.sleep() بین درخواست‌های API استفاده کنید. محدودیت استاندارد NCBI (بدون API Key) تقریباً 3 درخواست در ثانیه است. با یک کلید API، این محدودیت به 10 درخواست در ثانیه افزایش می‌یابد. به عنوان یک قاعده کلی، بین درخواست‌های متوالی حداقل 0.5 ثانیه مکث کنید و برای درخواست‌های دسته‌ای طولانی‌تر، این زمان را کمی بیشتر کنید.
  • استفاده از Entrez.api_key: این کلید نه تنها محدودیت نرخ شما را افزایش می‌دهد بلکه به NCBI در مدیریت بهتر ترافیک کمک می‌کند.
  • usehistory برای دسته‌های بزرگ: همانطور که دیدیم، استفاده از webenv و query_key در ESearch برای بازیابی نتایج دسته‌ای بسیار کارآمدتر از ارسال لیستی طولانی از IDها در هر EFetch است.
  • پردازش موازی با احتیاط: در صورت نیاز به سرعت بسیار بالا، می‌توانید از پردازش موازی یا چند رشته‌ای استفاده کنید، اما باید دقت کنید که از محدودیت‌های نرخ NCBI تجاوز نکنید. استفاده از صف‌ها (queues) و مدیریت دقیق مکث‌ها در هر رشته ضروری است.

قابلیت اطمینان و مدیریت خطا

  • بلوک‌های try-except: تمام فراخوانی‌های API باید در بلوک‌های try-except محصور شوند تا خطاهای شبکه، زمان‌بندی (timeout) و پاسخ‌های نامعتبر را مدیریت کنند.
  • تلاش مجدد (Retries): برای خطاهای موقت (مانند HTTP 503 Service Unavailable یا 429 Too Many Requests)، پیاده‌سازی مکانیزم تلاش مجدد با مکث افزایشی (exponential backoff) می‌تواند مفید باشد.
  • اعتبارسنجی داده‌ها: همیشه فرض کنید داده‌های دریافتی ممکن است ناقص یا نامعتبر باشند. کدهای تجزیه XML شما باید این موارد را در نظر بگیرند (مثلاً با استفاده از .get() برای دیکشنری‌ها یا بررسی وجود کلیدها).

عملکرد و مقیاس‌پذیری

  • حافظه پنهان (Caching): اگر مکرراً به دنبال اطلاعات یکسان هستید، نتایج را در یک حافظه پنهان محلی ذخیره کنید (مثلاً یک فایل JSON یا یک پایگاه داده SQLite) تا از درخواست‌های تکراری به NCBI جلوگیری شود و سرعت ابزار شما افزایش یابد.
  • فیلتر کردن در سمت سرور: تا حد امکان، از قابلیت‌های فیلترینگ Entrez (مانند فیلدهای جستجو و عملگرهای بولي) در ESearch استفاده کنید تا حجم داده‌های دریافتی و پردازشی در سمت کلاینت کاهش یابد.
  • بازیابی فقط اطلاعات لازم: از EFetch با rettype و retmode مناسب برای بازیابی فقط آن دسته از اطلاعاتی که واقعاً نیاز دارید، استفاده کنید. بازیابی رکورد کامل XML برای هر ژن می‌تواند سربار زیادی داشته باشد اگر فقط به چند فیلد نیاز دارید.

ملاحظات قانونی و اخلاقی

  • شرایط استفاده (Terms of Service) NCBI: همیشه شرایط استفاده از APIهای NCBI را مطالعه و رعایت کنید. این شرایط معمولاً شامل محدودیت‌های نرخ و الزامات اخلاقی برای استفاده از داده‌هاست.
  • حریم خصوصی و داده‌های حساس: اگر ابزار شما با داده‌های ژنتیکی انسانی کار می‌کند، مطمئن شوید که تمام قوانین و مقررات مربوط به حریم خصوصی و داده‌های حساس (مانند HIPAA در ایالات متحده یا GDPR در اروپا) را رعایت می‌کنید.

قابلیت‌های آینده و گسترش ابزار

  • پایگاه‌های داده دیگر: این ابزار را می‌توان به راحتی برای جستجو در پایگاه‌های داده دیگر NCBI (مانلاً PubMed برای مقالات، PDB برای ساختارهای پروتئین) گسترش داد.
  • تحلیل‌های downstream: نتایج استخراج شده می‌توانند به عنوان ورودی برای تحلیل‌های بعدی مانند غنی‌سازی مسیر (pathway enrichment)، تحلیل ژن‌های کاندیدا، یا ساخت شبکه‌های تعامل پروتئین-پروتئین استفاده شوند.
  • ادغام با ابزارهای دیگر: نتایج می‌توانند به فرمت‌های سازگار با سایر ابزارهای بیوانفورماتیکی صادر شوند (مثلاً GFF برای حاشیه‌نویسی ژنوم، VCF برای واریانت‌ها).
  • یادگیری ماشین: داده‌های استخراج شده می‌توانند برای آموزش مدل‌های یادگیری ماشین به منظور پیش‌بینی عملکرد ژن‌ها یا کشف الگوهای جدید استفاده شوند.

با رعایت این بهترین روش‌ها و برنامه‌ریزی برای گسترش‌های آینده، ابزار جستجوی ژن Biopython شما می‌تواند به یک جزء ارزشمند و پایدار در جعبه ابزار بیوانفورماتیک شما تبدیل شود.

“تسلط به برنامه‌نویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT”

قیمت اصلی 2.290.000 ریال بود.قیمت فعلی 1.590.000 ریال است.

"تسلط به برنامه‌نویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT"

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

ویژگی‌های کلیدی:

بدون نیاز به تجربه قبلی برنامه‌نویسی

زیرنویس فارسی با ترجمه حرفه‌ای

۳۰ ٪ تخفیف ویژه برای دانشجویان و دانش آموزان