CRISPR-Cas9
CRISPR-Cas9
  1. Часть I: Введение и базовый поиск (вы тута)

  2. Часть II: Скоринг, off-target и работа с реальными данными

Как я дошёл до жизни такой?

Несколько лет назад я наткнулся на статью про CRISPR-Cas9 и домашние биолаборатории - люди буквально у себя дома экспериментировали с редактированием генов. Тогда это казалось чем-то из научной фантастики, но мысль засела. Потом появились новости о генной терапии, одобренные препараты (вот FDA, а у нас я только про BIOCAD слышал - СМА и гемофилия B), и тема перестала быть просто хайпом.

Я бэкенд-разработчик, книги по биологии в последний раз открывал в школе, но желание разобраться и посмотреть от этого не меньше. Проблема в том, что большинство материалов по биоинформатике либо требуют серьёзного биобэкграунда, либо сразу кидают в тебя терминами вроде "гомологичная рекомбинация" без объяснений.

Поэтому я решил зайти с другой стороны: взять конкретную задачу и разобраться в биологии ровно настолько, насколько нужно для её решения. А заодно написать инструмент, который можно будет потрогать руками.

Что будем делать?

Напишем CLI-утилиту для поиска потенциальных guide RNA (gRNA) - это такие "наводчики" для CRISPR-Cas9, которые указывают, где именно резать ДНК.

Инструмент будет:

  1. Читать последовательность ДНК из FASTA-файла

  2. Искать все возможные "сайты" (здесь и далее сайт - просто место, участок на молекуле ДНК/РНК) для CRISPR на обеих цепях.

  3. Фильтровать кандидатов по GC-составу и наличию повторов

  4. Выводить результаты в удобном виде

Существуют готовые решения (CRISPOR, Benchling, CRISPRdirect), но наша задача просто понять, как они работают изнутри, а не конкурировать с ними.

P.S. Если вы, как и я, последний раз слышали про ДНК на уроке биологии - не переживайте, разберёмся вместе. Обещаю минимум терминов и максимум кода.

Минимум биологии (правда минимум)

Чтобы писать код, нужно понять несколько базовых вещей. Постараюсь объяснить без занудства.

ДНК - это строчка из 4 букв

ДНК - две цепочки, скрученные в спираль. Каждая цепочка - последовательность из четырёх нуклеотидов: A (аденин), T (тимин), G (гуанин), C (цитозин).

Цепочки комплементарны друг другу, то есть значение в одной цепочке связано со значением в другой на той же позиции, как то: A всегда напротив T, G всегда напротив C. Если одна цепочка AATGC, вторая - TTACG.

Когда записывают последовательность гена, обычно пишут только одну цепочку. Вторую можно вычислить через словарь подстановки (как в шифре Цезаря).

CRISPR-Cas9 - молекулярные ножницы

CRISPR-Cas9 состоит из двух частей:

  • Cas9 - белок, который физически разрезает обе цепи ДНК

  • guide RNA (gRNA) - короткая последовательность (~20 нуклеотидов), которая говорит Cas9, куда идти

gRNA комплементарна целевому участку ДНК. Она "прилипает" к нужному месту, Cas9 режет рядом.

PAM - обязательное условие

Cas9 не может резать где угодно. Ему нужно, чтобы сразу после целевого участка была специальная последовательность - PAM (Protospacer Adjacent Motif).

Для самого популярного Cas9 (из бактерии Streptococcus pyogenes) PAM - это NGG, где N - любой нуклеотид.

То есть структура выглядит так:

[20 нуклеотидов - сюда прилипает gRNA][NGG - PAM]

Нет PAM рядом - Cas9 там работать не будет.

Почему нельзя взять любые 20 букв?

Две причины:

  1. PAM должен быть рядом - не везде есть последовательность NGG в нужном месте

  2. Off-target эффекты - геном человека содержит 3 миллиарда пар нуклеотидов. Последовательность из 20 букв может встретиться не один раз. Если gRNA "прилипнет" не туда - Cas9 разрежет не тот ген

Поэтому при дизайне gRNA важно проверять уникальность последовательности.

Критерии хорошей gRNA

Кроме наличия PAM, есть ещё несколько критериев:

GC-состав - соотношение G и C в последовательности. Оптимально 40-60%. Слишком низкий - gRNA плохо связывается. Слишком высокий - может связываться неспецифично.

Повторы - если в gRNA есть AAAA или TTTT - это плохо. РНК-полимераза может "соскользнуть", gRNA будет синтезироваться с ошибками.

Off-target - нужно проверить, не встречается ли последовательность где-то ещё в геноме. Это самый важный критерий, но и самый сложный - требует поиска по всему геному. В этой части мы его пропустим, вернёмся во второй.

Пишем код

Хватит теории, погнали кодить.

Поиск PAM-сайтов

Начнём с функции, которая находит все позиции PAM (NGG) в последовательности:

