Metadata-Version: 2.5
Name: hkex-filing-scraper
Version: 2.4.0
Summary: Scrape and ingest HKEx (Hong Kong Stock Exchange) regulatory filings into nine databases — and query them live from AI agents over MCP.
Project-URL: Homepage, https://github.com/simonplmak-cloud/hkex-filing-scraper
Project-URL: Documentation, https://hkex-listco-updates.ascent-partners.com/
Project-URL: Repository, https://github.com/simonplmak-cloud/hkex-filing-scraper
Project-URL: Issues, https://github.com/simonplmak-cloud/hkex-filing-scraper/issues
Project-URL: Changelog, https://github.com/simonplmak-cloud/hkex-filing-scraper/blob/main/CHANGELOG.md
Author: Simon Mak
License-Expression: MIT
License-File: LICENSE
Keywords: ai-agents,clickhouse,data-engineering,data-pipeline,duckdb,etl,filings,financial-data,graph-database,hkex,hong-kong,hong-kong-stock-exchange,llm,mcp,model-context-protocol,mongodb,multi-database,mysql,neo4j,postgresql,rag,regulatory-filings,scraper,sqlite,surrealdb
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Database
Classifier: Topic :: Office/Business :: Financial
Requires-Python: >=3.10
Requires-Dist: beautifulsoup4>=4.11
Requires-Dist: requests>=2.28
Provides-Extra: all
Requires-Dist: clickhouse-connect>=0.7; extra == 'all'
Requires-Dist: duckdb>=1.0; extra == 'all'
Requires-Dist: mcp<2,>=1.30; extra == 'all'
Requires-Dist: neo4j>=5.20; extra == 'all'
Requires-Dist: openpyxl>=3.1; extra == 'all'
Requires-Dist: psycopg[binary,pool]>=3.1; extra == 'all'
Requires-Dist: pymongo>=4.6; extra == 'all'
Requires-Dist: pymysql[rsa]>=1.1; extra == 'all'
Requires-Dist: python-dotenv>=1.0; extra == 'all'
Provides-Extra: clickhouse
Requires-Dist: clickhouse-connect>=0.7; extra == 'clickhouse'
Provides-Extra: dev
Requires-Dist: hypothesis>=6.100; extra == 'dev'
Requires-Dist: pre-commit>=3.7; extra == 'dev'
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: responses>=0.25; extra == 'dev'
Requires-Dist: ruff<0.17,>=0.16; extra == 'dev'
Provides-Extra: duckdb
Requires-Dist: duckdb>=1.0; extra == 'duckdb'
Provides-Extra: excel
Requires-Dist: openpyxl>=3.1; extra == 'excel'
Provides-Extra: mcp
Requires-Dist: mcp<2,>=1.30; extra == 'mcp'
Provides-Extra: mongodb
Requires-Dist: pymongo>=4.6; extra == 'mongodb'
Provides-Extra: mysql
Requires-Dist: pymysql[rsa]>=1.1; extra == 'mysql'
Provides-Extra: neo4j
Requires-Dist: neo4j>=5.20; extra == 'neo4j'
Provides-Extra: pdf
Requires-Dist: camelot-py>=0.11; extra == 'pdf'
Requires-Dist: pymupdf4llm>=0.0.17; extra == 'pdf'
Requires-Dist: pymupdf>=1.23; extra == 'pdf'
Provides-Extra: postgres
Requires-Dist: psycopg[binary,pool]>=3.1; extra == 'postgres'
Description-Content-Type: text/markdown

# HKEx Filing Scraper

