@wenathlan/saddle 1.8.2 → 1.8.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -9
- package/browser/playwright.js +22 -0
- package/docs/.gitkeep +0 -0
- package/docs/gapmatrix.md +3 -3
- package/docs/libraryapi.md +1 -0
- package/docs/logs/.gitkeep +0 -0
- package/docs/packageaudit185.md +23 -0
- package/docs/plans/00.index.md +50 -0
- package/docs/plans/01.architecture.md +86 -0
- package/docs/plans/02.research.computer.use.md +58 -0
- package/docs/plans/03.research.captcha.bypass.md +68 -0
- package/docs/plans/04.research.sandbox.ai.md +52 -0
- package/docs/plans/05.capture.platform.md +57 -0
- package/docs/plans/06.dependencies.md +97 -0
- package/docs/plans/07.captcha.test.page.md +41 -0
- package/docs/plans/08.production.infra.md +70 -0
- package/docs/plans/09.database.schema.md +121 -0
- package/docs/plans/10.cloudinary.storage.md +57 -0
- package/docs/plans/11.movement.logs.json.md +72 -0
- package/docs/plans/12.research.atlas.agent.browser.md +79 -0
- package/docs/plans/13.research.anti.detection.md +898 -0
- package/docs/plans/14.research.proxy.md +1495 -0
- package/docs/plans/15.research.retry.rate.limit.md +1958 -0
- package/docs/plans/16.research.crawling.md +1417 -0
- package/docs/plans/17.research.caching.md +1610 -0
- package/docs/plans/18.research.content.extraction.md +1952 -0
- package/docs/plans/19.research.errors.events.md +1523 -0
- package/docs/plans/20.research.zod.validation.md +1350 -0
- package/docs/plans/21.research.batch.concurrency.md +1888 -0
- package/docs/plans/22.research.universal.runtime.md +944 -0
- package/docs/plans/23.research.ai.integration.md +1465 -0
- package/docs/plans/24.research.memory.persistence.md +1979 -0
- package/docs/plans/25.research.server.api.md +342 -0
- package/docs/plans/26.research.compilation.md +249 -0
- package/docs/plans/27.research.html.parsing.md +251 -0
- package/docs/plans/28.action.plan.md +50 -0
- package/docs/plans/29.api.reference.md +174 -0
- package/docs/plans/30.architecture.plan.md +94 -0
- package/docs/plans/31.auditoria.dados.md +163 -0
- package/docs/plans/32.bots.automacao.computacional.md +214 -0
- package/docs/plans/33.bots.codigo.revisao.md +220 -0
- package/docs/plans/34.bots.seguranca.cicd.md +366 -0
- package/docs/plans/35.comparativo.concorrencia.md +464 -0
- package/docs/plans/36.computational.memory.md +340 -0
- package/docs/plans/37.deploystrategy.md +394 -0
- package/docs/plans/38.flow.md +155 -0
- package/docs/plans/39.multi.platform.bot.md +252 -0
- package/docs/plans/40.npm.publish.md +250 -0
- package/docs/plans/41.o.que.falta.md +407 -0
- package/docs/plans/42.pesquisa.concorrencia.md +721 -0
- package/docs/plans/43.plan.universal.architecture.md +496 -0
- package/docs/plans/44.reference.md +100 -0
- package/docs/plans/45.robotarchitecture.md +237 -0
- package/docs/plans/46.scdnintegration.md +284 -0
- package/docs/plans/47.multiforge.readme.md +129 -0
- package/docs/plans/48.theory.v4.repo.os.md +152 -0
- package/docs/plans/49.third.party.infra.md +12 -0
- package/docs/plans/50.file.as.compute.md +39 -0
- package/docs/plans/51.architecture.virtual.processor.md +80 -0
- package/docs/plans/52.manifesto.v8.md +11 -0
- package/docs/plans/58.cdn.list.md +23 -0
- package/docs/plans/59.sql.frameworks.md +33 -0
- package/docs/plans/60.sql.thirdparty.md +26 -0
- package/docs/plans/61.objective.multiforge.md +63 -0
- package/docs/plans/62.huggingface.upload.md +26 -0
- package/docs/plans/63.kaggle.upload.md +24 -0
- package/docs/plans/64.npm.storage.md +30 -0
- package/docs/plans/65.rclone.terabox.md +32 -0
- package/docs/plans/66.buckets.and.models.todo.md +14 -0
- package/docs/plans/67.database.todo.md +13 -0
- package/docs/plans/68.deploy.packages.todo.md +12 -0
- package/docs/plans/69.report.human.operator.md +133 -0
- package/docs/plans/70.report.brain2qwerty.ems.md +135 -0
- package/docs/plans/71.report.hd.infinito.vram.md +155 -0
- package/docs/plans/72.plan.hd.infinito.node.md +146 -0
- package/docs/plans/73.plan.scifi.repos.md +125 -0
- package/docs/plans/74.000.manifesto.v8.flat.2..md +11 -0
- package/docs/plans/README.md +489 -0
- package/docs/plans/aggregate_platforms.mjs +146 -0
- package/docs/plans/examplesession.json +36 -0
- package/docs/plans/missing-facts.md +192 -0
- package/docs/plans/models.md +64 -0
- package/docs/plans/organize.cjs +270 -0
- package/docs/plans/platforms.md +2887 -0
- package/docs/plans/sites.md +31322 -0
- package/docs/platformpipelineaudit.md +18 -0
- package/docs/platformpipelines.md +13 -0
- package/docs/registryresearch.md +4 -0
- package/docs/release.md +4 -4
- package/docs/release182notes.md +2 -0
- package/docs/release184notes.md +7 -0
- package/docs/release185notes.md +7 -0
- package/docs/sources/farm.py +117 -0
- package/docs/sources/html/saddle1.html +132 -0
- package/docs/sources/html/saddle2.html +157 -0
- package/docs/sources/html/saddle3.html +119 -0
- package/docs/sources/html/saddle4.html +144 -0
- package/docs/sources/html/saddle5.html +72 -0
- package/docs/sources/html/saddle6.html +171 -0
- package/docs/sources/html/saddle7.html +236 -0
- package/docs/sources/saddle.ts +74 -0
- package/docs/sources/schema.prisma +88 -0
- package/docs/sources/script.sh +64 -0
- package/docs/sources/workflows.yml +458 -0
- package/docs/talks1/_body.txt +14 -0
- package/docs/talks1/_index.md +15 -0
- package/docs/talks1/_screenshot.png +0 -0
- package/docs/talks1/assistant-01.md +5 -0
- package/docs/talks1/assistant-02.md +5 -0
- package/docs/talks1/assistant-03.md +531 -0
- package/docs/talks1/assistant-04.md +26 -0
- package/docs/talks1/assistant-05.md +774 -0
- package/docs/talks1/assistant-06.md +1718 -0
- package/docs/talks1/scrape-share.cjs +185 -0
- package/docs/talks1/scrape-share.ts +183 -0
- package/docs/talks1/user-01.md +3 -0
- package/docs/talks1/user-02.md +3 -0
- package/docs/talks1/user-03.md +88 -0
- package/docs/talks1/user-04.md +3 -0
- package/docs/talks1/user-05.md +3 -0
- package/docs/talks1/user-06.md +88 -0
- package/docs/talks1/user-07.md +88 -0
- package/docs/talks2/_body.txt +14 -0
- package/docs/talks2/_index.md +16 -0
- package/docs/talks2/_screenshot.png +0 -0
- package/docs/talks2/assistant-01.md +5 -0
- package/docs/talks2/assistant-02.md +5 -0
- package/docs/talks2/assistant-03.md +424 -0
- package/docs/talks2/assistant-04.md +598 -0
- package/docs/talks2/assistant-05.md +1280 -0
- package/docs/talks2/assistant-06.md +1227 -0
- package/docs/talks2/assistant-07.md +1252 -0
- package/docs/talks2/user-01.md +3 -0
- package/docs/talks2/user-02.md +3 -0
- package/docs/talks2/user-03.md +88 -0
- package/docs/talks2/user-04.md +88 -0
- package/docs/talks2/user-05.md +88 -0
- package/docs/talks2/user-06.md +88 -0
- package/docs/talks2/user-07.md +3 -0
- package/docs/talks3/_body.txt +467 -0
- package/docs/talks3/_index.md +10 -0
- package/docs/talks3/_screenshot.png +0 -0
- package/docs/talks3/assistant-01.md +417 -0
- package/docs/talks3/assistant-02.md +417 -0
- package/docs/talks3/assistant-03.md +29 -0
- package/docs/talks3/assistant-04.md +727 -0
- package/docs/talks3/user-01.md +88 -0
- package/docs/talks3/user-02.md +88 -0
- package/docs/talks3/user-03.md +3 -0
- package/docs/talks3/user-04.md +3 -0
- package/docs/talks4/_body.txt +14 -0
- package/docs/talks4/_index.md +12 -0
- package/docs/talks4/_screenshot.png +0 -0
- package/docs/talks4/assistant-01.md +5 -0
- package/docs/talks4/assistant-02.md +5 -0
- package/docs/talks4/assistant-03.md +35 -0
- package/docs/talks4/assistant-04.md +512 -0
- package/docs/talks4/assistant-05.md +599 -0
- package/docs/talks4/user-01.md +3 -0
- package/docs/talks4/user-02.md +3 -0
- package/docs/talks4/user-03.md +88 -0
- package/docs/talks4/user-04.md +88 -0
- package/docs/talks4/user-05.md +7 -0
- package/docs/talks5/_body.txt +14 -0
- package/docs/talks5/_index.md +13 -0
- package/docs/talks5/_screenshot.png +0 -0
- package/docs/talks5/assistant-01.md +5 -0
- package/docs/talks5/assistant-02.md +5 -0
- package/docs/talks5/assistant-03.md +690 -0
- package/docs/talks5/assistant-04.md +758 -0
- package/docs/talks5/assistant-05.md +974 -0
- package/docs/talks5/user-01.md +3 -0
- package/docs/talks5/user-02.md +3 -0
- package/docs/talks5/user-03.md +105 -0
- package/docs/talks5/user-04.md +105 -0
- package/docs/talks5/user-05.md +63 -0
- package/docs/talks5/user-06.md +105 -0
- package/docs/talks6/_body.txt +14 -0
- package/docs/talks6/_index.md +9 -0
- package/docs/talks6/_screenshot.png +0 -0
- package/docs/talks6/assistant-01.md +5 -0
- package/docs/talks6/assistant-02.md +5 -0
- package/docs/talks6/assistant-03.md +1499 -0
- package/docs/talks6/user-01.md +3 -0
- package/docs/talks6/user-02.md +3 -0
- package/docs/talks6/user-03.md +88 -0
- package/docs/talks6/user-04.md +88 -0
- package/docs/talks7/_body.txt +14 -0
- package/docs/talks7/_index.md +10 -0
- package/docs/talks7/_screenshot.png +0 -0
- package/docs/talks7/assistant-01.md +5 -0
- package/docs/talks7/assistant-02.md +5 -0
- package/docs/talks7/assistant-03.md +523 -0
- package/docs/talks7/assistant-04.md +617 -0
- package/docs/talks7/user-01.md +3 -0
- package/docs/talks7/user-02.md +3 -0
- package/docs/talks7/user-03.md +105 -0
- package/docs/talks7/user-04.md +67 -0
- package/docs/talks8/conversa1.txt +1322 -0
- package/docs/talks8/conversa2.txt +237 -0
- package/docs/talks9/Beyond the Obvious_ 50 Plataformas Auto-Hospedadas de Forja de C/303/263digo para Al/303/251m de Gitea e GitLab.md" +174 -0
- package/docs/talks9/De NPM a Multi-Linguagem_ Uma Arquitetura T/303/251cnica para a Execu/303/247/303/243o Integrada de C/303/263digo no Ecossistema Node.js.md" +59 -0
- package/docs/talks9/De NPM a VMs Virtuais_ Uma An/303/241lise Arquitet/303/264nica para a Realiza/303/247/303/243o do Ciclo de Vida do Projeto SADDLE.md" +91 -0
- package/docs/talks9/Mapeamento da Engrenagem Computacional_ Uma Arquitetura para Execu/303/247/303/243o Isolada e Persist/303/252ncia em Ambientes Distribu/303/255dos.md" +116 -0
- package/docs/talks9/O Cen/303/241rio Pr/303/241tico do SADDLE_ Uma An/303/241lise de Viabilidade e Modelo de Ciclo de Vida Integrado.md" +128 -0
- package/docs/talks9/README (2).md +489 -0
- package/docs/talks9/README.md +198 -0
- package/docs/talks9/Viabilidade do Saddle_ Uma An/303/241lise T/303/251cnica da Transforma/303/247/303/243o de Armazenamento Remoto em Mem/303/263ria Computacional.md" +80 -0
- package/docs/talks9/conversa.txt +544 -0
- package/docs/talks9/other (2).md +39 -0
- package/docs/talks9/other.md +57 -0
- package/docs/talks9/outro.txt +24 -0
- package/extension/README.md +3 -3
- package/extension/serviceworker.js +63 -4
- package/extension/worker.js +3 -1
- package/package.json +12 -2
- package/packager/manifest.js +1 -1
- package/scrape/agent.ts +122 -0
- package/scrape/batch.ts +79 -0
- package/scrape/biome.json +76 -0
- package/scrape/browser.ts +222 -0
- package/scrape/cache.ts +84 -0
- package/scrape/chunking.ts +193 -0
- package/scrape/cli.ts +105 -0
- package/scrape/crawler.ts +115 -0
- package/scrape/dev-server.ts +94 -0
- package/scrape/errors.ts +132 -0
- package/scrape/events.ts +26 -0
- package/scrape/extract.ts +165 -0
- package/scrape/fetch.ts +105 -0
- package/scrape/formats.ts +85 -0
- package/scrape/headers.ts +71 -0
- package/scrape/index.ts +92 -0
- package/scrape/jsdom.d.ts +6 -0
- package/scrape/llms-txt.ts +84 -0
- package/scrape/middleware.ts +90 -0
- package/scrape/package-lock.json +9397 -0
- package/scrape/package.json +1420 -0
- package/scrape/pool.ts +95 -0
- package/scrape/port.ts +18 -0
- package/scrape/proxy.ts +103 -0
- package/scrape/rate-limiter.ts +95 -0
- package/scrape/renderer.ts +194 -0
- package/scrape/retry.ts +64 -0
- package/scrape/robots.ts +137 -0
- package/scrape/scrape.ts +123 -0
- package/scrape/serialize.ts +310 -0
- package/scrape/server.ts +137 -0
- package/scrape/session.ts +109 -0
- package/scrape/sitemap.ts +131 -0
- package/scrape/tokens.ts +45 -0
- package/scrape/tsconfig.json +28 -0
- package/scrape/types.ts +214 -0
- package/scrape/utils.ts +77 -0
- package/scrape/vite.config.ts +55 -0
- package/scrape/vitest.config.ts +17 -0
- package/workflow/templates.js +4 -4
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# Pesquisa: HTML Parsing e Extração (2026)
|
|
2
|
+
|
|
3
|
+
## 1. Parser Comparison
|
|
4
|
+
|
|
5
|
+
| Feature | **Cheerio** | **jsdom** | **node-html-parser** | **parse5** |
|
|
6
|
+
|---------|-------------|-----------|---------------------|------------|
|
|
7
|
+
| Weekly downloads | ~10M | ~8M | ~300K+ | ~235M (indirect) |
|
|
8
|
+
| Memory per page | ~1MB | ~50MB | ~1MB | <1MB |
|
|
9
|
+
| Startup time | Instant | Fast (~100ms) | Instant | Instant |
|
|
10
|
+
| API style | jQuery-like `$()` | Standard DOM `querySelector` | Simple DOM traversal | AST methods |
|
|
11
|
+
| Script execution | No | Yes (limited) | No | No |
|
|
12
|
+
| CSS selector support | Yes (css-select) | Yes (nwsapi) | Yes (basic) | No (AST only) |
|
|
13
|
+
| HTML5 spec compliance | Good | Good | Basic | Full |
|
|
14
|
+
| Malformed HTML | Good | Good | Basic | Excellent |
|
|
15
|
+
| Best for | Web scraping, HTML transform | Testing, DOM APIs | High-throughput | Build tools |
|
|
16
|
+
|
|
17
|
+
### Decision Tree
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
Is the content rendered by JavaScript (SPA/React/Vue)?
|
|
21
|
+
Yes → Use Playwright (not here)
|
|
22
|
+
No (static HTML):
|
|
23
|
+
Do you need DOM APIs (addEventListener, classList)?
|
|
24
|
+
Yes → jsdom
|
|
25
|
+
No → Do you need max throughput?
|
|
26
|
+
Yes → node-html-parser or parse5
|
|
27
|
+
No → Cheerio (best developer experience)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## 2. HTML Entities
|
|
31
|
+
|
|
32
|
+
| Library | Weekly Downloads | Performance (ops/s) | Standards |
|
|
33
|
+
|---------|-----------------|---------------------|-----------|
|
|
34
|
+
| **entities** | ~235M | 5,838,416 (fastest) | XML, HTML4, HTML5 |
|
|
35
|
+
| **html-entities** | ~31M | 2,919,637 | XML, HTML4, HTML5 |
|
|
36
|
+
| **he** | ~15M | 2,318,438 | HTML5 only |
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
import * as entities from "entities";
|
|
40
|
+
entities.decodeHTML("asdf & ÿ ü '"); // "asdf & ÿ ü '"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## 3. CSS Selector Engine: css-select
|
|
44
|
+
|
|
45
|
+
**Version:** 7.0.0 (March 2026)
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
import CSSselect from "css-select";
|
|
49
|
+
const results = CSSselect.selectAll('.article > h2:first-child', elements);
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Supports: `:scope`, `:is()`, `:where()`, `:has()`, attribute selectors, sibling combinators.
|
|
53
|
+
|
|
54
|
+
## 4. Structured Data Extraction
|
|
55
|
+
|
|
56
|
+
### Cheerio `$.extract()` (Declarative)
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
const $ = await cheerio.fromURL('https://example.com');
|
|
60
|
+
const data = $.extract({
|
|
61
|
+
title: 'title',
|
|
62
|
+
posts: [{
|
|
63
|
+
selector: '.post',
|
|
64
|
+
value: {
|
|
65
|
+
title: '.post-title',
|
|
66
|
+
link: { selector: 'a', value: 'href' },
|
|
67
|
+
date: { selector: 'time', value: 'datetime' },
|
|
68
|
+
tags: ['.tag'],
|
|
69
|
+
}
|
|
70
|
+
}]
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Extraction Priority Order
|
|
75
|
+
|
|
76
|
+
1. **JSON-LD** — `<script type="application/ld+json">`
|
|
77
|
+
2. **Microdata** — `itemscope`, `itemprop` attributes
|
|
78
|
+
3. **OpenGraph** — `og:` meta tags
|
|
79
|
+
4. **data-* attributes** — developer hooks
|
|
80
|
+
5. **CSS selectors** — last resort
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
// JSON-LD extraction
|
|
84
|
+
$('script[type="application/ld+json"]').each((_, el) => {
|
|
85
|
+
try {
|
|
86
|
+
const data = JSON.parse($(el).html());
|
|
87
|
+
if (data['@type'] === 'Product') {
|
|
88
|
+
console.log(data.name, data.offers?.price);
|
|
89
|
+
}
|
|
90
|
+
} catch {}
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## 5. Malformed HTML Handling
|
|
95
|
+
|
|
96
|
+
### htmlparser2 (Forgiving)
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
import htmlparser2 from "htmlparser2";
|
|
100
|
+
const parser = new htmlparser2.Parser({
|
|
101
|
+
onopentag(name, attribs) { /* ... */ },
|
|
102
|
+
ontext(text) { /* ... */ },
|
|
103
|
+
onclosetag(tagname) { /* ... */ },
|
|
104
|
+
}, {
|
|
105
|
+
decodeEntities: true,
|
|
106
|
+
xmlMode: false, // HTML mode (forgiving)
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### tag-soup (Lightweight, 20KB gzipped)
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
import { createDOMParser } from "tag-soup";
|
|
114
|
+
const parser = createDOMParser({
|
|
115
|
+
areUnbalancedEndTagsIgnored: true,
|
|
116
|
+
areUnbalancedStartTagsImplicitlyClosed: true,
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## 6. HTML Sanitization
|
|
121
|
+
|
|
122
|
+
### sanitize-html (Server-side, ~8.6M weekly downloads)
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
import sanitizeHtml from "sanitize-html";
|
|
126
|
+
const clean = sanitizeHtml(dirty, {
|
|
127
|
+
allowedTags: ['b', 'i', 'em', 'strong', 'a', 'p', 'ul', 'ol', 'li'],
|
|
128
|
+
allowedAttributes: { 'a': ['href', 'title'] },
|
|
129
|
+
transformTags: {
|
|
130
|
+
'a': sanitizeHtml.simpleTransform('a', { rel: 'noopener noreferrer' })
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### DOMPurify (Browser-side, ~40M weekly downloads)
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
import DOMPurify from "dompurify";
|
|
139
|
+
const clean = DOMPurify.sanitize('<b>hello</b><script>alert(1)</script>');
|
|
140
|
+
// Result: '<b>hello</b>'
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**Recommendation:** sanitize-html for server, DOMPurify for browser.
|
|
144
|
+
|
|
145
|
+
## 7. Text Extraction with Formatting
|
|
146
|
+
|
|
147
|
+
### Formatting-Preserving Walk
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
function extractFormattedText($: cheerio.CheerioAPI, element: cheerio.Element): string {
|
|
151
|
+
let result = '';
|
|
152
|
+
$(element).contents().each((_, node) => {
|
|
153
|
+
if (node.type === 'text') {
|
|
154
|
+
result += node.data;
|
|
155
|
+
} else if (node.type === 'tag') {
|
|
156
|
+
switch (node.name) {
|
|
157
|
+
case 'br': result += '\n'; break;
|
|
158
|
+
case 'p': result += extractFormattedText($, node) + '\n\n'; break;
|
|
159
|
+
case 'strong': case 'b': result += `**${extractFormattedText($, node)}**`; break;
|
|
160
|
+
case 'em': case 'i': result += `*${extractFormattedText($, node)}*`; break;
|
|
161
|
+
case 'h1': result += `# ${extractFormattedText($, node)}\n\n`; break;
|
|
162
|
+
case 'h2': result += `## ${extractFormattedText($, node)}\n\n`; break;
|
|
163
|
+
case 'li': result += `- ${extractFormattedText($, node)}\n`; break;
|
|
164
|
+
default: result += extractFormattedText($, node);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## 8. HTML-to-Markdown
|
|
173
|
+
|
|
174
|
+
### Performance Comparison (166KB Wikipedia page)
|
|
175
|
+
|
|
176
|
+
| Package | Time | Speed vs Turndown |
|
|
177
|
+
|---------|------|-------------------|
|
|
178
|
+
| **mdream** (Rust) | 0.34ms | 33x faster |
|
|
179
|
+
| **mdream** (JS) | 3.26ms | 3.5x faster |
|
|
180
|
+
| **Turndown** | 11.26ms | baseline |
|
|
181
|
+
| **node-html-markdown** | 14.31ms | 1.3x slower |
|
|
182
|
+
|
|
183
|
+
### Turndown (Industry Standard, 4.8M weekly downloads)
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
import TurndownService from 'turndown';
|
|
187
|
+
import turndownPluginGfm from 'turndown-plugin-gfm';
|
|
188
|
+
|
|
189
|
+
const turndownService = new TurndownService({
|
|
190
|
+
headingStyle: 'atx',
|
|
191
|
+
codeBlockStyle: 'fenced',
|
|
192
|
+
bulletListMarker: '-',
|
|
193
|
+
});
|
|
194
|
+
turndownService.use(turndownPluginGfm.gfm);
|
|
195
|
+
const markdown = turndownService.turndown(html);
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### html-to-text (Plain Text, v10.0.0)
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
import { convert } from 'html-to-text';
|
|
202
|
+
const text = convert(html, {
|
|
203
|
+
wordWrap: 80,
|
|
204
|
+
tables: true,
|
|
205
|
+
links: { hideLinkUrls: true },
|
|
206
|
+
});
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## 9. Table Parsing
|
|
210
|
+
|
|
211
|
+
### cheerio-tableparser (colspan/rowspan support)
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
import cheerioTableparser from "cheerio-tableparser";
|
|
215
|
+
cheerioTableparser($);
|
|
216
|
+
const data = $("table").parsetable(true, true, true);
|
|
217
|
+
// dupCols, dupRows, textMode
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## 10. Custom Extraction Rules
|
|
221
|
+
|
|
222
|
+
### Schema-Based Pattern
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
const schema = {
|
|
226
|
+
title: { selector: 'h1', type: 'text' },
|
|
227
|
+
price: {
|
|
228
|
+
selector: '.price',
|
|
229
|
+
type: 'text',
|
|
230
|
+
transform: (v: string) => parseFloat(v.replace(/[^0-9.]/g, ''))
|
|
231
|
+
},
|
|
232
|
+
tags: { selector: '.tag', type: 'text', multiple: true },
|
|
233
|
+
links: { selector: 'a', type: 'attribute', attribute: 'href', multiple: true },
|
|
234
|
+
};
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Recommended Stack (2026)
|
|
238
|
+
|
|
239
|
+
| Task | Package |
|
|
240
|
+
|------|---------|
|
|
241
|
+
| Static HTML parsing | **Cheerio** (default) |
|
|
242
|
+
| Full DOM APIs | **jsdom** |
|
|
243
|
+
| High-throughput parsing | **node-html-parser** |
|
|
244
|
+
| HTML entity encoding | **entities** (fastest) |
|
|
245
|
+
| HTML sanitization (server) | **sanitize-html** |
|
|
246
|
+
| HTML sanitization (browser) | **DOMPurify** |
|
|
247
|
+
| HTML to Markdown | **Turndown** (plugins) or **mdream** (speed) |
|
|
248
|
+
| HTML to plain text | **html-to-text** |
|
|
249
|
+
| Article extraction | **@mozilla/readability** |
|
|
250
|
+
| Malformed HTML | **htmlparser2** |
|
|
251
|
+
| Structured extraction | Cheerio `$.extract()` |
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Action Plan
|
|
2
|
+
|
|
3
|
+
## Phase: planning + research done
|
|
4
|
+
- [x] Folder structure (mirrors `cli`/`debonair`): `docs/plans`, `docs/talks`, `docs/logs`, `tests/{examples,output,scripts}`, `web`, `.github/workflows`.
|
|
5
|
+
- [x] Config files: `package.json` (`@devthink/saddle`, 65 deps), `tsconfig.json`, `biome.json`, `vitest.config.ts`, `.github/workflows/saddle-tests.yml`, `.gitignore`, `.npmrc`, `.nvmrc`, `README.md`.
|
|
6
|
+
- [x] Research done (computer use, hcaptcha bypass, vercel sandbox, atlas agent browser).
|
|
7
|
+
- [x] Planning docs in `docs/plans/` (01 to 12 + index).
|
|
8
|
+
- [x] Applied skill arch: no `src/`, no vercel/netlify functions, mysql2, socket, no localhost, lowercase files, production = `web/`.
|
|
9
|
+
|
|
10
|
+
## Next phase: implementation (waits for user approval)
|
|
11
|
+
The user stated: "when i give you the go ahead to build the platform". So no code is written now (`web/` logic, db, etc).
|
|
12
|
+
|
|
13
|
+
### Step 1 — Capture platform (after approval)
|
|
14
|
+
- Root files in `web/`: `humancursor.js`, `trajectory.js`, `fingerprint.js`, `recorder.js`, `replay.js`, `runcapture.js`, `capture.js`, `index.html`.
|
|
15
|
+
- Export to `docs/logs/<session>.json` (format in `11-movement-logs-json.md`).
|
|
16
|
+
|
|
17
|
+
### Step 2 — Captcha test page
|
|
18
|
+
- `tests/examples/hcaptchatest.html` (local) + real sites (cloudflare/google/hcaptcha).
|
|
19
|
+
- `tests/scripts/` with detect -> solve -> record -> assert.
|
|
20
|
+
- Evidence in `tests/output/`.
|
|
21
|
+
|
|
22
|
+
### Step 3 — Production infra
|
|
23
|
+
- `web/` with open node server (hono/express): routes `sessions`, `events`, `replays`, `captcha`.
|
|
24
|
+
- Db: drizzle + mysql2 (and prisma schema). Migrations in deploy.
|
|
25
|
+
- Cloudinary for assets/replays.
|
|
26
|
+
|
|
27
|
+
### Step 4 — Sandbox
|
|
28
|
+
- `@vercel/sandbox` for isolated agent execution.
|
|
29
|
+
- Agent https post/get requests to the deployed site.
|
|
30
|
+
|
|
31
|
+
### Step 5 — Tests
|
|
32
|
+
- `tests/*.test.ts` (vitest) for trajectory, recorder, schema, cloudinary helper.
|
|
33
|
+
- Example already at `tests/capture.test.ts`.
|
|
34
|
+
|
|
35
|
+
## Architecture constraints (skill arch)
|
|
36
|
+
- No `src/`. Root based in `web/`.
|
|
37
|
+
- No vercel functions. No netlify functions.
|
|
38
|
+
- Data: prisma, drizzle, mysql2, socket.
|
|
39
|
+
- No localhost. Host parametrized. Ports randomized then locked.
|
|
40
|
+
- Files lowercase. No underscore. No hyphen. English. Jsdoc.
|
|
41
|
+
|
|
42
|
+
## Dependencies registered
|
|
43
|
+
See `06-dependencies.md` (65 packages). Install with `npm install` when implementation starts.
|
|
44
|
+
|
|
45
|
+
## Acceptance criteria (defined by user)
|
|
46
|
+
- Capture movement (virtual mouse, virtual arrow, click, rotation, coordinates) in brave.
|
|
47
|
+
- Export json logs to `docs/logs/`.
|
|
48
|
+
- Open test page in brave and face hcaptcha/cloudflare/recaptcha.
|
|
49
|
+
- Production backend deployed (vercel/netlify) with db (drizzle/prisma/mysql2) + cloudinary.
|
|
50
|
+
- Agent https post/get requests to the deployed site.
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# API Reference
|
|
2
|
+
|
|
3
|
+
## Core Functions
|
|
4
|
+
|
|
5
|
+
### `scrapeUrl(url, options?)`
|
|
6
|
+
Scrape a URL using Playwright.
|
|
7
|
+
```ts
|
|
8
|
+
const result = await scrapeUrl('https://example.com', {
|
|
9
|
+
scroll: true, // Scroll to bottom before extracting
|
|
10
|
+
extractLinks: true, // Extract all links
|
|
11
|
+
extractImages: true, // Extract all images
|
|
12
|
+
extractTables: true, // Extract all tables
|
|
13
|
+
readable: false, // Use readable mode
|
|
14
|
+
screenshot: false, // Take a screenshot
|
|
15
|
+
headless: true, // Run browser headless
|
|
16
|
+
timeout: 30000, // Navigation timeout
|
|
17
|
+
});
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### `scrapeHtml(html, options?)`
|
|
21
|
+
Scrape raw HTML string without a browser.
|
|
22
|
+
```ts
|
|
23
|
+
const result = await scrapeHtml('<html>...</html>', { extractTables: true });
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### `scrapeWithBrowser(browser, url, options?)`
|
|
27
|
+
Reuse an existing AgentBrowser instance.
|
|
28
|
+
```ts
|
|
29
|
+
const browser = new AgentBrowser();
|
|
30
|
+
await browser.launch('chromium');
|
|
31
|
+
const result = await scrapeWithBrowser(browser, 'https://example.com');
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### `extractContent(html, options?)`
|
|
35
|
+
Extract structured content from HTML.
|
|
36
|
+
```ts
|
|
37
|
+
const content = await extractContent(html, {
|
|
38
|
+
extractLinks: true,
|
|
39
|
+
extractImages: true,
|
|
40
|
+
extractTables: true,
|
|
41
|
+
readable: false,
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
Returns `ExtractedContent` with: `title`, `author`, `text`, `links[]`, `images[]`, `tables[]`, `metadata`.
|
|
45
|
+
|
|
46
|
+
### `serializeResult(result, options)`
|
|
47
|
+
Serialize a ScrapeResult to target format.
|
|
48
|
+
```ts
|
|
49
|
+
const output = serializeResult(result, { format: 'markdown' });
|
|
50
|
+
const json = serializeResult(result, { format: 'json', pretty: true });
|
|
51
|
+
```
|
|
52
|
+
Returns `SerializedOutput` with: `content`, `format`, `extension`.
|
|
53
|
+
|
|
54
|
+
### `serializeHtml(html, options)`
|
|
55
|
+
Serialize raw HTML directly to target format.
|
|
56
|
+
```ts
|
|
57
|
+
const md = serializeHtml('<h1>Hello</h1>', { format: 'markdown' });
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### `formatForAgent(result, options?)`
|
|
61
|
+
Format scraped content for AI agent consumption.
|
|
62
|
+
```ts
|
|
63
|
+
const agent = formatForAgent(result, {
|
|
64
|
+
maxChunkSize: 4000, // Max tokens per chunk
|
|
65
|
+
includeSummary: true,
|
|
66
|
+
includeKeyPoints: true,
|
|
67
|
+
includeRelevantUrls: true,
|
|
68
|
+
includeMetadata: true,
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
Returns `AgentOutput` with: `summary`, `keyPoints`, `content`, `relevantUrls`, `chunks[]`, `tokenCount`.
|
|
72
|
+
|
|
73
|
+
## Classes
|
|
74
|
+
|
|
75
|
+
### `AgentBrowser`
|
|
76
|
+
Playwright browser automation manager.
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
const browser = new AgentBrowser({
|
|
80
|
+
headless: true,
|
|
81
|
+
stealth: true, // Enable stealth mode
|
|
82
|
+
adBlock: true, // Block ads and trackers
|
|
83
|
+
viewport: { width: 1280, height: 720 },
|
|
84
|
+
userAgent: 'custom', // Custom UA string
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
await browser.launch('chromium');
|
|
88
|
+
await browser.navigate(url, { waitUntil: 'networkidle' });
|
|
89
|
+
await browser.click(selector);
|
|
90
|
+
await browser.type(selector, text);
|
|
91
|
+
await browser.screenshot({ fullPage: true });
|
|
92
|
+
await browser.html(); // Get page HTML
|
|
93
|
+
await browser.text(); // Get page text
|
|
94
|
+
await browser.title(); // Get page title
|
|
95
|
+
await browser.evaluate(fn); // Run JS in page
|
|
96
|
+
await browser.scrollToBottom({ step: 500, delay: 100 });
|
|
97
|
+
await browser.executeCommands([ // Chain commands
|
|
98
|
+
{ action: 'goto', args: { url } },
|
|
99
|
+
{ action: 'extract' },
|
|
100
|
+
{ action: 'screenshot' },
|
|
101
|
+
]);
|
|
102
|
+
await browser.close();
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### `PygameRenderer`
|
|
106
|
+
Bridge to Python/Pygame for rendering.
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
const renderer = new PygameRenderer({ pythonPath: 'python3' });
|
|
110
|
+
await renderer.renderScreenshot(screenshotBuffer, 'output.png');
|
|
111
|
+
await renderer.renderDoom(frameBuffer, 'doom-frame.png');
|
|
112
|
+
renderer.close();
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## CLI
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
Usage: webscrape <url> [options]
|
|
119
|
+
|
|
120
|
+
Arguments:
|
|
121
|
+
url URL to scrape
|
|
122
|
+
|
|
123
|
+
Options:
|
|
124
|
+
-f, --format <fmt> Output format: markdown, json, xml, redis, text (default: markdown)
|
|
125
|
+
-o, --output <file> Output file
|
|
126
|
+
--pretty Pretty-print JSON/XML output
|
|
127
|
+
--scroll Scroll to bottom before extraction
|
|
128
|
+
--readable Use readable mode
|
|
129
|
+
--agent Format output for AI agent consumption
|
|
130
|
+
--screenshot Take a screenshot
|
|
131
|
+
-h, --help Show help
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Types
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
interface ScrapeResult {
|
|
138
|
+
url: string;
|
|
139
|
+
title: string;
|
|
140
|
+
html: string;
|
|
141
|
+
text: string;
|
|
142
|
+
links: { href: string; text: string }[];
|
|
143
|
+
images: { src: string; alt: string }[];
|
|
144
|
+
tables: { headers: string[]; rows: string[][] }[];
|
|
145
|
+
metadata: Record<string, string>;
|
|
146
|
+
screenshot?: Buffer;
|
|
147
|
+
timestamp: number;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
interface ExtractedContent {
|
|
151
|
+
title: string;
|
|
152
|
+
author: string;
|
|
153
|
+
text: string;
|
|
154
|
+
links: { href: string; text: string }[];
|
|
155
|
+
images: { src: string; alt: string }[];
|
|
156
|
+
tables: { headers: string[]; rows: string[][] }[];
|
|
157
|
+
metadata: Record<string, string>;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
interface SerializedOutput {
|
|
161
|
+
content: string;
|
|
162
|
+
format: string;
|
|
163
|
+
extension: string;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
interface AgentOutput {
|
|
167
|
+
summary: string;
|
|
168
|
+
keyPoints: string[];
|
|
169
|
+
content: string;
|
|
170
|
+
relevantUrls: { url: string; text: string }[];
|
|
171
|
+
chunks: { content: string; tokenCount: number }[];
|
|
172
|
+
tokenCount: number;
|
|
173
|
+
}
|
|
174
|
+
```
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
## Module Overview
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
┌─────────────┐ ┌──────────────┐ ┌────────────────┐
|
|
7
|
+
│ AgentBrowser│────▶│ scrapeUrl / │────▶│ extractContent │
|
|
8
|
+
│ (browser.ts)│ │ scrapeHtml │ │ (extract.ts) │
|
|
9
|
+
└─────────────┘ └──────────────┘ └────────────────┘
|
|
10
|
+
│
|
|
11
|
+
▼
|
|
12
|
+
┌─────────────┐ ┌──────────────────┐ ┌────────────────┐
|
|
13
|
+
│ formatFor │◀────│ serializeResult │◀────│ ScrapeResult │
|
|
14
|
+
│ Agent │ │ (serialize.ts) │ │ │
|
|
15
|
+
│ (agent.ts) │ │ formats: md, xml,│ └────────────────┘
|
|
16
|
+
└─────────────┘ │ json, redis, txt │
|
|
17
|
+
└──────────────────┘
|
|
18
|
+
│
|
|
19
|
+
▼
|
|
20
|
+
┌──────────────┐
|
|
21
|
+
│ Pygame │
|
|
22
|
+
│ Renderer │
|
|
23
|
+
│ (renderer.ts)│
|
|
24
|
+
└──────────────┘
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Module Responsibilities
|
|
28
|
+
|
|
29
|
+
### `browser.ts` — AgentBrowser
|
|
30
|
+
- Wraps Playwright `Browser`, `BrowserContext`, and `Page`
|
|
31
|
+
- Provides chainable command execution (`executeCommands`)
|
|
32
|
+
- Handles stealth mode, ad blocking, and browser lifecycle
|
|
33
|
+
|
|
34
|
+
### `scrape.ts` — Scraping Engine
|
|
35
|
+
- `scrapeUrl(url, options?)` — Launches browser, navigates, extracts
|
|
36
|
+
- `scrapeHtml(html, options?)` — Works offline with raw HTML
|
|
37
|
+
- `scrapeWithBrowser(browser, url, options?)` — Reuses existing AgentBrowser
|
|
38
|
+
|
|
39
|
+
### `extract.ts` — Content Extraction
|
|
40
|
+
- `extractContent(html, options?)` — Parses HTML via Cheerio
|
|
41
|
+
- Extracts: title, author, text, links, images, tables, metadata
|
|
42
|
+
- Returns structured `ExtractedContent`
|
|
43
|
+
|
|
44
|
+
### `serialize.ts` — Multi-Format Serialization
|
|
45
|
+
- `serializeResult(result, options)` — Converts `ScrapeResult` to target format
|
|
46
|
+
- `serializeHtml(html, options)` — Direct HTML-to-format conversion
|
|
47
|
+
- Supports: Markdown, HTML, JSON, XML, Redis, Plaintext
|
|
48
|
+
|
|
49
|
+
### `formats.ts` — Format Registry
|
|
50
|
+
- Maps format names to serializers
|
|
51
|
+
- Provides format detection (article, list, page)
|
|
52
|
+
- Extensions and MIME types
|
|
53
|
+
|
|
54
|
+
### `agent.ts` — AI Agent Output
|
|
55
|
+
- `formatForAgent(result, options?)` — Structures content for LLM consumption
|
|
56
|
+
- Generates: summary, key points, relevant URLs, content chunks with token counts
|
|
57
|
+
|
|
58
|
+
### `renderer.ts` — Pygame Bridge
|
|
59
|
+
- `PygameRenderer` — Renders screenshots and DOOM game frames via Python/Pygame
|
|
60
|
+
- Spawns Python subprocess for rendering
|
|
61
|
+
|
|
62
|
+
### `cli.ts` — Command-Line Interface
|
|
63
|
+
- Commander-based CLI for scraping from terminal
|
|
64
|
+
- Accepts format, pretty-print, scroll, readable, and agent options
|
|
65
|
+
|
|
66
|
+
### `utils.ts` — Utilities
|
|
67
|
+
- `slugify`, `truncate`, `tokenEstimate`, `chunkText`
|
|
68
|
+
- URL validation and classification (internal/external)
|
|
69
|
+
|
|
70
|
+
### `dev-server.mjs` — Development Server
|
|
71
|
+
- Standalone Node.js HTTP server
|
|
72
|
+
- POST `/api/scrape` endpoint wrapping Playwright
|
|
73
|
+
- Used by Vite dev server via proxy
|
|
74
|
+
|
|
75
|
+
## Package Organization
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
webscrape/
|
|
79
|
+
├── index.ts # Public API exports
|
|
80
|
+
├── browser.ts # AgentBrowser (Playwright wrapper)
|
|
81
|
+
├── scrape.ts # Scraping engine
|
|
82
|
+
├── extract.ts # HTML content extraction
|
|
83
|
+
├── serialize.ts # Multi-format serialization
|
|
84
|
+
├── formats.ts # Format registry
|
|
85
|
+
├── agent.ts # AI agent output
|
|
86
|
+
├── renderer.ts # Pygame bridge
|
|
87
|
+
├── cli.ts # CLI entry
|
|
88
|
+
├── utils.ts # Utilities
|
|
89
|
+
├── types.ts # TypeScript interfaces
|
|
90
|
+
├── web/ # Web demo UI
|
|
91
|
+
├── tests/ # Test suites
|
|
92
|
+
├── docs/ # Documentation
|
|
93
|
+
└── dev-server.mjs # API server for dev mode
|
|
94
|
+
```
|