web-dev-qa-db-ja.com

スクレイピーを使用して、AJAXを使用しているWebサイトから動的コンテンツをスクレイピングできますか?

私は最近Pythonを学んでおり、ウェブスクレーパーの構築に手を浸しています。それはまったく空想ではありません。その唯一の目的は、賭けのウェブサイトからデータを取得し、このデータをExcelに入れることです。

ほとんどの問題は解決可能であり、私は少し混乱しています。しかし、私は1つの問題に関して大きなハードルにぶつかっています。サイトが馬のテーブルをロードし、現在のベット価格をリストしている場合、この情報はどのソースファイルにもありません。手がかりは、このデータが時々ライブであり、数字が明らかにリモートサーバーから更新されることです。私のPCのHTMLには、サーバーが必要なすべての興味深いデータを押し通す穴があります。

現在、動的なWebコンテンツの使用経験は少ないため、このことは頭を悩ませています。

JavaまたはJavascriptがキーだと思いますが、これは頻繁に表示されます。

スクレーパーは、単にオッズ比較エンジンです。一部のサイトにはAPIがありますが、APIがない場合はこれが必要です。私はPython 2.7でscrapyライブラリを使用しています

この質問がオープンエンドすぎる場合は謝罪します。要するに、私の質問は、この動的データをスクレイプするためにどのようにスクレイピーを使用して、それを使用できるのかということです。このベッティングオッズデータをリアルタイムでスクレイピングできるように?

133
Joseph

Webkitベースのブラウザ(Google ChromeやSafariなど)には、組み込みの開発者ツールがあります。 Chromeで開くことができますMenu->Tools->Developer ToolsNetworkタブでは、すべての要求と応答に関するすべての情報を表示できます。

enter image description here

写真の下部で、リクエストをXHRまでフィルタリングしていることがわかります。これらはjavascriptコードによって行われたリクエストです。

ヒント:ページを読み込むたびにログがクリアされます。画像の下部にある黒いドットボタンはログを保存します。

要求と応答を分析した後、Webクローラーからこれらの要求をシミュレートし、貴重なデータを抽出できます。多くの場合、HTMLを解析するよりもデータを取得する方が簡単です。そのデータにはプレゼンテーションロジックが含まれておらず、JavaScriptコードでアクセスできるようにフォーマットされているためです。

Firefoxにも同様の拡張子があり、 firebug と呼ばれます。 firebugはさらに強力であると主張する人もいますが、私はwebkitのシンプルさが好きです。

73
Ski

AJAXリクエストを持つscrapyの簡単な例を示します。サイトを見てみましょう rubin-kazan.r

すべてのメッセージは、AJAXリクエストでロードされます。私の目標は、すべての属性(作成者、日付など)でこれらのメッセージを取得することです。

enter image description here

WebページはAJAXテクノロジーを使用しているため、ページのソースコードを分析すると、これらのメッセージをすべて見ることができません。ただし、Mozilla Firefox(または他のブラウザーの同等のツール)のFirebugを使用して、Webページでメッセージを生成するHTTPリクエストを分析できます。

enter image description here

ページ全体をリロードするのではなく、メッセージを含むページの一部のみをリロードします。この目的のために、私は下部にある任意の数のページをクリックします。

enter image description here

そして、メッセージ本文を担当するHTTPリクエストを観察します。

enter image description here

終了後、リクエストのヘッダーを分析します(このURLは、varセクションのソースページから抽出することを引用する必要があります。以下のコードを参照してください)。

enter image description here

リクエストのフォームデータコンテンツ(HTTPメソッドは「Post」):

enter image description here

そして、JSONファイルである応答のコンテンツ:

enter image description here

探しているすべての情報が表示されます。

これからは、これらすべての知識をスクレイピーに実装しなければなりません。この目的のためにクモを定義しましょう:

class spider(BaseSpider):
    name = 'RubiGuesst'
    start_urls = ['http://www.rubin-kazan.ru/guestbook.html']

    def parse(self, response):
        url_list_gb_messages = re.search(r'url_list_gb_messages="(.*)"', response.body).group(1)
        yield FormRequest('http://www.rubin-kazan.ru' + url_list_gb_messages, callback=self.RubiGuessItem,
                          formdata={'page': str(page + 1), 'uid': ''})

    def RubiGuessItem(self, response):
        json_file = response.body

parse関数には、最初の要求に対する応答があります。 RubiGuessItemには、すべての情報を含むJSONファイルがあります。

92
Badarau Petru

多くの場合、クロール時にページでレンダリングされるコンテンツがJavascriptで生成されるため、scrapyがクロールできなくなるという問題(例えば、ajaxリクエスト、jQueryの狂気)に遭遇します。

ただし、WebテストフレームワークSeleniumとともにScrapyを使用すると、通常のWebブラウザーに表示されているものをすべてクロールできます。

