PDF Table Extractor: Cross-Page Tables to CSV & JSON
Pricing
Pay per usage
PDF Table Extractor: Cross-Page Tables to CSV & JSON
Extract tables from PDFs and rejoin the ones a page break cut in half. Reads ruled and borderless tables, drops the header the file repeats on every page, names each column type, and returns one clean grid per table as JSON and CSV.
Pricing
Pay per usage
Rating
0.0
(0)
Developer
Ai-Q Labs
Maintained by CommunityActor stats
0
Bookmarked
3
Total users
2
Monthly active users
33 minutes ago
Last modified
Categories
Share
Extract tables from PDFs — and put back together the ones a page break cut in half.
Every other PDF table extractor hands you a table per page. A forty-page statistical release becomes forty fragments, each carrying its own copy of the header, and you finish the job by hand. This one recognises that a fragment continues overleaf, joins it to the one before, and deletes the header the file repeated.
Free to run. No key, no account beyond Apify, no per-page charge.
What it does
Give it links to PDF files. You get one row per table, with the grid as JSON and as CSV.
- Rejoins tables split across page breaks and drops the repeated header rows
- Reads ruled tables from the lines the file draws, including tables that rule only their columns and leave the rows to whitespace
- Reads borderless tables from column gaps that stay empty on every row
- Finds the header, including headers set two or three lines deep, and flattens
"June" over "2025" into
June 2025 - Names each column's type — number, currency, percent, date or text — after parsing
1,234,(1,234)as negative,1.234,56,98.6%,$12.50and footnote markers - Reports what it is unsure about: spanning cells, sparse grids, tables found from whitespace alone, and grids with no numeric column at all
Why this one
The PDF table Actors already on Apify were read before this was built. Between them they offer text, tables, metadata, accuracy scores and OCR. Not one mentions what happens when a table runs onto the next page — and every long table does.
| Others | This Actor | |
|---|---|---|
| Table split across pages | Two tables, header twice | One table, header once |
| Which pages a table came from | — | pages: [28, 29, 30, 31] |
| The document's caption | — | caption: "Table B-1. Employees on nonfarm payrolls…" |
| Column types | — | ["text","number","number",…] |
| Price | Pay per PDF or per page | Free |
Measured, not asserted
Both of these documents mark their own continuation pages, which gives a ground truth the run can be scored against. The test suite checks the extractor's answer against that list rather than against a number nobody can verify.
US Bureau of Labor Statistics, The Employment Situation (39 pages)
- pages the document prints "Continued" on: 13, 29, 30, 31
- pages absorbed by rejoining: 13, 29, 30, 31 — every one, and no others
- Table B-1 comes out as one table of 247 rows across four pages, with 12 repeated header rows removed
- Tables A-1, A-2 and A-3 have identical ten-column layouts and byte-identical headers. They stay three tables, because the caption says so.
Federal Reserve H.4.1 (11 pages, mixed landscape and portrait)
- three continuation pages hold a table; all three are absorbed
- Table 6 is rejoined even though it sits 8.6 pt further right on its second page
- Table 1A, which shares table 1's five columns in the same places, is not swallowed
Unit tests: 44 passing. Live tests against real PDFs: 36 passing.
Two details that decide whether this works at all
A vertical rule is often not a vertical line. In the BLS release every vertical rule in
every table is drawn as a horizontal stroke 0.6 pt long with a line width of 491 pt.
Stroking a segment paints a band of lineWidth around it, so a short fat stroke is a tall
thin bar. Read the segment and ignore the width and you find zero vertical rules on a
page full of them.
Rules say where the columns are; text says where the rows are. A typical BLS data page draws nine vertical rules and four horizontal ones — the verticals separate every column, the horizontals mark only the top, the header and the bottom. Forty data rows have no rule between them. Waiting for a full grid finds nothing; assuming rows are ruled finds three rows where there are forty. Which of the two applies is counted per table, not assumed.
Input
| Field | Default | What it does |
|---|---|---|
pdfUrls | — | Direct links to PDF files, one per line |
mergeAcrossPages | true | Rejoin tables split by a page break |
useWhitespaceTables | true | Also find tables that draw no lines |
onlyDataTables | false | Keep only tables with a numeric, currency, percent or date column |
includeCsv | true | Add a ready-to-save CSV string per table |
includeRecords | false | Add one object per row, keyed by the detected header |
minRows / minColumns | 2 / 2 | Floor on what counts as a table |
maxPages | 200 | Pages read per PDF |
maxRowsPerTable | 5000 | Rows kept per table |
maxFileMb | 50 | Download stops here rather than taking the run down |
maxConcurrency | 5 | PDFs at a time |
Output
One row per table:
Trimmed from a real run over the BLS release:
{"filename": "empsit.pdf","caption": "Table B-1. Employees on nonfarm payrolls by industry sector and selected industry detail","tableId": "empsit.pdf#28.1","pages": [28, 29, 30, 31],"splitAcrossPages": true,"pageFragments": 4,"repeatedHeaderRowsDropped": 12,"detectedBy": "rules","rowCount": 247,"columnCount": 10,"header": ["Industry", "June", "Apr.", "Not seasonally adjusted May p", "June p","June", "Apr.", "Seasonally adjusted May p", "June p", "Change from:"],"columnTypes": ["text", "number", "number", "number", "number","number", "number", "number", "number", "number"],"rows": [["Total nonfarm . . .", "159,299", "158,713", "159,398", "159,830","158,478", "158,798", "158,927", "158,984", "57"]],"csv": "Industry,June,Apr.,Not seasonally adjusted May p,…","confidence": 0.93,"status": "warning","issues": ["spanning_cells"],"notes": ["This table was split across pages 28, 29, 30, 31 and has been rejoined into one grid, dropping 12 repeated header row(s)."]}
The key-value store gets a SUMMARY with the totals and a list of every table that was
rejoined, with the pages it came from.
Issue codes
| Code | Meaning |
|---|---|
spanning_cells | A cell covers more than one column; its text is in the leftmost |
no_header_detected | The columns are unnamed |
no_ruling_lines | Found from whitespace alone — check the split before trusting it |
no_numeric_columns | Every column reads as text; may be a form rather than data |
sparse_grid | Under half the cells hold text |
rows_truncated / page_budget_reached | A limit was reached; raise it for the rest |
some_pages_have_no_text | Those pages are scans; any table on them is an image |
rotated_pages_skipped | Ruling lines were not used on rotated pages |
Limits, stated plainly
- No OCR. A scanned page has no text to read. It is reported, not guessed at.
- Rotated pages are read for text but not for rules, so a table there may be missed.
- Row-spanning cells are detected as column spans only; a cell that covers two rows lands in the upper one.
- Headers deeper than four lines keep their first four; the rest stay as body rows. The BLS table B-1 header is seven lines deep and shows this.
- A tax form is a grid, and it will be found as one.
onlyDataTablesis there for that. - Fragments are only joined when their columns line up and the caption or the repeated header agrees. A document that does neither keeps its fragments apart.
Built by Ai-Q Labs. Other Actors: PDF Inspector, PDF to Text & Markdown, HTTP Status Checker, Domain Availability Checker, Tech Stack Detector, Broken Link Checker, Bulk Domain Checker, AI Crawler Checker.