2022年4月1日、現状のGoogle検索結果で動作するように更新しました。
ニュースタブ・画像タブで動作します。
Googleの検索結果を手軽に一覧でCSV保存したい
Google検索はとても便利なので、日常的に利用しているのですが、
検索結果をまとめて一覧化したいことがありますよね。
検索画面に表示されるサイト名やページタイトル、URLをCSVファイルとして一覧で取得できれば、
ExcelやNumbers、Calcなどで使えて便利です。
スクレイピングやAPIで取得するなどの方法もありますが、
個人で利用するには、簡便に取得できれば十分でしたので、
ブラウザから誰でも利用できる、ブックマークレットを作りました。
ただし、最新のブラウザでなければ動作しません。
使い方
ブラウザで新しいブックマークを作り、次の内容をURL欄に貼り付けます。
ブックマークの名前は、Google検索結果CSVダウンロード
などで良いでしょう。
Googleの検索結果画面で、登録したブックマークをクリックすると動作します。
javascript:((e,t,i,n,r,o,d)=>{const c=(t,i=e)=>i.querySelector(t),s=(t,i=e)=>i.querySelectorAll(t),a=(t,i=e.body)=>i.appendChild(t),l=t=>e.createElement(t),v=()=>{void 0!==k&&k.remove()},u=(e,t,i=[])=>e.concat(t).concat(i),p=e=>`"${e.map(e=>I(e&&"string"!=typeof e&&d in e?e[d]:e,[/"/g,/\n/g],['""',""])).join('","')}"`,f=e=>{let t=e;try{t=decodeURI(e)}catch(e){}return t},h=()=>{const t=new Blob([new Uint8Array([239,187,191]),y(u([p(["href","decoded","title","breadcrumb","date","description"])],C))],{type:"text/csv"}),n=l("a"),r=URL.createObjectURL(t);n.download=e.title+".csv",n.href=r,n.click(),i(()=>URL.revokeObjectURL(r),1e3)},y=e=>e.join("\n"),m=(()=>{const e=s("#rso .g");return 0===e[o]?c("#rso"):e[e[o]-1].parentNode})(),b=async()=>{const e=U();if(0===e[o])return void alert("No item found.\nIf you think this script works incorrectly contact the creator.\nThx in advance.");const t=[`Found: ${e[o]}`,`Total: ${e[o]+C[o]}`];C=u(C,e),await L();const i=c("#pnnext",j);if(null===i)return alert(y(t)),v(),h();t.unshift("Continue next page?"),confirm(y(t))?(await g(i.href),b()):(v(),h())},w=(e,t)=>Array.from(s(e,j)).map(t),g=i=>{void 0===k&&(k=l("iframe"),R(k,{display:"none"}),a(k));const o=l("div");return R(o,{background:"black",color:"white",left:0,padding:"8px 20px",position:"fixed",top:0,zIndex:1e3}),o[d]="Loading...",a(o),new t(t=>{k[r]("load",()=>{j=k.contentWindow.document,o.remove();const i=l("div");i[n]=c("#rso",j)[n],a(i,m),j!==e&&(c("#xjs")[n]=c("#xjs",j)[n]),t()},{once:!0}),k.src=i})},x=(e,t,i="")=>(e||"").replace(t,i),I=(e,t,i)=>t.reduce((e,t,n)=>x(e,t,i[n]),e),U=()=>(e=>e.filter(e=>!!e))(u(w("#rso div.g > div",e=>{const t=c("div.yuRUbf a, div.ct3b9e a",e);if(!t)return!1;const i=t.href;let n,r,s,a;return c("div.dXiKIc",e)?(n=c("div.ct3b9e h3",e),r=(r=c("div.dXiKIc div.P7xzyf",e)).firstChild[d]+" · "+r.children[1][d],s=c("div.dXiKIc div.P7xzyf span span",e),a=c("div.dXiKIc div.Uroaid",e)):(n=c("div.yuRUbf h3",e),r=c("div.yuRUbf cite",e),s=c("div.uUuwM span.wuQ4Ob span, div.IsZvec div.uo4vr span",e),a=c("div.uUuwM div.VwiC3b, div.IsZvec div.VwiC3b, div.IsZvec span.aCOpRe",e)),p([i,f(i),n,r,"string"==typeof s?s:s&&0===s.children[o]?x(s[d],/ . ?$/):"",a?s?x(a[d],s[d]).trimLeft():a[d]:""])}),w("#rso div > div > a.WlydOe",e=>{const t=e.href;return p([t,f(t),c("[role=heading]",e),c("div.CEMjEf span",e),c("div.ZE0LJd span",e),c("div.GI74Re",e)])}),w("#islrg div.isv-r",e=>{const t=c('a[rel="noopener"]',e);if(null===t)return!1;const i=t.href,n=c("img",e).src;return p([i,f(i),t.title,c("div.fxgdke",t),c("span.dP3z3e",e),n.startsWith("data:")?"":n])}))),L=()=>new t(t=>{(c("#islmp")||e.body).scrollIntoView({behavior:"smooth",block:"end"});const n=()=>{clearTimeout(o),o=i(()=>{e.removeEventListener("scroll",n),t()},500)};let o;e[r]("scroll",n,{passive:!0}),n()}),R=(e,t)=>{for(let i in t)e.style[i]=t[i]};let k,j=e,C=[];b()})(document,Promise,setTimeout,"innerHTML","addEventListener","length","innerText")
ページをブックマークしておき、編集画面を選んだ後、URL欄にペーストするのがコツです。
動作イメージ
Google Chromeのブックマークツールバーを表示させている場合のイメージです。
Google検索結果画面でブックマークをクリックすると、ブックマークレットが実行され、ダイアログが表示されます。
OK
をクリックすると、次のページ以降の検索結果を取得します。
キャンセル
をクリックすると、CSVファイルがダウンロードされます。
次のページがない場合は
OK
ボタンのみ表示され、クリックするとすぐにCSVファイルがダウンロードされます。
2021年1月最新版から、ダウンロードされるCSVファイル名が、検索したキーワードとなります。
保存されるCSV
UTF-8でエンコードされています。
Excel、Numbers、CalcやGoogle Spreadsheetでそのまま開けます。
ヘッダ | 説明 | 空文字可能性 | 具体例 |
---|---|---|---|
href | URL | NO | https://ja.wikipedia.org/wiki/%E3%83%96%E3%83%83%E3%82%AF%E3%83%9E%E3%83%BC%E3%82%AF%E3%83%AC%E3%83%83%E3%83%88 |
decoded |
decodeURI() したURL |
NO | https://ja.wikipedia.org/wiki/ブックマークレット |
title | サイト名 or ページ名 | YES | ブックマークレット - Wikipedia |
breadcrumb | パンくずの表示 | YES | https://ja.wikipedia.org/wiki/ブックマークレット |
date | 更新日 | YES | 2019/1/15 |
description | ページの概要 | YES | ブックマークレット (Bookmarklet) とは、ユーザーがウェブブラウザのブックマークなどから起動し、なんらかの処理を行う簡易的なプログラムのことである。携帯電話のウェブブラウザで足りない機能を補ったり、ウェブアプリケーションの処理を起動する為に使 ... |
対象ブラウザ
最新のブラウザを利用してください。
動作確認結果
2022年4月1日時点
ブラウザ | バージョン | 動作 |
---|---|---|
Google Chrome | 100.0.4896.60 | OK |
Firefox | 98.0.2 | OK |
Safari | 15.4 | OK |
Edge (Blink) | 99.0.1150.55 | OK |
Edge (EdgeHTML) | 動作未確認 | 今後も対応しません。 |
Internet Explorer | 11.1082.18362.0 | NG 今後も対応しません。 |
ソースコード
ブックマークレット用に変換する前のソースコードです。
ここをクリックしてソースコードを表示
((document, Promise, setTimeout, html, on, size, text) => {
const $ = (query, doc = document) => doc.querySelector(query)
const $$ = (query, doc = document) => doc.querySelectorAll(query)
const appendChild = (child, parent = document.body) => parent.appendChild(child)
const createElement = tag => document.createElement(tag)
const cleanup = () => {
if (iframe !== undefined) {
iframe.remove()
}
}
const concat = (base, append, more = []) => base.concat(append).concat(more)
const convert = row => `"${row.map(s => replaceArr(
s && typeof s !== 'string' && text in s
? s[text]
: s,
[/"/g, /\n/g],
['""', '']
)).join('","')}"`
const decode = href => {
let out = href
try {
out = decodeURI(href)
} catch (e) {}
return out
}
const download = () => {
const blob = new Blob([
new Uint8Array([0xEF, 0xBB, 0xBF]),
joinLines(concat([
convert([
'href',
'decoded',
'title',
'breadcrumb',
'date',
'description'
])
], ret))
], {
type: 'text/csv'
})
const a = createElement('a')
const url = URL.createObjectURL(blob)
a.download = document.title + '.csv'
a.href = url
a.click()
setTimeout(() => URL.revokeObjectURL(url), 1000)
}
const filter = array => array.filter(item => !!item)
const joinLines = array => array.join('\n')
const last = (() => {
const g = $$('#rso .g')
if (g[size] === 0) {
return $('#rso')
} else {
return g[g[size] - 1].parentNode
}
})()
const main = async () => {
const found = scan()
if (found[size] === 0) {
alert('No item found.\nIf you think this script works incorrectly contact the creator.\nThx in advance.')
return
}
const message = [
`Found: ${found[size]}`,
`Total: ${found[size] + ret[size]}`
]
ret = concat(ret, found)
await scroll()
const pnnext = $('#pnnext', doc)
if (pnnext === null) {
alert(joinLines(message))
cleanup()
return download()
}
message.unshift('Continue next page?')
if (confirm(joinLines(message))) {
await next(pnnext.href)
main()
} else {
cleanup()
download()
}
}
const mapElements = (query, callback) => Array.from($$(query, doc)).map(callback)
const next = href => {
if (iframe === undefined) {
iframe = createElement('iframe')
setStyle(iframe, {
display: 'none'
})
appendChild(iframe)
}
const progress = createElement('div')
setStyle(progress, {
background: 'black',
color: 'white',
left: 0,
padding: '8px 20px',
position: 'fixed',
top: 0,
zIndex: 1000
})
progress[text] = 'Loading...'
appendChild(progress)
return new Promise(resolve => {
iframe[on]('load', () => {
doc = iframe.contentWindow.document
progress.remove()
const wrapper = createElement('div')
wrapper[html] = $('#rso', doc)[html]
appendChild(wrapper, last)
if (doc !== document) {
$('#xjs')[html] = $('#xjs', doc)[html]
}
resolve()
}, {
once: true
})
iframe.src = href
})
}
const replace = (string, from, to = '') => (string || '').replace(from, to)
const replaceArr = (string, from, to) => from.reduce((prev, pattern, index) => replace(prev, pattern, to[index]), string)
const scan = () => filter(concat(
// All tab
mapElements('#rso div.g > div', item => {
const a = $('div.yuRUbf a, div.ct3b9e a', item)
if (!a) {
return false
}
const href = a.href
let title
let breadcrumb
let date
let desc
if ($('div.dXiKIc', item)) {
// YouTube
title = $('div.ct3b9e h3', item)
breadcrumb = $('div.dXiKIc div.P7xzyf', item)
breadcrumb = breadcrumb.firstChild[text] + ' · ' + breadcrumb.children[1][text]
date = $('div.dXiKIc div.P7xzyf span span', item)
desc = $('div.dXiKIc div.Uroaid', item)
} else {
title = $('div.yuRUbf h3', item)
breadcrumb = $('div.yuRUbf cite', item)
date = $('div.uUuwM span.wuQ4Ob span, div.IsZvec div.uo4vr span', item)
desc = $('div.uUuwM div.VwiC3b, div.IsZvec div.VwiC3b, div.IsZvec span.aCOpRe', item)
}
return convert([
href, // href
decode(href), // decoded
title, // title
breadcrumb, // breadcrumb
typeof date === 'string' // date
? date
: date && date.children[size] === 0 ? replace(date[text], / . ?$/) : '',
desc // description
? (
date
? replace(desc[text], date[text]).trimLeft()
: desc[text]
)
: ''
])
}),
// News tab
mapElements('#rso div > div > a.WlydOe', item => {
const href = item.href
return convert([
href, // href
decode(href), // decoded
$('[role=heading]', item), // title
$('div.CEMjEf span', item), // breadcrumb
$('div.ZE0LJd span', item), // date
$('div.GI74Re', item) // description
])
}),
// Images tab
mapElements('#islrg div.isv-r', item => {
const link = $('a[rel="noopener"]', item)
if (link === null) {
return false
}
const href = link.href
const src = $('img', item).src
return convert([
href, // href
decode(href), // decoded
link.title, // title
$('div.fxgdke', link), // breadcrumb
$('span.dP3z3e', item), // date
src.startsWith('data:') ? '' : src // description
])
})
))
const scroll = () => {
return new Promise(resolve => {
($('#islmp') || document.body).scrollIntoView({
behavior: 'smooth',
block: 'end'
})
const listener = () => {
clearTimeout(timer)
timer = setTimeout(() => {
document.removeEventListener('scroll', listener)
resolve()
}, 500)
}
let timer
document[on]('scroll', listener, {
passive: true
})
listener()
})
}
const setStyle = (target, style) => {
for (let key in style) {
target.style[key] = style[key]
}
}
let doc = document
let iframe
let ret = []
main()
})(document, Promise, setTimeout, 'innerHTML', 'addEventListener', 'length', 'innerText')
※ Minifierはこちらを使用しています。
※ Minify後の文字数をなるべく削減するために、少々トリッキーなコーディングをしています。
免責
Google検索結果画面の仕様が変わると、動作しなくなることが予想されます。
その際にはご容赦ください。