Library / SDK
lbq110/weread-exporter avatar
lbq110/weread-exporter

weread-exporter: exporting WeRead books to Markdown with a Canvas hook

A WeChat Read (Weread) full-book export tool - uses Playwright and a Canvas hook to extract complete book content as Markdown.

322 stars66 forksPythonLicense varies

At a glance

What is it?
weread-exporter drives the WeRead web reader with Playwright and intercepts Canvas fillText calls to reconstruct book text and images as interleaved Markdown. It works only for accounts and titles that the web reader will actually open.
Who is it for?
weread-exporter is for readers who already have WeRead web access to a title and want a local Markdown copy with images in reading order, and who accept roughly 1 to 2 seconds per page plus a two-step export and download. It is not for DRM-protected titles, titles the publisher has restricted to the app, or anyone wanting a supported API.
Can I use it commercially?
Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
Is it still maintained?
Yes. The repository last received commits 102 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 8, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Why a WeRead export needs a Canvas hook at all

The WeRead web reader does not put book text in the DOM. According to the README, the page renders text through Canvas, so a normal HTML scrape returns structure and images but no prose. Images are different: they are ordinary DOM img elements with a class matching wr_readerImage. That split is the whole reason this project exists. A generic scraper can pull the images and the table of contents, but not the sentences between them.

The tool is aimed at someone who wants a personal, offline Markdown copy of a book they can already read on the web. The README states the account must have reading rights to the target title, meaning an unlimited-card membership or a purchase. It is a personal export utility, not a library service, and the README asks that it not be used commercially or for wide redistribution.

How the Playwright and fillText pipeline reconstructs a page

The mechanism has six documented stages. Playwright launches Chromium and keeps a persistent login session, so a QR scan is needed once and later runs reuse the stored profile. An injected hook wraps CanvasRenderingContext2D.fillText() and records the (x, y) coordinates of every character the reader draws.

The next two stages handle the reader's layout quirks. WeRead renders the current page and the next page onto the same Canvas, so the tool detects the y-coordinate reset point and splits the two pages apart. For images, it filters by getBoundingClientRect so only img[class*="wr_readerImage"] elements inside the current viewport are captured; this excludes preloaded images belonging to the next page or chapter, which is what causes images to be attributed to the wrong page in naive scrapers.

Finally, text lines and images are sorted by their screen y coordinate and interleaved, then line breaks introduced by Canvas rendering are merged back into natural paragraphs. The README describes the result as text and illustrations interleaved in exact reading order. The export and the image download are deliberately separate: fetching large images during page turns would block the paging loop, so the first pass records URLs and the second pass downloads concurrently.

Installing weread-exporter and running a first export

The README gives two install commands: the Playwright package, then the Chromium browser binary it drives. There is no packaging step and no published release, so the scripts are run from the repository checkout.

bash
pip install playwright
playwright install chromium

The export script accepts either a full reader URL or a bare book_id. The README recommends the URL form. The first run opens a browser and waits for a QR login; the session is then stored under cache/browser_profile/ and reused.

bash
python export_precise.py https://weread.qq.com/web/reader/d31323b0813abaf26g0137c2

Once logged in, the script clicks the first entry in the table of contents to jump to the start of the book, then pages forward with the ArrowRight key until it reaches the end of the catalog. Interrupted runs restart the browser and resume from the last chapter. During this pass the Markdown references images by relative path such as images/chXXXX_imgNN.jpg, but the files are not yet on disk.

bash
python download_images.py d31323b0813abaf26g0137c2

