html-get 2.24.10 → 2.25.0
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/package.json +1 -1
- package/src/index.js +74 -50
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "html-get",
|
|
3
3
|
"description": "Get the HTML from any website, fine-tuned for correction & speed",
|
|
4
4
|
"homepage": "https://nicedoc.com/microlinkhq/html-get",
|
|
5
|
-
"version": "2.
|
|
5
|
+
"version": "2.25.0",
|
|
6
6
|
"types": "index.d.ts",
|
|
7
7
|
"main": "src/index.js",
|
|
8
8
|
"bin": {
|
package/src/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { parseUrl, isMediaUrl, isPdfUrl } = require('@metascraper/helpers')
|
|
3
|
+
const { parseUrl, isMediaUrl, isPdfUrl, memoizeOne } = require('@metascraper/helpers')
|
|
4
4
|
const { readFile, writeFile } = require('fs/promises')
|
|
5
5
|
const timeSpan = require('@kikobeats/time-span')()
|
|
6
6
|
const debug = require('debug-logfmt')('html-get')
|
|
7
|
-
const {
|
|
7
|
+
const { execFileSync } = require('child_process')
|
|
8
8
|
const PCancelable = require('p-cancelable')
|
|
9
9
|
const { AbortError } = require('p-retry')
|
|
10
10
|
const htmlEncode = require('html-encode')
|
|
@@ -28,15 +28,7 @@ const PDF_SIZE_TRESHOLD = 150 * 1024 // 150kb
|
|
|
28
28
|
const fetch = PCancelable.fn(
|
|
29
29
|
async (
|
|
30
30
|
url,
|
|
31
|
-
{
|
|
32
|
-
getTemporalFile,
|
|
33
|
-
mutool,
|
|
34
|
-
pandoc,
|
|
35
|
-
reflect = false,
|
|
36
|
-
timeout = REQ_TIMEOUT,
|
|
37
|
-
toEncode,
|
|
38
|
-
...opts
|
|
39
|
-
},
|
|
31
|
+
{ getTemporalFile, mutool, pandoc, reflect = false, timeout = REQ_TIMEOUT, toEncode, ...opts },
|
|
40
32
|
onCancel
|
|
41
33
|
) => {
|
|
42
34
|
const reqTimeout = reflect ? timeout / 2 : timeout
|
|
@@ -55,9 +47,7 @@ const fetch = PCancelable.fn(
|
|
|
55
47
|
})
|
|
56
48
|
|
|
57
49
|
const redirects = []
|
|
58
|
-
req.on('redirect', res =>
|
|
59
|
-
redirects.push({ statusCode: res.statusCode, url: res.url })
|
|
60
|
-
)
|
|
50
|
+
req.on('redirect', res => redirects.push({ statusCode: res.statusCode, url: res.url }))
|
|
61
51
|
|
|
62
52
|
try {
|
|
63
53
|
const res = await req
|
|
@@ -65,8 +55,7 @@ const fetch = PCancelable.fn(
|
|
|
65
55
|
const html = await (async () => {
|
|
66
56
|
const contentType = getContentType(res.headers)
|
|
67
57
|
|
|
68
|
-
const officeFormat =
|
|
69
|
-
pandoc && getOfficeFormat({ contentType, url: [res.url, url] })
|
|
58
|
+
const officeFormat = pandoc && getOfficeFormat({ contentType, url: [res.url, url] })
|
|
70
59
|
|
|
71
60
|
// a recognized office file never goes through mutool, even if the
|
|
72
61
|
// response is mislabeled as application/pdf
|
|
@@ -86,7 +75,17 @@ const fetch = PCancelable.fn(
|
|
|
86
75
|
if (officeFormat) {
|
|
87
76
|
const file = getTemporalFile(res.url, officeFormat)
|
|
88
77
|
await writeFile(file.path, res.body)
|
|
89
|
-
|
|
78
|
+
try {
|
|
79
|
+
const converted = await pandoc(officeFormat, file.path)
|
|
80
|
+
if (converted) return converted
|
|
81
|
+
debug('pandoc:empty', { url: res.url, format: officeFormat })
|
|
82
|
+
} catch (error) {
|
|
83
|
+
debug('pandoc:error', {
|
|
84
|
+
url: res.url,
|
|
85
|
+
format: officeFormat,
|
|
86
|
+
message: error.message || error
|
|
87
|
+
})
|
|
88
|
+
}
|
|
90
89
|
}
|
|
91
90
|
|
|
92
91
|
return contentType === 'text/html' || !isMediaUrl(url)
|
|
@@ -232,12 +231,7 @@ const isFetchMode = url => {
|
|
|
232
231
|
}
|
|
233
232
|
|
|
234
233
|
const defaultGetMode = (url, { prerender }) => {
|
|
235
|
-
if (
|
|
236
|
-
prerender === false ||
|
|
237
|
-
isMediaUrl(url) ||
|
|
238
|
-
isPdfUrl(url) ||
|
|
239
|
-
isOfficeUrl(url)
|
|
240
|
-
) {
|
|
234
|
+
if (prerender === false || isMediaUrl(url) || isPdfUrl(url) || isOfficeUrl(url)) {
|
|
241
235
|
return 'fetch'
|
|
242
236
|
}
|
|
243
237
|
if (prerender === true) return 'prerender'
|
|
@@ -246,41 +240,68 @@ const defaultGetMode = (url, { prerender }) => {
|
|
|
246
240
|
|
|
247
241
|
const defaultGetTemporalFile = (input, ext) => {
|
|
248
242
|
const hash = crypto.createHash('sha256').update(input).digest('hex')
|
|
249
|
-
const filepath = path.join(
|
|
250
|
-
os.tmpdir(),
|
|
251
|
-
ext === undefined ? hash : `${hash}.${ext}`
|
|
252
|
-
)
|
|
243
|
+
const filepath = path.join(os.tmpdir(), ext === undefined ? hash : `${hash}.${ext}`)
|
|
253
244
|
return { path: filepath }
|
|
254
245
|
}
|
|
255
246
|
|
|
256
|
-
|
|
257
|
-
|
|
247
|
+
// Resolve a binary path once and cache it: html-get owns the pandoc/mutool
|
|
248
|
+
// lookup, so both the internal runners and the exposed accessors share a single
|
|
249
|
+
// `which` per binary for the whole process. execFileSync (no shell) is faster
|
|
250
|
+
// than execSync and free of any interpolation surface.
|
|
251
|
+
const whichCache = new Map()
|
|
252
|
+
const whichSync = bin => {
|
|
253
|
+
if (!whichCache.has(bin)) {
|
|
254
|
+
let resolved
|
|
258
255
|
try {
|
|
259
|
-
|
|
256
|
+
resolved = execFileSync('which', [bin], {
|
|
260
257
|
stdio: ['pipe', 'pipe', 'ignore']
|
|
261
258
|
})
|
|
262
259
|
.toString()
|
|
263
260
|
.trim()
|
|
264
|
-
return (...args) => $(`${mutoolPath} draw -q -F html ${args}`)
|
|
265
261
|
} catch (_) {}
|
|
266
|
-
|
|
262
|
+
whichCache.set(bin, resolved)
|
|
263
|
+
}
|
|
264
|
+
return whichCache.get(bin)
|
|
265
|
+
}
|
|
267
266
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
267
|
+
// The default runners are wired as default parameters (evaluated on every
|
|
268
|
+
// call), so memoizeOne builds each runner once instead of re-running the pandoc
|
|
269
|
+
// `--list-input-formats` probe and rebuilding the closure per request.
|
|
270
|
+
const defaultMutool = memoizeOne(() => {
|
|
271
|
+
const mutoolPath = whichSync('mutool')
|
|
272
|
+
if (!mutoolPath) return
|
|
273
|
+
return (...args) => $(`${mutoolPath} draw -q -F html ${args}`)
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
const defaultPandoc = memoizeOne(() => {
|
|
277
|
+
try {
|
|
278
|
+
const pandocPath = whichSync('pandoc')
|
|
279
|
+
if (!pandocPath) return
|
|
280
|
+
// a broken/incompatible pandoc can throw here (or on the runner below); in
|
|
281
|
+
// either case conversion stays disabled instead of breaking every getHTML
|
|
282
|
+
// call through the default-parameter evaluation
|
|
283
|
+
const supported = new Set(
|
|
284
|
+
execFileSync(pandocPath, ['--list-input-formats'], {
|
|
272
285
|
stdio: ['pipe', 'pipe', 'ignore']
|
|
273
286
|
})
|
|
274
287
|
.toString()
|
|
275
288
|
.trim()
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
|
|
289
|
+
.split(/\s+/)
|
|
290
|
+
)
|
|
291
|
+
return async (format, filepath) => {
|
|
292
|
+
if (!supported.has(format)) return
|
|
293
|
+
// array form keeps `filepath` a single argv entry even if it has spaces
|
|
294
|
+
const { stdout } = await $(pandocPath, [
|
|
295
|
+
`--from=${format}`,
|
|
296
|
+
'--to=html',
|
|
297
|
+
'--standalone',
|
|
298
|
+
'--embed-resources',
|
|
299
|
+
filepath
|
|
300
|
+
])
|
|
301
|
+
return stdout.trim() ? stdout : undefined
|
|
302
|
+
}
|
|
303
|
+
} catch (_) {}
|
|
304
|
+
})
|
|
284
305
|
|
|
285
306
|
const getContent = PCancelable.fn(
|
|
286
307
|
(
|
|
@@ -372,12 +393,7 @@ module.exports = PCancelable.fn(
|
|
|
372
393
|
|
|
373
394
|
let shadowDOM = hasShadowDOM($)
|
|
374
395
|
|
|
375
|
-
if (
|
|
376
|
-
mode === 'fetch' &&
|
|
377
|
-
getBrowserless &&
|
|
378
|
-
shadowDOM &&
|
|
379
|
-
prerender !== false
|
|
380
|
-
) {
|
|
396
|
+
if (mode === 'fetch' && getBrowserless && shadowDOM && prerender !== false) {
|
|
381
397
|
debug('shadow DOM detected, retrying with prerender', { url: targetUrl })
|
|
382
398
|
const prerenderPromise = getContent(targetUrl, 'prerender', {
|
|
383
399
|
getBrowserless,
|
|
@@ -411,3 +427,11 @@ module.exports.PDF_SIZE_TRESHOLD = PDF_SIZE_TRESHOLD
|
|
|
411
427
|
module.exports.isFetchMode = isFetchMode
|
|
412
428
|
module.exports.getContent = getContent
|
|
413
429
|
module.exports.defaultMutool = defaultMutool
|
|
430
|
+
module.exports.defaultPandoc = defaultPandoc
|
|
431
|
+
|
|
432
|
+
// html-get already resolves pandoc/mutool for office and PDF conversion; expose
|
|
433
|
+
// the cached paths so a consumer (e.g. the microlink api markdown pipeline)
|
|
434
|
+
// reuses them instead of running its own `which`. Each returns undefined when
|
|
435
|
+
// the binary is not installed. Lazy: nothing runs until first call.
|
|
436
|
+
module.exports.getPandocPath = () => whichSync('pandoc')
|
|
437
|
+
module.exports.getMutoolPath = () => whichSync('mutool')
|