【Python】Googleの検索結果をアクセス制限なしで取得する

ソースコード全体は[ここ(GitHub)](https://github.com/derodero24/Deropy/blob/master/google.py)でも見れます。

# 目次
* [はじめに](#はじめに)
* [注意](#注意)
* [ソースコード](#ソースコード)
* [使い方](使い方)


# はじめに
Googleの検索結果を大量にほしいと思っても、apiや既存のモジュールだとアクセス制限があったので、自分で汎用的なクラスを作ることにしました。
作成時の方針は以下の２つ。

* テキスト検索(普通の検索)と画像検索の両方に対応
* 何も考えずに使える

# 注意
こんなタイトルにしといてなんなのですが，アクセス制限を完全に突破することはできていません。一つ（もしくは数個程度）のキーワードで大量の検索結果を取得することはできますが，大量のキーワードの検索結果を数件ずつ取得するのには向いていません。使いすぎるとIPアドレスに一時的な制限がかかって検索結果を取得できなくなります。使用は自己責任でお願いします。

***** **追記（2019/05/07）** *****
Splashというブラウザを使うことで，アクセス制限にかからずに何度でも検索できることを確認しました（確認したのは150クエリ程度）。以下のプログラムを少し変えるだけなので，興味ある方はお試しください。そのうち別で記事を出すと思います。

```py
# 変更箇所
self.GOOGLE_SEARCH_URL = 'https://www.google.co.jp/search'
↓
self.GOOGLE_SEARCH_URL = 'http://localhost:8050/render.html?url=https://www.google.co.jp/search'
```

Splashのインストール方法・使い方参考
[【Python】HeadlessChromeに代わるクローリング用ブラウザSplash](https://qiita.com/derodero24/items/a181cd34b081bee055c7)



# ソースコード
```py
import json
from urllib import parse
import requests
from bs4 import BeautifulSoup


class Google:
    def __init__(self):
        self.GOOGLE_SEARCH_URL = 'https://www.google.co.jp/search'
        self.session = requests.session()
        self.session.headers.update({
            'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:57.0) Gecko/20100101 Firefox/57.0'})

    def Search(self, keyword, type='text', maximum=100):
        '''Google検索'''
        print('Google', type.capitalize(), 'Search :', keyword)
        result, total = [], 0
        query = self.query_gen(keyword, type)
        while True:
            # 検索
            html = self.session.get(next(query)).text
            links = self.get_links(html, type)

            # 検索結果の追加
            if not len(links):
                print('-> No more links')
                break
            elif len(links) > maximum - total:
                result += links[:maximum - total]
                break
            else:
                result += links
                total += len(links)

        print('-> Finally got', str(len(result)), 'links')
        return result

    def query_gen(self, keyword, type):
        '''検索クエリジェネレータ'''
        page = 0
        while True:
            if type == 'text':
                params = parse.urlencode({
                    'q': keyword,
                    'num': '100',
                    'filter': '0',
                    'start': str(page * 100)})
            elif type == 'image':
                params = parse.urlencode({
                    'q': keyword,
                    'tbm': 'isch',
                    'filter': '0',
                    'ijn': str(page)})

            yield self.GOOGLE_SEARCH_URL + '?' + params
            page += 1

    def get_links(self, html, type):
        '''リンク取得'''
        soup = BeautifulSoup(html, 'lxml')
        if type == 'text':
            elements = soup.select('.rc > .r > a')
            links = [e['href'] for e in elements]
        elif type == 'image':
            elements = soup.select('.rg_meta.notranslate')
            jsons = [json.loads(e.get_text()) for e in elements]
            links = [js['ou'] for js in jsons]
        return links
```

# 使い方
「ドラえもん」の検索結果が200件ほしいなら、こんな感じで使います。resultにはurlのリストが渡ります。これなら何も考えずに使えるかな？

```py
google = Google()
# テキスト検索
result = google.Search('ドラえもん', type='text', maximum=200)
# 画像検索
result = google.Search('ドラえもん', type='image', maximum=200)
```