目次
いつかはやるだろうなーと思っていたことが実行されました。
Googleの検索結果をクローリングする際、従来よく使われた板Seleniumを使用した方法では、Google側が自動アクセスを検知し、制限をかけることが増えてきました。なのでクローリングが途中でブロックされたり、データ取得が失敗する問題が発生していると思います。
また、Splashを使用して完全にレンダリングされたページを取得する方法も試してる人もいると思います。Googleの検索結果ページで動作する一方、検索結果から得たリンク先のページをクローリングする際に、タイムアウトが頻発するという別の問題が発生しているはずです。
これらの問題を解決するために、Googleの検索結果ページのクローリングにはSplashを使用し、検索結果で得たリンク先のページのクローリングにはSeleniumを使用する手法について、コード例を交えて詳しく説明します。
よくあるコード例:SeleniumによるGoogleクローリング
def start_requests(self):
url = self.base_url + self.keyword
yield scrapy.Request(url, meta={"list_url": url, 'action': 'se'}, callback=self.parse_list)
問題点
- Googleの制限: Seleniumはブラウザ自動化ツールとして非常に便利ですが、Googleはこのツールによるアクセスを検知し、アクセス制限をかけることがあります。
- パフォーマンスの低下: 全てのページをSeleniumでレンダリングするため、クローリング処理が非常に遅くなる傾向にありました。
解決例:SplashとSeleniumのハイブリッド手法
Splashの導入
def start_requests(self):
url = self.base_url + self.keyword
yield SplashRequest(url, meta={"list_url": url, 'action': 'se'}, callback=self.parse_list)
SplashRequestの使用理由
SplashはJavaScriptレンダリングエンジンを搭載したヘッドレスブラウザで、動的なWebページのHTMLを取得するために使用されます。Seleniumに比べて軽量で、Googleの自動化検出を回避しやすいというメリットがあります。
リンク先のページクローリングにSeleniumを使用
def parse_list(self, response):
links = response.xpath('//div[@class="yuRUbf"]/a/@href').extract()
for link in links:
print(link)
url = link
crawled_site_item = CrawledSite()
crawled_site_item['source_url'] = link
yield scrapy.Request(
url,
meta={"list_url": url, 'action': 'se', "crawled_site_item": crawled_site_item},
callback=self.parse_detail
)
Seleniumの使用理由
Splashでは、リンク先の複雑なWebページをすべて処理する際にタイムアウトが発生する問題がありました。一方、Seleniumはレンダリング能力が高く、動的要素を含むページでも問題なくデータを取得できます。
SplashとSeleniumを組み合わせた利点
- Google制限の回避: Splashを使用することで、Googleの自動化検出を回避し、検索結果ページのデータを安全に取得できます。
- タイムアウト問題の解消: 検索結果から得たリンク先ページはSeleniumで処理することで、複雑なページでも安定したクローリングが可能になります。
- 効率的なリソース使用: 全てのページをSeleniumで処理するよりも、Splashと組み合わせることでリソースを効率的に使用できます。
シンプルな例ですが、
クローリングのエラーで解決する人がいると思いますので参考にしてください。
コード例の全文の例
import scrapy
from scrapy_splash import SplashRequest
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
class GoogleSpider(scrapy.Spider):
name = "google"
def __init__(self, keyword=None, *args, **kwargs):
super(GoogleSpider, self).__init__(*args, **kwargs)
chrome_options = Options()
chrome_options.add_argument('--headless')
chrome_options.add_argument('--disable-gpu')
chrome_options.add_argument('--no-sandbox')
chrome_options.add_argument('--disable-dev-shm-usage')
self.driver = webdriver.Remote(command_executor='http://localhost:4444/wd/hub', options=chrome_options)
self.base_url = "https://www.google.com/search?q="
self.keyword = keyword
def start_requests(self):
if not self.keyword:
raise ValueError("Keyword is required")
url = self.base_url + self.keyword
yield SplashRequest(url, self.parse_search_results, args={'wait': 5}, endpoint='render.html')
def parse_search_results(self, response):
# Extract links from Google search results
links = response.xpath('//div[@class="yuRUbf"]/a/@href').extract()
for link in links:
yield scrapy.Request(url=link, callback=self.parse_detail)
def parse_detail(self, response):
# Extract title and description from the target page
title = response.xpath('//title/text()').get()
description = response.xpath('//meta[@name="description"]/@content').get()
yield {
'url': response.url,
'title': title,
'description': description
}
def closed(self, reason):
self.driver.quit()
見ての通り、
Splashを設定し、ローカル環境またはDockerコンテナで起動し、
Seleniumリモートドライバ(例: localhost:4444)をセットアップし、
キーワードを引数としてスパイダーを実行するという感じです。
どれだけ対策されても1日で解決するイタチごっこです。