1. 2scraper
  2. E-commerce & retail
  3. Montblanc
Open source · MITRuns with no key, proxy or account

Montblanc Scraper

Per-market prices, stock, collections and full variant tables from montblanc.com — no key, no proxy, no account.

  • Playwright
  • Selenium
  • Puppeteer
  • Scraping Browser API
  • JSON
  • CSV
montblanc-scraper
# Category listings
$ python playwright_scraper.py --url "https://www.montblanc.com/en-us/writing-instruments" --pages 3 --format both
✓ 12 sample rows · 24 columns in sample_output.json
3modes: category, search, product
0keys, proxies or accounts needed
70rows from a 3-page category run, Sep 2026
24columns in one row schema
Available Montblanc scrapers

What the Montblanc scraper collects

Scrapes montblanc.com category listings, keyword searches and product pages, with one row per variant in product mode. Rows carry the market's own price and currency, stock, collection, colour, size and special-edition flags, and the locale they came from. Output is JSON and CSV with a .meta.json sidecar recording the catalogue's own result count.

Category listings

Input: A category URL, or --category with --locale

One row per product: price, currency, stock from the tile itself, collection, colour, size and image, in the market the URL names.

Keyword search

Input: A search term and a market locale

Search results in the same row shape as a category listing.

Product variant tables

Input: One product page URL

One row per variant, each with its own sku and price, plus base_sku and variant_of linking it to its group — a sixteen-nib pen is sixteen rows.

Quick start

From zero to Montblanc data in 3 steps

  1. Install

    Clone the repository and install one browser engine. Python 3.9+.

    $ git clone https://github.com/2scraper/montblanc-scraper.git && cd montblanc-scraper
    $ python -m venv .venv-playwright
    $ .venv-playwright/bin/pip install -r requirements.txt -r requirements-playwright.txt
    $ .venv-playwright/bin/playwright install chromium
  2. Run a scraper

    Pick what you need and run it. Every flag is documented in the README.

    $ python playwright_scraper.py --url "https://www.montblanc.com/en-us/writing-instruments" --pages 3 --format both
  3. Get structured data

    Rows land in JSON or CSV (--format), with a .meta.json sidecar recording status, pages fetched and why the run stopped.

Output

Montblanc data fields

24 columns per row in a predictable schema. The most useful ones:

FieldWhat it holds
skuMontblanc product or variant code, e.g. MB128502
titleProduct name
priceThe market's own price as a number
currencyCurrency of that market
price_sourcejsonld, or dom_range for a "From $515" minimum over a variant group
in_stockRead from the tile's own availability, not the structured-data default
localeThe market the row came from, e.g. en-us or de-de
categoryCategory name
collectionCollection, e.g. Fountain Pen
sub_collectionSub-collection where stated
colorColour
sizeWatch case width or belt length, where it applies
special_editionWhether the product is a special edition
base_skuThe product group's base code
variant_ofProduct mode: the group a variant belongs to
sortThe ordering requested for the run
[
  {
    "source": "montblanc.com",
    "scraped_at": "2026-09-17T22:36:53.099719+00:00",
    "url": "https://www.montblanc.com/en-us/piston-converter-starwalker-MB128502.html",
    "sku": "MB128502",
    "title": "Piston Converter StarWalker",
    "brand": "montblanc",
    "price": 15.0,
    "currency": "USD",
    "in_stock": true,
    "image_url": "https://www.montblanc.com/dw/image/v2/BHDB_PRD/on/demandware.static/-/Sites-montblanc-m…",
    "category": "Fountain Pen Accessories",
    "price_source": "jsonld",
    "page": 1,
    "position": 1,
    "mode": "category",
    "locale": "en-us",
    "base_sku": "MB128502",
    "collection": "Fountain Pen",
    "sub_collection": null,
    "color": "Black",
    "size": null,
    "special_edition": false,
    "variant_of": null,
    "sort": "price-asc"
  },
  {
    "source": "montblanc.com",
    "scraped_at": "2026-09-17T22:36:53.099780+00:00",
    "url": "https://www.montblanc.com/en-us/ink-bottle-great-characters-homage-to-enzo-ferrari-purp…",
    "sku": "MB128080",
    "title": "Ink Bottle, Great Characters Homage to Enzo Ferrari, Purple - 50ml",
    "brand": "montblanc",
    "price": 55.0,
    "currency": "USD",
    "in_stock": false,
    "image_url": "https://www.montblanc.com/dw/image/v2/BHDB_PRD/on/demandware.static/-/Sites-montblanc-m…",
    "category": "Ink bottles",
    "price_source": "jsonld",
    "page": 1,
    "position": 2,
    "mode": "category",
    "locale": "en-us",
    "base_sku": "MB128080",
    "collection": "Fountain Pen",
    "sub_collection": "Ink Bottle",
    "color": "Purple",
    "size": "50 ml",
    "special_edition": false,
    "variant_of": null,
    "sort": "price-asc"
  }
]