![HKEx Filing Scraper — one scraper, many databases](https://raw.githubusercontent.com/simonplmak-cloud/hkex-filing-scraper/main/docs/social_preview.png)

[![CI](https://github.com/simonplmak-cloud/hkex-filing-scraper/actions/workflows/ci.yml/badge.svg)](https://github.com/simonplmak-cloud/hkex-filing-scraper/actions/workflows/ci.yml)
[![GitHub Release](https://img.shields.io/github/v/release/simonplmak-cloud/hkex-filing-scraper?color=green)](https://github.com/simonplmak-cloud/hkex-filing-scraper/releases)
[![PyPI](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fpypi.org%2Fpypi%2Fhkex-filing-scraper%2Fjson&query=%24.info.version&label=PyPI&color=blue)](https://pypi.org/project/hkex-filing-scraper/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
[![MCP](https://img.shields.io/badge/MCP-server-blueviolet)](https://hkex-listco-updates.ascent-partners.com/ai-agents/)
[![Docs](https://img.shields.io/badge/docs-hkex--listco--updates.ascent--partners.com-blue)](https://hkex-listco-updates.ascent-partners.com/)
[![Ruff](https://img.shields.io/badge/lint-ruff-261230.svg)](https://github.com/astral-sh/ruff)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md)

[![PostgreSQL](https://img.shields.io/badge/PostgreSQL-4169E1?logo=postgresql&logoColor=white)](https://www.postgresql.org)
[![MySQL](https://img.shields.io/badge/MySQL-4479A1?logo=mysql&logoColor=white)](https://www.mysql.com)
[![SQLite](https://img.shields.io/badge/SQLite-003B57?logo=sqlite&logoColor=white)](https://sqlite.org)
[![MongoDB](https://img.shields.io/badge/MongoDB-47A248?logo=mongodb&logoColor=white)](https://www.mongodb.com)
[![Neo4j](https://img.shields.io/badge/Neo4j-4581C3?logo=neo4j&logoColor=white)](https://neo4j.com)
[![ClickHouse](https://img.shields.io/badge/ClickHouse-FFCC01?logo=clickhouse&logoColor=black)](https://clickhouse.com)
[![DuckDB](https://img.shields.io/badge/DuckDB-FFF000?logo=duckdb&logoColor=black)](https://duckdb.org)
[![SurrealDB](https://img.shields.io/badge/SurrealDB-FF00A0?logo=surrealdb&logoColor=white)](https://surrealdb.com)

An open-source Python tool that scrapes 25+ years of Hong Kong Stock Exchange (HKEx)
regulatory filings and ingests them into **any combination of nine databases** — with
full-text and table extraction, chunk-level coverage, optional graph linking, and a
**read-only MCP server** so AI agents can query the corpus or the live site.

<!-- mcp-name: io.github.simonplmak-cloud/hkex-filings -->

It speaks the undocumented HKEx JSON API directly, which is faster and more resilient than
driving a browser.

## Two ways to use it

| | **Hosted MCP gateway** | **Local pipeline** |
| --- | --- | --- |
| What | A public endpoint you point an AI agent at | The `hkex-scraper` CLI |
| Setup | None — paste a URL | `pip install` + one environment variable |
| Data | Live from HKEx, nothing stored | Stored in your database(s) |
| Docs | [Live MCP gateway](docs/live-mcp.md) · [AI agent support](docs/ai-agents.md) | [Getting started](docs/getting-started.md) |

![Example: install, scrape filings into SQLite, then query the hosted MCP gateway from an AI agent](https://raw.githubusercontent.com/simonplmak-cloud/hkex-filing-scraper/main/docs/assets/demo.svg)

## Use the hosted MCP gateway

POST, Streamable HTTP, **no API key**:

```text
https://hkex-listco-updates.ascent-partners.com/api/mcp
```

Three read-only tools: `get_server_info`, `search_filings` (a window of at most 31 days), and
`get_filing` (downloads one document and extracts its text and tables).

![Two ways to reach HKEx filings from an AI agent: the hosted MCP gateway or the local stdio server](https://raw.githubusercontent.com/simonplmak-cloud/hkex-filing-scraper/main/docs/assets/mcp.png)

Point a client at it — for example opencode:

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "hkex-live": {
      "type": "remote",
      "url": "https://hkex-listco-updates.ascent-partners.com/api/mcp"
    }
  }
}
```

Then ask:

```text
Use hkex-live to list the filings published between 2026-09-01 and 2026-09-18,
then summarise the interim report.
```

Ready-made configuration for Claude, ChatGPT, Cursor, VS Code/Copilot, Gemini CLI, opencode,
Manus, and Perplexity is in [AI agent support](docs/ai-agents.md) — and for a stored corpus,
the [stdio MCP server](docs/mcp.md) exposes a wider tool catalog. The gateway is listed in the
[official MCP Registry](https://registry.modelcontextprotocol.io/) as
`io.github.simonplmak-cloud/hkex-filings`.

## Quick start (local)

```bash
pip install hkex-filing-scraper        # core; SQLite needs no server
pip install "hkex-filing-scraper[all]" # Excel + dotenv + every driver + the MCP server
cp .env.example .env                   # then set DATABASE_TARGET (below)
hkex-scraper --metadata-only --limit 100
```

Optional extras: `excel`, `postgres`, `mysql`, `duckdb`, `mongodb`, `clickhouse`, `neo4j`,
`mcp`, `pdf`, `all`, `dev`.

`DATABASE_TARGET` is an ordered, comma-separated list of sink ids; the order decides which
sink serves reads. To start with no server:

```ini
DATABASE_TARGET=sqlite
SQLITE_PATH=hkex.db
```

`hkex-scraper` runs the full pipeline (metadata + documents + graph); `hkex-scraper
--full-history` covers everything since April 1999. The schema is created automatically.
Full install options and per-sink settings are in [Getting started](docs/getting-started.md).

## Database support

Every sink is a first-class destination; rows are in documented popularity order. The full
matrix — licenses, capability differences, per-engine notes — is in
[Database sinks](docs/sinks/README.md).

| Sink | Model | License | Extra | Idempotent upsert |
| ---- | ----- | ------- | ----- | ----------------- |
| `postgres` | relational | PostgreSQL License | `postgres` | `ON CONFLICT DO UPDATE` |
| `mysql` / `mariadb` | relational | GPLv2 | `mysql` | `ON DUPLICATE KEY UPDATE` |
| `sqlite` | relational | Public domain | — | `ON CONFLICT DO UPDATE` |
| `mongodb` | document | SSPL¹ | `mongodb` | `update_one(upsert=True)` |
| `neo4j` | graph | GPLv3 (Community) | `neo4j` | `MERGE` |
| `clickhouse` | columnar | Apache-2.0 | `clickhouse` | `ReplacingMergeTree` + read-merge |
| `duckdb` | relational | MIT | `duckdb` | `ON CONFLICT DO UPDATE` |
| `surrealdb` | graph + document | BSL 1.1¹ | — | `UPSERT` / `RELATE` |

¹ Source-available, not OSI-approved — labelled exceptions per
[ADR 0003](docs/adr/0003-sink-support-policy.md).

Valid sink ids, in documented order: `postgres`, `mysql`, `sqlite`, `mongodb`, `mariadb`, `neo4j`, `clickhouse`, `duckdb`, `surrealdb`. Set one variable and the same run feeds every sink:

```ini
# Order sets read precedence.
DATABASE_TARGET=postgres,sqlite
POSTGRES_DSN=postgresql://user:password@localhost:5432/hkex
SQLITE_PATH=hkex.db
```

## How it works

```mermaid
flowchart LR
    A[HKEx JSON API] --> B[Phase 1: metadata]
    B --> C[Canonical record]
    C --> D{DATABASE_TARGET}
    D --> E[(PostgreSQL)]
    D --> F[(MySQL / MariaDB)]
    D --> G[(SQLite)]
    D --> H[(MongoDB)]
    D --> I[(Neo4j)]
    D --> J[(ClickHouse)]
    D --> K[(DuckDB)]
    D --> L[(SurrealDB)]
    B --> M[Graph linking]
    M --> D
    B --> N[Phase 2: download and extract]
    N --> C
```

- **Phase 1** scrapes filing metadata through a JSF session, splitting the range into monthly
  chunks and deduplicating on a 16-character MD5 `filingId`.
- **Phase 2** downloads each filing's PDF/HTML/Excel document, extracts text and tables to
  Markdown, and writes the payload.
- **Graph linking** (optional) writes `has_filing` and `references_filing` edges when
  `COMPANY_TABLE` is set.
- **Failure isolation** — a failure on one sink is logged and counted but never blocks
  another; the run exits non-zero if any configured sink failed.

Deeper detail: [Architecture](docs/architecture.md) · [ADR 0002](docs/adr/0002-multi-sink-architecture.md).

## Features

- **Fast API scraping** — direct HKEx JSON API; no browser or Selenium.
- **Full history** — every filing from April 1999 to today, with chunk-level coverage checks.
- **Document processing** — PDF/HTML/Excel text and structured tables, extracted to Markdown.
- **Multi-sink** — any ordered combination of nine databases, each with native idempotent upserts.
- **AI-ready** — a hosted live MCP gateway plus a local stdio MCP server.
- **Resumable and observable** — batching, parallel downloads, stalled-job detection, per-sink
  counters, and `--coverage-report` / `--parity-report` / `--verify`.
- **Optional dependencies** — the core is `requests` + `beautifulsoup4`; drivers and document
  extraction are extras with graceful fallbacks.

## Documentation

- [Getting started](docs/getting-started.md) · [Configuration](docs/configuration.md) · [CLI](docs/cli.md)
- [Database sinks (matrix)](docs/sinks/README.md) — [PostgreSQL](docs/sinks/postgresql.md), [MySQL/MariaDB](docs/sinks/mysql.md), [SQLite](docs/sinks/sqlite.md), [MongoDB](docs/sinks/mongodb.md), [Neo4j](docs/sinks/neo4j.md), [ClickHouse](docs/sinks/clickhouse.md), [DuckDB](docs/sinks/duckdb.md), [SurrealDB](docs/sinks/surrealdb.md)
- [Live MCP gateway](docs/live-mcp.md) · [AI agent support](docs/ai-agents.md) · [MCP server](docs/mcp.md)
- [Architecture](docs/architecture.md) · [Troubleshooting](docs/troubleshooting.md) · [Testing](docs/testing.md)
- [Roadmap](docs/roadmap.md) · [De-risking register](docs/de-risking.md) · [Upgrading](docs/upgrading.md)
- [What's new](docs/news.md) · [Releasing](docs/releasing.md) · [Legal & Terms of Use](docs/legal.md) · [Changelog](CHANGELOG.md)
- **Docs site:** <https://hkex-listco-updates.ascent-partners.com/> · [Try it locally (`examples/`)](examples/README.md)

## Development

```bash
pip install -e ".[dev,all]"
ruff check           # lint (py310, line-length 100)
ruff format --check  # formatting
pytest               # unit tests (no DB or network required)
```

Tests are pure unit tests; SQLite and DuckDB contract tests run in-process, and integration
tests that need a server are skipped unless that sink is configured. See
[Testing](docs/testing.md).

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md); report security issues per [SECURITY.md](SECURITY.md).
Ideas and questions are welcome in
[Discussions](https://github.com/simonplmak-cloud/hkex-filing-scraper/discussions).

If this saves you time, a star helps others find it.

## License

MIT — see [LICENSE](LICENSE). That covers **this project's code only**; optional dependencies
carry their own licenses, notably the `pdf` extra (PyMuPDF / pymupdf4llm), which is
**AGPL-3.0** and deliberately excluded from `.[all]`. See
[docs/legal.md](docs/legal.md#third-party-licenses).

**Data & Terms of Use:** this is a research tool for the undocumented HKEx JSON API, and it is
not affiliated with or endorsed by HKEx. Commercial redistribution of HKEx data may require a
licensed HKEx feed; see [docs/legal.md](docs/legal.md).
