2021年1月、現状のGoogle検索結果で動作するように更新しました。
次のページを取得することもできます。
また、ニュースタブでも動作します。
Googleの検索結果を手軽に一覧でCSV保存したい
Google検索はとても便利なので、日常的に利用しているのですが、
検索結果をまとめて一覧化したいことがありますよね。
検索画面に表示されるサイト名やページタイトル、URLをCSVファイルとして一覧で取得できれば、
ExcelやNumbers、Calcなどで使えて便利です。
スクレイピングやAPIで取得するなどの方法もありますが、
個人で利用するには、簡便に取得できれば十分でしたので、
ブラウザから誰でも利用できる、ブックマークレットを作りました。
ただし、最新のブラウザでなければ動作しません。
使い方
ブラウザで新しいブックマークを作り、次の内容をURL欄に貼り付けます。
ブックマークの名前は、Google検索結果CSVダウンロード
などで良いでしょう。
Googleの検索結果画面で、登録したブックマークをクリックすると動作します。
javascript:((e,t,n,o,r,i,c,d)=>{const a=(t,n=e)=>n.querySelector(t),s=(t,n=e)=>n.querySelectorAll(t),l=(t,n=e.body)=>n.appendChild(t),v=t=>e.createElement(t),p=()=>{void 0!==T&&T.remove()},u=(e,t)=>e.concat(t),f=e=>`"${e.map(e=>L((e&&"string"!=typeof e&&d in e?e[d]:e)||"",/"/g,'""')).join('","')}"`,m=e=>{let t=e;try{t=decodeURI(e)}catch(e){}return t},b=()=>{const t=new Blob([new Uint8Array([239,187,191]),h(u([f(["href","decoded","title","breadcrumb","date","description"])],I))],{type:"text/csv"}),n=v("a"),r=URL.createObjectURL(t);n.download=e.title+".csv",n.href=r,n.click(),o(()=>URL.revokeObjectURL(r),1e3)},h=e=>e.join("\n"),y=(()=>{const e=s("#rso .g");return 0===e[c]?a("#rso"):e[e[c]-1].parentNode})(),g=async()=>{const e=R(),t=[`Found: ${e[c]}`,`Total: ${e[c]+I[c]}`];I=u(I,e),await U();const n=a("#pnnext",k);if(null===n)return alert(h(t)),p(),b();t.unshift("Continue next page?"),confirm(h(t))?(await x(n.href),g()):(p(),b())},w=(e,t)=>Array.from(s(e,k)).map(t),x=t=>{void 0===T&&(T=v("iframe"),j(T,{display:"none"}),l(T));const o=v("div");return j(o,{background:"black",color:"white",left:0,padding:"8px 20px",position:"fixed",top:0,zIndex:1e3}),o[d]="Loading...",l(o),new n(n=>{T[i]("load",()=>{k=T.contentWindow.document,o.remove();const t=v("div");t[r]=a("#rso",k)[r],l(t,y),k!==e&&(a("#xjs")[r]=a("#xjs",k)[r]),n()},{once:!0}),T.src=t})},L=(e,t,n="")=>e.replace(t,n),R=()=>u(w("#rso div.rc",e=>{const t=a("div.yuRUbf a",e).href,n=a("div.IsZvec span.f",e),o=a("div.IsZvec span.aCOpRe",e);return f([t,m(t),a("div.yuRUbf h3",e),a("div.yuRUbf cite",e),n&&0===n.children[c]?L(n[d],/ . $/):"",o?n?L(o[d],n[d]).trimLeft():o[d]:""])}),w("#rso g-card div.dbsr",e=>{const t=a("div.dbsr > a",e).href;return f([t,m(t),a("[role=heading]",e),a("div.XTjFC",e),a("span.WG9SHc",e),a("div.Y3v8qd",e)])})),U=()=>new n(t=>{e.body.scrollIntoView({behavior:"smooth",block:"end"});const n=()=>{clearTimeout(r),r=o(()=>{e.removeEventListener("scroll",n),t()},100)};let r;e[i]("scroll",n,{passive:!0}),n()}),j=(e,t)=>{for(let n in t)e.style[n]=t[n]};let T,k=e,I=[];g()})(document,navigator,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) とは、ユーザーがウェブブラウザのブックマークなどから起動し、なんらかの処理を行う簡易的なプログラムのことである。携帯電話のウェブブラウザで足りない機能を補ったり、ウェブアプリケーションの処理を起動する為に使 ... |
対象ブラウザ
最新のブラウザを利用してください。
動作確認結果
2021年1月7日時点
ブラウザ | バージョン | 動作 |
---|---|---|
Google Chrome | 87.0.4280.88 | OK |
Firefox | 84.0.1 | OK |
Safari | 14.0.2 | OK |
Edge (Blink) | 87.0.664.66 | OK |
Edge (EdgeHTML) | 動作未確認 | 今後も対応しません。 |
Internet Explorer | 11.1082.18362.0 | NG 今後も対応しません。 |
ソースコード
ブックマークレット用に変換する前のソースコードです。
※ Minifierはこちらを使用しています。
ここをクリックしてソースコードを表示
((document, navigator, 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) => base.concat(append)
const convert = row => `"${row.map(s => replace((s && typeof s !== 'string' && text in s ? s[text] : s) || '', /"/g, '""')).join('","')}"`
const decode = href => {
let ret = href
try {
ret = decodeURI(href)
} catch (e) {}
return ret
}
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 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()
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 scan = () => concat(
mapElements('#rso div.rc', rc => {
const href = $('div.yuRUbf a', rc).href
const date = $('div.IsZvec span.f', rc)
const desc = $('div.IsZvec span.aCOpRe', rc)
return convert([
href,
decode(href),
$('div.yuRUbf h3', rc),
$('div.yuRUbf cite', rc),
date && date.children[size] === 0 ? replace(date[text], / . $/) : '',
desc ? (date ? replace(desc[text], date[text]).trimLeft() : desc[text]) : ''
])
}),
mapElements('#rso g-card div.dbsr', dbsr => {
const href = $('div.dbsr > a', dbsr).href
return convert([
href,
decode(href),
$('[role=heading]', dbsr),
$('div.XTjFC', dbsr),
$('span.WG9SHc', dbsr),
$('div.Y3v8qd', dbsr)
])
})
)
const scroll = () => {
return new Promise(resolve => {
document.body.scrollIntoView({
behavior: 'smooth',
block: 'end'
})
const listener = () => {
clearTimeout(timer)
timer = setTimeout(() => {
document.removeEventListener('scroll', listener)
resolve()
}, 100)
}
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, navigator, Promise, setTimeout, 'innerHTML', 'addEventListener', 'length', 'innerText')
※ Minify後の文字数をなるべく削減するために、少々トリッキーなコーディングをしています。
免責
Google検索結果画面の仕様が変わると、動作しなくなることが予想されます。
その際にはご容赦ください。