<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: ゴエンチョウ</title>
    <description>The latest articles on DEV Community by ゴエンチョウ (@yolow92).</description>
    <link>https://dev.to/yolow92</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4200665%2F387d48c4-1725-44b0-9568-73fc14cc0792.jpg</url>
      <title>DEV Community: ゴエンチョウ</title>
      <link>https://dev.to/yolow92</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yolow92"/>
    <language>en</language>
    <item>
      <title>One Engine, Seven Open-Source Projects: PageSift 1.4 and the yolow Family</title>
      <dc:creator>ゴエンチョウ</dc:creator>
      <pubDate>Sat, 14 Nov 2026 01:37:54 +0000</pubDate>
      <link>https://dev.to/yolow92/one-engine-seven-open-source-projects-pagesift-14-and-the-yolow-family-o19</link>
      <guid>https://dev.to/yolow92/one-engine-seven-open-source-projects-pagesift-14-and-the-yolow-family-o19</guid>
      <description>&lt;p&gt;PageSift started as the ingestion layer of an AI research platform I was building: something had to fetch web pages and documents and turn them into clean Markdown that an LLM pipeline could actually use. It grew into a self-hosted web scraper, crawler and document parser — Firecrawl- and Jina-compatible API, MCP server, Python/Node SDKs, one Rust binary.&lt;/p&gt;

&lt;p&gt;Over the 1.x series I kept doing one thing: every time a piece of the engine turned out to be generally useful, I cut it out and gave it its own repository, its own versioning, and an MIT license. PageSift 1.4 finishes that split — and adds the subsystem the extraction was making room for.&lt;/p&gt;

&lt;h2&gt;
  
  
  PageSift 1.4
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The split, completed.&lt;/strong&gt; Wave one (1.2.7) turned the base64, PDF, HTML and content crates into thin shims over standalone libraries. Wave two (1.4.0) does the same for the remaining three: &lt;code&gt;pagesift-office&lt;/code&gt; → &lt;strong&gt;yolowdoc&lt;/strong&gt;, &lt;code&gt;pagesift-syntax&lt;/code&gt; → &lt;strong&gt;yolowsyntax&lt;/strong&gt;, &lt;code&gt;pagesift-compress&lt;/code&gt; → &lt;strong&gt;yolowzstd&lt;/strong&gt;. The engine is now a composition of seven independently versioned open-source projects — and since 1.3, PageSift itself is Apache-2.0 (it was AGPL before).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DataGraph.&lt;/strong&gt; The new crate, &lt;code&gt;pagesift-graph&lt;/code&gt; (~4,000 lines), is what the cleanup was for. A normal scraper returns text and forgets everything else. DataGraph keeps the receipts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every fetch becomes a node; links between documents become &lt;strong&gt;citations&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Provenance&lt;/strong&gt; is recorded as edges: redirect chains, canonical alias clusters, attachments and their parent documents.&lt;/li&gt;
&lt;li&gt;Refetch the same resource and you get &lt;strong&gt;version lineage&lt;/strong&gt; instead of an overwrite.&lt;/li&gt;
&lt;li&gt;Extracted fields carry &lt;strong&gt;evidence spans&lt;/strong&gt; — byte offsets into the source document, so any claim can be pointed back at the exact text it came from.&lt;/li&gt;
&lt;li&gt;Claims about the same entity (keyed by subject keys like &lt;code&gt;sku&lt;/code&gt;) are clustered into &lt;strong&gt;facts&lt;/strong&gt; across sources, and when sources disagree you get a &lt;strong&gt;conflict record&lt;/strong&gt;: both variants, both excerpts, and a preferred variant with its reason. Two pages pricing the same product at $10 and $12 stop being a silent data-quality bug and become a queryable fact.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's exposed everywhere PageSift is: CLI (&lt;code&gt;scrape --graph&lt;/code&gt;, &lt;code&gt;crawl --graph&lt;/code&gt;, &lt;code&gt;graph inspect&lt;/code&gt;), the HTTP API (a &lt;code&gt;graph&lt;/code&gt; slice on scrape responses, plus &lt;code&gt;/v2/graph/history&lt;/code&gt; and &lt;code&gt;/v2/graph/conflicts&lt;/code&gt;), the MCP tool &lt;code&gt;pagesift_graph_query&lt;/code&gt;, and the Python and Node SDKs.&lt;/p&gt;

&lt;p&gt;An LLM pipeline doesn't just need text. It needs to know where a claim came from, what it contradicts, and which version is current. That's the layer DataGraph adds.&lt;/p&gt;

&lt;h2&gt;
  
  
  The six libraries
&lt;/h2&gt;

&lt;h3&gt;
  
  
  yolow64 0.3.0 — base64, ~20 GB/s
&lt;/h3&gt;

&lt;p&gt;A fast Base64 codec with runtime-dispatched SIMD (AVX2/NEON), zero runtime dependencies, streaming and data-URL APIs. The 0.3 release added dedicated short-input kernels and fused hash-table tail processing: around &lt;strong&gt;20.4 GB/s from 8 KiB to 1 MiB&lt;/strong&gt;, and short payloads (8–64 bytes) got 1.5–2.8× faster than in 0.2. Short inputs are the common case in real pipelines — tokens, hashes, embedded blobs — so that's where the work went.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cargo add yolow64&lt;/code&gt; · &lt;a href="https://github.com/YOLOW92/yolow64" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://crates.io/crates/yolow64" rel="noopener noreferrer"&gt;crates.io&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  yolowpdf 0.1.2 — PDF to Markdown, no C libraries
&lt;/h3&gt;

&lt;p&gt;A PDF-to-Markdown library and OCR engine that parses the format itself — no Poppler, no MuPDF, nothing to link against. The workspace includes GPU-accelerated OCR (PP-OCRv6). The README benchmark clocks &lt;strong&gt;1,900 pages/s&lt;/strong&gt; on born-digital documents (i7-10700). This is the component that taught the family its hardest rule: every allocation a hostile file can influence is budgeted &lt;em&gt;before&lt;/em&gt; it happens.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cargo add yolowpdf&lt;/code&gt; · &lt;a href="https://github.com/YOLOW92/yolowpdf" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://crates.io/crates/yolowpdf" rel="noopener noreferrer"&gt;crates.io&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  yolowhtml 0.1.0 — arena HTML5 parsing and extraction
&lt;/h3&gt;

&lt;p&gt;Two crates: &lt;code&gt;yolowhtml-dom&lt;/code&gt;, a zero-copy HTML5 parser over a contiguous arena (&lt;code&gt;Vec&amp;lt;Node&amp;gt;&lt;/code&gt;, &lt;code&gt;u32&lt;/code&gt; ids, CSS selectors), and &lt;code&gt;yolowhtml&lt;/code&gt;, a single-pass main-content extractor that produces Markdown, metadata and publication dates from the same parse. On the README's test corpus it scores &lt;strong&gt;1.000 structural F1&lt;/strong&gt; for Markdown across 15 page categories, and &lt;strong&gt;0.945 F1&lt;/strong&gt; for publication-date extraction across 20 languages. Both crates are &lt;code&gt;#![forbid(unsafe_code)]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cargo add yolowhtml&lt;/code&gt; · &lt;a href="https://github.com/YOLOW92/yolowhtml" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://crates.io/crates/yolowhtml" rel="noopener noreferrer"&gt;crates.io&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  yolowzstd 0.1.2 — pure-Rust Zstandard
&lt;/h3&gt;

