Back to Home
Gistify Snippet
kyiov
Sep 5·1 file(s)
Public

cse.js — cari di Google Programmable Search Engine (element API, pure JS)

cse.js — cari di Google Programmable Search Engine (element API, pure JS)

#github-gist#javascript#gist:01680dc12d08ead52e347b3212091a9d#gistsync:2026-09-04T02:58:45Z
4
Raw
cse.jsjavascript
134 lines
1#!/usr/bin/env node
2// cse.js — cari di Google Programmable Search Engine (element API, pure JS)
3
4const CX = '006516753008110874046:1ugcdt3vo7z' // download/direct-link search
5const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36'
6const BASE = 'https://cse.google.com'
7
8let jar = {} // cookie jar: name -> value
9
10async function get(url, referer) {
11 const r = await fetch(url, {
12 headers: {
13 'User-Agent': UA,
14 'Accept': '*/*',
15 'Accept-Language': 'en-US,en;q=0.9',
16 ...(referer ? { 'Referer': referer } : {}),
17 ...(Object.keys(jar).length ? { 'Cookie': Object.entries(jar).map(([k, v]) => `${k}=${v}`).join('; ') } : {})
18 }
19 })
20 const raw = await r.text()
21 // simpan cookie baru dari response
22 let sc
23 if (typeof r.headers.getSetCookie === 'function') sc = r.headers.getSetCookie()
24 else if (r.headers.get('set-cookie')) sc = [r.headers.get('set-cookie')]
25 if (sc) for (const c of sc) {
26 const [pair] = c.split(';')
27 const eq = pair.indexOf('=')
28 if (eq > 0) jar[pair.slice(0, eq).trim()] = pair.slice(eq + 1).trim()
29 }
30 return { status: r.status, raw }
31}
32
33// ambil sca_esv dari halaman utama
34async function mainPage(cx) {
35 const { raw } = await get(`${BASE}/cse?cx=${encodeURIComponent(cx)}`)
36 const m = /sca_esv\\x3d([a-f0-9]+)/.exec(raw) || /sca_esv=([a-f0-9]+)/.exec(raw)
37 return m ? m[1] : null
38}
39
40// ambil cse_token dari cse.js
41async function cseTok(sca, cx) {
42 const { raw } = await get(`${BASE}/cse.js?sca_esv=${sca}&hpg=1&cx=${encodeURIComponent(cx)}`, `${BASE}/cse?cx=${encodeURIComponent(cx)}`)
43 const m = /"cse_token":\s*"([^"]+)"/.exec(raw)
44 if (!m) throw new Error('cse_token tidak ditemukan')
45 return m[1]
46}
47
48// ambil hasil
49async function search(cx, q, start, num) {
50 const sca = await mainPage(cx)
51 const tok = await cseTok(sca, cx)
52 const cb = `google.search.cse.api${Date.now() % 100000}`
53 const params = new URLSearchParams({
54 rsz: 'filtered_cse', num: String(num), hl: 'en', source: 'gcsc',
55 cselibv: '93e57cb47c499c53', cx, q, safe: 'off', cse_tok: tok,
56 filter: '1', sort: '', exp: 'cc,esbfa', fexp: '121991397,121991398,121877338,121877336',
57 oq: q, gs_l: 'partner-generic.3...3113.3113.0.3141.1.1.0.0.0.0.0.0..0.0.csems,nrl=10...0.....34.partner-generic..1.0.0.',
58 cseclient: 'hosted-page-client', callback: cb,
59 rurl: `${BASE}/cse?cx=${encodeURIComponent(cx)}`
60 })
61 if (start) params.set('start', String(start))
62 const { status, raw } = await get(`${BASE}/cse/element/v1?${params}`, `${BASE}/cse?cx=${encodeURIComponent(cx)}`)
63 if (status !== 200) throw new Error(`HTTP ${status}`)
64 const i = raw.indexOf('(')
65 const j = raw.lastIndexOf(')')
66 return JSON.parse(raw.slice(i + 1, j))
67}
68
69function clean(s) {
70 if (s == null) return ''
71 return String(s).replace(/<[^>]+>/g, '').replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&quot;/g, '"').replace(/&#39;/g, "'").trim()
72}
73
74function fmt(d) {
75 return (d.results || []).map(r => ({
76 title: clean(r.title),
77 link: r.unescapedUrl || r.url || '',
78 domain: r.visibleUrl || '',
79 snippet: clean(r.content)
80 }))
81}
82
83async function run() {
84 const args = process.argv.slice(2)
85 const asJson = args.includes('--json')
86 const outIdx = args.indexOf('--out')
87 const outFile = outIdx >= 0 ? (args[outIdx + 1] && !args[outIdx + 1].startsWith('--') ? args[outIdx + 1] : 'output/cse.json') : null
88 let n = 10, start = 0
89 const ni = args.indexOf('--n')
90 if (ni >= 0) n = parseInt(args[ni + 1], 10) || 10
91 const pi = args.indexOf('--page')
92 if (pi >= 0) start = (parseInt(args[pi + 1], 10) || 1) * 10
93 const q = args.find(a => !a.startsWith('--'))
94 if (!q) {
95 console.error('usage: node cse.js <kata> [--n 30] [--page 2] [--json] [--out f]')
96 process.exit(1)
97 }
98
99 // kunjungan pertama ke cse.google.com untuk dapat NID
100 await get(`${BASE}/cse?cx=${encodeURIComponent(CX)}`)
101
102 const all = []
103 let total = 0
104 for (let s = start; s < start + n && all.length < n; s += 10) {
105 const d = await search(CX, q, s, Math.min(10, n - all.length))
106 if (s === start) total = parseInt((d.cursor || {}).estimatedResultCount, 10) || 0
107 const items = fmt(d)
108 if (!items.length) break
109 all.push(...items)
110 if (items.length < 10) break
111 }
112
113 const out = { query: q, total, shown: all.length, results: all }
114 if (asJson) {
115 console.log(JSON.stringify(out, null, 2))
116 } else {
117 console.log(`${q}: ${total} hasil (${all.length} ditampilkan)`)
118 all.forEach((r, i) => {
119 console.log(`\n[${i + 1}] ${r.title}`)
120 if (r.domain) console.log(` ${r.domain}`)
121 console.log(` ${r.link}`)
122 if (r.snippet) console.log(` ${r.snippet.slice(0, 160)}`)
123 })
124 }
125 if (outFile) {
126 const { mkdir, writeFile } = await import('node:fs/promises')
127 const { dirname } = await import('node:path')
128 await mkdir(dirname(outFile), { recursive: true })
129 await writeFile(outFile, JSON.stringify(out, null, 2))
130 console.error(`\nsaved: ${outFile}`)
131 }
132}
133
134run().catch(e => { console.error('error:', e.message); process.exit(1) })
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.