# Ouest-France Scraper (`memo23/ouest-france-scraper`) Actor

Scrape news articles from ouest-france.fr — title, lead, full body, author, dates, categories, photos, paywall status, and reading time. Accepts search URLs, article URLs, or free-text queries. Mobile JSON API, no proxy needed. JSON or CSV out.

- **URL**: https://apify.com/memo23/ouest-france-scraper.md
- **Developed by:** [Muhamed Didovic](https://apify.com/memo23) (community)
- **Categories:** News, Marketing
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Ouest-France Scraper

Scrape news articles from **ouest-france.fr** — France's largest regional newspaper — and export them as structured JSON. Paste a search URL, a direct article URL, or a free-text query, and get titles, leads, full body text, authors, dates, categories, photos, and paywall status for every article. No headless browser, no proxy, no anti-bot to fight: the actor talks to Ouest-France's own mobile JSON API directly.

#### How it works

![How Ouest-France Scraper works](https://raw.githubusercontent.com/muhamed-didovic/muhamed-didovic.github.io/main/assets/how-it-works-ouest-france.png)

#### ✨ Why use this scraper?

- **Mobile JSON API, not a headless browser.** The ouest-france.fr website sits behind Akamai bot protection (returns 403 to plain HTTP). The Android app's API has no such wall — the actor fetches a short-lived Algolia key, searches the articles index, and pulls full content in three clean JSON calls. Faster, cheaper, and more reliable than rendering pages.
- **Search or direct URLs.** Run a free-text query ("Rennes", "immigration", "Nantes"), paste a recherche URL, or drop in individual article URLs. All three work and can be mixed in one run.
- **Full article body, not just cards.** Toggle `fetchFullDetails` to pull the complete article text (assembled from the content API's structured body blocks), byline, reading time, and all photos — or leave it off for fast card-only harvests.
- **Paywall-aware.** Each row carries a `paywall` flag and `articleType`. Paywalled articles (`CONTENU_PAYANT`) return their lead only; free articles return the full body. No pretending paywalled content is free.
- **One row per article, deduped.** No padding rows. If the same article shows up in two search queries, it appears once.
- **No proxy required.** The mobile API returns 200 with a plain User-Agent. Run direct and save the proxy budget.

#### 🎯 Use cases

| Audience | What they do with it |
|---|---|
| News monitors | Track coverage of a topic, region, or event across Ouest-France's archive (1.4M+ articles searchable) |
| Media analysts | Build datasets of headlines, leads, and publication dates for trend analysis |
| Data journalists | Harvest regional news (Bretagne, Rennes, Nantes) for investigation or archive |
| NLP / LLM pipelines | Feed structured article text (title, lead, body, categories) into models for summarization or classification |
| Content archivists | Snapshot articles with full body, author, and photo metadata before they change |

#### 📥 Supported inputs

| Input type | Example | What happens |
|---|---|---|
| Search URL | `https://www.ouest-france.fr/recherche/#!r/requete=Rennes&onglet=actualites` | Query extracted from the hash fragment, run against the articles index |
| Article URL | `https://www.ouest-france.fr/bretagne/rennes-35000/...-48996954-bdb4-11f1-81a4-4fea3020cab7` | Resolved to full content via the by-url endpoint |
| Free-text query | `searchQueries: ["Rennes", "immigration"]` | Each query paginated through the Algolia articles index |

**Not supported:** Ouest-France e-paper editions (feuilleteur), real estate listings (ouest-france.immo), funeral notices (avis-de-deces), and content behind a subscriber login.

#### 🔄 How it works

1. The actor fetches a short-lived Algolia secured key from the search-engine configuration endpoint.
2. For each search query, it paginates the `articles` Algolia index (50 hits per page), collecting card metadata.
3. Direct article URLs are resolved via the `v2/entities/by-url` endpoint to full content.
4. If `fetchFullDetails` is on, each card is enriched with a `v1/contents/{id}` call for the full body, byline, photos, and paywall status.
5. Results are deduped by article ID and pushed to the dataset, one row per article.

#### ⚙️ Input parameters

| Field | Type | Default | Description |
|---|---|---|---|
| `startUrls` | array | `["https://www.ouest-france.fr/recherche/#!r/requete=Rennes&onglet=actualites"]` | Ouest-France search URLs and/or direct article URLs. Auto-classified. |
| `searchQueries` | array | `[]` | Free-text queries to run against the articles index. Alternative to search URLs. |
| `fetchFullDetails` | boolean | `false` | Open each article for the full body, byline, photos, and paywall status. |
| `maxItems` | integer | `100` | Hard cap on articles. Free-plan users are capped at 5. |
| `maxConcurrency` | integer | `10` | Parallel article-detail fetches (only when `fetchFullDetails` is on). |
| `proxy` | object | `{ "useApifyProxy": false }` | The mobile API has no anti-bot, so a proxy is usually unnecessary. |

#### 📊 Output overview

Each dataset row is one Ouest-France article. Card-only runs (the default) return title, lead, url, date, categories, source, paywall flag, type, and photo counts. With `fetchFullDetails` on, each row also carries the full body text (plain + HTML), author, all publication dates, reading time, main tag, geo zones, and photo URLs.

#### 📦 Output sample

```json
{
  "type": "article",
  "articleId": "47c8fb5f-f25b-48b5-9b0f-04c657749bae",
  "title": "Fortes tensions à Rennes et interpellations… revivez la matinée de blocage des lycées en Ille-et-Vilaine",
  "url": "https://www.ouest-france.fr/societe/manifestation/direct-la-mobilisation-lyceenne-se-poursuit-ce-vendredi-a-rennes-47c8fb5f-f25b-48b5-9b0f-04c657749bae",
  "lead": "Après les barrages, affrontements et dégradations de la veille, la mobilisation lycéenne se poursuit, ce vendredi 2 octobre 2026...",
  "body": "Après les barrages, affrontements et dégradations de la veille, la mobilisation lycéenne se poursuit...",
  "bodyHtml": "Après les barrages, affrontements et dégradations de la veille...",
  "author": "Anthony Chaud, Ludivine Doméon, avec les rédactions Ouest-France",
  "datePublished": "2026-10-02T05:40:42.000Z",
  "firstPublicationDate": "2026-10-02T05:40:42Z",
  "lastPublicationDate": "2026-10-02T11:39:49Z",
  "lastUpdatedDate": "2026-10-02T13:23:47Z",
  "categories": ["École collège lycée", "Manifestation", "Société", "Éducation"],
  "source": "of",
  "paywall": false,
  "articleType": "GENERAL",
  "isLive": true,
  "photos": [{ "url": "https://media.ouest-france.fr/v1/pictures/...", "credit": null, "caption": null }],
  "nbPhotos": 1,
  "nbVideos": 0,
  "readingTimeMinutes": 2,
  "mainTag": "Manifestation",
  "zonesGeo": ["Fougères", "Bretagne", "Rennes", "Saint-Malo", "Vitré", "Ille-et-Vilaine", "Régions"],
  "fetchedFullDetails": true
}
```

#### 🗂 Key output fields

| Field | Description |
|---|---|
| `articleId` | Ouest-France article UUID (the Algolia objectID) |
| `title` | Article headline |
| `url` | Full ouest-france.fr article URL |
| `lead` | Article lead paragraph (chapeau) |
| `body` | Full article text, HTML stripped (only with `fetchFullDetails`) |
| `bodyHtml` | Full article body as HTML (only with `fetchFullDetails`) |
| `author` | Byline / signature (only with `fetchFullDetails`) |
| `datePublished` | Publication date (ISO 8601) |
| `firstPublicationDate` | First publication timestamp (only with `fetchFullDetails`) |
| `lastPublicationDate` | Last publication timestamp (only with `fetchFullDetails`) |
| `lastUpdatedDate` | Last update timestamp (only with `fetchFullDetails`) |
| `categories` | Topic categories (e.g. Société, Éducation) |
| `source` | Content source (usually `of` for Ouest-France) |
| `paywall` | `true` if the article is behind a paywall (`CONTENU_PAYANT`) |
| `articleType` | Article type (`GENERAL`, `LOCAL`, `CONTENU_PAYANT`, `live`, etc.) |
| `isLive` | `true` for live-blog articles |
| `photos` | Array of photo objects with `url`, `credit`, `caption` |
| `nbPhotos` / `nbVideos` | Media counts |
| `readingTimeMinutes` | Estimated reading time (only with `fetchFullDetails`) |
| `mainTag` | Primary tag (only with `fetchFullDetails`) |
| `zonesGeo` | Geographic zones the article covers |
| `fetchedFullDetails` | Whether full content was fetched for this row |

#### ❓ FAQ

**Why does the actor use the mobile API instead of scraping the website?**
The ouest-france.fr website is behind Akamai bot protection and returns 403 to non-browser HTTP clients. The Android app's JSON API has no such protection and returns the same data in a cleaner, structured format. This makes the actor faster, cheaper, and more reliable than a headless-browser approach.

**Why are some article bodies short?**
Articles marked `CONTENU_PAYANT` (paywall: true) are behind Ouest-France's subscriber paywall. The API returns only the lead paragraph for these, not the full body. Free articles return the complete text. The `paywall` and `articleType` fields tell you which is which.

**Do I need a proxy?**
No. The mobile JSON API returns 200 with a plain User-Agent. A proxy is only useful if you need to mask your IP for volume reasons, not for access.

**How many articles can I scrape?**
The Algolia articles index holds over 1.4 million articles. `maxItems` caps each run. Free-plan users are capped at 5 articles per run.

**Can I search for articles by date or region?**
The free-text query supports whatever Algolia search syntax Ouest-France exposes. Geographic filtering comes through the `zonesGeo` field on each result. For date filtering, sort by `datePublished` in post-processing.

#### 💬 Support

Found a bug or want a feature? Open an issue on the [Apify Console](https://apify.com/memo23/ouest-france-scraper) and we'll take a look.

#### 🛠 Additional services

Need a custom integration, a specific filter, or a larger volume? Reach out via the Apify Console — we build tailored scraping pipelines on request.

#### 🔎 Explore more scrapers

Looking for other news or directory scrapers? Check the full portfolio on [Apify](https://apify.com/memo23).

***

### ⚠️ Disclaimer

This Actor is an independent tool and is not affiliated with, endorsed by, or sponsored by Ouest-France, SIPA Ouest-France, or any of their subsidiaries or affiliates. All trademarks mentioned are the property of their respective owners.

The scraper accesses only publicly available article data via Ouest-France's mobile API — no authenticated endpoints, subscriber-only content, or content behind a login. Paywalled articles return only their publicly visible lead paragraph. Users are responsible for ensuring their use complies with Ouest-France's Terms of Service, applicable data-protection law (GDPR, CCPA, etc.), and any contractual obligations of their own organisation.

***

### SEO Keywords

ouest-france scraper, scrape ouest-france, ouest-france api, apify ouest-france, ouest-france news scraper, french news scraper, ouest-france article extractor, ouest-france search scraper, regional press scraper, bretagne news scraper, rennes news scraper, nantes news scraper, ouest-france algolia, ouest-france json api, news article archive scraper, french regional news data, ouest-france headlines scraper, ouest-france content scraper, ouest-france data extraction, ouest-france crawler

# Actor input Schema

## `startUrls` (type: `array`):

Ouest-France URLs. Accepts search URLs (https://www.ouest-france.fr/recherche/#!r/requete=Rennes\&onglet=actualites) and direct article URLs (https://www.ouest-france.fr/.../{uuid}). Search URLs are parsed for the query term; article URLs are resolved to full content.

## `searchQueries` (type: `array`):

Free-text search queries to run against the Ouest-France articles index. Alternative to pasting a search URL. e.g. \["Rennes", "Nantes", "immigration"]

## `fetchFullDetails` (type: `boolean`):

Open each article for the byline, full body text, paywall flag, reading time, and all photos. When false, only the search-card fields (title, lead, url, date, categories) are returned.

## `maxItems` (type: `integer`):

Hard cap on articles collected. Free-plan users are capped at 5.

## `maxConcurrency` (type: `integer`):

Maximum number of article-detail fetches running in parallel. Only applies when fetchFullDetails is true.

## `proxy` (type: `object`):

The mobile JSON API has no anti-bot, so a proxy is usually unnecessary. Leave default for a direct connection.

## Actor input object example

```json
{
  "startUrls": [
    "https://www.ouest-france.fr/recherche/#!r/requete=Rennes&onglet=actualites"
  ],
  "searchQueries": [],
  "fetchFullDetails": false,
  "maxItems": 100,
  "maxConcurrency": 10,
  "proxy": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `results` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "startUrls": [
        "https://www.ouest-france.fr/recherche/#!r/requete=Rennes&onglet=actualites"
    ],
    "searchQueries": [],
    "fetchFullDetails": false,
    "proxy": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("memo23/ouest-france-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "startUrls": ["https://www.ouest-france.fr/recherche/#!r/requete=Rennes&onglet=actualites"],
    "searchQueries": [],
    "fetchFullDetails": False,
    "proxy": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("memo23/ouest-france-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "startUrls": [
    "https://www.ouest-france.fr/recherche/#!r/requete=Rennes&onglet=actualites"
  ],
  "searchQueries": [],
  "fetchFullDetails": false,
  "proxy": {
    "useApifyProxy": false
  }
}' |
apify call memo23/ouest-france-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,memo23/ouest-france-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/vai0Y0OftfezYHfb8/builds/PkIZghFbetGeDrAIK/openapi.json
