Public procurement scraper for French BOAMP and PLACE sources with SQLite persistence.
AOTracker automates the search for public procurement calls (AO) in France from two sources:
- BOAMP: Official Bulletin of Public Procurement Announcements (OpenData API)
- PLACE: Public Procurement Dematerialization Platform (web scraping)
The script searches daily for new AOs matching your keywords (BOAMP) and CPV codes (PLACE), stores them in an SQLite database, and sends you a Pushbullet notification if new AOs are found.
- Python 3.7+
- pip
# 1. Clone or download the project
cd AOTracker
# 2. Create a virtual environment
python -m venv venv
source venv/bin/activate # Linux/Mac
# or
venv\Scripts\activate # Windows
# 3. Install dependencies
pip install -r requirements.txt
# 4. Configure the project
cp config.local.cfg.example config.local.cfg
# Edit config.local.cfg with your BOAMP keywords and PLACE CPV codesEdit config.local.cfg (not versioned):
[BOAMP]
KEYWORDS =
Python
Docker
Sqlite
[PLACE]
CPV_CODES =
72000000
30220000
[DCE]
STORAGE_PATH = dce_storage
[Affichage]
Historique = 10
[Deadline]
ROUGE = 10
JAUNE = 20To receive notifications, create a .env file at the root:
API_KEY=your_pushbullet_key
PUSHBULLET_DEVICE_IDEN=your_optional_device_id
python run_daily.pyThe script:
- Scrapes BOAMP and PLACE
- Inserts/updates AOs in the database (
tracker.db) - Downloads DCE archives for newly inserted AOs (PLACE only) into
STORAGE_PATH/<scraper_source_name>/<reference>/ - Extracts ZIP archives (including nested ZIP files)
- Sends a notification if new AOs are found
AOTracker/
├── services/
│ ├── scraper.py # BaseScraper (ABC)
│ ├── database.py # AORecord + TrackerDatabase + AOPersistence
│ ├── config_utils.py # Config loading
│ ├── notifications.py # Pushbullet
│ └── source_runner.py # Standardized execution
├── boampgetter.py # BoampScraper (keywords)
├── place.py # PlaceScraper (CPV codes)
├── web_app.py # Flask API (query the DB)
├── run_daily.py # Orchestrator
└── tracker.db # SQLite (generated)
- run_daily.py loads config and creates scrapers
- BoampScraper + PlaceScraper inherit from BaseScraper and return
List[AORecord] - AOPersistence merges keywords (set union) and persists to SQLite
- send_daily_summary_notification sends a Pushbullet notification
python web_app.py
# Access: http://localhost:5000MIT