# StyloBot-utsläppsserien: En lokal markering

*Cloudflare's "Markdown-modit " ger AI-crawlers ren text istället för utformatet HTMLM SK3 StyloBot-operatörer får samma förmåga genom StyloExtractMska4 som kör lokalt på Stylobot-portaalenM Ska5 tillräckligt snabbt för att sitta på den varma vägen Mska6 utan några ändringar i uppströmsplatsen M Ska7 Det är vad Styloextract gör Mske8 hur det pluggar in sig i Stylo Bot , vad dogfooding via lucidVIEW fångar, Mska10 och vad*

<!--category-- Architecture, StyloBot, StyloExtract, Markdown, lucidVIEW -->
---


## Vad jag ville kopiera

Cloudflare skickar med sig en egenskap där, om en identifiserad AI-crawlare frågar efter en sida , kanten serverar en Markdown-version av kroppen istället för den fulla HTML-en . Blättern får den faktiska innehållet utan några kromer M SK3 inga JavaScript-en| , inga cookie-banner MSC5 användaren MST6 webbsitet stannar kvar i samma position

Architekturen är uppenbar utifrån.

Samma förmåga för en StyloBot-operatör har tre begränsningar Cloudflare inte

1. **Platsen är uppströms från StyloBot-portaalet.** Cloudflare kan tjäna markerdown eftersom de är kanten. Ett StyloBot-grind sätter sig framför uppströmskällorna det inte controlerar' , förflyttar sig med YARPM SK3 innehållet måste extraheras på portaalhopMSC4 inte vid ursprunget och inte vid någon SaaS rundaMska5 resaM Ska6 Uppströmsplatsen stannar orörd
2. **Lokala. Ingen Python, ingen nodM SK2 ingen separat tjänstMSC3** Gatewayt är en enda AOT-utlagd binär. Att lägga till en Python sidecar för HTML i Markdown misslyckas med deploymentformen
3. **tillräckligt snabbt för att sitta på den varma vägen.** Om "serve Markdown till AI-crackern " lägger till ♫ 200 ♫ ms per begäran , ♫ så avbryter operatören den ♫

StyloBot sidan av detta har redan gjorts: detektorleitungen klassificerar AI-kralare (`BotType.AiBot`) med samma hög hastighet - bana latens som varje annan bedömelse `DetectionPolicyMiddleware` i 7.x skickar namn på handlingspolitiker av domning `extract-markdown` policy till "AiBot på /docs/*" är en konfigurerings linje. Den sakna biten var extraktorn i sig.

Att StyloExtract- -HTML- --- --- -Markdown- -lake som kombineras med StyloBot- -'- -detektorn.`Mostlylucid.BotDetection.StyloExtract`) och regeln som avfyrar det är sex linjer från JSON . En operatör släpper ner paketet iM SK2 lägger till regeln , och AI-kralare börjar få Markdown för samma URLs som fortfarande serverar HTML till människor

## Vad """ betyder egentligen

Om du läser specifikationen vid ansiktsvaluta, " konverterar HTML till Markdown" är ett löst problemMSC3 Det finns dussintals bibliotekM SK4 Verkligheten är att de alla bryts ner på riktiga sidor eftersom de tar dokumentet som platt innehåll och sidan som scoping

Verkliga-world- webbsidor är 80-95% boilerplate . Artikeln du faktiskt vill ha lever inuti två eller tre ompackningar `<div>`s ur två eller tre hundra.

Så extraktorn måste göra två saker

1. **Identifisera kroppen.** Vilket underträd i den analyserade DOM-damen innehåller den faktiska innehållet, och vilka underträd är krom?
2. **Rendera kroppen.** När du har isolerat det, projekterar du att underträdet till Markdown som bevarar strukturen en nedgående läsare. ( människa eller LLM, ) bryr sig om,