First 2 of 12 rows from a real run — sample_output.json. Long values shortened here.

Built for Montblanc

Handles what breaks naive Montblanc scrapers

Per-market prices kept apart

Montblanc sets prices per market rather than converting them — one backpack was EUR 2000 on en-fi and EUR 1900 on de-de. Every row carries its locale, and diff_runs.py refuses to compare runs from different markets.

Price ranges labelled, not passed off as prices

Variant groups omit a price from the JSON-LD and render "From $515". That minimum is recorded with price_source dom_range so it can be excluded from comparisons, and --mode product gives each variant's real price.

Stock from the tile, not a template default

offers.availability said InStock on 192 of 192 rows; the tile said sold out on 7 of 384. The scraper reads the tile, and leaves in_stock null where there is none.

A stable default ordering

The site's default is a sales-velocity sort that shares 0 of 24 page-1 products with price-ascending. The scraper defaults to --sort price-asc so multi-page runs and price diffs are stable; --sort recommended reproduces the visitor view.

Everything built in

Scraping infrastructure included

The same tested core runs every 2scraper repository, so the hard parts work the same way on every site.

CAPTCHA handling

Detects the challenge a page really serves and, with a 2Captcha key, solves it only when it blocks the data — a capped number of solves per page.

Proxy rotation

Single proxy or a pool from a file, rotated on blocks with a fresh browser per exit. Credentials stay out of argv and logs.

Fingerprints

Optional complete device identity from the 2Captcha Fingerprint API — user agent, client hints, locale and timezone that agree with each other.

Honest run metadata

Every run writes a .meta.json sidecar and distinct exit codes: complete, blocked, empty and partial are never confused, and a failed run never overwrites good data.

Diff two runs

diff_runs.py compares two complete Montblanc snapshots by id — new, removed and changed rows for monitoring.

Use cases

What teams do with Montblanc data

Cross-market price comparison

Scrape the same products in several locales and compare prices that Montblanc sets per market, joined on sku.

Luxury price monitoring

Track price and stock changes across writing instruments, leather goods and watches with diff_runs.py.

Variant and configuration data

Use product mode to get every nib, colour or size variant of a product with its own sku and price.

Assortment and collection tracking

Follow which collections and special editions appear, sell out or disappear in a category over time.

Access & pricing

Free to run. Pay only for what the site requires.

Runs with no key, proxy or account

Measured on 2026-09-17 from a bare datacentre address with no key and no proxy, category, search and product pages all returned HTTP 200 with no challenge. Montblanc runs Akamai Bot Manager, which did not challenge. The paid 2Captcha products mainly buy a specific market's default landing behaviour (the bare host geo-redirects on exit address), volume from many addresses, and no local browser via the Scraping Browser API or the Scraper API — not access.

Optional 2Captcha services — captcha solving, the Scraping Browser API, residential proxies and fingerprints — work behind one key.

Good to know

  • Rows priced from a "From …" range (price_source dom_range) are a minimum over variants, not a variant's price.
  • The site never states which ordering it applied, so the sort column records what was requested.
  • Selenium cannot use an authenticated CDP endpoint or proxy; pyppeteer has no --concurrency.

2scraper vs. hosted scraper APIs

2scraperTypical hosted API
CodeOpen source, MIT — read and patch itClosed
CostFree; optional services billed only when usedSubscription or per request / per result
Runs onYour machine, server or CIThe vendor's infrastructure
OutputJSON/CSV files + run metadataAPI responses
When the site changesFix it yourself or open an issue / PRWait for the vendor
FAQ

Montblanc scraper FAQ

Do I need a 2Captcha account, proxy or key?

No. All page kinds were served from a datacentre address with no challenge, and the daily canary runs with no credentials.

How do I scrape a different market?

Put the locale in the URL or pass --locale. Category paths differ between markets (German uses /de-de/lederwaren/…), so swapping only the locale segment can 404.

Is there a page cap?

No. Pagination uses start/sz and is planned from the catalogue's own result count; a walk of /en-fi/writing-instruments returned exactly the 280 results it stated.

Why does my own HTTP script time out?

Montblanc's edge kills connections whose User-Agent names an HTTP client library, such as curl or python-requests. Set a browser-shaped User-Agent; the three browser engines never meet this.

What output formats does it write?

JSON, CSV or both, plus a .meta.json sidecar. A run that finds nothing writes nothing, so the previous good output is kept.

Is scraping Montblanc legal?

It reads the same public product pages a browser does, with no login and no personal data. Check robots.txt and the site's terms, keep --delay sane, and follow the law where you operate.