The second script reads the URLs from raw/*.json, downloads with 8 threads, and skips files that already exist, so it can be rerun to fill in failures. Output lands under output/<book_id>/, with per-chapter Markdown in chapters/, images in images/, URL records in raw/, and a merged full-book file named after the book. Opening that merged file in Typora or Obsidian is the intended end state.

The IPv4 workaround and other operational friction

One detail in the README is worth reading before the first download run. On macOS, urllib tries IPv6 before IPv4 by default, and if the route is unreachable each image can stall for roughly 120 seconds. The script forces IPv4, which the README says restores second-level download times. That is a platform-specific workaround baked into download_images.py rather than a configurable option.

Paging speed is the other constraint: the README puts export at about 1 to 2 seconds per page. A long book therefore takes a while, and because the run is browser-driven, it depends on the reader's own rendering staying stable for the duration. The auto-resume behaviour exists precisely because runs do stall.

Where weread-exporter fails or is the wrong tool

The README is direct about the hard limits. A valid WeRead account with reading rights to the target book is mandatory. Some publishers restrict web reading entirely and show a prompt telling the user to read in the app; the README states those books cannot be exported. No amount of configuration fixes that, because the content is never served to the browser.

There is also a known accuracy gap. In image-heavy gallery chapters, captions and images can occasionally be paired off by one position. The README qualifies this: in ordinary prose chapters the position of images relative to paragraphs is accurate. So the tool is reliable for text with occasional figures and less so for art books or illustrated galleries.

If you need guaranteed fidelity, structured metadata, or a stable interface you can build on, this is the wrong tool. It is a browser automation script that depends on the reader's current rendering behaviour, and the README documents no versioning or compatibility guarantees.

How it compares with generic scraping and toolbox approaches

The searches people run around this project mix several different tools, so it is worth separating the approaches. A generic Playwright or requests scraper reads the DOM: it can collect the catalog and the img elements, but on WeRead it returns no book text, because the text lives in Canvas draw calls. That is the gap the fillText hook closes, and it is the single design decision that distinguishes this project from a plain scraper.

Browser-extension toolboxes and sync integrations take a different route again: they operate inside the reader session or push content to a note service, which means the export format and the destination are decided by that tool rather than by you. weread-exporter produces files on disk, with per-chapter Markdown, a merged full-book file, and an images directory you control. The trade-off is that you own the run, including the login, the paging time, and the second download step. There is no service to fall back on if a title is restricted to the app.

Maintenance, licensing and the cost of upgrading

The repository is not archived. The last push was on 2026-07-01, which is recent enough that the code has not been left to rot, but the project has no retrieved releases, no homepage, and a repository layout of four top-level files plus requirements.txt. There is no changelog to read, so an upgrade means diffing export_precise.py and download_images.py yourself.

The dependency surface is small: requirements.txt pins only playwright>=1.40. That is good for install simplicity and bad for reproducibility, because the Canvas hook depends on how the reader draws text and how Chromium exposes fillText. A Playwright or Chromium change, or a change on the WeRead side, can break the hook without any version number in this repository moving. Budget for that when deciding whether to build a workflow around it.

The licence is not stated in the README or the repository files, so the terms under which you may use or redistribute the code are unclear. Separately, the README's own declaration limits use to personal study and research and asks users to respect copyright. Those are two different questions, and neither is settled by the other; if you plan to redistribute anything, resolve the licence question first rather than assuming the README's statement covers it.

Editorial conclusion

weread-exporter is for readers who already have WeRead web access to a title and want a local Markdown copy with images in reading order, and who accept roughly 1 to 2 seconds per page plus a two-step export and download. It is not for DRM-protected titles, titles the publisher has restricted to the app, or anyone wanting a supported API. Before committing to a long book, verify three things: that the target title opens in the web reader at all, that the run reaches the end of the catalog, and that the interleaved output in output/<book_id>/chapters/ places images where the source did.

Frequently asked questions

Does weread-exporter need a paid WeRead account?

Yes. The README states you need a valid WeRead account with reading rights to the target book, meaning an unlimited-card membership or a purchase. Titles the publisher has restricted to the app cannot be exported at all.

Why does weread-exporter export text and download images in two separate commands?

Because downloading large images during page turns would block the paging loop. The export pass only records image URLs in raw/*.json and references them by relative path, and download_images.py fetches them afterwards with 8 threads.

Can weread-exporter resume an export that was interrupted?

Yes. The README states that if the run gets stuck it reopens the browser and continues from the last chapter. The image download script is also rerunnable, since existing files are skipped.

Official sources

  1. Issues
  2. lbq110/weread-exporter on GitHub
  3. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/lbq110-weread-exporter.svg)](https://hysenlabs.com/projects/lbq110-weread-exporter)