Identifiering är den svårare. *extraktion av webbinnehåll* och det etablerade måttstocket är WCXB. Den senaste Standpunkten när jag började läsa var ett Python-verktyg som heter Trafilatura *modelllärning* genom fingeravtryck

Formen är:

```
parse HTML → clean → fingerprint the structure → does this fingerprint
  match a template I've seen on this host before?
    yes → apply the cached extractor → render
    no  → run the heuristic classifier → induce a fresh extractor →
          cache it under this host's fingerprint → render
```

Första gången portaal ser en sida från en värden, så betalar den fulla heuristiska kostnaden- klassifier kostnaden M SK2μs för en mellanlig sida ). Varje efterföljande sida från den värden vars strukturella fingeravtryck matchar får den kasserade extraktorn iställetMska4 som är en handfull CSS Mska5 väljare sökningar | Mska6μs | mska7 | Samma värden med samma template | Мska9 | vilket är större delen av en sida |Mska10 | gå genom den snabba vägen |mska11 | sidor med nyskapade layouter utlösar en refit

Fingeravtrycket är en MinHash skiss över en normaliserad DOM-representation. ,, med en LSH-bander längst upp för sub- -millisekundersonden. Matchmatten spelar ingen roll.

## Gåraren

Renderingssidan är där den dogfooding-loopen fick mig att tjäna det.

Den första versionen av StyloExtract utlöste Markdown genom att gå igenom klassifierade blocker och projicera `element.TextContent.Trim()` in i utgången . Varje block blev en punkt `# text` oberoende av om det var H1 eller H 4. Ankar kollapsade till bar text

Detta är tekniskt sett "Markdown." Det är också oanvändbartM SK3 Den AI-kralare ber Markdown att *undvika att analysera krom*, och vi ger den en vägg av paragrafer där det enda signalet ibland är platt - textbegreppsord fraser, . Hela poängen med att använda Markdown är att strukturen bär med sig information

Så nästa version av StyloExtract går genom dom i varje klassificerad innehållsblock och släpper ut riktiga GFM:

- `<h1>` genom `<h6>` blir 1 genom 6 `#` tecken
- `<a href>` blir `[text](href)`
- `<em>` och `<strong>` att hålla fokus
- `<code>` stannar bakstängd, `<pre>` blir en fenced block med `language-x` förflyttad
- `<ul>` och `<ol>` utsläppa riktiga kula och nummererade listor
- `<blockquote>` prefixer varje linje med `> ` ( och multiplikationen i §1 av - får `> body\n>\n> body` konventionen)
- `<table>` rekonstrueras genom en WHATWG-slot-nätsalgoritm med korrekt colspan / radsspan ♫ / caption ♫ МSK3 alignment handling ♫, rinner tillbaka till rådata HTML när källan är för komplex för GFM att uttrycka ♫
- Bilder renderar som `![alt](src)` vare sig de är inline i prose eller självständiga i `<figure>`

