# Правило обхода

> follow, next, depth, pages, same_site, allow и deny — из чего складывается маршрут

Источник: https://docs.parreq.com/crawl-rules/

Правило обхода отвечает на три вопроса: откуда начать, куда идти и где
остановиться. Всё остальное — параметры страницы, они уходят в `fetch` и
работают ровно так же, как в обычном [Fetch](https://docs.parreq.com/fetch).

| параметр | обязателен | по умолчанию | значение |
|---|---|---|---|
| `url` | да | — | адрес, с которого начинается обход |
| `pages` | да | — | сколько страниц взять, 1…200 (`PARREQ_CRAWL_MAX_PAGES`) |
| `follow` | хотя бы одно из двух | — | CSS или XPath: ссылки вглубь |
| `next` | хотя бы одно из двух | — | CSS или XPath: кнопка следующей страницы |
| `depth` | нет | 1 | потолок глубины для `follow`, 0…5 |
| `same_site` | нет | `true` | не уходить на чужой домен |
| `allow` | нет | — | до 20 регулярных выражений: куда можно |
| `deny` | нет | — | до 20 регулярных выражений: куда нельзя |
| `extract` | нет | — | [разбор каждой страницы](https://docs.parreq.com/crawl-extract) в структуру |
| `fetch` | нет | — | параметры страницы: `device`, `gl`, `wait_for`, `select`… |
| `async` | нет | `false` | вернуть идентификатор, не дожидаясь |

## Разбор настоящего каталога

`books.toscrape.com` — витрина, устроенная как обычный магазин: категория,
пагинация внизу, карточки товаров. Селекторы у неё честные и проверяемые,
поэтому пример дальше можно запустить как есть.

Категория «Mystery» — 32 книги по 20 на страницу, то есть два листинга.
Кнопка пагинации — `li.next a`, ссылка на карточку — `.product_pod h3 a`.

```python Python
res = client.crawl(
    url="https://books.toscrape.com/catalogue/category/books/mystery_3/index.html",
    pages=34,
    next="li.next a",
    follow=".product_pod h3 a",
    depth=1,
)
```

```javascript Node
const res = await client.crawl({
  url: "https://books.toscrape.com/catalogue/category/books/mystery_3/index.html",
  pages: 34,
  next: "li.next a",
  follow: ".product_pod h3 a",
  depth: 1,
});
```

```bash cURL
curl -X POST https://api.parreq.com/v1/crawl \
  -H "Authorization: Bearer pr_ВАШКЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://books.toscrape.com/catalogue/category/books/mystery_3/index.html",
    "pages": 34,
    "next": "li.next a",
    "follow": ".product_pod h3 a",
    "depth": 1
  }'
```

Тридцать четыре — это два листинга плюс тридцать две карточки. Ровно столько
и получится: лишние страницы браться неоткуда, `next` на второй странице
кнопки уже не найдёт, и обход закончится сам, со статусом `done` и
`pages_done: 34`.

## `next`: вдоль пагинации

`next` — селектор **одной** ссылки: той, что ведёт на следующую страницу
списка. Обход нажимает её раз за разом, получая цепочку листингов, и
останавливается, когда селектор перестал совпадать — то есть когда кнопки
больше нет.

Важное свойство: пагинация **не считается углублением**. Двадцатая страница
каталога находится на том же уровне, что и первая, и `depth` её не
ограничивает. Ограничивает только `pages`.

> **Примечание.**
>   Селектор должен указывать на ссылку — элемент с `href`. Кнопка, которая
>   листает скриптом и адрес не меняет, обходу не годится: следующей страницы
>   как адреса просто не существует. Такое место — работа для
>   [Browser API](https://docs.parreq.com/browser-click).

## `follow`: вглубь по карточкам

`follow` — селектор ссылок, которых на странице обычно много. Все совпавшие
адреса становятся кандидатами, каждый — на уровень глубже текущего.

```
depth 0   стартовая страница + вся её пагинация по next
depth 1   карточки, найденные по follow на страницах уровня 0
depth 2   ссылки, найденные по follow уже на карточках
```

`depth: 0` означает «не углубляться вовсе»: `follow` не сработает ни разу, и
обход сведётся к пагинации. `depth: 1` — значение по умолчанию и обычный
случай: листинги и карточки с них. Потолок — `5`; глубже начинается обход
сайта целиком, а это другая задача и другие деньги.

> **Внимание.**
>   Глубина растёт умножением. Каталог по 40 ссылок на страницу при `depth: 2`
>   даёт тысячи адресов — и упрётся не в правило, а в `pages` и в баланс.
>   Ставьте `depth` тот, который вам вправду нужен, а не «с запасом».

## `pages`: где остановиться

`pages` — не «сколько страниц найти», а **потолок**: сколько страниц обход
имеет право пройти. Меньше — бывает: ссылки кончились раньше. Больше — нет
никогда.

Потолок обязателен именно потому, что чужой сайт неизвестной величины: без
него ошибка в селекторе означала бы обход, который идёт, пока не кончатся
деньги. Верхняя граница — 200 страниц за задание (`PARREQ_CRAWL_MAX_PAGES`).

## `same_site`: не уходить с сайта

По умолчанию `true`: обход остаётся на домене начального адреса. Поддомены
считаются своими — `catalog.example.com` и `www.example.com` для
`example.com` не чужие, потому что на живых магазинах карточки и листинги
регулярно разъезжаются по поддоменам.

`same_site: false` снимает ограничение целиком. Осмысленно это ровно тогда,
когда вы идёте по ссылкам **наружу** — например, собираете, куда ведёт
партнёрская выдача, — и обязательно вместе с `allow`: без него обход уйдёт в
рекламу, в соцсети и в счётчики.

## `allow` и `deny`: сузить маршрут

Списки регулярных выражений, до 20 в каждом и до 200 символов каждое,
проверяются по адресу ссылки. Неразбираемое выражение отклоняется сразу,
`400`, а не молча не срабатывает:

- `allow` задан — годится только адрес, совпавший **хотя бы с одним** из
  выражений;
- `deny` задан — адрес, совпавший **хотя бы с одним** выражением,
  отбрасывается.

Оба списка работают вместе с `same_site` и с селекторами, а не вместо них:
адрес должен пройти все проверки, а не любую одну.

```python Python
res = client.crawl(
    url="https://books.toscrape.com/catalogue/category/books/mystery_3/index.html",
    pages=40,
    next="li.next a",
    follow="a",
    allow=[r"/catalogue/[^/]+/index\.html$"],
    deny=[r"/category/", r"\?"],
)
```

```javascript Node
const res = await client.crawl({
  url: "https://books.toscrape.com/catalogue/category/books/mystery_3/index.html",
  pages: 40,
  next: "li.next a",
  follow: "a",
  allow: ["/catalogue/[^/]+/index\\.html$"],
  deny: ["/category/", "\\?"],
});
```

```bash cURL
curl -X POST https://api.parreq.com/v1/crawl \
  -H "Authorization: Bearer pr_ВАШКЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://books.toscrape.com/catalogue/category/books/mystery_3/index.html",
    "pages": 40,
    "next": "li.next a",
    "follow": "a",
    "allow": ["/catalogue/[^/]+/index\\.html$"],
    "deny": ["/category/", "\\?"]
  }'
```

Здесь `follow` намеренно грубый — просто `a`, все ссылки подряд, — а отбор
делают выражения: берём только карточки товара и выбрасываем разделы и
адреса с параметрами. Так удобнее там, где у карточек нет своего класса, но
есть узнаваемый вид адреса.

> **Примечание.**
>   В работу идут первые 20 выражений каждого списка, остальные отбрасываются. А
>   выражение, которое не удалось разобрать, просто не срабатывает: оно ничего не
>   разрешает и ничего не запрещает. Проверяйте свои выражения у себя — молча
>   пропущенное выражение выглядит как «фильтр не работает», и понять это по
>   ответу нельзя.

## `fetch`: как брать каждую страницу

Всё, что вы обычно передаёте в `/v1/fetch`, живёт во вложенном объекте
`fetch` и применяется **к каждой** странице обхода: `device`, `gl`, `hl`,
`include`, `wait_ms`, `wait_for`, `network_ms`, `scroll`, `start_at`,
`select`, `select_all`, `exclude`, `strip`.

```python Python
res = client.crawl(
    url="https://books.toscrape.com/catalogue/category/books/mystery_3/index.html",
    pages=34,
    next="li.next a",
    follow=".product_pod h3 a",
    fetch={
        "device": "mobile",
        "gl": "de",
        "wait_for": ".product_main",
        "select": ".product_main",
        "strip": True,
    },
)
```

```javascript Node
const res = await client.crawl({
  url: "https://books.toscrape.com/catalogue/category/books/mystery_3/index.html",
  pages: 34,
  next: "li.next a",
  follow: ".product_pod h3 a",
  fetch: {
    device: "mobile",
    gl: "de",
    wait_for: ".product_main",
    select: ".product_main",
    strip: true,
  },
});
```

```bash cURL
curl -X POST https://api.parreq.com/v1/crawl \
  -H "Authorization: Bearer pr_ВАШКЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://books.toscrape.com/catalogue/category/books/mystery_3/index.html",
    "pages": 34,
    "next": "li.next a",
    "follow": ".product_pod h3 a",
    "fetch": {"device": "mobile", "gl": "de", "wait_for": ".product_main",
              "select": ".product_main", "strip": true}
  }'
```

> **Внимание.**
>   `select` подрезает разметку **после** того, как обход нашёл на странице
>   ссылки: маршрут строится по целой странице, а обрезка касается только того,
>   что уезжает вам. Пользуйтесь этим: подрезка листинга до блока с карточками
>   уменьшает ответ в разы, а `wait_for` на медленном сайте экономит секунды на
>   каждой из ста страниц.

Платные надстройки в `include` тоже работают — и тоже на каждой странице.
Скриншот на обходе из ста страниц стоит четыреста кредитов сверх обхода, и
это стоит посчитать заранее: [Цена обхода](https://docs.parreq.com/crawl-pricing).

## Эхо правила в ответе

Всё, с чем задание вправду пошло — с подставленными умолчаниями, — приходит
обратно в `crawl_parameters`:

```json
{
  "crawl_parameters": {
    "url": "https://books.toscrape.com/catalogue/category/books/mystery_3/index.html",
    "pages": 34,
    "follow": ".product_pod h3 a",
    "next": "li.next a",
    "depth": 1,
    "same_site": true,
    "allow": [],
    "deny": [],
    "extract": null,
    "fetch": { "device": "desktop", "gl": "us", "hl": "en", "wait_ms": 3000 }
  }
}
```

Это удобно при разборе полётов через сутки: видно не то, что вы собирались
отправить, а то, что задание вправду исполняло.

## Дальше

**[Задания](https://docs.parreq.com/crawl-jobs)**
    Что приходит в ответ, как опрашивать и как отменять.

**[Обход с разбором](https://docs.parreq.com/crawl-extract)**
    Готовые объекты вместо разметки на каждой странице.

**[Тонкая настройка разметки](https://docs.parreq.com/fetch-tuning)**
    Что умеют `select`, `start_at`, `exclude` и `strip`.

**[Справочник](https://docs.parreq.com/api-reference/crawl-post)**
    Все параметры `POST /v1/crawl`.
