Back to Home
Gistify Snippet
Public
vanceai.js — scrape halaman tool VanceAI (meta + JSON-LD)
vanceai.js — scrape halaman tool VanceAI (meta + JSON-LD)
#github-gist#javascript#gist:9f69d12e75e708a972298c9ee1d4647f#gistsync:2026-09-04T02:20:26Z
3
Rawvanceai.jsjavascript
136 lines| 1 | // vanceai.js — scrape halaman tool VanceAI (make-photo-hd). |
| 2 | // Ambil meta + JSON-LD (HowTo steps, FAQ, info tool). Pure JS. |
| 3 | |
| 4 | const TARGET = 'https://vanceai.com/make-photo-hd/' |
| 5 | const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36' |
| 6 | |
| 7 | async function get(url) { |
| 8 | const res = await fetch(url, { |
| 9 | headers: { 'User-Agent': UA, Accept: 'text/html,*/*;q=0.9' }, |
| 10 | redirect: 'follow', |
| 11 | signal: AbortSignal.timeout(60000), |
| 12 | }) |
| 13 | if (!res.ok) throw new Error(`HTTP ${res.status} ${url}`) |
| 14 | return { status: res.status, text: await res.text() } |
| 15 | } |
| 16 | |
| 17 | function meta(html, prop) { |
| 18 | let m = new RegExp(`<meta[^>]+(?:property|name)=["']${prop}["'][^>]+content=["']([^"']*)["']`, 'i').exec(html) |
| 19 | if (!m) m = new RegExp(`<meta[^>]+content=["']([^"']*)["'][^>]+(?:property|name)=["']${prop}["']`, 'i').exec(html) |
| 20 | return m ? m[1].replace(/&/g, '&').trim() : '' |
| 21 | } |
| 22 | |
| 23 | // ambil JSON-LD (@graph) dari halaman |
| 24 | function ld(html) { |
| 25 | const m = /<script type="application\/ld\+json"[^>]*>([\s\S]*?)<\/script>/i.exec(html) |
| 26 | if (!m) return [] |
| 27 | try { |
| 28 | const d = JSON.parse(m[1]) |
| 29 | return Array.isArray(d) ? d : d['@graph'] || [d] |
| 30 | } catch { |
| 31 | return [] |
| 32 | } |
| 33 | } |
| 34 | |
| 35 | function howTo(graph) { |
| 36 | const h = graph.find((x) => x['@type'] === 'HowTo') |
| 37 | if (!h) return null |
| 38 | return { |
| 39 | name: h.name || '', |
| 40 | totalTime: h.totalTime || '', |
| 41 | steps: (h.step || []).map((s, i) => ({ |
| 42 | no: i + 1, |
| 43 | name: s.name || '', |
| 44 | text: s.text || '', |
| 45 | })), |
| 46 | } |
| 47 | } |
| 48 | |
| 49 | function faq(graph) { |
| 50 | const f = graph.find((x) => x['@type'] === 'FAQPage') |
| 51 | if (!f) return [] |
| 52 | return (f.mainEntity || []).map((q) => ({ |
| 53 | q: q.name || '', |
| 54 | a: q.acceptedAnswer?.text || '', |
| 55 | })) |
| 56 | } |
| 57 | |
| 58 | function appInfo(graph) { |
| 59 | const a = graph.find((x) => x['@type'] === 'WebApplication') |
| 60 | if (!a) return {} |
| 61 | const pick = (o) => (o && o['@type'] === 'Offer' ? { price: o.price, currency: o.priceCurrency } : null) |
| 62 | return { |
| 63 | name: a.name || '', |
| 64 | url: a.url || '', |
| 65 | applicationCategory: a.applicationCategory || '', |
| 66 | operatingSystem: a.operatingSystem || '', |
| 67 | offers: (Array.isArray(a.offers) ? a.offers : [a.offers]).filter(Boolean).map(pick).filter(Boolean), |
| 68 | aggregateRating: a.aggregateRating || undefined, |
| 69 | } |
| 70 | } |
| 71 | |
| 72 | const isDirectRun = process.argv[1] && import.meta.url === new URL(`file://${process.argv[1]}`).href |
| 73 | |
| 74 | if (isDirectRun) { |
| 75 | ;(async () => { |
| 76 | const args = process.argv.slice(2) |
| 77 | const asJson = args.includes('--json') |
| 78 | const url = args.find((a) => /^https?:\/\//i.test(a)) || TARGET |
| 79 | |
| 80 | if (!asJson) { |
| 81 | console.log(`== vanceai scraper ==`) |
| 82 | console.log(`target : ${url}`) |
| 83 | } |
| 84 | |
| 85 | try { |
| 86 | const page = await get(url) |
| 87 | const html = page.text |
| 88 | const graph = ld(html) |
| 89 | const data = { |
| 90 | url, |
| 91 | status: page.status, |
| 92 | title: meta(html, 'og:title') || meta(html, 'twitter:title'), |
| 93 | description: meta(html, 'description') || meta(html, 'og:description'), |
| 94 | canonical: meta(html, 'og:url'), |
| 95 | howto: howTo(graph), |
| 96 | faq: faq(graph), |
| 97 | app: appInfo(graph), |
| 98 | } |
| 99 | |
| 100 | if (asJson) { |
| 101 | console.log(JSON.stringify(data, null, 2)) |
| 102 | } else { |
| 103 | console.log(`\n== ${data.title || url} ==`) |
| 104 | if (data.description) console.log(`desc : ${data.description.slice(0, 200)}`) |
| 105 | if (data.app && data.app.aggregateRating) { |
| 106 | const r = data.app.aggregateRating |
| 107 | console.log(`rating : ${r.ratingValue} (${r.reviewCount} ulasan)`) |
| 108 | } |
| 109 | if (data.howto) { |
| 110 | console.log(`\nhow-to : ${data.howto.name}`) |
| 111 | data.howto.steps.forEach((s) => { |
| 112 | console.log(` ${s.no}. ${s.name}${s.text ? ' — ' + s.text.slice(0, 100) : ''}`) |
| 113 | }) |
| 114 | } |
| 115 | if (data.faq.length) { |
| 116 | console.log(`\nfaq : ${data.faq.length} item`) |
| 117 | data.faq.forEach((f, i) => { |
| 118 | console.log(` ${i + 1}. ${f.q}`) |
| 119 | console.log(` ${f.a.slice(0, 160)}`) |
| 120 | }) |
| 121 | } |
| 122 | console.log('\ndone') |
| 123 | } |
| 124 | } catch (e) { |
| 125 | console.error(`ERROR: ${e.message || e}`) |
| 126 | process.exit(1) |
| 127 | } |
| 128 | })() |
| 129 | } |
| 130 | |
| 131 | /* |
| 132 | * USAGE |
| 133 | * node vanceai.js scrape make-photo-hd (URL di code) |
| 134 | * node vanceai.js <url> scrape tool VanceAI lain |
| 135 | * node vanceai.js --json json lengkap |
| 136 | */ |
Discussion & Comments0
No comments yet — be the first to leave feedback or start a code discussion.
Sign in with your GitHub account to write comments and join the discussion.