scrape monitoruloficial.ro/e-monitor → save pdfs → get text → make nice / explorable.
This repo was extracted from a larger monorepo into a standalone repo. Scripts use
__file__-relative paths for both imports (utils/) and data (data/), so cwd does not
matter — you can invoke them from anywhere, including via cron. The old advice to run from
the repo root is no longer required.
python main.py # orchestrator: get_index.py then fetch_p3+.py, last 2 weeks
python main.py -start 2024-01-01 # from a given date (forwarded to both scripts)
python get_index.py # fetch daily index → SQLite + HTML cache (last 15 days)
python get_index.py -start 2024-01-01 -end 2024-03-01
python get_index.py -m l-7 # last 7 days
python get_index.py -m all # from 2000-01-04 through today
python fetch_p3+.py # download ephemeral Parts III–VII (online only ~10 days)
python fetch_pdfs.py # download persistent Parts I/II PDFs
python concat_pages.py # merge per-page PDFs into single-document PDFs
python concat_pages.py --dry-run # preview without writing
python convert.py # convert PDFs → Markdown (all sections, skip existing)
python convert.py -s PV # convert one section (smoke test)
python convert.py -s PI PII PIM PV # flat sections only (ready without concat_pages.py)
python convert.py --overwrite # force reconvert
python convert.py --dry-run # preview counts only
python mof-convert-txt.py # experimental PyPDF2 PDF→markdown conversion (superseded)Dependencies (no requirements file — install manually): requests, beautifulsoup4, tqdm,
urllib3, PyPDF2, pypdf (for concat_pages.py), poppler / pdftotext
(for convert.py — install via brew install poppler on macOS). Python 3.
CLI flags are per-script:
| script | flags |
|---|---|
get_index.py |
-start -end --overwrite/--no-overwrite -m/--mode --debug |
fetch_p3+.py |
-start -end -days --overwrite/--no-overwrite -m/--mode --debug |
fetch_pdfs.py |
-start -end --debug |
concat_pages.py |
[root] --dry-run --overwrite/--no-overwrite --debug |
convert.py |
-s/--sections --dry-run --overwrite/--no-overwrite -w/--workers --debug |
main.py |
-start only |
--overwrite re-processes items already on disk; --no-overwrite (the default) skips them.
-m l-<x> means "last x days" (e.g. -m l-7). -m all runs from 2000-01-04 to today.
Two tiers: build a per-day index of available parts, then download the PDFs for those parts.
get_index.py— POSTs{today, rand}to.../emonitor/get_mo.phpper weekday, parses the returned HTML (div.card-body→ol.breadcrumbsection name +a.btnlinks), and upserts one row per day into SQLite. Also writes a prettified HTML snapshot per day todata/html_cache/<date>.html. Skips days already in the DB unless--overwrite.fetch_pdfs.py— reads DB rows and downloads the persistent parts (Parts I, II, PIM, V) intodata/<Px>/<year>/, skipping files that already exist. Accepts-start/-endto scope by date (e.g.-start 2011-01-01skips pre-2011 rows that have no PDFs on the server); without flags, processes all rows newest-first.fetch_p3+.py— downloads only the ephemeral parts (shy_parts=["III-a","IV-a","VI-a","VII-a"], online ~10 days). Multi-step per part: scrapevar fidfrom the part page → POSTgidf.phpfor page count + folder → download each page as a separate PDF plus a jsonp into thedata/pdfs/_p3+/tmp/<date>/<filename>/staging tree. Page PDFs are not yet concatenated (see roadmap). Anti-ban: paces every request with random sleeps, retries at most once (retry bursts keep the server's IP ban hot), and aborts after 2 consecutive network failures — the abort path logs anerrorrun record (via_abort()), including arequests_madecounter, so the run doesn't show up as a dangling?instats.py.mof-convert-txt.py— standalone PyPDF2 experiment that converts sample PDFs to markdown.
shy_parts is the contract between the two PDF scrapers: fetch_pdfs.py skips those parts,
fetch_p3+.py fetches only them.
Imported as from common import ... (after sys.path.append("utils/")). Provides:
Helpers
generate_dates(start, end, format)— weekday-only date listparse_date(value)— returnsdatetime; accepts either a string or adatetimeunchangedbase_headers(which)— pre-built request headers ('headers1'for AJAX/POST,'headers2'for document GETs)make_session()—requests.Sessionwith single-retry, ban-aware backoffis_pdf(content)/pdf_ok(resp)— validate a response body before writing to disksection_dir(sectiune)— maps any section name to itsdata/<Px>folder code (exact then substring match)setup_logging(debug, logfile)— shared logging configuration (INFO or DEBUG)readfile(path)/writefile(path, content)— encoding-tolerant file I/O
Canonical constants (single source of truth for all scripts)
- Paths:
DATA_ROOT,DB_PATH,HTML_CACHE - URLs:
URL_BASE,URL_GET_MO,URL_GIDF,URL_VIEW - Data model:
PART_FOLDER(full-name → folder code),SHY_PARTS,TABLE_NAME - Pacing:
PACE(between documents),PACE_PAGE(between pages of the same document)
SQLite data/mo.db, table dates_lists: date TEXT PRIMARY KEY, json TEXT. The JSON maps each
section name (e.g. "Partea I") → { part-number: href }.
PDFs land under data/<Px>/<year>/ for all parts. For the ephemeral parts (PIII, PIV, PVI,
PVII), fetch_p3+.py stages per-page downloads in data/<Px>/<year>/<date>/<filename>/ before
concatenation (roadmap). <Px> is one of PI, PII, PIII, PIV, PV, PVI, PVII,
PIM (Partea I Maghiară). The whole data/ dir is gitignored.
- Rate limiting everywhere: random sleeps between requests + a longer pause every N items.
Pacing constants live in
utils/common.py(PACE,PACE_PAGE). urllib3.disable_warnings(...)+verify=Falsethroughout — the site has SSL issues.get_index.py,fetch_p3+.py, andconvert.pyend withos.system('say -v ioana ...')— a macOS-only spoken "done" announcement, guarded bysys.platform == 'darwin'.fetch_p3+.pywrites a<name>.donefile (containing the page count) only after all pages download successfully. This is the resumability marker: a partial download is re-attempted on the next run; a complete one is skipped._obsolete/andtestbench/subdirs (gitignored) hold scratch/old versions — ignore them.
main.py orchestrator (get_index.py + fetch_p3+.py; forwards only -start)
get_index.py day index → SQLite + HTML cache
fetch_pdfs.py Part I/II PDFs (persistent)
fetch_p3+.py Parts III–VII PDFs (ephemeral, ~10 days)
concat_pages.py merge per-page PDFs into single-document PDFs
convert.py batch PDF → Markdown (pdftotext; sibling .md output; supersedes mof-convert-txt.py)
mof-convert-txt.py one-off PDF→markdown experiment (superseded by convert.py)
utils/common.py shared helpers, constants, session factory
toolbench/ maintenance one-offs (e.g. cleanup-p3folder.py)
docs/ backlog.md / activity-log.md
data/ gitignored: mo.db, html_cache/, PI/ PII/ PIII/ … PVII/, text/
All scripts write timestamped log entries to data/logs/mof.log (5 MB rotating, 10 backups).
Each run is also recorded in the runs table of mo.db.
Check recent run history:
python stats.py # last 20 runs, all scripts
python stats.py --last 50
python stats.py --script fetch_p3+.pyStatus icons: ✓ ok · ~ partial (some errors) · ✗ error (crash)
- Clone the repo on the VPS.
- Copy the index DB from local (one-time — the DB holds all PDF URLs, ~6 MB):
rsync -avz data/mo.db user@vps:/path/to/repo/data/
- Run
fetch_pdfs.pyto download all missing PI/PII PDFs. Use-start 2011-01-01to skip the pre-2011 rows that have no PDFs on the server:Already-existing files are skipped automatically; safe to re-run at any time to fill gaps.python fetch_pdfs.py -start 2011-01-01
- Run
fetch_p3+.pyfor ephemeral parts (Parts III–VII are only online for ~10 days):python fetch_p3+.py # defaults to last 10 days - Set up cron — copy and adapt
docs/crontab.example.
Do not commit mo.db to git — each machine's cron updates it independently. Committing it produces constant binary-file merge conflicts. Use rsync for the initial copy; cron keeps it current on the VPS.
-
local cache
- fetch daily părți
- download pdfs
- fetch P-III - P-VII (online for 10 days)
- bypass rate limiting, rotating proxies or VPN
- fetch individual pages
- fetch jsonp's
- concatenate pdfs (
concat_pages.py)- OCR needed pages
-
structured text/html from PDF
- PDF → Markdown (
convert.py, pdftotext) - PDF → HTML see pdf2txt.xslx
- split into chapters → initial UI
- NLP, detect entities
- PDF → Markdown (
-
UI
-
updater cron
-
notifications
-
annotations, relative links
Scraping: ciocan/monitorul-ii, Ansvar-Systems/Romanian-law-mcp, v-khdumi/MonitorulOficialPDF
| proiect | obs | price |
|---|---|---|
| monitoruljuridic.ro | no formatting | gratis |
| ro-lex.ro | gratis | |
| monitorul.ai | ||
| lege-online.ro | ||
| lege5.ro | formatted, linked | paid |
| idrept.ro | formatted, linked | paid |
vezi și: senisioi/rolegal