注意するべき事柄:

  • これが機能するには、PythonバージョンのSelenium RCがインストールされている必要があり、Seleniumを適切にセットアップしている必要があります。また、これは単なるテンプレートクローラーです。あなたは物事でもっとクレイジーでより高度になることができますが、私は基本的なアイデアを見せたかっただけです。コードが今立っているので、あなたは与えられたURLに対して2つのリクエストをすることになります。 1つの要求はScrapyによって行われ、もう1つの要求はSeleniumによって行われます。おそらく、Seleniumに唯一の要求を行わせることができるようにする方法があると確信していますが、それを実装することはせず、2つの要求を実行することでScrapyでページをクロールすることもできます。

  • これは非常に強力です。レンダリングされたDOM全体をクロールできるようになり、ScrapyのすべてのNiceクロール機能を引き続き使用できるからです。これにより、もちろんクロールが遅くなりますが、レンダリングされたDOMがどれだけ必要かによっては、待つ価値があります。

    from scrapy.contrib.spiders import CrawlSpider, Rule
    from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor
    from scrapy.selector import HtmlXPathSelector
    from scrapy.http import Request
    
    from Selenium import Selenium
    
    class SeleniumSpider(CrawlSpider):
        name = "SeleniumSpider"
        start_urls = ["http://www.domain.com"]
    
        rules = (
            Rule(SgmlLinkExtractor(allow=('\.html', )), callback='parse_page',follow=True),
        )
    
        def __init__(self):
            CrawlSpider.__init__(self)
            self.verificationErrors = []
            self.Selenium = Selenium("localhost", 4444, "*chrome", "http://www.domain.com")
            self.Selenium.start()
    
        def __del__(self):
            self.Selenium.stop()
            print self.verificationErrors
            CrawlSpider.__del__(self)
    
        def parse_page(self, response):
            item = Item()
    
            hxs = HtmlXPathSelector(response)
            #Do some XPath selection with Scrapy
            hxs.select('//div').extract()
    
            sel = self.Selenium
            sel.open(response.url)
    
            #Wait for javscript to load in Selenium
            time.sleep(2.5)
    
            #Do some crawling of javascript created content with Selenium
            sel.get_text("//div")
            yield item
    
    # Snippet imported from snippets.scrapy.org (which no longer works)
    # author: wynbennett
    # date  : Jun 21, 2011
    

参照: http://snipplr.com/view/66998/

39
A T

別の解決策は、ダウンロードハンドラーまたはダウンロードハンドラーミドルウェアを実装することです。 (ダウンローダーミドルウェアの詳細については、 scrapy docs を参照してください)以下は、ヘッドレスphantomjs WebドライバーでSeleniumを使用するサンプルクラスです。

1)middlewares.pyスクリプト内でクラスを定義します。

from Selenium import webdriver
from scrapy.http import HtmlResponse

class JsDownload(object):

    @check_spider_middleware
    def process_request(self, request, spider):
        driver = webdriver.PhantomJS(executable_path='D:\phantomjs.exe')
        driver.get(request.url)
        return HtmlResponse(request.url, encoding='utf-8', body=driver.page_source.encode('utf-8'))

2)DOWNLOADER_MIDDLEWARE内の変数settings.pyJsDownload()クラスを追加します。

DOWNLOADER_MIDDLEWARES = {'MyProj.middleware.MiddleWareModule.MiddleWareClass': 500}

3)your_spider.py内にHTMLResponseを統合します。応答本文をデコードすると、目的の出力が得られます。

class Spider(CrawlSpider):
    # define unique name of spider
    name = "spider"

    start_urls = ["https://www.url.de"] 

    def parse(self, response):
        # initialize items
        item = CrawlerItem()

        # store data as items
        item["js_enabled"] = response.body.decode("utf-8") 

オプションのアドオン:
使用するミドルウェアを異なるスパイダーに伝える機能が必要だったため、このラッパーを実装しました。

def check_spider_middleware(method):
@functools.wraps(method)
def wrapper(self, request, spider):
    msg = '%%s %s middleware step' % (self.__class__.__name__,)
    if self.__class__ in spider.middleware:
        spider.log(msg % 'executing', level=log.DEBUG)
        return method(self, request, spider)
    else:
        spider.log(msg % 'skipping', level=log.DEBUG)
        return None

return wrapper

ラッパーが機能するためには、すべてのスパイダーが最低限必要です:

middleware = set([])

ミドルウェアを含めるには:

middleware = set([MyProj.middleware.ModuleName.ClassName])

利点:
スパイダーではなくこの方法で実装することの主な利点は、リクエストを1つしか作成しないことです。たとえば、A Tのソリューションでは、ダウンロードハンドラーがリクエストを処理し、スパイダーに応答を渡します。スパイダーは、parse_page関数でまったく新しいリクエストを行います。これは、同じコンテンツに対する2つのリクエストです。

31
rocktheartsm4l

