LlamaIndex Readers Integration: PrintFriendly
LlamaIndex reader for the PrintFriendly API. Give it web page addresses and it returns each article as a Document of clean Markdown, without ads, menus, cookie banners or other clutter.
Install
pip install -U llama-index-readers-printfriendly
Get an API key
Get a free key at printfriendly.com/me/api-key. The free plan includes 100 documents a month and needs no card. Paid plans include more.
export PRINTFRIENDLY_API_KEY="your key"
Or pass it in code with api_key="...".
To check that a key works without using a document, call the free usage endpoint. It is never counted:
curl -H "Authorization: Bearer $PRINTFRIENDLY_API_KEY" https://api.printfriendly.com/v2/usage
Usage
from llama_index.readers.printfriendly import PrintFriendlyReader
reader = PrintFriendlyReader() # reads PRINTFRIENDLY_API_KEY
documents = reader.load_data(
urls=[
"https://en.wikipedia.org/wiki/Eclipse",
"https://en.wikipedia.org/wiki/Solar_eclipse",
]
)
print(documents[0].text[:200])
print(documents[0].metadata)
# {'source': 'https://en.wikipedia.org/wiki/Eclipse',
# 'title': 'Eclipse - Wikipedia',
# 'site': 'en.wikipedia.org',
# 'author': 'Contributors to Wikimedia projects',
# 'date': '2001-11-02',
# 'excerpt': 'Totality during the solar eclipse of August 11, 1999, ...',
# 'image': 'https://thumb.wikimedia.org/wikipedia/commons/thumb/0/0d/Umbra01.svg/330px-Umbra01.svg.png?...',
# 'word_count': 5097}
Load lazily, one page at a time:
for document in reader.lazy_load_data(urls=urls):
print(document.metadata["title"])
Or asynchronously (uses httpx.AsyncClient):
documents = await reader.aload_data(urls=urls)
Pages are read one after another, in the order you gave them.
Build an index
from llama_index.core import VectorStoreIndex
index = VectorStoreIndex.from_documents(documents)
query_engine = index.as_query_engine()
print(query_engine.query("What is a total solar eclipse?"))
Each Document has the page URL as its doc_id. Loading the same page again and passing it through an IngestionPipeline with a docstore updates the stored copy instead of adding a second one.
The noisy metadata (source, image, word_count, excerpt) is excluded from the text that gets embedded, so embeddings are about the page and not about its URL. title, site, author and date stay in. The LLM sees source, title, site, author, date and excerpt.
Options
| Argument | Default | What it does |
|---|---|---|
api_key |
$PRINTFRIENDLY_API_KEY |
Your API key. It is never included in to_dict() or the repr. |
output |
"markdown" |
"markdown", "text" or "html". |
include_links |
False |
Keep link and image addresses in Markdown and text. Off by default: links keep their words and lose their addresses. |
timeout |
60 |
Seconds to wait for each response. The API answers within 27 seconds. |
max_retries |
2 |
Retries after a rate limit, a dropped connection or an error the API marks retryable. 0 turns retries off. |
continue_on_failure |
False |
Skip a page that cannot be read, with a logged warning, instead of raising. |
base_url |
https://api.printfriendly.com |
Only change this for testing. |
load_data, lazy_load_data, aload_data and alazy_load_data take urls: one address or a list. Each must be an http or https address.
Metadata
| Key | Meaning |
|---|---|
source |
The URL you passed in. |
title |
The page title. |
site |
The host name of the page. |
author |
The author, when the page names one. |
date |
The publish date as ISO 8601, when known. |
excerpt |
A short summary of the page. |
image |
The main image of the article. |
word_count |
Words in the extracted article. |
A field the page does not have is left out, not set to None.
Errors, retries and limits
Every failure raises PrintFriendlyError:
from llama_index.readers.printfriendly import PrintFriendlyError
try:
documents = reader.load_data(urls=urls)
except PrintFriendlyError as error:
print(error.code) # for example "fetch_failed" or "quota_exceeded"
print(error.retryable) # whether trying again can work
print(error.request_id) # quote this to PrintFriendly support
- Rate limits. Free keys allow 10 requests a minute. On a
429the reader waits for the time inRetry-Afterand tries again, up tomax_retriestimes. If the API asks for more than 60 seconds, the reader raises instead of blocking. - Retryable errors. Errors the API marks
retryable(a page that timed out, for example) are retried with a short backoff, up tomax_retriestimes. A dropped connection is retried too. A read timeout is not, because the document may already have been counted. - Everything else raises at once, with the API's error code and message:
invalid_parameters,fetch_failed,parse_failed,content_blocked,render_failedand so on. continue_on_failure=Trueskips pages that fail one by one. Key and plan errors (api_key_required,invalid_api_key,subscription_inactive,limit_exceeded,quota_exceeded) still raise, because every later page would fail the same way.- Billing. Only a successful read counts as a document on your plan. Failed reads are free. The plan limit comes back as
quota_exceededwith anupgrade_url.
See the API reference for every code.
Development
The project uses uv.
make test # unit tests, no network
make lint # ruff, mypy, codespell
make live_test # the real API; needs PRINTFRIENDLY_API_KEY and reads two pages
The tests mock the HTTP layer with respx, as LlamaIndex asks of integrations, and run with network access switched off. Coverage is above 90%. make live_test uses two documents of your plan and is skipped without the key.
Links
License
MIT. See LICENSE.
Metadata
Release files for llama-index-readers-printfriendly 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| llama_index_readers_printfriendly-0.1.0.tar.gz | 18.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| llama_index_readers_printfriendly-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 31.9 kB
Release files / llama_index_readers_printfriendly-0.1.0.tar.gz
| Download URL | llama_index_readers_printfriendly-0.1.0.tar.gz |
|---|---|
| Size | 18.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
79728756ba618d4a9952cdad7644402b4659bbf3f52466a23cb92de86f3382b1
|
|
BLAKE2b-256 checksum How to use checksums |
52be756e7fe68c0e02637dbfb2ef508fc3e359b306ccc4df4bb62e6f66229262
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.
Transparency logRelease files / llama_index_readers_printfriendly-0.1.0-py3-none-any.whl
| Download URL | llama_index_readers_printfriendly-0.1.0-py3-none-any.whl |
|---|---|
| Size | 13.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
921c2149a28bc527e1629337c48826bb92b0045ed44e520d9b99c05882a34bdb
|
|
BLAKE2b-256 checksum How to use checksums |
00b8db8bae9c45fe60202681a0d5bca276b9106b1d82ccd3cb5543108b1052a8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.
Transparency log