At a glance
For local files, count each readable PDF into a ledger with a filename, count and result. Sum confirmed numeric counts only and report the number of unresolved files separately. A folder count does not discover website links or automatically deduplicate files with different names.
The confirmed total is 28 pages plus one unresolved file, not 28 pages for the complete folder.
| File | Pages | Result |
|---|---|---|
| letter.pdf | 4 | counted |
| report.pdf | 24 | counted |
| protected.pdf | encrypted |
Scroll sideways to see all columns. Use arrow keys when the table is focused.
Work on a trusted, bounded folder
Make a separate working folder of PDFs you are authorized to inspect. The example reads files and writes a new CSV; it does not rewrite any source. Install pypdf in a local Python environment, record its version and keep the file list fixed while comparing results.
This short example is intended for trusted local files. It has no malware service, process isolation or time/memory limits and is not a production ingestion service. For an untrusted or very large collection, use a maintained isolated processing workflow with explicit resource limits.
Write a result for every file
Strict reading makes structural problems visible. Encrypted files are deliberately left unresolved; handle any authorized password through a separate secure workflow. A parser result should not be described as independent verification or accessibility validation.
The CSV keeps an empty count for failures and a fixed result label. When importing filenames into a spreadsheet, prefix names that begin with a spreadsheet-formula character so the sheet treats them as text.
from pathlib import Path
import csv
from pypdf import PdfReader
def safe_name(value):
risky = value.lstrip().startswith(('=', '+', '-', '@'))
return "'" + value if risky else value
with open('pdf-page-counts.csv', 'w', newline='', encoding='utf-8-sig') as out:
writer = csv.writer(out)
writer.writerow(['file', 'pages', 'result'])
files = (path for path in Path('pdfs').iterdir()
if path.is_file() and path.suffix.lower() == '.pdf')
for path in sorted(files):
pages, result = '', 'unreadable_or_invalid'
try:
with path.open('rb') as source:
reader = PdfReader(source, strict=True)
if reader.is_encrypted:
result = 'encrypted'
else:
count = len(reader.pages)
if count > 0:
pages, result = count, 'counted'
except Exception:
pass # Keep an unresolved row; never substitute zero.
writer.writerow([safe_name(path.name), pages, result])Check the ledger before using the total
Open several ordinary files in a viewer and compare their counts with the CSV. Investigate every unresolved row with the owner; do not remove it from the collection simply because it is inconvenient to count. Warning, encrypted and malformed states may need different tools or a fresh source export.
If duplicate handling matters, add a byte-digest column and a clearly documented grouping rule before totaling unique pages. If you instead sum every counted row, label the measure pages across local files. Keep the script, parser version, file-list date and unresolved count with the reported number.
- Inspect the CSV’s result column.
- Sum only counted rows.
- Show unresolved files beside the total.
- Preserve a dated ledger and document any duplicate rule.
Sources and scope
Methods, worksheets and illustrative examples are DocAccessible editorial contributions. These references support the technical or product details described above. W3C techniques are informative guidance; they are not a certification scheme.
- QPDF: PDF inspection and exit status
Structural inspection, page-count options and warning versus error outcomes.
- pypdf: reading PDF files
Encrypted-reader behavior and the need for an authorized password.
- Python hashlib documentation
SHA-256 hashing of file bytes.
Written by the DocAccessible team under our editorial policy. Suggest a correction if a source or product detail needs updating.
See the evidence in a working example
Explore the public sample inventory report to try its table, full-screen view and missing-count reasons. The downloadable counting fixture check records what the local counter returned for seven self-authored inputs, with hashes and explicit limits.