&lt;p&gt;A Zstandard (RFC 8878) encoder/decoder written from the spec: zero dependencies, &lt;code&gt;#![forbid(unsafe_code)]&lt;/code&gt;, multi-core encode/decode that emits standard concatenated frames — interoperability is tested against the official zstd CLI. It's built for the small-record end of the world: on 64-byte payloads it decodes at &lt;strong&gt;3,257 MB/s (1.6× C libzstd)&lt;/strong&gt; and encodes at &lt;strong&gt;264 MB/s (4.1×)&lt;/strong&gt; on an i7-10700, which is exactly the shape of PageSift's cache records. One stated gap: no dictionary support yet.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cargo add yolowzstd&lt;/code&gt; · &lt;a href="https://github.com/YOLOW92/yolowzstd" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://crates.io/crates/yolowzstd" rel="noopener noreferrer"&gt;crates.io&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  yolowsyntax 0.1.2 — tree-sitter, without the C
&lt;/h3&gt;

&lt;p&gt;A pure-Rust runtime for tree-sitter grammars: the C runtime reimplemented in Rust, with &lt;strong&gt;41 languages'&lt;/strong&gt; parse tables converted and embedded — 6.22 MiB total, Zstd-compressed (with yolowzstd, its only runtime dependency) and decompressed lazily per language. It extracts code outlines for 32 symbol-bearing languages. No C FFI, &lt;code&gt;#![forbid(unsafe_code)]&lt;/code&gt;. Stated limits: no Query DSL and no incremental re-parsing.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cargo add yolowsyntax&lt;/code&gt; · &lt;a href="https://github.com/YOLOW92/yolowsyntax" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://crates.io/crates/yolowsyntax" rel="noopener noreferrer"&gt;crates.io&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  yolowdoc 0.1.1 — every document format, one call
&lt;/h3&gt;

&lt;p&gt;Documents to Markdown behind a single &lt;code&gt;convert(bytes, format, opts)&lt;/code&gt;: Office (DOCX/XLSX/PPTX, plus legacy DOC/XLS/PPT and XLSB), OpenDocument, RTF, WordPerfect, HWP/HWPX, e-books (EPUB/MOBI/FB2), email (EML/MBOX/MSG), archives (ZIP/TAR/7Z/GZ/BZ2/XZ — the decompressors are pure Rust too), SQLite, dBASE, CSV and Jupyter notebooks. The per-format parsers go deep — pivot tables, charts, equations — and a &lt;code&gt;Limits&lt;/code&gt; budget is checked before any work starts.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cargo add yolowdoc&lt;/code&gt; · &lt;a href="https://github.com/YOLOW92/yolowdoc" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://crates.io/crates/yolowdoc" rel="noopener noreferrer"&gt;crates.io&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How they fit together
&lt;/h2&gt;

&lt;p&gt;The family is a small lattice, not a pile: PageSift depends on all six libraries; yolowsyntax depends on yolowzstd; yolowdoc builds on both; yolowpdf uses yolow64. The rules are shared across all of them — pure Rust, no C dependencies, unsafe code forbidden in the libraries, and resource budgets enforced before allocation, because every one of these parsers eats untrusted input for a living.&lt;/p&gt;

&lt;p&gt;PageSift is the engine. These six are its organs, now living their own lives — each usable without the others, each on crates.io under MIT, with PageSift itself under Apache-2.0.&lt;/p&gt;

&lt;p&gt;Links: &lt;a href="https://github.com/YOLOW92/pagesift" rel="noopener noreferrer"&gt;PageSift&lt;/a&gt; · &lt;a href="https://github.com/YOLOW92/yolow64" rel="noopener noreferrer"&gt;yolow64&lt;/a&gt; · &lt;a href="https://github.com/YOLOW92/yolowpdf" rel="noopener noreferrer"&gt;yolowpdf&lt;/a&gt; · &lt;a href="https://github.com/YOLOW92/yolowhtml" rel="noopener noreferrer"&gt;yolowhtml&lt;/a&gt; · &lt;a href="https://github.com/YOLOW92/yolowzstd" rel="noopener noreferrer"&gt;yolowzstd&lt;/a&gt; · &lt;a href="https://github.com/YOLOW92/yolowsyntax" rel="noopener noreferrer"&gt;yolowsyntax&lt;/a&gt; · &lt;a href="https://github.com/YOLOW92/yolowdoc" rel="noopener noreferrer"&gt;yolowdoc&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Try them, break them, and tell me where they break — issues and benchmarks from other machines are the most useful feedback I can get.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>opensource</category>
      <category>programming</category>
      <category>ai</category>
    </item>
    <item>
      <title>yolow64 0.2: a new decode kernel that beats Turbo-Base64 under full validation</title>
      <dc:creator>ゴエンチョウ</dc:creator>
      <pubDate>Wed, 11 Nov 2026 15:32:02 +0000</pubDate>
      <link>https://dev.to/yolow92/yolow64-02-a-new-decode-kernel-that-beats-turbo-base64-under-full-validation-55nn</link>
      <guid>https://dev.to/yolow92/yolow64-02-a-new-decode-kernel-that-beats-turbo-base64-under-full-validation-55nn</guid>
      <description>&lt;h1&gt;
  
  
  yolow64 0.2: a new decode kernel that beats Turbo-Base64 under full validation
&lt;/h1&gt;

&lt;p&gt;yolow64 is the Base64 codec extracted from &lt;a href="https://github.com/YOLOW92/pagesift" rel="noopener noreferrer"&gt;PageSift&lt;/a&gt;, published as 0.1 on Nov 9. Version 0.2 (plus a docs-only 0.2.1) is out, and the headline is decode.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decode: new kernel
&lt;/h2&gt;

&lt;p&gt;The bulk decoder was replaced with a four-block AVX2 hash loop using clean-room translation/validation tables, speculative validation, and per-quad constants held in registers. Full validation is retained for every block — nothing was traded away for speed. BMI2 &lt;code&gt;pext&lt;/code&gt; tails handle padded 24- and 172-character inputs, and exact-length no-padding inputs of 16/32/64/128 characters bypass the general dispatch entirely.&lt;/p&gt;

&lt;p&gt;The comparison that matters is against Turbo-Base64 built with &lt;code&gt;FULLCHECK=1&lt;/code&gt;. Turbo's default build checks only the first of four blocks in its hot loop; comparing against that would be comparing against 25% validation. With validation equalized (GB/s, i7-10700, six-process medians):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;yolow64 0.2&lt;/th&gt;
&lt;th&gt;Turbo-Base64 FULLCHECK&lt;/th&gt;
&lt;th&gt;Delta&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;16 KiB&lt;/td&gt;
&lt;td&gt;19.54&lt;/td&gt;
&lt;td&gt;19.68&lt;/td&gt;
&lt;td&gt;−0.7%, tie&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;64 KiB&lt;/td&gt;
&lt;td&gt;20.08&lt;/td&gt;
&lt;td&gt;19.36&lt;/td&gt;
&lt;td&gt;+3.7%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;256 KiB&lt;/td&gt;
&lt;td&gt;20.16&lt;/td&gt;
&lt;td&gt;16.76&lt;/td&gt;
&lt;td&gt;+20.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1 MiB&lt;/td&gt;
&lt;td&gt;20.17&lt;/td&gt;
&lt;td&gt;16.68&lt;/td&gt;
&lt;td&gt;+20.9%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4 MiB&lt;/td&gt;
&lt;td&gt;17.06&lt;/td&gt;
&lt;td&gt;13.89&lt;/td&gt;
&lt;td&gt;+22.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Short inputs (ns/call): first at 12 B (7.00), 16 B (8.53), 24 B (10.14), 48 B (12.20), 96 B (15.68) and 128 B (17.18) in an eight-library run. Not first at 8, 32 and 64 B — those cells are still someone else's, and the raw tables in the repo say so.&lt;/p&gt;

