Skip to content
github-actions[bot] edited this page Oct 5, 2026 · 1 revision

Neo4j sink

Persist filings and graph edges to Neo4j as nodes and relationships.

  • License: GPLv3 (Community Edition) — OSI-approved open source. The neo4j Python driver is Apache-2.0, so connecting to a server does not affect this project's MIT license.
  • Driver: neo4j (Bolt).
  • Extra: neo4j.

Install

pip install ".[neo4j]"

Configure

DATABASE_TARGET=neo4j
NEO4J_URI=bolt://localhost:7687
NEO4J_USER=neo4j
NEO4J_PASSWORD=secret
# NEO4J_DATABASE=neo4j          # optional; omit for the default database

Run it:

hkex-scraper --database-target neo4j --limit 100

Constraints and indexes are created automatically on startup (Neo4j 5 IF NOT EXISTS syntax).

Graph model

(:Company {id})-[:HAS_FILING]->(:Filing {filingId})
(:Filing {filingId})-[:REFERENCES_FILING]->(:Company {id})

(:Filing) carries the filing metadata as properties (companyTicker, stockCode, title, filingDate, …) plus document properties (documentText, documentTables as a JSON string, documentTableCnt, documentStatus, …). referencedTickers is a string-array property. (:Company {id}) uses the ticker-derived key (451_HK), matching the other sinks.

Every write uses MERGE, so re-running is idempotent.

// filings for one company
MATCH (c:Company {id: '451_HK'})-[:HAS_FILING]->(f:Filing)
RETURN f.title, f.filingDate ORDER BY f.filingDate DESC LIMIT 10;

// filings that mention another company
MATCH (f:Filing)-[:REFERENCES_FILING]->(c:Company {id: '700_HK'})
RETURN f.filingId, f.title;

// documents that still need processing
MATCH (f:Filing)
WHERE f.documentStatus IS NULL AND f.documentUrl IS NOT NULL
RETURN count(f);

// most-referenced companies
MATCH (:Filing)-[r:REFERENCES_FILING]->(c:Company)
RETURN c.id, count(r) AS mentions ORDER BY mentions DESC LIMIT 10;

Notes and limitations

  • Nested data: Neo4j properties cannot hold nested maps, so documentTables is stored as a JSON string. Parse it client-side (apoc.convert.fromJsonList or in your application).
  • Company nodes are created by MERGE for every referenced/owning ticker; the SurrealDB sink, by contrast, only links tickers that already exist in the configured company table.
  • Traversal is where Neo4j shines; for tabular analytics prefer a relational sink.
  • Credentials are never logged.

Troubleshooting

Symptom Cause Fix
Neo4j sink requires the neo4j driver Extra not installed pip install ".[neo4j]"
Neo4j sink requires NEO4J_URI URI not set Set NEO4J_URI
Neo4j sink requires NEO4J_USER and NEO4J_PASSWORD Credentials missing Set both
ServiceUnavailable Server unreachable Check NEO4J_URI and the Bolt port (7687)
An equivalent constraint already exists Older Neo4j without IF NOT EXISTS Upgrade to Neo4j 5, or create the constraints manually

Home

Clone this wiki locally