Neo4j sink¶
Persist filings and graph edges to Neo4j as nodes and relationships.
- License: GPLv3 (Community Edition) — OSI-approved open source. The
neo4jPython 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
documentTablesis stored as a JSON string. Parse it client-side (apoc.convert.fromJsonListor in your application). - Company nodes are created by
MERGEfor 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 |