Blättaren är en StringBuilder per block , en inre scratchbuffer som återanvänds över hela cellerna | / | listadecken |, | och ett enda genomdrag över DOM | МSK3 | Efter att調整a | ( | var det första klippet ♫ 304 | μs | MiSk6 | miSk7 | KB för en tabell | MISk8 | tung sida |MISk9 | den sitter vid

Bäckern är också den del som gick sönder först

## Hundfood-loopet

lucidVIEW är en markerdown-viewer som jag byggde tillsammans med StyloExtract. Produkten är en webblader - sidareader som tar en URL , tar fram den M SK3 kör Styloextract över reaktionen MSC4 och visar resultatet av Markdown Mska5 användandet är Mska6 Jag vill läsa den här bloggposten på en långsam koppling utan JavaScript M Ska7 eller på ett telefon utan reklamkatalogenM Ska8 eller bara för att den ursprungliga sidan har öga M ska9 blädd Csss

Vad lucidVIEW gav mig var en dogfooding-loop med tänder... De andra StyloBot-utsläppen, - och serierna, talar om brädan som den dogfoodande ytan.;, att ' är sann på protocollayret. . Men bräden läser data som det detekteringsmotorn producerar *jag*. IM SK1m i kretsloppet . I+'får jag förlåta en hel del konstiga apparater som ser ut så länge de underliggande mätarna är rätt

lucidVIEW renderar Markdown till en nedströmsreader (me, men läsa innehållet ). Det är samma form som den AI-crawlers användande fallet *materia*, inte som instrumentation . När utgången är felaktiga, ser man den omedelbartM SK3 Det finns inga abstraktionslayer mellan | " | extraktorn som producerade X | МSK6 | och |" | Jag läser X

Så jag öppnar lucidVIEW och pekar på min egen bloggindex. `[Post title](/blog/post)` - den bokstavliga stavra texten - istället för en stilad, klickabar länk

Enheten-test suite är grönt. Befestelsen I 'd skrevs för M SK3anker inuti inräknade HTMLMSC4 passerarMska5 Utgången innehåller raden `[Post title](/blog/post)`. Markdown är ändå fel

## Den bugklassen skulle inte ha blivit upptäckt

Blogindexet är byggt med Tailwind. Tail wind-utgången är inräknad för läsbarhet : varje wrapper div sätter två utrymmen längre än dess moder

```html
<section class="...">
    <div class="container...">
        <div class="grid...">
            <div class="card...">
                <a href="/blog/post-a">Post title</a>
                ...
```

Trädmatten' texten - handledaren, `AppendEscapedInline`, kollapsar rader av vita utrymme till ett enda utrymmen . Inuti en sändning gör den detta korrekt `prevWs` flagga som trycker ner ett järjestet vitt utrymme släpper ut. Mellan samtalen , löser flaggan tillbaka. Så när gåaren besöker ett järjestett textM SK3 nodMska4avMske5whitespace syskon | - | vilket är exakt vad inräknade HTML producerar mellan tomma element i linje |- | varje roep släppt ut en kollapsad ledande utrymd på linje ♫ - | start ♫

Fyra inteckningsbeläggningar, fyra texter - nod, - vitrutor, syskon, M SK3 fyra kollapsade utrymmen, . CommonMark, Mska5, inräknat, M Ska6, kod, Mka7, stängsregeln aktiveras på fyra utrymme, Mkka8, Wanderaren, Msaka9 `[Post title](/blog/post)` landar på en linje som börjar med fyra utrymmen. Markdig tolkar hela linjen som en kodblock . Klapptexten visar texten som bokstavlig

Enhetens test går förbi eftersom testet bekräftar raden `[Post title](/blog/post)` dyker upp någonstans i utgången.

Bestämningen är två linjer : vid linjen-startM SK2 primär `prevWs = true` så att *först* den ledande vita ytan från varje textknoten hoppar bort istället för att skicka ut den. Den ledande blanken ytan inuti inräknade HTML når aldrig utgången . Insidan - stycket kör fortfarande kollapsa till ett enda utrymme

Det intressanta är *borde* har påstått. Ytan -text `.Contains()` kontrollerar *tecken* är närvarande. De var . De stod bara i fel CommonMark blocktyp *strukturell* AST ger dig det gratis.

- Noll inräknade kodblock om det inte var ett beskyddat block som var källan
- Varje länk i källan HTML överlever som en riktig `LinkInline` i AST
- Den fenced-blockräkningen matchar den deklarerade förväntan

För att bevisa att lint hade tänder, Jag vände tillbaka originalfixen och körde lint-skiten . En exakt enda test misslyckades - den som använder fyra apparater

Den där ena checken går nu över varje löparetest i paketet. Framtidens regressioner av samma klass får fångats strukturellt , inte genom att matcha strängar M SK2 Vilket betyder att I' hittar dem i CI istället för i lucidVIEW .

![mest uppenbart.net gjord i lucidVIEW efter v1.7.1 walker fix : varje bloggM SK3postkort löser sig som en verklig, klikalig länk med sin sammanfattning som en korrekt punkt](lucidview-mostlylucid.png)

## Vad dogfooding egentligen kräver

Det finns en fas i varje produkt där enhetens tester är gröna och demonstrationen fungerar, och operatoren har satt rätt konfigurationsflaggor och du skickar ut en Veröffentlichung och en dag senare öppnar en användare ett problem som säger: "" " - det här fungerar inte - ' - det funkar inte på riktig innehåll - M SK3 - varje produkt träffar detta - . - Den ärliga svaret är att testerna och demonstrationsinhalten är sterila på ett sätt som den riktiga innehållet inte är.

Hundfooding-loop är det billigaste sättet att komma ut ur den här fällan. *separat produkt*, med separata användare *råkar konsumera StyloExtract's utgång som dess produkt yta*. Om lucidVIEW visar en bruten markeringM SK1 lucidVIEW ' dess användare klagar på lucidVIEW

Den separationen spelar roll.. Om lucidVIEW var en StyloExtract-testfixering, skulle jag ha skrivit den med sterila innehåll eftersom jag vet vad extraktorn gillar. . Eftersom lucidVIEW finns i sin egen värld, så är det ,, som pekar på min riktiga blogg, ,, som använder Tailwind.

En stor del av v1.7.x-utsläppen spårar tillbaka till den här loopen . Den strukturerade-walker-outputten M SK3vMska4 skickades eftersom lucidVIEWM Ska5s första version var oläslig när StyloExtract emitterade platta paragrafer

Patternet, ', är inte nytt. *konsumentprodukter* parallellt med *infrastrukturprodukt*, och konsumera din egen infrastruktur genom konsumenten *klass* omfattningen vid infrastrukturlagret ökar med allting som konsumenterna inser att du inte tänkte skriva en test för

## Avstängda loopar över högen

StyloExtract / lucidVIEW är ett exempel på en mönster som jag kör över varje projekt

```mermaid
flowchart LR
    PB[visible product<br/>behaviour] --> TR[test/review<br/>loop]
    TR --> AP[architectural<br/>primitive]
    AP --> RL[reusable<br/>library]
    RL --> WE[written<br/>explanation]
    WE --> LG[LLM-readable<br/>grammar]
    LG --> NP[next<br/>product]
    NP --> PB
```

Ett bug täcker ytan i ett produkt. Rättandet blir en strukturell assertion | ( | test | / | review loop | МSK3 | The asseration turns into a primitive that other things can use

Abhängighetsgrafen som detta producerar i alla mina projekt ser ut som

```mermaid
flowchart TB
    LV[lucidVIEW<br/>Avalonia markdown reader]
    SB[StyloBot<br/>detection gateway]
    SF[StyloFlow<br/>policy + flow runtime]
    SE[StyloExtract<br/>HTML → Markdown]
    NA[mostlylucid.Naiad<br/>Mermaid renderer fork]
    DA[mostlylucid.dagre<br/>graph layout]
    EP[mostlylucid.ephemeral<br/>signal + sketch primitives]
    UT[Mostlylucid.Avalonia.UITesting<br/>Avalonia UI test framework]

    SB --> SF
    SB --> SE
    LV --> SE
    LV --> NA
    NA --> DA
    SE --> EP
    SF --> EP
    LV -.tests via.-> UT
```

`Mostlylucid.Avalonia.UITesting` stänger den likvärdiga kretsloppet på skrivbordets sida. StyloExtract.Playwright stänger det på den utan huvuden - webbsidan . Samma mönster M SK4 olika yta Mska5 en testning Mske6 automatiserad anpassning låter den agentiska kretsningen faktiskt kontrollera produktbeteendet som den förväntades fixa

Världshandeln är verklig. Grapet är okänt. En bug i `mostlylucid.ephemeral` rinner upp genom StyloExtract och StyloFlow till StyloBot och lucidVIEW. Förenklingen är tid - inboxade releases ( ingen dragning i gränssnittet , versionerna skepper när skalan som de påstår skepper M SK4 och att varje lager har sin egen test-skiva MSC5 fördelen är att jag kan arbeta över alla av dem på en vecka och kostnaden för kontexten

Det betyder också att skrivandet av den här artikeln inte är en sida. StyloExtract har ett strukturellt lint-hellar och ett dokumenterat promenadkontrakt.

Det här fungerar för mig eftersom slutet av kretsar är vad jag tror.

## Hur lucidVIEW kopplar in den

```mermaid
flowchart LR
    URL[URL bar] --> Fetch[HttpClient GET<br/>Accept: text/markdown, text/html]
    Fetch --> Sniff{Content-Type}
    Sniff -->|text/markdown| Render[LiveMarkdown.Avalonia]
    Sniff -->|text/html| Convert[HtmlToMarkdownService]
    Convert --> Sparse{< 200 chars +<br/>SPA markers?}
    Sparse -->|yes| Stub[Stub: needs JS,<br/>open in browser?]
    Sparse -->|no| Render
    Stub --> Render
```

Fyra paketreferenser och en tjänst:

```csharp
public sealed class HtmlToMarkdownService
{
    private readonly IHtmlDomParser _parser = new AngleSharpHtmlDomParser();
    private readonly IDomCleaner _cleaner = new DomCleaner();
    private readonly IBlockSegmenter _segmenter = new BlockSegmenter();
    private readonly IBlockClassifier _classifier =
        HeuristicBlockClassifier.LoadFromEmbeddedResources();
    private readonly IMarkdownRenderer _renderer = new TypedMarkdownRenderer();

    public string Convert(string html, Uri? sourceUri = null)
    {
        var doc = _parser.Parse(html, sourceUri);
        _cleaner.Clean(doc);
        var blocks = _classifier.Classify(_segmenter.Segment(doc));
        return _renderer.Render(blocks, ExtractionProfile.RagFull);
    }
}
```

Det är "'", it , . . En status- --- -bar- -icon visar vilken väg som körts. ( -direkt MD - -/ - omvandlat. ♫ / -SPA- -stub -M SK6 - så att när något ser fel ut kan användaren peka på högra skivan

![Wikipedia's markerad artikel gjord inuti lucidVIEW via StyloExtract: titningshierarchy oföränderlig , linjära länkar liveM SK3 kodsamples bevarade](lucidview-wikipedia.png)

## Pipelinet, ,, i olika steg

```mermaid
flowchart LR
    HTML[HTML] --> P[AngleSharpHtmlDomParser]
    P --> C[DomCleaner<br/>strip script/style]
    C --> S[BlockSegmenter<br/>candidate subtrees]
    S --> K[HeuristicBlockClassifier<br/>role + score]
    K --> W[DomMarkdownWalker<br/>GFM per block]
    W --> R[TypedMarkdownRenderer<br/>profile gate]
    R --> MD[Markdown]
```

Parser + renare är AngleSharp wrappers. Den intressanta koden börjar vid segmentering

**Segmenter.** Gå genom kroppen, spola vilken semantisk tag som helst (`main`/`article`/`section`/`h1`-`h6`/`ul`/`pre`/...) plus något annat `<div>`/`<section>` att allt annat är för litet för att vara nöjd.

```csharp
public IReadOnlyList<IElement> Segment(IDocument document)
{
    if (document.Body is null) return Array.Empty<IElement>();
    var result = new List<IElement>();
    Walk(document.Body, result);
    return result;
}

private static void Walk(IElement element, List<IElement> sink)
{
    if (SemanticTags.Contains(element.TagName))   sink.Add(element);
    else if (IsBlockyDiv(element))                sink.Add(element);
    foreach (var child in element.Children) Walk(child, sink);
}
```

**Klassifier.** Punkter varje kandidat, ,, välja icke--, överlappande vinnare, M SK2, märke med `BlockRole` (MainContent , Heading, NavigationM SK3 KolplattaMST4 upprepade elementM ST5 MST6 Varje vald block får sin DOM gå in i strukturerad MarkdownM st7

```csharp
foreach (var element in selected)
{
    var role = ClassifyRole(element);
    yield return new ExtractedBlock
    {
        Role     = role,
        Text     = element.TextContent.Trim(),
        Markdown = ShouldRenderMarkdown(role) ? DomMarkdownWalker.Render(element) : "",
        Links    = ExtractLinks(element),
        XPath    = XPathBuilder.For(element),
        // ...
    };
}
```

**Walker.** Ett StringBuilder , ett DOM-pass. Den `<a>` fallet är där v1.7.1 bug levde

```csharp
case "a":
    var href = el.GetAttribute("href") ?? "";
    if (href.Length == 0) { WriteInlineChildren(dest, el); return; }
    dest.Append('[');
    WriteInlineChildren(dest, el);
    dest.Append("](").Append(href).Append(')');
    return;
```

( Rättelsen var ' inte här `AppendEscapedInline`, som matade in ledande whitespace i `dest` innan det här fallet någonsin gick. `[` gör att CommonMark analyserar linjen som en kodblock.)

**Renderer.** Tor efter profil. `RagFull` håller artikeln-kontext M SK1Breadcrumb, förknippade länkarMSC3 och droppar krom (FooterMST5 HeaderM ST6 CookieBannerM st7 NavMst8 `MainContentOnly` håller bara kroppen. `AgentNavigation` håller bara nav (for crawl-link discoveryM SK2

```csharp
return p switch
{
    ExtractionProfile.RagFull => b.Role is not (BlockRole.Footer or BlockRole.Header
        or BlockRole.Advertisement or BlockRole.CookieBanner or BlockRole.Boilerplate
        or BlockRole.Unknown or BlockRole.PrimaryNavigation or BlockRole.SecondaryNavigation),
    ExtractionProfile.MainContentOnly => b.Role is BlockRole.MainContent or BlockRole.Article
        or BlockRole.Heading or BlockRole.Summary or BlockRole.Table or BlockRole.CodeBlock
        or BlockRole.RepeatedItem,
    ExtractionProfile.AgentNavigation => b.Role is BlockRole.PrimaryNavigation
        or BlockRole.SecondaryNavigation or BlockRole.Breadcrumb or BlockRole.Form,
    _ => true
};
```

## Spelare för JS-överlämnade sidor

StyloExtract's heuristik fungerar på den statiska HTML som servern gav tillbaka. Det där ' det där är okej för SSR-sidorna | ( Wikipedia |, | GitHub | МSK5 | de flesta nyhetsartikeln URLs ♫ ). | Det bryts på SPA skalor där det synliga innehållet är hydraterad klient ♪ - ♪ utanför en JSON-blob ♪ `__NEXT_DATA__`, `__NUXT__`, `__APOLLO_STATE__`. Den statiska HTML finns med meta-tag + en skelett , och det finns ingen kropp som klassifieren kan hitta

`Mostlylucid.StyloExtract.Playwright` plugg i uppström av verket. Det implementerar `IRenderedHtmlFetcher`, driver en huvudlös Chromium via Playwright , väntar på hydration M SK2 och skickar tillbaka posten-JS HTML till resten av extraktorn :

```csharp
public async Task<RenderedHtmlResult> FetchAsync(
    Uri uri, RenderOptions? options = null, CancellationToken cancellationToken = default)
{
    var page = await _context.NewPageAsync();
    await page.GotoAsync(uri.ToString(), new PageGotoOptions
    {
        WaitUntil = WaitUntilState.NetworkIdle,
        Timeout = options?.NavigationTimeoutMs ?? 15000,
    });
    var html = await page.ContentAsync();
    await page.CloseAsync();
    return new RenderedHtmlResult { Html = html, FinalUrl = page.Url };
}
```

Samma parser, ,, renare, ,, segmenter, M SK2, klassificerer,,, walker, ,, renderer nedströms, MSC5. Den enda skillnaden är att ingången till HTML nu innehåller den hydraterade DOM som JS producerade,

```mermaid
flowchart LR
    URL[URL] --> Choice{Render mode}
    Choice -->|static| HTTP[HttpClient]
    Choice -->|js-required| PW[PlaywrightHtmlFetcher<br/>headless Chromium]
    HTTP --> Pipeline[parse → clean → segment<br/>→ classify → walk → render]
    PW --> Pipeline
    Pipeline --> MD[Markdown]
```

Det här är det rätta svaret när en operatör driver StyloExtract server-sidan och en huvudlös webblader är en rimlig beroende . CLI skickar två binärsystem för exakt den här splittningen `stylo-extract` (, AOT, ,, ~12, MB, ,, statisk, M SK4, endast ) och `stylo-extract-playwright` (~120MB en gång Playwright 's webblapp landar, J.S.

**lucidVIEW använder den inte.** lucidVIEW är en markerdown-reader ; dess identitet är " inget kromium , ingen webblader-motor M SK3 inget | 200 MB av beroenden av en egenskap MSC5 Bundling Playwright skulle placera en huvudlös webbladare inuti en app vars topp är att det inte finns någon i den.

## Andra saker som dogfooding visar sig

Fyra icke-- bugs som formade lucidVIEW

- **Specen som drev v1.7.0.** Den första uppbyggnaden av utsläppta platta paragrafer (`element.TextContent.Trim()` per block). Wikipedia var oläsbar , bloggare kort förlorade varje anker [`docs/styloextract-markdown-spec.md`](https://github.com/scottgal/lucidview/blob/main/docs/styloextract-markdown-spec.md) räkna ut vad som saknade: skriftsnivåer , inline-runnningar, listorM SK3 GFM-tabellarMska4 blockbilderMske5 med fyra återskapare URLsMsek6 Den strukturerade Mska7 gåva funktionen i vM Ska8 implementerar den specifika

- **Spasidor.** `bbc.com/news` är Next.jsM SK1 statiskt HTML är metatag + a `__NEXT_DATA__` JSON-blöd hydraterad av JS. StyloExtract återvänder korrekt | ~1 | grej eftersom det inte finns någon kropp |' | . | lucidVIEW nu luktar på kända rammarkörer`__NEXT_DATA__`, `__NUXT__`, `__APOLLO_STATE__`, `data-reactroot`, `ng-version=`, `__REMIX_DATA__`) ochM SK1 på sparse utgång + markering *" Den här sidan använder { konstruktionen M SK2 lucidVIEW funkar inte JavaScript . öppna i webblader MSC4* BBC's artikel URLs (`/news/articles/<id>`) tjänar riktiga `<main>` och konvertera rent. Homepage / artikelasymmetry, inte en omvandlares misslyckandeM SK3
  
  ![lucidVIEW laddar bbc.comM SK1nyhetslärmningar : detekterar nästa.jsMSC4 visar en ledtråd som förklarar klientenMska5 sida-representation är inte supporteradMske6 erbjuder inget öppetM Ska7 iMska8 webbladerhandoffM ska9 Statusbalken visar varningen Mska10 triangel källa M Ska11 mode-icon plus ett | Mska12 | klient | Мska13 | sida-referenserad | mska14 | ingen omvandling mska15 | tag |Mska16 |](lucidview-bbc-spa.png)

- **Semantik- fria sidor.** `example.com` har inget `<main>`, nej `<article>`. Helistiken ger inte rätt något tillbaka . Samma punktflöde fångar det

- **Korrekta-men-dödlösa utsläppM SK2** My blog's kategorifilter är `<select><option>`. Gåraren hittar inget `<a href>`, släpper ut valtekstin som en punkt *"Allt | . | NET | (41) | МSK3 | s |(1) | AKP | m(1) | AI | M(69)..."* Correct (options are't linksM SK2 but visually awful . A future walker pass could detect `<select>` och skicka ut en definitionslista eller `[42 categories]` sammanfattning.

Plus en skyddsrail som kom ut ur att motstå den uppenbara genvägen. När extraktionen är sparsam är frestelsen " att falla tillbaka till Jina Reader | / | r | . |jina | МSK4 | ai |/ | Trafilatura | ." | lucidVIEW gör det inte ♫ ' | t | . ♫ Att spåra användar-URL: s genom en tredje person ♫ - | parters läsare gör läsaren till en hemlighetsdröjning ♪ ; | varje URL som användaren besöker flödar genom någon annan leverantör ♪ `<meta>` / `og:` tag, sedan M SK1öppna i webbläsaren ?" Varje steg matas *StyloExtrakt* en annan fetch från samma ursprung

## Det godtyckliga "-" -decken på plats

Vad dogfood-loopet är bra på att hitta är bristande resultat på bra.

Jag har en rökare nu som tar fram ett dussin verkliga - World sites och kastar ut extraktorn ' utgången . På den strukturellt kooperativa halvan - nyheter ( BBC , Guardian ), Wikipedia , GitHub repo READMEs , Ghost .org ' changelog , Hacker News - the heuristic classifier finds the main content cleanly and the walker produces reader - grad Markdown . Ten till trettio kilobyte ren prosa per sida

På den andra halvan - Allbirds *ingenting*. Noll MainContent blocks . Varje kandidat blir demonterad till boilerplate eftersom sidan inte använder `<main>` eller `<article>` och klassernas namn matchar inga av mönsterna i ramverket.

Att lägga till fler element i den JSON-fiilen är den uppenbara rörelsen.

Den verkliga lösningen är en tränad modell. Per, -elements egenskaper (tagidentitet,, klass, - namnstambukar,, text,M SK6 längd,MST7 länk,Mst, MST8, densitet, Mst, 9, symbiolika former, M st, 10, präsensens av förfäder,M st, 11, till ett litet gradient, M ST, 12, trängt klassificering, M St, 13, tränad på WCXB-bemärkt korpus, M S T 14, exporterad till ONNX, M T 15, som kallas från M S t 16, NET via `Microsoft.ML.OnnxRuntime`. En ♫ 45- ♫ egenskapsvektor per kandidat ♫ ♫, ♫

Designen för detta är skriven i StyloExtract repo (`docs/ml-classifier-v2-design.md`). Implementationen är inte gjord, ' inte gjort, . Saken som det är blockerat på är inte ' inte ingenjörskonst, M SK5, det är ' som operatörens fråga om att skicka en 25 MB ONNX Runtime native binary in i porten, MSC9 dess utplacering, MST10, eller att behålla ML som ett separat optionellt paket som operatörern väljer att använda. MST11. Det optionella svaret på MST12 är det rätta för ett FOSS-paket ekosystem, MTS13 men det betyder att modellen inte är standardmässigt gjord. MTS15, vilket betyder att de flesta operatörerna kommer att vinna, MNT16, inte få det, MITT17, vilket innebär att hursomhelst hanurismen bär den största delen av produktionslasten.

I det mellanspelet, ,, får operatörerna *handmatig* escape hatch() : operator (operatör) - autorerad YAML-templates , en file per host , analyserad AOT - ren , varm - överrider induktionskedjan för den hosten `weird-shopify-tenant.com` producerar tomma markering, ,, skriver ett femtal YAML-Datei med linjer - som säger """ för den här värdena. `.product-description-body`", kastar in den `config/templates/`, och starttid plockar upp den via FileSystemWatcher inom några sekunder