How to Parse All 16 SDS Sections from PDF to JSON Without Sending the Document to an LLM
A deterministic workflow for text PDFs, scanned documents, page-level evidence, warnings, and revision checks
Safety data sheets look standardized until you try to process them as data. A human sees Sections 1 through 16. A script sees repeated headers, page breaks, tables, inconsistent heading styles, and sometimes nothing but scanned pixels.
If your downstream system needs structured JSON, sending the whole PDF to an LLM is not the only option. For a narrow, standardized document type, a deterministic pipeline can be easier to audit: extract text locally, identify known section headings, attach page evidence, and surface anything uncertain as a warning instead of silently filling gaps.
This article walks through that approach and a real public test document.
Scope note: this is document extraction, not a chemical, legal, or regulatory compliance determination. A parser can show what the document says and where it says it. It cannot certify that the document itself is correct.
The output contract matters more than raw text
Plain PDF-to-text output is useful for search, but weak for automation. An EHS intake workflow or supplier-onboarding job usually needs a stable record:
{
"status": "ok",
"sourceUrl": "https://www.fishersci.com/store/msds?...",
"extractionMethod": "text",
"pageCount": 11,
"sectionCount": 16,
"identifiers": {
"productName": { "value": "Acetone", "page": 1 },
"revisionDate": { "value": "18-Dec-2025", "page": 1 },
"casNumbers": [{ "value": "67-64-1", "page": 1 }],
"unNumbers": [{ "value": "UN1090", "page": 7 }]
},
"warnings": []
}
The important fields are not only the extracted values. extractionMethod, page numbers, section count, and warnings tell the next system whether the record can flow automatically or should go to a review queue.
A five-stage deterministic pipeline
1. Fetch the actual PDF safely
Accept a public HTTPS URL or an uploaded file, but treat the input as untrusted. Validate that the response is really a PDF, reject URLs with embedded credentials, block private and reserved network targets, cap file size, and bound retries.
The implementation used here limits each run to five documents, 15 MB and 50 pages per document. Temporary HTTP failures such as 429, 502, and 503 get up to three attempts. A persistent failure becomes an explicit failed item; the parser never substitutes a different document.
2. Prefer embedded text, then use OCR as a fallback
For text PDFs, preserve approximate layout during extraction. That reduces damage to headings and tables compared with flattening every token into one stream.
For scanned PDFs, render pages at a bounded resolution and run OCR locally. Auto mode should switch to OCR only when embedded text is missing or too sparse. This keeps the common path fast while still handling image-only files.
In this implementation, Poppler handles text extraction and page rendering, while Tesseract provides English and German OCR. The document is processed inside the Actor container and is not sent to a third-party LLM provider.
3. Detect section boundaries, not arbitrary topics
An SDS has a known section sequence. Use explicit English and German heading patterns, then validate the result:
- Which of Sections 1–16 are present?
- Is any section duplicated?
- Are sections out of order?
- Which pages does each section span?
This sounds simple, but production documents contain traps. For example, a line that mentions “Section 313” inside regulatory text is not the start of SDS Section 3. Repeated running headers should not automatically create duplicate sections. Those cases need targeted parsing rules and regression tests.
Each returned section should include its detected heading, full normalized text, start and end page, and a short evidence excerpt. When a heading is missing, return a warning rather than inventing the section.
4. Extract narrow identifiers with evidence
Known identifiers are good candidates for deterministic extraction:
- product name;
- revision date;
- CAS and EC numbers;
- H and P statements;
- UN transport numbers.
Return the page and evidence excerpt with every value. The excerpt makes human review faster and helps distinguish a real identifier from a number that merely matches the same pattern.
Not every SDS contains every identifier in a form the parser recognizes. An empty array is safer than a guessed value.
5. Make failure visible
A successful container exit is not enough. A useful batch contract distinguishes:
ok: all requested documents parsed;partial: at least one document parsed and at least one failed;failed: no document parsed.
Individual failed documents should remain visible in the Dataset, while the run itself should fail if every document fails. That prevents a scheduler from treating an empty result as healthy.
What happened on a public Fisher Scientific SDS
I ran this workflow against Fisher Scientific's public acetone SDS. The most recent checked public task run, on September 10, 2026, produced:
- 11 pages processed through the embedded-text path;
- all 16 standard sections detected;
- product
Acetoneon page 1; - revision date
18-Dec-2025on page 1; - CAS
67-64-1on page 1; - transport number
UN1090on page 7; - zero extraction warnings.
The result is reproducible from the public example task. The source document is public, so you can compare the JSON with the original pages instead of trusting a screenshot or a hand-picked snippet.
The test suite also covers synthetic English and German 16-section documents, missing and duplicate headings, private-network blocking, a controlled Section 9 revision, and an image-only OCR fixture. These tests establish specific supported behaviors; they are not an accuracy percentage across every SDS layout in the wild.
Comparing two revisions
If you supply one current SDS plus a previous revision, normalize both into the same section structure and compare section by section. Return one of four statuses for each section:
{
"section": 9,
"status": "changed"
}
This is deliberately a coarse change detector. It tells a reviewer where to look; it does not interpret whether a change is material for workplace safety or compliance.
When this approach fits — and when it does not
Use a deterministic SDS parser when you need repeatable structure, page-level traceability, bounded processing, and explicit exceptions. It fits intake pipelines, inventories, revision monitoring, and automation tools such as n8n or Make.
Do not use it as the final authority for chemical correctness, regulatory classification, or safety decisions. Also expect to add test fixtures when a new supplier uses an unusual layout. A narrow parser becomes reliable through representative documents and regression tests, not through confident wording.
I packaged the implementation as the SDS/MSDS PDF Parser on Apify. You can run the public acetone example first, inspect the Dataset, and then replace the URL with a document you are allowed to process.
If you test a different public SDS and find a missed heading or identifier, the most useful bug report includes the public URL, expected section, actual warning, and run ID. Do not attach confidential safety documents to a public issue.
Disclosure: this article was prepared with AI assistance and reviewed against the parser source code, automated tests, and the cited public Apify run. The Actor is my own project.