&lt;h2&gt;
  
  
  Encode: streaming and cache-aware
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Adaptive non-temporal stores kick in above 3/7 of the detected LLC.&lt;/li&gt;
&lt;li&gt;A streaming encode API and a memory-bounded &lt;code&gt;encode_to_writer&lt;/code&gt; join the one-shot calls.&lt;/li&gt;
&lt;li&gt;An optional &lt;code&gt;parallel&lt;/code&gt; feature splits large inputs across cache-aware workers — and stays out of the way when the single-thread streaming path measures faster.&lt;/li&gt;
&lt;li&gt;Short 8/16/32-byte fast paths skip the generic tail.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Strictness went up, not down
&lt;/h2&gt;

&lt;p&gt;Padding-bit checks now reject non-zero trailing bits at the short padded lengths and at the generic tail. AVX2 and BMI2 are detected independently: &lt;code&gt;pext&lt;/code&gt; paths run only when both are present, AVX2-only and scalar CPUs fall back cleanly — a test-only switch forces the no-BMI2 path on a BMI2 machine to prove the fallback decodes identically. Exact-output canary tests cover caller-provided buffers from 1 B to 8 KiB, so the fast paths cannot write past your buffer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="k"&gt;[dependencies]&lt;/span&gt;
&lt;span class="na"&gt;yolow64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0.2"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Zero runtime dependencies, MIT, MSRV 1.89.&lt;/p&gt;

&lt;p&gt;Caveats, as always: one shared machine (i7-10700, 16 GiB, AVX2+BMI2, no AVX-512), medians of repeated runs, snapshots rather than guarantees. Method, raw CSVs and the losing cells are in the repo: &lt;a href="https://github.com/YOLOW92/yolow64" rel="noopener noreferrer"&gt;https://github.com/YOLOW92/yolow64&lt;/a&gt; — the release record is &lt;code&gt;research/release-0.2.0.md&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Its sibling extraction, &lt;a href="https://crates.io/crates/yolowpdf" rel="noopener noreferrer"&gt;yolowpdf&lt;/a&gt;, also landed on crates.io this week. If you push Base64 through a hot path, I'd like to hear where 0.2 lands on your hardware.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>performance</category>
      <category>opensource</category>
    </item>
    <item>
      <title>yolowpdf: a PDF-to-Markdown parser written from scratch in Rust, plus its own OCR engine</title>
      <dc:creator>ゴエンチョウ</dc:creator>
      <pubDate>Mon, 09 Nov 2026 15:55:38 +0000</pubDate>
      <link>https://dev.to/yolow92/yolowpdf-a-pdf-to-markdown-parser-written-from-scratch-in-rust-plus-its-own-ocr-engine-25fn</link>
      <guid>https://dev.to/yolow92/yolowpdf-a-pdf-to-markdown-parser-written-from-scratch-in-rust-plus-its-own-ocr-engine-25fn</guid>
      <description>&lt;h1&gt;
  
  
  yolowpdf: a PDF-to-Markdown parser written from scratch in Rust — plus its own OCR engine
&lt;/h1&gt;

&lt;p&gt;PDF parsing in Rust usually means wrapping a C library. &lt;code&gt;yolowpdf&lt;/code&gt; reads the file format itself: no Poppler, no MuPDF, no native PDF library at all.&lt;/p&gt;

&lt;p&gt;It is the PDF engine extracted from &lt;a href="https://github.com/YOLOW92/pagesift" rel="noopener noreferrer"&gt;PageSift&lt;/a&gt; (from the 1.2.4 tree), now an independent MIT-licensed workspace. It is also the second spin-off in a week, after &lt;a href="https://github.com/YOLOW92/yolow64" rel="noopener noreferrer"&gt;yolow64&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it exists
&lt;/h2&gt;

&lt;p&gt;PageSift needed to turn PDFs found in the wild into clean Markdown without installing a native PDF stack on every machine. Wild PDFs are not clean: broken xref tables, object streams, encrypted streams, CID fonts with no ToUnicode map, multi-column layouts, tables, inline formulas, images, and scanned pages with no text layer at all.&lt;/p&gt;

&lt;p&gt;Handling all of that grew into a standalone reader: file structure and damaged-file repair, fonts (Type1, TrueType, CFF, CID, predefined CMaps, CJK collections without ToUnicode), layout reconstruction (paragraphs, headings, reading order, headers/footers, lists), ruled and borderless tables, inline math rebuilt as LaTeX from glyph metrics, image extraction (JPEG, JPEG 2000, JBIG2, CCITT, PNG, TIFF, WebP) — and a &lt;code&gt;PageOcr&lt;/code&gt; trait for scanned pages.&lt;/p&gt;

&lt;p&gt;The OCR side became a second crate: &lt;code&gt;yolowpdf-ocr&lt;/code&gt; implements PP-OCRv6 detection and recognition, with optional document-layout and formula models, on CPU or an NVIDIA GPU, with automatic fallback to CPU and a reported reason when the GPU cannot start. The core crate never depends on a model runtime; OCR is opt-in.&lt;/p&gt;

&lt;p&gt;The API is deliberately small:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"document.pdf"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;yolowpdf&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;extract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="py"&gt;.markdown&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pages: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="py"&gt;.pages&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"scanned: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="py"&gt;.scanned&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;extract_with&lt;/code&gt; adds limits, image options and the OCR engine. Errors are &lt;code&gt;Malformed&lt;/code&gt;, &lt;code&gt;Encrypted&lt;/code&gt;, &lt;code&gt;TooLarge&lt;/code&gt; or &lt;code&gt;Timeout&lt;/code&gt; — and a parser panic is caught and returned as an error instead of taking the caller down.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benchmarks (exploratory, with the caveats kept)
&lt;/h2&gt;

&lt;p&gt;Runs on public &lt;a href="https://huggingface.co/datasets/allenai/olmOCR-bench" rel="noopener noreferrer"&gt;olmOCR-bench&lt;/a&gt; data, one machine (i7-10700, 15 GB RAM, GTX 1070). These are exploratory runs, &lt;strong&gt;not certified official scores&lt;/strong&gt;; tools differ in features and conditions, and the full methodology — actual worker counts, caps, raw results — is in the repo's &lt;code&gt;benchmarks/&lt;/code&gt; report.&lt;/p&gt;

&lt;p&gt;Born-digital pages (1,269 pages):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Completed&lt;/th&gt;
&lt;th&gt;Pages/s&lt;/th&gt;
&lt;th&gt;p50&lt;/th&gt;
&lt;th&gt;Peak MB/worker&lt;/th&gt;
&lt;th&gt;Score&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;yolowpdf&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1269/1269&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1,900&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3.2 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;24&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.592&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pymupdf4llm&lt;/td&gt;
&lt;td&gt;1213/1269&lt;/td&gt;
&lt;td&gt;6.56&lt;/td&gt;
&lt;td&gt;845.6 ms&lt;/td&gt;
&lt;td&gt;600&lt;/td&gt;
&lt;td&gt;0.489&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pypdf&lt;/td&gt;
&lt;td&gt;1213/1269&lt;/td&gt;
&lt;td&gt;79.4&lt;/td&gt;
&lt;td&gt;73.9 ms&lt;/td&gt;
&lt;td&gt;186&lt;/td&gt;
&lt;td&gt;0.385&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MarkItDown&lt;/td&gt;
&lt;td&gt;1204/1269&lt;/td&gt;
&lt;td&gt;13.6&lt;/td&gt;
&lt;td&gt;172.8 ms&lt;/td&gt;
&lt;td&gt;1,171&lt;/td&gt;
&lt;td&gt;0.373&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On the pdf.js corpus of hard files (1,089 files), yolowpdf finished 830 files at 1,663 files/s; pymupdf4llm finished slightly more (841) at 1.31 files/s under different I/O conditions. That cell is in the README too — a fast parse is not the same as understanding a difficult page, and the README says so itself.&lt;/p&gt;