def find_ngg_pam(sequence: str) -> list[int]:
    """Находит все позиции PAM (NGG) в последовательности."""
    candidates_positions = []
    last_idx = -1

    while True:
        last_idx = sequence.find('GG', last_idx + 1)

        if last_idx > 0:
            candidates_positions.append(last_idx - 1)
        elif last_idx == -1:
            break
    
    return candidates_positions

Логика простая: ищем GG и берём позицию на один символ раньше (там N, то есть любой из A, C, T, G). Проверка last_idx > 0 отсекает случай, когда GG в самом начале строки, так как перед ним нет нуклеотида.

Извлечение кандидатов на роль gRNA

Теперь для каждого PAM берём 20 нуклеотидов перед ним:

def extract_grna_candidates(sequence: str, pam_positions: list[int]) -> list[dict]:
    """Извлекает кандидатов на gRNA (20 нуклеотидов перед PAM)."""
    grna_candidates = []

    for pam_pos in pam_positions:
        if pam_pos >= 20:
            grna_seq = sequence[pam_pos - 20:pam_pos]
            grna_candidates.append({
                'sequence': grna_seq,
                'pam_position': pam_pos
            })

    return grna_candidates

Если перед PAM меньше 20 нуклеотидов - пропускаем, там не получится полноценная gRNA.

GC-состав

def calculate_gc_content(sequence: str) -> float:
    """Считает процент G и C в последовательности."""
    gc_count = sequence.count('G') + sequence.count('C')
    return (gc_count / len(sequence)) * 100 if len(sequence) > 0 else 0.0

Поиск повторов

def has_poly_repeats(sequence: str, min_length: int = 4) -> bool:
    """Проверяет наличие повторов одного нуклеотида."""
    for base in 'ACGT':
        if base * min_length in sequence:
            return True
    return False

Reverse complement

ДНК двухцепочечная, CRISPR может работать на обеих цепях. Чтобы искать на второй цепи, нужно сделать reverse complement - заменить буквы на комплементарные и перевернуть строку:

def reverse_complement(sequence: str) -> str:
    """Возвращает обратную комплементарную последовательность."""
    complement = str.maketrans('ACGT', 'TGCA')
    return sequence.translate(complement)[::-1]

Собираем всё вместе

def _analyze_sequence(sequence: str, is_forward: bool) -> list[dict]:
    """Анализирует одну цепь."""
    pam_positions = find_ngg_pam(sequence)
    grna_candidates = extract_grna_candidates(sequence, pam_positions)

    analyzed_candidates = []
    for candidate in grna_candidates:
        gc_content = calculate_gc_content(candidate['sequence'])
        poly_repeats = has_poly_repeats(candidate['sequence'])
        
        analyzed_candidates.append({
            'sequence': candidate['sequence'],
            'pam_position': candidate['pam_position'],
            'gc_content': gc_content,
            'has_poly_repeats': poly_repeats,
            'is_forward': is_forward
        })
    
    return analyzed_candidates


def analyze_sequence(sequence: str) -> list[dict]:
    """Анализирует обе цепи ДНК."""
    candidates = []

    candidates.extend(_analyze_sequence(sequence, is_forward=True))
    rev_comp_sequence = reverse_complement(sequence)
    candidates.extend(_analyze_sequence(rev_comp_sequence, is_forward=False))

    return candidates

Фильтрация

def filter_candidates(
    candidates: list[dict], 
    gc_min: float = 40.0, 
    gc_max: float = 60.0, 
    allow_repeats: bool = False
) -> list[dict]:
    """Фильтрует кандидатов по критериям качества."""
    filtered = []
    for candidate in candidates:
        if gc_min <= candidate['gc_content'] <= gc_max:
            if allow_repeats or not candidate['has_poly_repeats']:
                filtered.append(candidate)
    return filtered

CLI-интерфейс

Обернём всё в CLI с помощью typer. Заодно добавим парсер FASTA-файлов:

import typer
from pathlib import Path

app = typer.Typer()


def parse_fasta(file_path: Path) -> dict[str, str]:
    """Парсит FASTA файл, возвращает {название: последовательность}."""
    if not file_path.exists():
        typer.echo(f"Файл не найден: {file_path}")
        raise typer.Exit(code=1)

    raw_data = file_path.read_text()
    results = {}
    sequence = ""
    name = ""

    for line in raw_data.splitlines():
        if line.startswith(">"):
            if sequence and name:
                results[name] = sequence
            name = line[1:].strip()
            sequence = ""
        else:
            sequence += line.strip().upper()

    if sequence and name:
        results[name] = sequence

    return results