私はカスタムダウンローダーミドルウェアを使用していましたが、キャッシュを機能させることができなかったため、あまり満足していませんでした。

より良いアプローチは、カスタムダウンロードハンドラを実装することでした。

実例があります here 。次のようになります。

# encoding: utf-8
from __future__ import unicode_literals

from scrapy import signals
from scrapy.signalmanager import SignalManager
from scrapy.responsetypes import responsetypes
from scrapy.xlib.pydispatch import dispatcher
from Selenium import webdriver
from six.moves import queue
from twisted.internet import defer, threads
from twisted.python.failure import Failure


class PhantomJSDownloadHandler(object):

    def __init__(self, settings):
        self.options = settings.get('PHANTOMJS_OPTIONS', {})

        max_run = settings.get('PHANTOMJS_MAXRUN', 10)
        self.sem = defer.DeferredSemaphore(max_run)
        self.queue = queue.LifoQueue(max_run)

        SignalManager(dispatcher.Any).connect(self._close, signal=signals.spider_closed)

    def download_request(self, request, spider):
        """use semaphore to guard a phantomjs pool"""
        return self.sem.run(self._wait_request, request, spider)

    def _wait_request(self, request, spider):
        try:
            driver = self.queue.get_nowait()
        except queue.Empty:
            driver = webdriver.PhantomJS(**self.options)

        driver.get(request.url)
        # ghostdriver won't response when switch window until page is loaded
        dfd = threads.deferToThread(lambda: driver.switch_to.window(driver.current_window_handle))
        dfd.addCallback(self._response, driver, spider)
        return dfd

    def _response(self, _, driver, spider):
        body = driver.execute_script("return document.documentElement.innerHTML")
        if body.startswith("<head></head>"):  # cannot access response header in Selenium
            body = driver.execute_script("return document.documentElement.textContent")
        url = driver.current_url
        respcls = responsetypes.from_args(url=url, body=body[:100].encode('utf8'))
        resp = respcls(url=url, body=body, encoding="utf-8")

        response_failed = getattr(spider, "response_failed", None)
        if response_failed and callable(response_failed) and response_failed(resp, driver):
            driver.close()
            return defer.fail(Failure())
        else:
            self.queue.put(driver)
            return defer.succeed(resp)

    def _close(self):
        while not self.queue.empty():
            driver = self.queue.get_nowait()
            driver.close()

スクレーパーが「スクレーパー」と呼ばれるとします。上記のコードを「scraper」フォルダーのルートにあるhandlers.pyというファイル内に配置すると、settings.pyに追加できます。

DOWNLOAD_HANDLERS = {
    'http': 'scraper.handlers.PhantomJSDownloadHandler',
    'https': 'scraper.handlers.PhantomJSDownloadHandler',
}

そして、ボイラー、JSはDOMを解析し、スクレイピーキャッシュ、リトライなどを行いました。

10
Ivan Chaer

スクレイピーを使用してこの動的データをスクレイプして使用できるようにするにはどうすればよいですか?

Scrapyのみを使用したソリューションを誰も投稿していないのはなぜでしょうか。

Scrapyチームのブログ投稿をチェックしてください SCRAPING INFINITE SCROLLING PAGES 。例では、スクラップ http://spidyquotes.herokuapp.com/scroll 無限スクロールを使用するWebサイトをスクラップします。

アイデアは、ブラウザの開発者ツールを使用してAJAXリクエストに気づき、その情報に基づいてScrapyのリクエストを作成することです

import json
import scrapy


class SpidyQuotesSpider(scrapy.Spider):
    name = 'spidyquotes'
    quotes_base_url = 'http://spidyquotes.herokuapp.com/api/quotes?page=%s'
    start_urls = [quotes_base_url % 1]
    download_delay = 1.5

    def parse(self, response):
        data = json.loads(response.body)
        for item in data.get('quotes', []):
            yield {
                'text': item.get('text'),
                'author': item.get('author', {}).get('name'),
                'tags': item.get('tags'),
            }
        if data['has_next']:
            next_page = data['page'] + 1
            yield scrapy.Request(self.quotes_base_url % next_page)
3
Chankey Pathak

はい、Scrapyは動的なWebサイト(javaScriptを介してレンダリングされるWebサイト)を破棄できます。

この種のWebサイトをスクレイピングするには、2つのアプローチがあります。

最初、

splashを使用してJavascriptコードをレンダリングし、レンダリングされたHTMLを解析できます。ここでドキュメントとプロジェクトを見つけることができます Scrapy splash、git

第二に、

誰もが述べているように、network callsを監視することで、はい、データを取得するapi呼び出しを見つけることができます。

1
ThunderMind

SeleniumとFirefox Webドライバーを使用して、ajaxリクエストを処理します。クローラーをデーモンとして必要とする場合はそれほど高速ではありませんが、手動のソリューションよりもはるかに優れています。短いチュートリアルを書いた こちら 参考のため

1
narko