&lt;p&gt;For scanned pages, &lt;code&gt;yolowpdf-ocr&lt;/code&gt; (GPU, text only) completed all 134 olmOCR-bench scan pages at 2.245 pages/s; adding layout and formula models raised the score to 0.5428.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;

&lt;p&gt;On crates.io:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="n"&gt;dependencies&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;yolowpdf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"0.1"&lt;/span&gt;

&lt;span class="c"&gt;# Optional OCR:&lt;/span&gt;
&lt;span class="n"&gt;yolowpdf-ocr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"0.1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rust 1.89+, edition 2024, MIT licensed. Models for OCR are downloaded by the caller (pinned revisions and SHA-256 values are listed in the source); nothing is bundled.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/YOLOW92/yolowpdf" rel="noopener noreferrer"&gt;https://github.com/YOLOW92/yolowpdf&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you feed PDFs into LLM pipelines, I'd like to hear what breaks. Hard files especially welcome.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>pdf</category>
      <category>ocr</category>
      <category>opensource</category>
    </item>
    <item>
      <title>yolow64: a Base64 codec for Rust that outruns the incumbents — extracted from PageSift</title>
      <dc:creator>ゴエンチョウ</dc:creator>
      <pubDate>Mon, 09 Nov 2026 04:48:37 +0000</pubDate>
      <link>https://dev.to/yolow92/yolow64-a-base64-codec-for-rust-that-outruns-the-incumbents-extracted-from-pagesift-1210</link>
      <guid>https://dev.to/yolow92/yolow64-a-base64-codec-for-rust-that-outruns-the-incumbents-extracted-from-pagesift-1210</guid>
      <description>&lt;p&gt;While building PageSift (a Rust web-content engine for LLM pipelines), I kept hitting Base64 in places where throughput and allocation control actually matter: browser screenshots, MCP resources, proxy authentication. The codec that grew inside PageSift is now its own MIT-licensed library: &lt;strong&gt;yolow64&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Runtime-dispatched SIMD: AVX2 (with an assembly bulk decode loop on Linux x86-64), NEON on AArch64, scalar fallback; runtime detection is cached.&lt;/li&gt;
&lt;li&gt;Zero runtime dependencies, std only, Rust 1.89+.&lt;/li&gt;
&lt;li&gt;Slice / append / owned-output APIs, validation without decoding, streaming decode, and a one-shot data-URL builder.&lt;/li&gt;
&lt;li&gt;Whitespace-tolerant mode ignores exactly space/tab/CR/LF in a single pass — built for MIME-style input.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Measured on an i7-10700 (same process, three full runs, medians of seven-round medians; full method, charts and raw CSVs in the repo):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Strict mode at 4 KiB–4 MiB: ~6.5–9× faster than the &lt;code&gt;base64&lt;/code&gt; crate, ~1.7–2.5× faster than &lt;code&gt;base64-simd&lt;/code&gt;, encode and decode.&lt;/li&gt;
&lt;li&gt;CRLF-tolerant decode: ~4× faster than base64-simd's &lt;code&gt;forgiving_decode&lt;/code&gt; at the same sizes.&lt;/li&gt;
&lt;li&gt;On the same machine it runs level with moego, the fastest Go implementation I measured.&lt;/li&gt;
&lt;li&gt;Honest footnote: at ≤128 B inputs the three libraries trade wins — those differences are nanoseconds and noise-sensitive, and the repo's tables print them as-is. The whitespace modes also don't accept exactly the same inputs across libraries; the comparison covers valid padded CRLF inputs.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="n"&gt;dependencies&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;yolow64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"0.1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repo: &lt;a href="https://github.com/YOLOW92/yolow64" rel="noopener noreferrer"&gt;https://github.com/YOLOW92/yolow64&lt;/a&gt; — benchmarks are reproducible from the included harness; corrections welcome.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>opensource</category>
      <category>performance</category>
      <category>webdev</category>
    </item>
    <item>
      <title>PageSift 1.2: ITNA, an access-barrier recovery engine — plus the 1.1 roundup</title>
      <dc:creator>ゴエンチョウ</dc:creator>
      <pubDate>Sat, 07 Nov 2026 02:20:10 +0000</pubDate>
      <link>https://dev.to/yolow92/pagesift-12-itna-an-access-barrier-recovery-engine-plus-the-11-roundup-4gd6</link>
      <guid>https://dev.to/yolow92/pagesift-12-itna-an-access-barrier-recovery-engine-plus-the-11-roundup-4gd6</guid>
      <description>&lt;p&gt;PageSift started as a Rust crawler chasing extreme performance. It has since grown into a web content engine — web scraping, document parsing, structured extraction, and an AI-agent surface — and 1.2 adds one more engine to it.&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/YOLOW92/pagesift" rel="noopener noreferrer"&gt;https://github.com/YOLOW92/pagesift&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1.2: ITNA, an access-barrier recovery engine
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;When an HTTP fetch gets blocked, it decides intelligently whether to escalate to a browser retry&lt;/li&gt;
&lt;li&gt;TLS/HTTP2 browser fingerprinting over HTTP, HTTPS, and SOCKS5 proxies&lt;/li&gt;
&lt;li&gt;A session identity pool: cookies, egress, and fingerprint managed together, isolated per user&lt;/li&gt;
&lt;li&gt;Safe HTTP → browser handoff of session state&lt;/li&gt;
&lt;li&gt;Tuned browser automation markers, with waiting for non-interactive JS challenges&lt;/li&gt;
&lt;li&gt;A shared request budget that caps retries, timeouts, and resource use&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ITNA is off by default and enabled on demand. It does not solve interactive CAPTCHAs, and it does not promise to bypass every anti-bot system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Catching up on 1.1: documents and languages, massively expanded
&lt;/h2&gt;

&lt;p&gt;I never got around to posting about the 1.1 series, so here it is in one go:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A long list of new file formats: legacy Office, ODF, WordPerfect, e-books, email, archives, databases, and more&lt;/li&gt;
&lt;li&gt;373 source-code languages recognized, with function/class-level structure extracted for mainstream languages&lt;/li&gt;
&lt;li&gt;New bindings for Go, Java, C#, Ruby, PHP, Lua, Swift, Zig and more, plus LangChain and LlamaIndex integrations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Measured on 1.1:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PDF → Markdown: 2.4 ms/page&lt;/li&gt;
&lt;li&gt;HTML → Markdown: 0.24 ms/page&lt;/li&gt;
&lt;li&gt;Main-content extraction: 1.0 ms/page&lt;/li&gt;
&lt;li&gt;Office parsing: 91.7 files/sec, about 3.40× xberg&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  1.1.1–1.1.3: still digging into the Rust layer
&lt;/h2&gt;

&lt;p&gt;I implemented Zstandard compression/decompression in pure Rust — FSE, Huffman, LZ matching, cache-aware hash chains, all written from scratch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it stands
&lt;/h2&gt;

&lt;p&gt;PageSift has passed 200 stars on GitHub, and it is still developed and maintained by one person.&lt;/p&gt;

&lt;p&gt;The goal was never just a fast crawler. I want it to be genuinely high-performance, low-overhead, self-hostable AI data infrastructure.&lt;/p&gt;

