--- name: wikipedia-pageviews-scraper description: Pull daily or monthly Wikipedia pageview counts via the Apify Actor arman-bd/wikipedia-pageviews-scraper. Returns each article's series with the points themselves plus the total, mean, peak day, peak count and a computed trend figure, in either one record per article or one record per data point. Use when a task needs topic or brand attention over time, a free directional substitute for search volume, spike correlation against news, seasonality analysis or content prioritisation, in any language edition back to 2015. Not for user-level data, search queries, edit history or article content. --- # Wikipedia Pageviews Scraper: Traffic & Trend Analysis Apify Actor `arman-bd/wikipedia-pageviews-scraper`. Give it article titles or Wikipedia URLs and a date window, and get the view counts back with the aggregates already computed: total, mean, peak day and a trend figure. Pageviews are public anonymised aggregates, per topic, per day, in every language edition, back to mid-2015. It takes no credentials. ## When to use it - Tracking public attention on a company, product, person or event over time. - Correlating attention spikes with news, launches or press coverage. - A free directional stand-in for keyword volume when a licensed tool is not available. - Seasonality work, pulling several years at monthly granularity in one run. - Comparing platforms or audiences: desktop against mobile, or human against automated traffic. ## When not to use it - Anything user-level. These are anonymised aggregates and no per-visitor data exists. - Search queries or referrers. You get views of an article, not how anyone got there. - Article content, edits, editors or revision history. - Weekly buckets. Only daily and monthly exist; aggregate the daily points yourself. - Traffic before mid-2015, which is not in this dataset in this shape. ## Call it ```js import { ApifyClient } from 'apify-client'; const client = new ApifyClient({ token: process.env.APIFY_TOKEN }); const run = await client.actor('arman-bd/wikipedia-pageviews-scraper').call({ articles: ['Web scraping', 'Python (programming language)'], project: 'en.wikipedia', fromDate: '20260101', toDate: '20260131', granularity: 'daily', accessMethod: 'all-access', agent: 'user', outputMode: 'series', }); const { items } = await client.dataset(run.defaultDatasetId).listItems(); const { value: summary } = await client .keyValueStore(run.defaultKeyValueStoreId) .getRecord('RUN_SUMMARY'); ``` One-shot over HTTP, when you want the rows back in the same request: ```bash curl -X POST "https://api.apify.com/v2/acts/arman-bd~wikipedia-pageviews-scraper/run-sync-get-dataset-items?token=$APIFY_TOKEN" \ -H "Content-Type: application/json" \ -d '{"articles":["Web scraping"],"fromDate":"20260101","toDate":"20260131"}' ``` The Actor is also exposed through Apify's MCP server as `arman-bd/wikipedia-pageviews-scraper`, so an MCP-capable agent can call it with no extra wiring. ## Input | Field | Type | Required | Default | Notes | |---|---|---|---|---| | `articles` | string[] | yes | | Titles, slugs or full article URLs. Spaces become underscores and the first letter is capitalised; everything after that is **case-sensitive and must match the URL form**. A URL also sets that entry's project and overrides `project`. De-duplicated on project plus title. | | `project` | string | no | `"en.wikipedia"` | Project used for plain titles: `de.wikipedia`, `en.wikinews`, `fr.wiktionary` and so on. A scheme is stripped if you include one. | | `fromDate` | string | yes | 31 days ago | `YYYYMMDD` or `YYYY-MM-DD`. Anything with at least eight digits is accepted and truncated to the first eight; a value with fewer falls back to the default rather than throwing. | | `toDate` | string | yes | yesterday | Same parsing. The data lags roughly a day, which is why the default is not today. `fromDate` after `toDate` throws. | | `granularity` | string | no | `"daily"` | `daily` or `monthly`. Anything else throws before the first request. | | `accessMethod` | string | no | `"all-access"` | `all-access`, `desktop`, `mobile-web`, `mobile-app`. | | `agent` | string | no | `"user"` | `user` excludes known bots and is the honest interest signal. Also `all-agents`, `spider`, `automated`. | | `outputMode` | string | no | `"series"` | `series` gives one record per article with the points nested. `rows` gives one record per data point, which exports straight to CSV. | **`outputMode` changes the record shape, not the data**, and it is the one choice that will trip up code written against the other mode. `series` is the right default for an agent: one row per article, aggregates at the top level, the points nested under `series`. `rows` is for charting and spreadsheets, and it repeats each series' totals on every point while dropping `averageViews`, `dataPoints`, `fromDate` and `toDate` entirely. Pick one per pipeline and do not mix them in the same dataset. The second decision worth making deliberately is `agent`: leave it at `user` for anything you will call demand, and only switch to `all-agents` when you specifically want crawler traffic included. ## Output In `series` mode, one record per article. | Field | Type | Notes | |---|---|---| | `article` | string | Underscore-encoded title as queried, not as you typed it. | | `project` | string | The project this series came from, which is the URL's project when you passed a URL. | | `granularity` | string | Echoed from the input. | | `accessMethod` | string | Echoed from the input. | | `agent` | string | Echoed from the input. | | `fromDate` | string | `YYYYMMDD`, normalised. | | `toDate` | string | `YYYYMMDD`, normalised. | | `dataPoints` | number | Points actually returned. Can be fewer than the days in the window. | | `totalViews` | number | Sum across the returned points. | | `averageViews` | number | Mean across the returned points, rounded to an integer. | | `peakDate` | string | `YYYY-MM-DD` of the busiest point. Earliest wins a tie. | | `peakViews` | number | That point's count. | | `trendPercent` | number \| null | Mean of the second half of the window against the first half, in per cent, one decimal. `null` below four points or when the first half was zero. | | `series` | object[] | The points: `timestamp` (`YYYYMMDD00`), `date` (`YYYY-MM-DD`), `views`. | | `scrapedAt` | string | Run timestamp, ISO 8601. | In `rows` mode, one record per point, carrying its series' aggregates. | Field | Type | Notes | |---|---|---| | `article` | string | As above. | | `project` | string | As above. | | `granularity` | string | As above. | | `timestamp` | string | **`YYYY-MM-DD` here**, unlike `series[].timestamp`, which is `YYYYMMDD00`. | | `views` | number | This point's count. | | `accessMethod` | string | Echoed from the input. | | `agent` | string | Echoed from the input. | | `totalViews` | number | The whole series' total, repeated on every row. | | `peakDate` | string | Repeated on every row. | | `peakViews` | number | Repeated on every row. | | `trendPercent` | number \| null | Repeated on every row. | | `scrapedAt` | string | Run timestamp, ISO 8601. | A series-mode record, points trimmed: ```json { "article": "Web_scraping", "project": "en.wikipedia", "granularity": "daily", "accessMethod": "all-access", "agent": "user", "fromDate": "20260101", "toDate": "20260131", "dataPoints": 31, "totalViews": 15367, "averageViews": 496, "peakDate": "2026-01-23", "peakViews": 1068, "trendPercent": 2.9, "series": [ { "timestamp": "2026010100", "date": "2026-01-01", "views": 249 }, { "timestamp": "2026010200", "date": "2026-01-02", "views": 368 } ], "scrapedAt": "2026-08-06T11:44:00.000Z" } ``` The same data in `rows` mode, one record per point: ```json { "article": "Berlin", "project": "en.wikipedia", "granularity": "daily", "timestamp": "2026-01-01", "views": 3750, "accessMethod": "all-access", "agent": "user", "totalViews": 27726, "peakDate": "2026-01-07", "peakViews": 4701, "trendPercent": 11.2, "scrapedAt": "2026-08-06T11:44:00.000Z" } ``` ## RUN_SUMMARY Written to the run's key-value store under the key `RUN_SUMMARY`. **Read it.** It is where a partial run admits that it was partial. ```json { "seriesRequested": 3, "seriesSaved": 2, "seriesFailed": 1, "rowsSaved": 62, "failures": [ { "source": "en.wikipedia:Web_Scraping", "error": "no data, check the exact article title and the date range (404)" } ], "filters": { "project": "en.wikipedia", "fromDate": "20260101", "toDate": "20260131", "granularity": "daily", "accessMethod": "all-access", "agent": "user", "outputMode": "rows" }, "finishedAt": "2026-08-06T11:44:02.884Z" } ``` `failures[].source` is `project:Article` exactly as queried, which is the fastest way to see that a title was capitalised or spelled differently from the one that exists. Almost every failure here is a title problem rather than an outage, and re-running it unchanged will fail identically. `rowsSaved` is present only in `rows` mode. `filters` echoes the normalised window, so check it when a series looks shorter than you expected: an unparseable `fromDate` falls back to 31 days ago without complaint. ## Behaviour to plan around - **The title must match the article's URL form.** Capitalisation and punctuation after the first letter are significant, and a mismatch is reported as no data rather than as a wrong title. Pasting the full article URL is the reliable input. - **A URL overrides `project`.** Mixing a German article URL into a run with `project: "en.wikipedia"` queries the German project for that entry only, which is correct but means one dataset can span projects. Group on `project` before comparing. - **`dataPoints` can be short of the window.** Points with no data are absent from `series` rather than present with `views: 0`, so a sparse article yields a shorter series and a mean over fewer days. Reindex against your own calendar if you need a dense series. - **`timestamp` means two different formats.** `YYYYMMDD00` inside `series[]`, `YYYY-MM-DD` at the top level in `rows` mode. In series mode use `series[].date` when you want a plain date. - **`trendPercent` is window-relative.** It compares the second half of the range you asked for against the first half, so it changes meaning when you change the window and cannot be compared across runs with different ranges. It is `null` below four points. - **`averageViews` is rounded to an integer.** Do not use it to reconstruct `totalViews`; use `totalViews` directly. - **`agent: "user"` and `agent: "all-agents"` are not comparable.** Switching mid-series produces a step change that looks like a traffic event and is not one. - **A missing article never aborts the run.** It is recorded in `RUN_SUMMARY.failures` and the remaining articles continue. The Actor throws only when every article failed. - **Not-found is final, transient failures are retried** four times with exponential backoff and jitter. - **Data lags roughly a day**, so a run scheduled for today's date returns nothing for today. Leave `toDate` at yesterday or later than your window of interest. ## Recipes **Rolling 30-day attention table.** Scheduled daily, series mode, human traffic only. ```json { "articles": ["Web scraping", "Python (programming language)", "Large language model"], "project": "en.wikipedia", "fromDate": "20260707", "toDate": "20260806", "granularity": "daily", "agent": "user", "outputMode": "series" } ``` Sort by `trendPercent` for what is rising, by `totalViews` for what is big. **Seasonality over several years.** Monthly points, CSV-ready shape. ```json { "articles": ["Christmas", "Halloween"], "project": "en.wikipedia", "fromDate": "2021-01-01", "toDate": "2026-01-01", "granularity": "monthly", "outputMode": "rows" } ``` Pivot on `timestamp` against `article` and plot `views` directly. **Platform split for one topic.** Two runs, one per `accessMethod`, joined on the date. ```json { "articles": ["https://de.wikipedia.org/wiki/Kartoffel"], "fromDate": "20260101", "toDate": "20260131", "accessMethod": "mobile-web", "agent": "user", "outputMode": "rows" } ``` Repeat with `accessMethod: "desktop"` and compare `views` per `timestamp`. **Spike hunting across a topic set.** Series mode, wide window, look at the peaks. ```json { "articles": ["Berlin", "Munich", "Hamburg"], "fromDate": "20260101", "toDate": "20260630", "granularity": "daily", "outputMode": "series" } ``` Take `peakDate` and `peakViews` per article, then pull the surrounding days out of `series` to size the event.