Пустой снимок страницы должен приводить к ошибке извлечения. В нашем тесте он дал обеденный набор Mainstays из 5 предметов по цене $149, с рейтингом 4.5 звезды и 120 отзывами. По этому URL Walmart на самом деле продавалась электрическая мультиварка за $59. Выдуманная моделью запись прошла нашу JSON Schema, а встроенный скрейпер Walmart во всех трёх прогонах вернул мультиварку.
Этот пример показывает проблему извлечения структурированных данных: корректно сформированный JSON всё равно может описывать не тот товар. Чтобы измерить разницу, мы проверили три пути на 25 страницах товаров Amazon и 25 страницах Walmart: именованный скрейпер Crawlbase, autoparse=true и Markdown с последующим строгим вызовом GPT-4o-mini по JSON Schema. Каждый путь выполнялся три раза. Мы измеряли охват полей, повторяемость, значения, отсутствующие в полученном тексте, задержку, а также число запросов и токенов модели.
Сопутствующий репозиторий содержит реализацию, девять пошаговых скриптов, офлайн-фикстуры и тесты. Его список URL задаёт кандидатов и резерв, а записанные результаты хранят измерения прогонов от 2 ноября 2026 года.
- Два пути с парсерами прошли схему приложения в 297 из 300 попыток без единого токена модели. Путь Markdown плюс GPT-4o-mini был принят в 130 из 150.
- Строгий структурированный вывод убрал ошибки разбора, но не выдумывание. Модель вернула полный объект на каждую проверку на бота и каждое пустое тело, которые ей передали, и один из этих объектов прошёл схему.
- Сначала попробуйте поддерживаемый парсер, нормализуйте результат, проверьте его по собственному контракту и вызывайте модель только для записей, которые проверку не прошли. Маршрутизатор в репозитории делает ровно это.
Выберите, как страница превращается в JSON
Crawling API поддерживает как получение страниц, так и поддерживаемое извлечение. В сценарии scraper API для Amazon отправка параметра scraper=amazon-product-details возвращает JSON товара. Для Walmart используется scraper=walmart-product-details. С параметром autoparse=true API определяет поддерживаемый тип страницы и применяет соответствующий скрейпер.
Первые пробные запросы вернули один и тот же объект товара через autoparse и через именованный скрейпер. Когда подходящего скрейпера нет, Crawlbase откатывается к generic-extractor, который возвращает общие данные страницы. Это по-прежнему отдельные запросы: в последующем бенчмарке есть расхождения в возвращённых значениях и таймауты. «Детерминированное извлечение» описывает подход к разбору; оно не делает живую страницу магазина или каждый ответ неизменными.
LLM-маршрут получает страницу с format=md, а затем отправляет в OpenAI Markdown, URL товара, инструкции и схему из восьми полей. При успешной попытке без кэша это добавляет к запросу один вызов модели. Обзор извлечения данных с помощью ИИ объясняет этот паттерн «сначала получить, потом извлечь»; здесь мы сравниваем его с поддерживаемыми парсерами товаров, используя для получения данных тот же API извлечения.
Пробный запрос также проверил scraper= вместе с format=md на Amazon. Ответ содержал только JSON скрейпера. Поэтому бенчмарк получает разобранные данные и Markdown отдельными запросами. Это важно при интерпретации его проверок по свидетельствам: модель сверяется с собственным полученным входом, а каждый парсер сверяется с другим снимком того же URL.
Определите одну запись, прежде чем сравнивать экстракторы
Трём путям нужно общее представление, прежде чем их результаты можно будет сравнивать. Общая схема и маппинги задают восемь полей: title, id, brand, price, currency, availability, rating и reviews_count.
Живые ответы скрейперов использовали другие ключи:
| Общее поле | Ответ Amazon | Ответ Walmart |
|---|---|---|
title |
name |
title |
id |
ASIN внутри productInformation, с запасным вариантом из URL в маппере |
itemId |
brand |
brand |
brand |
price |
rawPrice, с откатом на price
|
price |
currency |
currency |
currency |
availability |
inStock |
В записанных ответах поля наличия нет |
rating |
customerReview |
ratings |
reviews_count |
customerReviewCount |
reviewsCount |
Эти наблюдаемые ключи отличались от задокументированных имён, использованных при разработке, поэтому маппинги следуют записанным ответам. Наличие Walmart остаётся null, когда поле запаса не передано. Маппер также обрабатывает outOfStock или availability , если любое из них появляется в ответе.
Извлечение по схеме через LLM использует отдельный контракт вывода. Каждый ключ должен присутствовать, но любое значение может быть JSON null. Промпт требует использовать только значения, напечатанные на странице, а описание каждого поля велит модели возвращать null, если значение отсутствует:
MAX_MARKDOWN_CHARS = 60_000 SYSTEM_PROMPT = ( "You extract one product record from a retail product page converted to Markdown. " "Use only values printed on the page. If a value is not on the page, return null. " "Do not guess, convert, or fill in values from general knowledge." )
LLM-клиент запрашивает строгий структурированный вывод при температуре 0 и отправляет первые 60 000 символов Markdown:
response = self.client.chat.completions.create( model=self.model, temperature=0, messages=[ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": f"URL: {url}\n\n{page}"}, ], response_format={ "type": "json_schema", "json_schema": {"name": "product_v1", "strict": True, "schema": LLM_SCHEMA}, }, )
После извлечения приложение нормализует результат и проверяет его по product.v1. Эта схема приёмки требует заголовок и идентификатор длиной не менее трёх символов, неотрицательные цены и счётчики, рейтинг от 0 до 5 и допустимое значение наличия или null. Числовая цена требует строку валюты из трёх заглавных букв:
PRODUCT_SCHEMA = { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": SCHEMA_VERSION, "type": "object", "required": FIELDS + ["_meta"], "additionalProperties": False, "properties": { "title": {"type": "string", "minLength": 3}, "id": {"type": "string", "minLength": 3}, "brand": {"type": ["string", "null"]}, "price": {"type": ["number", "null"], "minimum": 0}, "currency": {"type": ["string", "null"], "pattern": "^[A-Z]{3}$"}, "availability": {"enum": AVAILABILITY + [None]}, "rating": {"type": ["number", "null"], "minimum": 0, "maximum": 5}, "reviews_count": {"type": ["integer", "null"], "minimum": 0}, "_meta": { "type": "object", "required": ["url", "path", "extractor", "schema_version"], "properties": { "url": {"type": "string"}, "path": {"enum": ["scraper", "autoparse", "markdown_llm"]}, "extractor": {"type": "string"}, "schema_version": {"const": SCHEMA_VERSION}, }, }, }, "allOf": [ {"if": {"properties": {"price": {"type": "number"}}, "required": ["price"]}, "then": {"properties": {"currency": {"type": "string"}}}}, ], }
Код нормализует знакомые символы валют, но сам по себе этот шаблон не проверяет принадлежность к списку валют ISO 4217. Различие между этими двумя схемами объясняет результаты: модель может вернуть полный, структурно корректный объект с null-значениями, который приложение потом отклонит из-за отсутствующего заголовка.
Сравните 50 страниц в трёх прогонах
Стенд бенчмарка начинает с 25 URL Amazon вида /dp/ , 25 URL Walmart вида /ip/ и резерва. Markdown первого прогона для каждого выбранного URL должен превышать 1 500 символов и содержать идентификатор товара. Так одно и то же входное правило применяется до измерения трёх путей, хотя это и означает, что набор данных начинается со страниц, давших пригодный Markdown.
При отборе было заменено 28 кандидатов. Amazon дал семь: шесть страниц 404 и один недоступный товар, страница которого не называла свой ASIN. Walmart дал 21: шесть проверок на бота, десять экранов согласия, два пустых тела, две страницы «не найдено» и одну страницу, не прошедшую входное правило. Пять кандидатов Walmart и весь резерв Walmart пришли из одной органической выдачи walmart-serp по запросу «kitchen essentials».
Три прогона прошли подряд 2 ноября 2026 года. Опубликованная матрица содержит 450 результатов по путям: 50 URL, умноженные на три пути и три прогона. Из сохранённых результатов 412 использовали Normal token, а 38 использовали JavaScript token после повтора. Эти итоги описывают записанные исходы; запросы на отбор URL и дополнительные повторные попытки добавляются к фактической сетевой работе.
Зависимости Python фиксируют Crawlbase SDK 1.1.0. Записанные вызовы LLM запрашивали gpt-4o-mini и возвращали gpt-4o-mini-2024-07-18.
Клиент краулинга задаёт клиентский таймаут 120 секунд. Если настроен JavaScript token, он допускает одно переключение после cb_status 207 или 503, либо после успешного ответа, содержащего распознанную проверку, почти пустое тело Markdown или результат парсера без заголовка. Исходные ответы 404 и 410 из этого повтора исключены. Транспортные исключения записываются как сбои, а не повторяются этим клиентом:
TIMEOUT_S = 120 RETRY_ON_JS = {"207", "503"} # A 200 from the API can still carry an interstitial instead of the product page. CHALLENGE_MARKERS = ("robot or human", "health information consent", "enter the characters you see below") MIN_MARKDOWN_CHARS = 40 def unusable(result: "CrawlResult") -> bool: """Same rule for every path: no product content in an otherwise successful response.""" if result.cb_status != "200" or result.error: return False if result.path == "markdown": text = (result.markdown or "").lower() return len(text) < MIN_MARKDOWN_CHARS or any(m in text for m in CHALLENGE_MARKERS) body = result.data.get("body") if isinstance((result.data or {}).get("body"), dict) else result.data return not (isinstance(body, dict) and (body.get("name") or body.get("title")))
Модуль метрик измеряет несколько разных свойств. Доля заполненных полей: среднее покрытие значениями, отличными от null, по восьми полям. Попытки без записи дают ноль заполненных полей; записи, отклонённые схемой приложения, всё равно засчитывают свои непустые поля. Согласованность требует совпадения нормализованной записи из восьми полей во всех трёх прогонах; метаданные исключены. Принятие схемой считает записи, прошедшие product.v1, а задержка включает длительность запроса (обе попытки, если был повтор с JavaScript) плюс длительность работы модели для LLM-пути.
Для проверки по тексту страницы значения LLM сравниваются с префиксом Markdown, который она получила, а URL также доступен как свидетельство для идентификатора товара. Значения парсеров сравниваются с отдельным Markdown-снимком того же прогона, и проверка пропускается, если этот снимок не проходит правило страницы товара. Бенчмарк намеренно отключает контентный фильтр Markdown в маршрутизаторе, чтобы измерить, что модель делает с проверками на бота и пустыми страницами.
Прочитайте измеренные результаты
| Показатель | Встроенный скрейпер | autoparse |
Markdown + gpt-4o-mini |
|---|---|---|---|
| Средняя доля заполненных полей, Amazon | 88.3% | 89.0% | 90.5% |
| Средняя доля заполненных полей, Walmart | 83.0% | 86.5% | 74.2% |
| Идентичны во всех 3 прогонах, Amazon | 12 из 25 | 13 из 25 | 12 из 25 |
| Идентичны во всех 3 прогонах, Walmart | 19 из 25 | 17 из 25 | 2 из 25 |
| Значений нет в тексте страницы, Amazon | 36 из 522 | 26 из 526 | 10 из 543 |
| Значений нет в тексте страницы, Walmart | 6 из 353 | 10 из 374 | 21 из 445 |
| Записей, прошедших схему | 147 из 150 | 150 из 150 | 130 из 150 |
| Медианная задержка на страницу | 13.9 с | 15.6 с | 13.4 с |
| Токены модели | 0 | 0 | 1 803 338 |
Два пути с парсерами прошли схему в 297 из 300 попыток без токенов модели. Все три сбоя именованного скрейпера были вызовами Walmart, упёршимися в клиентский таймаут. LLM возвращала объект при каждой попытке, но нормализация и проверка приложением приняли 130 из 150 записей.
Walmart показал самый большой разрыв. Из его 75 Markdown-снимков 21 содержал проверку на бота, экран согласия или пустое тело. Все 21 случай пришлись на 50 снимков второго и третьего прогонов, уже после того как отбор первого прогона допустил эти URL. Модель всё равно выдала объект для каждого из них. Пятнадцать заголовков были строкой "null", а 16 ответов указали in_stock без подтверждающего текста о наличии. Выдуманная запись с обеденным набором из введения была единственным нетоварным снимком Walmart, который после нормализации всё же прошёл схему приложения.
Amazon требует другого прочтения. У LLM охват полей был немного выше, а её проверка по тексту страницы отметила меньше значений, чем у любого из парсеров. В 27 из 36 отмеченных значений именованного скрейпера модель вернула null для этого поля, читая Markdown того же прогона. Среди записанных снимков были страницы в стиле листинга без блока покупки, так что часть расхождений отражала разный контент, возвращённый отдельными запросами одного URL.
Цена, рейтинг и число отзывов менялись между прогонами на 11 или 12 URL Amazon для каждого пути с парсером и на 7-11 URL для пути с моделью. Заголовки модели менялись на пяти URL Amazon; заголовки парсеров не менялись ни на одном. Эти наблюдения показывают, почему согласованность нужно интерпретировать вместе с полученными входами. Полный Markdown ни одного URL не был побайтно идентичен во всех трёх прогонах, так что эксперимент не отделил случайность модели от изменчивости страниц. Среди девяти URL Walmart с Markdown страницы товара в каждом прогоне запись LLM была идентична для двух, а запись именованного скрейпера для семи.
Меньшая задержка LLM на Walmart также включает короткие промежуточные страницы и пустые входы. Более быстрый неудачный вход не доказывает более быстрого успешного извлечения товара. Точно так же приведённая медиана считается на попытку, а не как время на принятую запись или общая пропускная способность пакета.
Проверьте, что на самом деле означают возвращённые значения
Проверка по тексту страницы ищет нормализованное свидетельство для каждого значения. Она ловит и выдумки модели, и расхождения парсеров. На одной странице Walmart именованный скрейпер вернул reviewsCount равным "122" , хотя на странице было 12 292; autoparse вернул "12292". На другой странице два пути поменялись исходами. Охват полей сам по себе засчитал неверное число как заполненное.
def is_supported(field: str, value, text: str) -> bool: if value is None: return True if field in ("title", "brand", "id"): return _has_text(text, str(value)) if field == "price": return _has_number(text, float(value)) if field == "reviews_count": return _has_number(text, float(value)) or _has_abbreviated(text, float(value)) if field == "rating": return _has_rating(text, float(value)) if field == "currency": return any(cue in text for cue in CURRENCY_CUES.get(value, (value.lower(),))) if field == "availability": cues = IN_STOCK_CUES if value == "in_stock" else OUT_OF_STOCK_CUES return any(cue in text for cue in cues) return True def invented_fields(record: dict, markdown: str, fields: list[str], url: str = "") -> list[str]: """The URL counts as input: an id taken from it is not invented.""" text = page_text(markdown) + " " + url.lower() return [f for f in fields if record.get(f) is not None and not is_supported(f, record[f], text)]
Ручная проверка трёх страниц Amazon и двух страниц Walmart привела к четырём уточнениям. Сокращённые счётчики принимаются с показанной точностью: 27 406 соответствует «27.4K», а 14 084 соответствует «14K». Сопоставление текста игнорирует пробелы, так что «HeroCosmetics» совпадает с «Hero Cosmetics». «Pickup as soon as 3pm» у Walmart считается признаком наличия. Рейтинги Walmart исключены из проверки подтверждения для всех путей, потому что полученный Markdown показывал рейтинги других товаров, а не самого товара.
Это исключение не убирает рейтинги из расчёта доли заполненных полей и из итогов непустых значений, используемых как знаменатель доли подтверждённых значений. Поэтому приведённые счётчики и проценты на графике следует читать как измерения текстовой поддержки этого стенда, а не как оценку точности по одинаково проверенному набору полей.
Два расхождения, найденные вручную, иллюстрируют оставшееся ограничение. Проверка приняла out_of_stock , когда недоступным было только предложение open-box, и приняла число отзывов 30, потому что несвязанное «30» встречалось в другом месте страницы. Присутствие в тексте устанавливает, что значение где-то во входе есть; чтобы связать его с правильным товаром, предложением и полем, нужен контекст.
Строгий вывод решил другую задачу. 150 вызовов модели не дали ни одной ошибки разбора JSON и ни одного частичного объекта, но строгая структура не предотвратила неподтверждённые значения наличия или выдуманный товар. Требование всех ключей по-прежнему допускает честный JSON null. Сбой происходил, когда модель заполняла поле без свидетельства, а не потому, что схема требовала ответа, отличного от null.
Для смежных сценариев на основе схемы Firecrawl Extract предоставляет интерфейс извлечения, документация которого теперь представляет /agent как его преемника. Его руководство по режиму JSON рекомендует явную обработку null, сфокусированные промпты, меньшие схемы, когда число полей достигает примерно 30 и больше, и удаление для массивов ограничений minItems и maxItems , которые не управляют тем, сколько контента собирается. Измеренный в этой статье LLM-путь: прямой вызов OpenAI поверх Markdown от Crawlbase.
Отчёты об ошибках также показывают, почему сбои вывода нужно диагностировать отдельно. Отчёты Firecrawl #1836 и #3300 касаются преобразования Zod в JSON Schema, которое давало пустые или отсутствующие данные. Отчёт #2491 касается интеграции самостоятельно размещённой модели, которая не сгенерировала объект. Эти примеры описывают сбои схем и интеграций; они не измеряют частоту галлюцинаций и не доказывают, что строгий режим их исправил бы.
Нормализуйте и проверяйте, прежде чем переходить к запасному пути
Когда режимы отказа видны, правило маршрутизации становится простым: попробуйте поддерживаемый парсер, нормализуйте его вывод и принимайте его, только если он соответствует контракту приложения. Нормализатор превращает строки-заглушки вроде "null", "none" и "n/a" в null, разбирает цены и сокращённые счётчики, чистит метки брендов и сопоставляет текст о наличии с in_stock, out_of_stock или null.
Превращение заголовков "null" в настоящие null-значения обратило 15 фиктивных заголовков в отказы схемы приложения. Нормализатор также очищает валюту, когда цены нет. Поддерживаемые им числовые форматы покрывают примеры из этого эксперимента, включая $1,299.99 и €39,90; для других локалей нужны явные правила разбора.
Упакованный маршрутизатор возвращает первый принятый результат:
def route(url: str, crawl: CrawlClient, llm: LLMExtractor, run: int = 1) -> Extraction: """First accepted record wins. A rejected record falls through to the next path.""" attempts = [] if scraper_for(url): attempts.append(lambda: via_scraper(url, crawl, run)) attempts.append(lambda: via_autoparse(url, crawl, run)) attempts.append(lambda: via_markdown_llm(url, crawl, llm, run)) result = None for attempt in attempts: result = attempt() if result.accepted: return result return result
Каждая функция via_* возвращает объект Extraction . За успешным извлечением следуют нормализация, проставление метаданных и проверка по JSON Schema. Здесь accepted означает, что запись существует и не имеет ошибок валидации. Если все пути не прошли, маршрутизатор возвращает последний отклонённый результат, а write_record() ничего не записывает.
Перед вызовом модели via_markdown_llm отклоняет Markdown короче 40 символов или содержащий одну из трёх распознаваемых фраз-проверок. Для этих случаев она возвращает not_a_product_page :
def via_markdown_llm(url: str, crawl: CrawlClient, llm: LLMExtractor, run: int = 1, gate: bool = True) -> Extraction: res = crawl.fetch(url, "markdown", run) extractor = f"openai:{llm.model}" if not res.ok: return Extraction(url, "markdown_llm", extractor, None, [res.error or f"cb_status {res.cb_status}"], res.to_dict()) if gate and unusable(res): # A 200 that carries a challenge or an empty body never reaches the model. return Extraction(url, "markdown_llm", extractor, None, ["not_a_product_page"], res.to_dict()) out = llm.extract(url, res.markdown, run) if not out.ok: return Extraction(url, "markdown_llm", extractor, None, [out.error], res.to_dict(), out.to_dict()) return _finish(url, "markdown_llm", f"openai:{out.model or llm.model}", out.data, res.to_dict(), out.to_dict())
Применённый к записанным снимкам Walmart, этот фильтр удержал бы все 21 непригодную страницу от модели. Это точечная проверка контента, а не полный детектор любой промежуточной страницы или ответа не с той страницей.
Решение маршрутизатора о приёмке не запускает проверку по тексту страницы из бенчмарка. Добавьте контекстные проверки свидетельств в собственную политику приёмки, если валидная по схеме, но неверная цена или значение наличия повлияют на дальнейшие решения. Так структурная валидация и проверка фактов остаются явными шагами реализации.
Принятые записи содержат _meta.url, _meta.path, _meta.extractor и _meta.schema_version. Примеры: crawlbase:amazon-product-details, openai:gpt-4o-mini-2024-07-18 и product.v1. Они идентифицируют маршрут и имя модели или скрейпера; само по себе имя скрейпера не идентифицирует конкретную ревизию парсера.
Пошаговые скрипты показывают по отдельности вызов встроенного скрейпера, вызов autoparse, извлечение через LLM и сравнение по трём прогонам.
Одна конечная точка для всего маршрута: поддерживаемый скрейпер товаров, autoparse или чистый Markdown для вашей собственной модели, с JavaScript token, когда странице нужен браузер. Неудачные запросы не тарифицируются. Начните с до 5 000 бесплатных запросов, без карты.
Использование токенов и кэширование
Дисковый кэш позволяет повторять анализ без повторного получения тех же страниц. Записи краулинга ключуются по пути, номеру прогона и URL. Записи модели включают запрошенную модель, номер прогона, URL и хеш полного Markdown. Ни один кэш не истекает автоматически, а ключ модели не включает промпт и схему. Очистите соответствующие записи, прежде чем измерять пересмотренный контракт извлечения или получать свежие страницы.
Записанные результаты были пересчитаны из сохранённых ответов. Ноль вызовов API в метаданных того запуска описывает воспроизведение, а не исходный эксперимент. Для пересчёта этих измерений нужен сохранённый кэш ответов; один лишь опубликованный файл метрик сырых снимков не содержит.
Размер входа остаётся отдельной статьёй масштабирования. 150 вызовов модели потребили 1 793 923 токена промпта и 9 415 токенов ответа, итого 1 803 338. Amazon в среднем давал 22 131 токен на вызов; Walmart в среднем 1 914. Сорок девять из 75 вызовов Amazon превысили лимит Markdown в 60 000 символов и были усечены. Повышение лимита отправляет больше контекста; понижение рискует отрезать поля в конце страницы.
Диаграмма иллюстративна. При фиксированном среднем размере входа оба подхода растут линейно с числом страниц; путь с моделью добавляет обработку токенов на каждую страницу. Его более крутая линия не задаёт измеренного соотношения цены или скорости. Бенчмарк считает логические попытки краулинга и токены модели, тогда как реальная стоимость получения зависит ещё и от повторов, сложности домена, рендеринга и хранения.
Релиз Firecrawl v2.11.0 описывает другой способ сократить повторную работу модели: deterministicJson генерирует переиспользуемый экстрактор и кэширует его для каждого сайта. Этот механизм отвечает той же инженерной цели амортизации работы по извлечению между повторяющимися страницами, хотя в этом эксперименте он измеренным путём не был.
Поставьте сбор в очередь и получайте результаты по RID
Для больших пакетов шаг получения может завершаться независимо от процесса, отправляющего URL. Асинхронный пошаговый скрипт отправляет async=true&store=true, сразу получает идентификатор запроса, а позже забирает результат из Cloud Storage:
def submit(api, url: str) -> str | None: from urllib.parse import urlparse options = {"async": "true", "store": "true"} name = SCRAPERS.get(urlparse(url).hostname or "") options.update({"scraper": name} if name else {"autoparse": "true"}) res = api.get(url, options) payload = res.get("json") if not isinstance(payload, dict): try: payload = json.loads(res.get("body") or b"{}") except ValueError: payload = {} return payload.get("rid") # the SDK puts the async rid in res["json"], not res["rid"] def fetch_stored(storage, rid: str, attempts: int = 12, wait_s: float = 10.0) -> dict | None: for _ in range(attempts): try: res = storage.get(rid, {"format": "json"}) except KeyError: # crawlbase 1.1.0 raises this while the rid is not stored yet res = {} if res.get("status_code") == 200: stored = res.get("json_body") if isinstance(stored, dict) and stored: return stored body = res.get("body") or b"" if body: return {"body": body.decode("utf-8", "replace") if isinstance(body, bytes) else body} time.sleep(wait_s) return None
В SDK 1.1.0 асинхронный идентификатор доступен в res["json"]["rid"]. Скрипт опрашивает по RID, трактуя наблюдаемый у SDK KeyError для ещё недоступного элемента как ожидающий результат. В тесте с двумя URL результат Amazon был читаем в течение двух минут. Walmart отсутствовал при первых двух проверках и появился при более поздней. Это записанные наблюдения завершения, а не гарантированный интервал доставки.
Пакетные вспомогательные функции также принимают callback URL. Crawlbase отправляет POST с готовым результатом на эту конечную точку, а store=true сохраняет копию для получения. Пошаговый скрипт с колбэками Zapier демонстрирует этот паттерн доставки. Асинхронный пример сопоставляет и нормализует полученные ответы; примените ту же политику валидации приложения, прежде чем сохранять их как принятые записи товаров.
Сохранение страницы добавляет 0.5 кредита к запросу, который её получил, как указано на странице цен, и страница хранится 30 дней. Получение бесплатно. Заложите хранение в бюджет вместе с краулингом и работой модели.
Тарификация также зависит от статуса ответа. Справочник Crawling API перечисляет тарифицируемые запросы как cb_status 200 или 207 при original_status 200, 201, 301, 302, 303, 307, 308, 404 или 410. Распознанная блокировка 503 не тарифицируется. Ответы с проверкой на бота в этом эксперименте приходили как успешные 200 и тарифицировались как таковые, поэтому осмотр тела оставался необходимым даже после проверки статуса.
Документация API рекомендует асинхронную доставку для пакетов свыше нескольких сотен URL и Crawler для задач большого объёма, например миллионов URL. Для особых требований к пропускной способности и обслуживанию корпоративное предложение даёт следующий операционный шаг.
Используйте модель там, где интерпретация добавляет ценность
Поддерживаемый парсер: практичная отправная точка для поддерживаемых страниц товаров. Он убирает вызов модели на каждую страницу и даёт известный набор полей. Эксперимент показывает и компромисс: наличие Walmart было недоступно через протестированный парсер, а неверные счётчики отзывов всё равно требовали проверки. Выбирайте маршрут извлечения по полям, которые действительно нужны вашему приложению.
Модель полезна для магазинов без подходящего парсера, нерегулярных документов вроде веток форумов и пресс-релизов или разовых исследований, где построение отдельного экстрактора обойдётся дороже вызовов модели. Таким документам нужны свои схема и маппинги; этот маршрутизатор реализует контракт именно для товаров. Руководства по ChatGPT, Gemini и Perplexity дают смежные реализации «получить, затем интерпретировать», а руководство по извлечению цен Amazon применяет паттерн к данным ритейла.
Модели могут работать и после детерминированного извлечения. Запись товара может поддерживать классификацию по собственной таксономии или подбор кандидатов на соответствие между Walmart и Amazon. Для разметки жалоб нужен текст отзывов, так что передайте и этот контент из подходящего скрейпера или снимка. Каждая задача может получать нужные ей поля вместо всей страницы.
Для регулярной ценовой аналитики сначала собирайте и нормализуйте цену, а затем добавляйте интерпретацию моделью там, где она нужна. Руководство по регулярному скрапингу Amazon охватывает повторяющееся получение данных. Явное разделение получения, извлечения полей, валидации и интерпретации упрощает проверку веб-скрапинга в JSON, когда какое-то значение меняется.
Заключение
На этих 50 страницах товаров пути с поддерживаемыми скрейперами дали 297 принятых схемой записей из 300 попыток без токенов модели. Markdown плюс GPT-4o-mini показал более высокий охват полей на Amazon, но промежуточные страницы Walmart обнажили риск принятия правдоподобных записей из непригодных входов. Записанные результаты говорят в пользу того, чтобы начинать с доступного парсера, проверять возвращённые поля и оставлять извлечение моделью для пробелов, которые вашему приложению нужно закрыть.
Полная реализация содержит маппинги, маршрутизатор, бенчмарк и офлайн-примеры. Чтобы опробовать её на своём каталоге, создайте аккаунт Crawlbase, настройте токены и просмотрите небольшой набор принятых записей, прежде чем расширять запуск. Скрейпер Amazon, скрейпер Walmart и более общий обзор скрапинга e-commerce помогут определить маршрут получения данных для ваших целей.
Часто задаваемые вопросы (FAQ)
Для каких сайтов есть встроенные скрейперы Crawlbase?
Индекс скрейперов перечисляет поддерживаемые экстракторы для Amazon, Walmart, eBay, AliExpress, Best Buy, Google, Bing, Facebook, Instagram, TikTok, LinkedIn, Reddit, Booking.com, Airbnb и других сайтов, а также универсальные экстракторы. Выберите скрейпер под тип страницы и передайте его имя через scraper=. Пример доменного маршрутизатора сопоставляет только страницы товаров Amazon и Walmart; расширьте его для других целей.
Работает ли autoparse на любой странице?
С autoparse=true Crawlbase определяет тип страницы и применяет подходящий поддерживаемый скрейпер. Когда подходящего скрейпера нет, он откатывается к generic-extractor, который возвращает общие данные страницы, а не запись товара конкретного ритейлера. В этом репозитории приложение сопоставляет возвращённый JSON со своей схемой товара и проверяет его. Если этот процесс не даёт приемлемой записи товара, приложение делает отдельный запрос Markdown и пытается извлечь данные через LLM. Этот дополнительный запасной путь относится к логике маршрутизации приложения.
Как часто ломаются встроенные скрейперы и кто их поддерживает?
Crawlbase поддерживает свои встроенные скрейперы и обновляет их, когда изменения на сайтах влияют на извлечение. Поломки регистрируются автоматически и обычно исправляются в течение нескольких часов. Если вы заметили отсутствующие поля, неверные значения или проблему, которая не исчезает, обратитесь в поддержку Crawlbase с затронутым URL и примером ответа, чтобы команда могла разобраться.
Гарантирует ли валидация по JSON Schema точность извлечённых данных?
Нет. JSON Schema проверяет структуру записи, обязательные поля и ограничения значений. Она не проверяет, что значения описывают правильный товар. Убедитесь, что полученная страница содержит пригодный контент товара, а затем сверьте важные значения со свидетельствами, относящимися к правильному товару и предложению.
Обходите любой сайт в масштабе, без борьбы с инфраструктурой.
Crawlbase берёт на себя прокси, отпечатки и CAPTCHA, чтобы ваша команда занималась конвейерами данных, а не поддержкой инфраструктуры обхода. До 5 000 запросов бесплатно, карта не нужна.