&lt;p&gt;Fully open source — search YOLOW92 / PageSift on GitHub. Tests, issues, and technical discussion are all welcome.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>opensource</category>
      <category>webdev</category>
      <category>ai</category>
    </item>
    <item>
      <title>PageSift 1.0: the Rust web content engine is feature-complete</title>
      <dc:creator>ゴエンチョウ</dc:creator>
      <pubDate>Wed, 04 Nov 2026 03:44:32 +0000</pubDate>
      <link>https://dev.to/yolow92/pagesift-10-the-rust-web-content-engine-is-feature-complete-51m7</link>
      <guid>https://dev.to/yolow92/pagesift-10-the-rust-web-content-engine-is-feature-complete-51m7</guid>
      <description>&lt;p&gt;Fifteen days, twenty-five releases. PageSift went public as 0.20 on October 20; today it ships &lt;strong&gt;1.0&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;PageSift is a web content engine written in Rust. It turns URLs, PDFs and Office files into clean Markdown and structured data for AI agents and LLM pipelines — one self-hosted binary with a Firecrawl-compatible HTTP API, a Jina Reader-compatible endpoint (including &lt;code&gt;/s/&lt;/code&gt;), an MCP server, and Python / Node.js / Rust SDKs.&lt;/p&gt;

&lt;p&gt;1.0 is not a speed release — performance landed with the 0.9 architecture rebuild. 1.0 is &lt;strong&gt;feature completion&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What 1.0 adds
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;OCR, built from scratch.&lt;/strong&gt; Scanned PDFs were PageSift's documented weak spot. 1.0 ships its own OCR pipeline — text detection, recognition, formula recognition (LaTeX out), layout analysis — running on a self-written inference runtime (safetensors executor; no ONNX Runtime) and a self-written CUDA GPU layer. Models are open weights, downloaded on first use, off by default. On the olmOCR-bench scanned set it scores 0.543 vs Docling (GPU) at 0.410.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Selector extraction with memory.&lt;/strong&gt; Give it a JSON schema; when a site redesign breaks your selectors, it relocates elements using element fingerprints and a statistical posterior — and reports &lt;code&gt;relocated&lt;/code&gt;, &lt;code&gt;ambiguous&lt;/code&gt;, or &lt;code&gt;missing&lt;/code&gt; instead of silently returning wrong data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Change monitoring.&lt;/strong&gt; A &lt;code&gt;/v2/monitor&lt;/code&gt; endpoint family with block-level diffing that separates noise changes (prices, dates, counters) from substantive ones.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Browser actions, sessions, and an agent endpoint&lt;/strong&gt; across the HTTP, MCP and Jina surfaces — plus WARC files as input, a hooks + &lt;code&gt;[[rules]]&lt;/code&gt; system, and per-host secret injection.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture in three ideas
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Measured instead of fixed.&lt;/strong&gt; The hundreds of hand-tuned timeouts, thresholds and concurrency limits are gone. A statistical kernel derives them from runtime observations — survival analysis (Kaplan–Meier) for timeouts, a Universal Scalability Law fit for global concurrency, and "posterior expected value − shadow price × cost" for render decisions. One global meta-parameter remains: δ = 0.05. With no observations, behaviour matches the old fixed values; an estimate takes over only when the difference is statistically significant.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scheduling that follows the hardware.&lt;/strong&gt; One resident CPU worker per core (no blocking thread pool), pinned to NUMA node sets rather than individual cores — because measurements said so: hard per-core pinning cost −33.9% throughput in the 0.70 experiments, node-set pinning gained +8.63%. The code comments cite the measurement behind each such decision.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failures are reported, never dressed up as success.&lt;/strong&gt; 0.91 was a release about exactly this: jobs whose results couldn't be saved, replicated, or delivered by webhook previously ended as &lt;code&gt;completed&lt;/code&gt;. They now fail loudly, with an errors endpoint. 1.0 extends the rule to the API surface: options the engine cannot honor are rejected (unknown MCP arguments error; unimplementable Jina headers return &lt;code&gt;UNSUPPORTED_OPTION&lt;/code&gt;), and the release notes carry a Known limits section that lists what isn't tested and what doesn't work — including security caveats.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Performance (re-measured 2026-11-04)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Workload&lt;/th&gt;
&lt;th&gt;PageSift 1.0&lt;/th&gt;
&lt;th&gt;Fastest rival&lt;/th&gt;
&lt;th&gt;Gap&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;PDF → Markdown, olmOCR-bench 1,269 pages, single thread&lt;/td&gt;
&lt;td&gt;2.4 ms/page&lt;/td&gt;
&lt;td&gt;pypdf 50.0 ms/page&lt;/td&gt;
&lt;td&gt;×20.8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTML → Markdown, 600 documents&lt;/td&gt;
&lt;td&gt;0.24 ms/page&lt;/td&gt;
&lt;td&gt;Turndown 1.9 ms/page&lt;/td&gt;
&lt;td&gt;×8.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Main content, Firecrawl 597 pages, single thread p50&lt;/td&gt;
&lt;td&gt;1.0 ms/page&lt;/td&gt;
&lt;td&gt;crw 7.4 ms/page&lt;/td&gt;
&lt;td&gt;×7.3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Office / e-books, 81 files&lt;/td&gt;
&lt;td&gt;93.6 files/s&lt;/td&gt;
&lt;td&gt;Kreuzberg 19.5&lt;/td&gt;
&lt;td&gt;×4.8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wide crawl, 200 sites × 100 pages, unthrottled&lt;/td&gt;
&lt;td&gt;3,500–4,400 pages/s, 280–316 MB peak&lt;/td&gt;
&lt;td&gt;Spider 2,000–2,700 pages/s, 5.5 GB&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Honest footnotes, because they matter: competitor numbers are the recorded late-October runs, not re-run for this release; and the workloads where PageSift &lt;em&gt;loses&lt;/em&gt; are listed in the same document ("Largest gaps") — e.g. a single-host replay looks slow mostly because of the default 1-second per-host politeness interval, which is policy, not engine speed. Full data and reproduction scripts live in the repo (&lt;code&gt;docs/benchmarks.md&lt;/code&gt;, &lt;code&gt;benchmarks/&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Battle-tested once, properly
&lt;/h2&gt;

&lt;p&gt;In late October an external team integrated PageSift into their production system and immediately hit a real bug: the official Node binding required a newer glibc than their server had (2.32). 0.81.5 fixed it — build baseline lowered to glibc 2.28, plus a release gate that fails the release if any artifact exceeds it. A user found it; the project fixed it and made it impossible to reintroduce silently. That's the workflow 1.0 is built around.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who it's for / not for
&lt;/h2&gt;

&lt;p&gt;For: teams feeding RAG or agent pipelines who want a self-hosted Firecrawl/Jina alternative without per-page pricing, and anyone who needs web pages, PDFs and Office files in one pipeline.&lt;/p&gt;

&lt;p&gt;Not for: people who need a hosted cloud API (PageSift is self-hosted; a hosted version is not part of 1.0), or bypassing hard anti-bot targets (robots.txt and politeness are respected; captchas are reported, not solved).&lt;/p&gt;

&lt;p&gt;PageSift is a solo project. It exists because the crawler feeding my other project was too slow and not clean enough, so I wrote my own. 1.0 closes the feature list. What I want next is more production workloads breaking it in interesting ways.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/YOLOW92/pagesift" rel="noopener noreferrer"&gt;https://github.com/YOLOW92/pagesift&lt;/a&gt; — AGPL-3.0, commercial licensing available. Issues and head-to-head benchmarks welcome, good or bad.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>opensource</category>
      <category>webdev</category>
      <category>ai</category>
    </item>
    <item>
      <title>My Crawler Now Tells You What It Missed</title>
      <dc:creator>ゴエンチョウ</dc:creator>
      <pubDate>Tue, 27 Oct 2026 14:25:18 +0000</pubDate>
      <link>https://dev.to/yolow92/my-crawler-now-tells-you-what-it-missed-lll</link>
      <guid>https://dev.to/yolow92/my-crawler-now-tells-you-what-it-missed-lll</guid>
      <description>&lt;h1&gt;
  
  
  My Crawler Now Tells You What It Missed
&lt;/h1&gt;

&lt;p&gt;The worst failure mode of a data pipeline is not an error. It's silent junk: a 404 page filed as content, a recipe with the ingredients dropped, a forum thread quietly missing its replies — all returned as "success." If you feed LLMs for a living, you have eaten this. I have shipped this.&lt;/p&gt;

&lt;p&gt;PageSift 0.80 is the release where I tried to eliminate that failure mode from my own engine. The theme is not speed this time (0.70 was speed). The theme is: &lt;strong&gt;results should say what they are, including what they failed to get.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Results that confess
&lt;/h2&gt;

&lt;p&gt;Extraction is heuristic and always will be. The bug is pretending otherwise. In 0.80, when extraction likely kept too little, the result says so — with numbers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If article extraction keeps only a small share of the page, you get an explicit incompleteness warning instead of a confident stub.&lt;/li&gt;
&lt;li&gt;If a forum page's replies were left out, the warning quantifies it: how many characters sat in comment sections, how much was extracted, and how to ask for the whole page if you want it.&lt;/li&gt;
&lt;li&gt;Video pages with no readable body don't return empty success anymore; they fall back to the page's embedded video metadata and label that they did.&lt;/li&gt;
&lt;li&gt;Error-status pages carry an explicit &lt;code&gt;http_error_status&lt;/code&gt; warning and the real status code in metadata. Server errors (and empty error pages) fail hard; nothing passes as silent success.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These labels exist because my own production audit embarrassed me into them: 95% of my old crawler's rendered output was being thrown away, and nobody had noticed, because every result claimed to be fine.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. PDF: from text extraction to structure preservation
&lt;/h2&gt;

&lt;p&gt;0.80 replaces the third-party PDF library with a file layer I wrote myself. Headings are the difference between a document and a wall of text, so that's what I measured — on a set of real-world PDFs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Heading recall: 0.595 → 0.947&lt;/li&gt;
&lt;li&gt;Heading precision: 0.400 → 0.871&lt;/li&gt;
&lt;li&gt;Hierarchy consistency: 0.819 → 0.957&lt;/li&gt;
&lt;li&gt;Phantom tables: 196 → 9 (all 143 real tables kept)&lt;/li&gt;
&lt;li&gt;Memory: −25% at the same speed&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. Documents, all of them, all hand-written
&lt;/h2&gt;

&lt;p&gt;Word, Excel, PowerPoint, EPUB and CSV are now supported, parsed by my own Rust code — including the ZIP and XML layers; the only borrowed piece is a decompression library. On a held-out set of files collected after the rules were frozen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Structure retention: 0.988 (MarkitDown: 0.717, pandoc: 0.705)&lt;/li&gt;
&lt;li&gt;81 files parsed in 657 ms total (MarkitDown: ~48.8 s)&lt;/li&gt;
&lt;li&gt;Median peak memory per file: 14.2 MB (MarkitDown: 140.1 MB)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. HTML parsing, closer to the spec
&lt;/h2&gt;

&lt;p&gt;The parser now passes 89.3% of the official html5lib test suite (was 77.7%); parsing differences that actually change the extracted content dropped from 143 to 6. Parsing got faster while getting more correct, which was not the direction I expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Fuzzing: the inputs that used to kill it
&lt;/h2&gt;

&lt;p&gt;A fuzz pass found inputs that hung the engine or blew up memory. Before → after:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;720 KB of pathological text: 19.5 s → 24 ms&lt;/li&gt;
&lt;li&gt;1.6 MB of adversarial HTML: 52 s → 0.19 s&lt;/li&gt;
&lt;li&gt;One PDF: killed at &amp;gt;3 GB memory → 81 ms at 73 MB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Browsers orphaned when the process is killed are now reaped on the next start, too.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule I held myself to
&lt;/h2&gt;

&lt;p&gt;This was the correctness release, and the standing rule was: &lt;strong&gt;correctness may not be bought with speed.&lt;/strong&gt; Against 0.73, single-thread extraction is 3.7% faster, 16-thread 4.4% faster, PDF memory down 25%, per-request CPU flat. The rule held.&lt;/p&gt;

&lt;h2&gt;
  
  
  Still open, honestly
&lt;/h2&gt;

&lt;p&gt;Steam's real age gate still slips past classification — it comes back flagged only as thin content, not recognized as a gate. It's been the standing exception for three releases now, and I'd rather list it here than let you find it. Deletion tracking for sync pipelines (gone pages as a first-class signal) isn't built yet either; a reader asked for exactly that, and it's on the list.&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/YOLOW92/pagesift" rel="noopener noreferrer"&gt;https://github.com/YOLOW92/pagesift&lt;/a&gt; — the 0.80 release notes have the full tables. Next on the 0.8x line: the hosted API. If your pipeline has a silent-junk horror story, I want to hear it in the comments.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>opensource</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I Profiled My Production Crawler. 95% of Its Output Was Being Thrown Away.</title>
      <dc:creator>ゴエンチョウ</dc:creator>
      <pubDate>Sun, 25 Oct 2026 18:46:57 +0000</pubDate>
      <link>https://dev.to/yolow92/i-profiled-my-production-crawler-95-of-its-output-was-being-thrown-away-55g1</link>
      <guid>https://dev.to/yolow92/i-profiled-my-production-crawler-95-of-its-output-was-being-thrown-away-55g1</guid>
      <description>&lt;p&gt;I run &lt;a href="https://aiseek.dev" rel="noopener noreferrer"&gt;AiSeek&lt;/a&gt;, an information platform where every published claim has to trace back to fetched evidence — a source page, a filing, a dataset. That makes the fetch layer the supply chain of the whole product: if it is slow, wasteful, or silently wrong, everything downstream inherits it.&lt;/p&gt;

&lt;p&gt;Until October 21, that layer was Crawl4AI — a capable, popular Python framework — driving Chromium for the pages that needed rendering. I had a feeling it was heavy. Instead of trusting the feeling, I audited it: three days of production jobs (8,563 of them, October 18–20), the production database, and local reproductions run under the same limits as the production box (6 cores, 10 GB). Every number below is production data unless I say otherwise.&lt;/p&gt;

&lt;p&gt;This is what the audit found, what I built because of it, and the part people usually leave out of launch posts.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the crawler actually did all day
&lt;/h2&gt;

&lt;p&gt;On a normal day, AiSeek fetched roughly 2,000 articles over plain HTTP and sent 600–900 to the browser — about 28% of article fetches ended up rendered. The two paths lived in different latency worlds:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Jobs&lt;/th&gt;
&lt;th&gt;p50&lt;/th&gt;
&lt;th&gt;p95&lt;/th&gt;
&lt;th&gt;p99&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;HTTP only&lt;/td&gt;
&lt;td&gt;2,879&lt;/td&gt;
&lt;td&gt;1.6 s&lt;/td&gt;
&lt;td&gt;5.8 s&lt;/td&gt;
&lt;td&gt;10.9 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rendered (Crawl4AI)&lt;/td&gt;
&lt;td&gt;1,249&lt;/td&gt;
&lt;td&gt;7.0 s&lt;/td&gt;
&lt;td&gt;24.4 s&lt;/td&gt;
&lt;td&gt;40.2 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Rendered pages were mostly the ones that needed it least, as it turned out. But first, the bigger surprise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 1: I was using a framework as a browser driver
&lt;/h2&gt;

&lt;p&gt;Of 174 successful renders in my local reproduction, &lt;strong&gt;166 used my own trafilatura extraction for the final text. Only 8 used the Markdown Crawl4AI generated.&lt;/strong&gt; Its Markdown was computed on every single page — and thrown away 95% of the time.&lt;/p&gt;

&lt;p&gt;What I actually consumed from this large framework was a minimal surface:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the rendered HTML,&lt;/li&gt;
&lt;li&gt;the final URL after redirects,&lt;/li&gt;
&lt;li&gt;a status code,&lt;/li&gt;
&lt;li&gt;the link list,&lt;/li&gt;
&lt;li&gt;a title fallback.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the irony compounds: the heaviest single parsing step in the pipeline wasn't Crawl4AI's either. My own trafilatura call cost a median of 82 ms per page; Crawl4AI's scraping plus Markdown generation together cost about 40 ms. The library I suspected of being slow was, by itself, mostly innocent.&lt;/p&gt;

&lt;p&gt;I wasn't using Crawl4AI. I was renting a browser with a framework attached — and paying the framework's tax on every page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 2: 55% of render time was pure waste
&lt;/h2&gt;

&lt;p&gt;The decision rule for rendering was one line of logic: if the HTTP fetch produced fewer than 1,200 characters of text, render it in a browser. That rule cannot tell a JavaScript wall from a short article — and a lot of AiSeek's sources are short news briefs, many of them in Chinese and Japanese, where 800 characters is a complete story.&lt;/p&gt;

&lt;p&gt;Replaying the production decision logic locally: in 59 cases where HTTP had already retrieved a usable page but the rule sent it to the browser anyway, &lt;strong&gt;37 renders (63%) came back with the same amount of text as HTTP&lt;/strong&gt; — NHK at 849 characters before rendering, 849 after. Those wasted renders consumed 85 seconds of 154.6 total: &lt;strong&gt;55% of all rendering time, spent re-fetching pages we already had.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This was the most valuable finding of the audit, and it had nothing to do with languages or engines: &lt;em&gt;the biggest speedup available was a better decision about when to render.&lt;/em&gt; Rewriting that rule was worth more than any engine swap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 3: the real costs were Chromium, and a shared event loop
&lt;/h2&gt;

&lt;p&gt;What actually consumed the machine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Chromium itself.&lt;/strong&gt; Each concurrent rendered page costs roughly 0.7 CPU cores and a couple of hundred MB, plus a ~1.7 s cold start. No driver language changes that arithmetic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A single event loop doing synchronous work.&lt;/strong&gt; Crawl4AI runs its HTML cleaning and Markdown conversion synchronously on the Python event loop. With 4 concurrent pages, my probe measured loop stalls at p99 of 154–172 ms, worst case &lt;strong&gt;2.2 seconds&lt;/strong&gt; — during which everything else in that process waited, including plain HTTP fetches and the GLiNER and MiniCheck models that shared the process and its 10 GB container. An old code comment of mine, from October 9, reads: "plain fetches took 8 s at the median while pages rendered." Now I knew why.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A low concurrency ceiling.&lt;/strong&gt; Sweeping the page limit locally on 6 cores: 4 pages gave 1.87 req/s, 8 pages topped out at 2.19 req/s, and 16 pages went &lt;em&gt;backwards&lt;/em&gt; to 1.72 req/s with p50 latency ballooning to 18 s. Past a point, more pages just means a longer queue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weight.&lt;/strong&gt; The service image was 6.64 GB. The fetch path alone pulled 115 packages, about 1 GB — including several libraries we never called.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Finding 4: some failures were never the crawler's fault
&lt;/h2&gt;

&lt;p&gt;About 25% of fetches failed in production. I took 70 URLs from the failed group and fetched them simultaneously from my home connection and from the production server. From home, 39 of 70 returned full text. From production: &lt;strong&gt;zero.&lt;/strong&gt; The rest were refused (403/451) or rate-limited (429) — entire publisher groups block our datacenter exit, and some of it is geography, not IP reputation (451 is "unavailable for legal reasons"; the production box sits in Türkiye).&lt;/p&gt;

&lt;p&gt;No engine fixes that, and per AiSeek's rules I don't route around refusals: honest user agent, robots respected, a refusal is final. I include this because crawler benchmarks never mention it — in production, a quarter of your failures may have nothing to do with your stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bugs I promised not to copy
&lt;/h2&gt;

&lt;p&gt;The audit also catalogued defects I did not want the replacement to inherit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Status codes from the first redirect hop.&lt;/strong&gt; 136 of 174 successful renders reported the 301/302/308 they started with — so a "301 → 404" could be stored as a success.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An SSRF route guard that stacked per page.&lt;/strong&gt; One more handler on the shared browser context for every page opened — up to 200 by recycle time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DNS failure reported as an SSRF refusal.&lt;/strong&gt; Same 400 for "this host is forbidden" and "this host temporarily doesn't resolve" — two situations a caller must handle oppositely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Network errors collapsing into a bare 500&lt;/strong&gt;, indistinguishable from a bug in our own code.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The replacement: build the minimal surface, in Rust
&lt;/h2&gt;

&lt;p&gt;So I built &lt;a href="https://github.com/YOLOW92/pagesift" rel="noopener noreferrer"&gt;PageSift&lt;/a&gt; to the audit, not to a feature checklist:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Only the minimal surface.&lt;/strong&gt; Rendered HTML, final URL, final status code plus the redirect chain, links, and Rust-side extraction — no Markdown generated just to be discarded, no 115-package tax.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A separate process.&lt;/strong&gt; Its own CPU and memory limits, restartable without touching the models. Nothing synchronous sits on anyone else's event loop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A smarter render decision first, engine second.&lt;/strong&gt; HTTP first; rendering is spent only where it buys something.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A browser chain instead of a browser.&lt;/strong&gt; Lightpanda renders first; Chrome's headless shell takes the pages Lightpanda can't. Both run sandboxed — never &lt;code&gt;--no-sandbox&lt;/code&gt; — which on our host meant writing AppArmor and seccomp profiles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Honest failures.&lt;/strong&gt; A block is reported as a block, never replaced with invented content. For an evidence platform, a declared gap beats a fabricated page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The same API contract.&lt;/strong&gt; The worker kept calling &lt;code&gt;POST /v1/fetch&lt;/code&gt; and validating the same response schema. The swap touched one sidecar, nothing else.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On October 21 the sidecar went in, and Crawl4AI left the code and the image the same day. My deploy note from that day records the decision in parentheses: &lt;em&gt;(owner: no week of observation)&lt;/em&gt;. The audit was the observation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part launch posts leave out: production still runs v0.01
&lt;/h2&gt;

&lt;p&gt;Here is the confession. AiSeek's production builds PageSift from a pinned commit — the first working build, call it 0.01 — and &lt;strong&gt;it still runs that build today.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Since then, the open-source project has sprinted: &lt;a href="https://dev.to/yolow92/i-crawled-the-tranco-top-100k-with-a-rust-engine-that-never-passed-188-mb-of-ram-4bij"&gt;0.50 crawled the Tranco Top 100k&lt;/a&gt;, 0.61 followed, and this week 0.70 landed with a rewritten NUMA-aware data plane (about 950k pages/second against a local test origin on a 192-vCPU box), a three-engine adaptive chain, and a memory envelope that squeezes the full pipeline under 888 MiB. Production has &lt;strong&gt;none&lt;/strong&gt; of it. Not the data plane, not the third engine, not six versions of fixes.&lt;/p&gt;

&lt;p&gt;Upgrading is deliberately boring: change one pinned commit, update two engine checksums, validate on the server, then push. It hasn't happened, for the same reason the audit happened. At ~2,800 fetches a day, throughput was never AiSeek's binding constraint — the audit showed the pain lived in a judgement rule and a shared process, and 0.01 already fixed both. What production buys with its stillness is that every claim on the site traces to evidence fetched by a binary whose behavior I know exactly.&lt;/p&gt;

&lt;p&gt;When the upgrade does happen, it will be audited the same way this replacement was: production numbers first, announcement second. That will be Part 3.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Takeaway, if you run anything like this: profile your crawler before you replace it. You may find you are paying for a framework and using a browser — and that your fastest available optimization is a one-line decision rule, not a new engine.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;PageSift is open source (AGPL-3.0, commercial licenses available): &lt;a href="https://github.com/YOLOW92/pagesift" rel="noopener noreferrer"&gt;github.com/YOLOW92/pagesift&lt;/a&gt;. The product that keeps it honest is &lt;a href="https://aiseek.dev" rel="noopener noreferrer"&gt;aiseek.dev&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>webdev</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I crawled the Tranco Top 100k with a Rust engine that never passed 188 MB of RAM</title>
      <dc:creator>ゴエンチョウ</dc:creator>
      <pubDate>Fri, 23 Oct 2026 13:34:32 +0000</pubDate>
      <link>https://dev.to/yolow92/i-crawled-the-tranco-top-100k-with-a-rust-engine-that-never-passed-188-mb-of-ram-4bij</link>
      <guid>https://dev.to/yolow92/i-crawled-the-tranco-top-100k-with-a-rust-engine-that-never-passed-188-mb-of-ram-4bij</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; I built &lt;a href="https://github.com/YOLOW92/pagesift" rel="noopener noreferrer"&gt;PageSift&lt;/a&gt;, a single-binary web scraping engine in Rust. It's Firecrawl v1/v2 API-compatible, turns pages into clean Markdown for LLM pipelines, and just hit v0.50.0. On a live crawl of the Tranco Top 100,000 domains, its peak memory stayed flat at &lt;strong&gt;188.4 MB&lt;/strong&gt; while Spider (also Rust) ballooned to &lt;strong&gt;~9.0 GB&lt;/strong&gt; and got OOM-killed. Benchmarks, raw data, and reproduction scripts are all in the repo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built it
&lt;/h2&gt;

&lt;p&gt;I run a small AI product, and web scraping was the part that kept hurting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Self-hosted Firecrawl spins up 7 services by default and is AGPL.&lt;/li&gt;
&lt;li&gt;Crawl4AI is Python + Playwright + Node + Chromium — an 847 MB venv before you even start.&lt;/li&gt;
&lt;li&gt;Worst of all, captcha and interstitial pages kept landing in my database &lt;em&gt;as if they were content&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So PageSift is one binary. No Redis, no database, no message queue. And when a site blocks it, you get an honest &lt;code&gt;ACCESS_BARRIER&lt;/code&gt; error instead of a fake page.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 100k live test
&lt;/h2&gt;

&lt;p&gt;Setup: AMD EPYC server (32 cores, 128 GB RAM), Tranco Top 100k live domains, identical conditions for both engines — 768 async concurrency, 3.5 s connect / 10 s total timeout, Blake3 dedup, streaming Markdown extraction.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;PageSift 0.50&lt;/th&gt;
&lt;th&gt;Spider 2.53.9&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Peak RSS&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;188.4 MB (flat)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;8,978.9 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Valid Markdown docs&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;60,659&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;59,803&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Extraction conversion&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;96.38%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;82.82% (17.2% empty shells)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory behavior&lt;/td&gt;
&lt;td&gt;zero leaks&lt;/td&gt;
&lt;td&gt;leaked to 14 GB, OOM-killed at the 12 GB cap&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The full dataset and scripts: &lt;a href="https://github.com/YOLOW92/pagesift/blob/main/bench/results/final/BENCHMARK_100K_PAGESIFT_VS_SPIDER.md" rel="noopener noreferrer"&gt;&lt;code&gt;bench/results/final/BENCHMARK_100K_PAGESIFT_VS_SPIDER.md&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Extraction quality and speed
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Main-content extraction: &lt;strong&gt;0.85 ms/page (p50)&lt;/strong&gt;, F1 &lt;strong&gt;0.716&lt;/strong&gt; on Firecrawl's labelled dataset — crw 0.711, Crawl4AI 0.670. On a 545-page calibrated WebMainBench subset it beats Essence on average, and still &lt;strong&gt;loses on 117 pages&lt;/strong&gt;. Per-page scores are published; I'd rather show the losses than cherry-pick.&lt;/li&gt;
&lt;li&gt;PDF → Markdown: &lt;strong&gt;71.0&lt;/strong&gt; on olmOCR-bench, formulas recovered as LaTeX at &lt;strong&gt;44.3%&lt;/strong&gt; (pymupdf4llm: 0%), at &lt;strong&gt;3.4 ms/page&lt;/strong&gt; vs 320 ms.&lt;/li&gt;
&lt;li&gt;RAG: the BM25 query filter answers in &lt;strong&gt;1.35 ms/query&lt;/strong&gt;, keeping 93% of the answer text in ~1,150 words where Crawl4AI's filter keeps 67% in 13,500 words.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Quick start
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 3002:3002 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;PAGESIFT_API_KEYS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;change-me ghcr.io/yolow92/pagesift:0.50.0

&lt;span class="c"&gt;# or&lt;/span&gt;
cargo &lt;span class="nb"&gt;install &lt;/span&gt;pagesift
pagesift serve   &lt;span class="c"&gt;# Firecrawl-compatible API on :3002&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pagesift&lt;/span&gt;

&lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pagesift&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scrape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;markdown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's also a native Node package (&lt;code&gt;@pagesift/node&lt;/code&gt;), an MCP server for agents, and JS rendering that only fires when a page actually needs it (Lightpanda → moli → Chrome, chosen per host).&lt;/p&gt;

&lt;h2&gt;
  
  
  How it's fast
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;zero-copy HTML parser&lt;/strong&gt;: text and attribute values borrow from the source string; only entity-containing fragments get decoded.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;Readability reimplementation&lt;/strong&gt; that scores paragraphs in a single pass over precomputed text statistics — no DOM mutation, no repeated length calculations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hand-written scanners instead of regexes&lt;/strong&gt; in the hot paths (class/id word classification is a compile-time &lt;code&gt;match&lt;/code&gt; with a stack buffer).&lt;/li&gt;
&lt;li&gt;The workspace is &lt;code&gt;forbid(unsafe_code)&lt;/code&gt; — with exactly one exception: a single &lt;code&gt;mallopt&lt;/code&gt; call that pins glibc's mmap threshold so page-sized buffers return to the OS instead of bloating RSS. That one call cut peak RSS in local crawl tests from 188 MB to ~75 MB.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  It plays by the rules
&lt;/h2&gt;

&lt;p&gt;PageSift respects &lt;code&gt;robots.txt&lt;/code&gt; and &lt;code&gt;Crawl-delay&lt;/code&gt;, rate-limits per host, uses an honest User-Agent, and does &lt;strong&gt;no&lt;/strong&gt; anti-bot bypassing: no TLS fingerprinting, no captcha solving, no proxy rotation. PRs adding stealth features are not accepted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest limitations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;17 capability categories are still marked &lt;strong&gt;partial&lt;/strong&gt; in my own inventory — persistent browser interaction and autonomous agent workflows are explicitly out of scope.&lt;/li&gt;
&lt;li&gt;The license is &lt;strong&gt;AGPL-3.0 + a commercial option&lt;/strong&gt;. Fully open for self-hosting; closed-source products need the commercial license.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/YOLOW92/pagesift" rel="noopener noreferrer"&gt;https://github.com/YOLOW92/pagesift&lt;/a&gt; — docs in English, 中文， 日本語 and Español.&lt;/p&gt;

&lt;p&gt;If you run Firecrawl or Crawl4AI in production, I'd genuinely like to know: what breaks first for you?&lt;/p&gt;

</description>
      <category>rust</category>
      <category>opensource</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
