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


145

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

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

現在、動的Webコンテンツの経験は少ないので、これは頭を悩ませるのに苦労しています。

私はJavaまたはJavascriptが鍵だと思います。これは頻繁に現れます。

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

この質問の記述が多すぎると申し訳ありません。要するに、私の質問は、私が使用できるように、scrapyを使用してこの動的データをスクレイピングする方法を教えてください。このベッティングオッズデータをリアルタイムで取得できるようにするにはどうすればよいですか。


1
動的でライブなデータであるこのデータをどのように取得できますか?
ジョセフ

1
ページにJavaScriptがある場合は、これを試してください
reclosedev '18

3
またはなどのFirefox拡張機能を試して、ajaxリクエストを使用しているページをロードします。Scrapyはajaxリクエストを自動的に識別しないため、適切なajax URLを手動で検索し、それを使用してリクエストを実行する必要があります。httpFoxliveHttpHeaders
Aamir Adnan 2011

よろしくお願いします。Firefox拡張機能に小便を与えます
Joseph

オープンソースのソリューションはたくさんあります。しかし、特に大規模なワークロードでこれを行う簡単で迅速な方法を探している場合は、SnapSearch(snapsearch.io)をチェックしてください。検索エンジンのクロール機能を必要とするJS、HTML5、SPAサイト用に作成されました。デモを試してください(空のコンテンツがある場合、これはサイトが実際には本文のコンテンツを返さないことを意味します。つまり、301リダイレクトを意味します)。
CMCDragonkai

回答:


74

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

ここに画像の説明を入力してください

画像の下部に、リクエストをフィルタリングして絞り込んだことがわかりXHRます。これらはJavaScriptコードによって行われたリクエストです。

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

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

Firefoxにも同様の拡張機能があり、firebugと呼ばれています。firebugはさらに強力であると主張する人もいますが、私はwebkitの単純さを気に入っています。


141
「スクレイピー」という単語が含まれていない場合、これはどのように受け入れられる答えになるのでしょうか?
ツールキット

それは動作し、Pythonでjsonモジュールを使用して解析するのは簡単です。それが解決策です!それと比較して、セレンや他の人が提案しているものを使ってみてください、それはより頭痛の種です。代替の方法がもっと複​​雑だった場合は、私はあなたにそれを与えますが、ここではそうではありません@Toolkit
Arion_Miles

1
これは実際には関係ありません。問題は、スカーピーを使用して動的Webサイトをスクレイピングする方法でした。
E. Erfan

「これは一体、どのようにして受け入れられる答えになるのか」-実用化は政治的正しさを打ち負かすため。人間はコンテキストを理解します。
エスプレッソ

98

scrapyAJAXリクエストを使用した簡単な例を次に示し ます。サイトrubin-kazan.ruを見てみましょう。

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

ここに画像の説明を入力してください

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

ここに画像の説明を入力してください

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

ここに画像の説明を入力してください

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

ここに画像の説明を入力してください

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

ここに画像の説明を入力してください

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

ここに画像の説明を入力してください

そして、JSONファイルである応答の内容:

ここに画像の説明を入力してください

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

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

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ファイルを持っています。


6
こんにちは。「url_list_gb_messages」とは何か説明してもらえますか?理解できません。ありがとう。
分極

4
これは間違いなく良いです。
1a1a11a 2015年

1
@polariseそのコードはreモジュール(正規表現)を使用しており、文字列を検索'url_list_gb_messages="(.*)"'し、同じ名前の変数内の括弧の内容を分離します。これは素晴らしいイントロです:guru99.com/python-regular-expressions-complete-tutorial.html
MGP

42

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

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

注意すべき点:

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

  • これで、レンダリングされたDOM全体をクロールできるようになり、Scrapyの優れたクロール機能をすべて使用できるため、これは非常に強力です。もちろん、これによりクロールが遅くなりますが、レンダリングされた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/


きちんとした解決策!このスクリプトをFirefoxに接続するためのヒントはありますか?(OSはLinux Mintです)。「[Errno 111]接続が拒否されました」と表示されます。
Andrew

1
このコードは、もはやのために働かないselenium=3.3.1python=2.7.10セレンからセレンをインポートするときに、エラー
benjaminz

1
セレンのバージョンでは、あなたのimport文は次のようになります from selenium import webdriverchromedriverまたはあなたが使用することが起こるものは何でも。ドキュメントの 編集:ドキュメント参照を追加して、私の恐ろしい文法を変更してください!
nulltron 2017


33

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

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)JsDownload()クラスを変数DOWNLOADER_MIDDLEWARE内の変数に追加しますsettings.py

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

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

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つのリクエストしか作成しないことです。たとえば、ATのソリューションでは、ダウンロードハンドラーが要求を処理し、応答をスパイダーに渡します。次に、スパイダーはparse_page関数でまったく新しいリクエストを作成します。これは、同じコンテンツに対する2つのリクエストです。



@ rocktheartsm4lだけで、使用しての何が問題になってprocess_requestsif spider.name in ['spider1', 'spider2']代わりにデコレータの
パッド

@pad何も問題はありません。私のスパイダークラスがミドルウェアという名前のセットを持っていることがより明確になりました。このようにして、任意のスパイダークラスを調べ、どのミドルウェアが実行されるかを正確に確認できます。私のプロジェクトには多くのミドルウェアが実装されていたので、これは理にかなっています。
rocktheartsm4l 2014

これはひどい解決策です。それはスクレイピーに関連していないだけでなく、コード自体も非常に非効率的であり、アプローチ全体は一般に、
スクレイピーな

2
ダウンローダーミドルウェアを使用しているため、SOで見た他のどのソリューションよりもはるかに効率的です。そのため、ページに対して要求が1つだけ作成されます。非常にひどい場合は、より良いソリューションを考えて共有せず、露骨に一方的な主張をする。「スクレイピーとは関係ありません」あなたは何かを吸っていますか?クレイジーで複雑で堅牢なカスタムソリューションを実装する以外に、これはほとんどの人が使用している方法です。唯一の違いは...ほとんどが作られる複数の要求を引き起こしクモにおけるセレンの一部を実装することである
rocktheartsm4l

10

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

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

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

# 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を解析し、スクレイピーキャッシュや再試行などを行います。


私はこの解決策が好きです!
rocktheartsm4l

素晴らしい解決策。Seleniumドライバーはまだ唯一の選択肢ですか?
Motheus

素晴らしいソリューション。どうもありがとう。
CrazyGeek

4

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

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)

私たちは再び同じ問題に直面します:Scrappyはこの目的のために作られておらず、ここで私たちは同じ問題に直面します。phantomJSに進むか、他の人が提案するように、独自のダウンロードミドルウェアを作成します
rak007

@ rak007 PhantomJS対Chromeドライバー。どれを提案しますか?
Chankey Pathak 2017

2

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

この種のWebサイトをスクレイピングするには、2つの方法があります。

最初、

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

第二に、

みんなが言っているように、を監視することでnetwork calls、はい、データを取得するapi呼び出しを見つけることができます。


1

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.