@app.command()
def find(
    fasta_path: Path = typer.Argument(..., help="Путь к FASTA файлу"),
    gc_min: float = typer.Option(40.0, help="Минимальный GC-состав"),
    gc_max: float = typer.Option(60.0, help="Максимальный GC-состав"),
    allow_repeats: bool = typer.Option(False, help="Разрешить повторы"),
):
    """Поиск CRISPR gRNA кандидатов в последовательности."""
    sequences = parse_fasta(fasta_path)

    for name, sequence in sequences.items():
        typer.echo(f"\n=== {name} ===")

        candidates = analyze_sequence(sequence)
        filtered = filter_candidates(candidates, gc_min, gc_max, allow_repeats)

        if not filtered:
            typer.echo("Кандидатов не найдено")
            continue

        for c in filtered:
            strand = "+" if c["is_forward"] else "-"
            typer.echo(
                f"  {c['sequence']} | pos: {c['pam_position']} | "
                f"GC: {c['gc_content']:.1f}% | strand: {strand}"
            )


if __name__ == "__main__":
    app()

Проверяем

Создадим тестовый FASTA-файл:

>test_sequence
GCCTACGGATCCTGAAGGCTTAGCCTGAGGAATCG

Запускаем:

python crispr_finder.py test.fasta

Получаем:

=== test_sequence ===
  GATCCTGAAGGCTTAGCCTG | pos: 27 | GC: 55.0% | strand: +
  ATTCCTCAGGCTAAGCCTTC | pos: 22 | GC: 50.0% | strand: -
  GCTAAGCCTTCAGGATCCGT | pos: 31 | GC: 55.0% | strand: -

Один кандидат на прямой цепи, два на обратной. Все в диапазоне 40-60% GC, без повторов.

Что дальше?

Статья получилась не очень большая, так как здесь я говорю только о базовых вещах, поэтому не обессудьте - обещаю, что в следующей части будет интереснее.

В следующей части мы:

  1. Добавим скоринг кандидатов (не все gRNA с хорошим GC одинаково полезны)

  2. Реализуем упрощённую проверку на off-target

  3. Прогоним инструмент на реальном гене из NCBI

Код статьи


P.S. Если вы биоинформатик и видите, что я где-то наврал - буду рад комментариям. Если вы разработчик и впервые слышите про CRISPR - надеюсь, было не слишком больно.

P.P.S Если есть желание предметнее обсудить тему статьи или обложить меня последними словами (по какой бы то ни было причине), то буду рад видеть вас тут: каналья.

Комментарии (7)


  1. Diacut
    04.01.2026 08:55

    Так, в следующей статье будет инструкция, ка всё это применить на практике (кухне, т.е.)? А то упомянутой выше статье уже пять лет. Прогресс есть?


    1. egnodus Автор
      04.01.2026 08:55

      Что будет в следующей части буквально написано в конце этой.

      Если Вы спрашиваете о практическом применении CRISPR/Cas9, то помимо упомянутых в начале одобренных препаратах и текущих исследованиях, есть известный и разобранный, насколько это возможно, случай с CCR5, который будет затронут в следующей части. Но это большая наука, если же Вы хотите именно про кухни, то вот - https://habr.com/ru/companies/madrobots/articles/371015/ или вот https://www.techinsider.ru/science/491092-sverhchelovek-zachem-lyudi-zanimayutsya-biohakingom-doma/.


      1. Diacut
        04.01.2026 08:55

        Ссылки на статьи восьмилетней давности. Про "кухню" я, конечно, погорячился, но хотя бы на уровне провинциальной лаборатории? Типа... пришёл к участковому терапевту, сдал анализы, тот заказал наборчик химикатов, перемешал в пробирке, сделал укольчик и — вуаля! В этом направлении за 5-8 лет что-то подвинулось?


        1. egnodus Автор
          04.01.2026 08:55

          Нет, это очень сложная терапия. Сейчас повысилась доступность в плане того, что увеличилось количество компаний, которые этим занимаются - активно ведут исследования и выводят препараты на рынок (у нас это BIOCAD), и государство стало активнее заходить в эту тему, в качестве примера - https://ria.ru/20251121/meditsina-2056726815.html.


          1. Diacut
            04.01.2026 08:55

            Что-то общее, ни о чём.

            Давайте с другой стороны подойдём.

            Допустим, мы уже определили, что в какой-то хромосоме следует поменять (удалить, добавить) конкретный участок ДНК. На питоне понятно, леххко.

            А на практике? Видимо, надо то, что там на питоне посчитали, синтезировать (ПЦР, всё такое...) .

            А дальше? Короче, в теории всё предельно ясно. Как это делается на практике? Тут полная непонятка. Разве что вирусы упоминаются, посредством которых всё это доставляется к месту назначения. Перед внутренним взором сюрреалистическая картинка возникает вроде этой мадам в очках https://www.sb.by/articles/lechim-geny.html


            1. egnodus Автор
              04.01.2026 08:55

              Есть любительские наборы лабораторные, вот:

              1. https://www.the-odin.com/complete-genetic-engineering-home-lab/ - пример самого набора

              2. https://conductscience.com/diy-home-crispr-comprehensive-guide/ - а вот как раз по DIY CRISPR пошаговая инструукция, если интересно)

              3. https://www.the-odin.com/diyhumancrispr/ - тут от производителя гайд, но менее подробный


              1. Diacut
                04.01.2026 08:55

                Спасибо, прояснилось.