Scrapy, AJAX kullanan web sitelerindeki dinamik içeriği kazımak için kullanılabilir mi?


145

Son zamanlarda Python öğreniyorum ve elimi bir web kazıyıcı inşa etmeye daldım. Hiç fantezi bir şey değil; tek amacı, verileri bir bahis web sitesinden çıkarmak ve bu verilerin Excel'e konmasını sağlamaktır.

Sorunların çoğu çözülebilir ve etrafta iyi bir karışıklık yaşıyorum. Ancak bir konuda büyük bir engelle karşılaşıyorum. Bir site at tablosu yüklüyorsa ve mevcut bahis fiyatlarını listeliyorsa, bu bilgi herhangi bir kaynak dosyasında bulunmaz. İpucu, bu verilerin bazen canlı olması ve sayıların bazı uzak sunuculardan açıkça güncellenmesi. Bilgisayarımdaki HTML, sunucularının ihtiyacım olan tüm ilginç verileri ilettiği bir deliğe sahip.

Dinamik web içeriği ile ilgili deneyimim düşük, bu yüzden bu şey kafamı dolaşmakta sorun yaşıyorum.

Java veya Javascript'in bir anahtar olduğunu düşünüyorum, bu sık sık ortaya çıkıyor.

Kazıyıcı basitçe bir oran karşılaştırma motorudur. Bazı sitelerin API'leri var ama bunu yapanlar için buna ihtiyacım var. Python 2.7 ile terapi kütüphanesini kullanıyorum

Bu soru çok açık uçlu ise özür dilerim. Kısacası sorum şu: Bu dinamik veriyi kullanabilmem için scrapy nasıl kullanılabilir? Bu bahis oranları verilerini gerçek zamanlı olarak kazıyabilmem için?


1
Bu verileri, dinamik ve canlı verileri nasıl alabilirim?
Joseph

1
Sayfanızda javascript varsa, bunu deneyin
reclosedev

3
Veya Firefoxgibi bazı uzantıları deneyin ve ajax isteği kullanan bir sayfa yükleyin. Scrapy, ajax isteklerini otomatik olarak tanımlamaz, uygun ajax URL'sini manuel olarak aramanız ve ardından bununla istekte bulunmanız gerekir. httpFoxliveHttpHeaders
Aamir Adnan

alkış, ben Firefox uzantıları bir wizz vereceğim
Joseph

Bir dizi açık kaynaklı çözüm var. Ancak bunu özellikle büyük iş yükleri için yapmanın kolay ve hızlı bir yolunu arıyorsanız, SnapSearch ( snapsearch.io ) adresine bakın . Arama motoru taranabilirliği gerektiren JS, HTML5 ve SPA siteleri için oluşturulmuştur. Demoyu deneyin (boş içerik varsa, bu site aslında hiçbir vücut içeriği döndürmediği anlamına gelir, bu da potansiyel olarak 301 yönlendirmesi anlamına gelir).
CMCDragonkai

Yanıtlar:


74

Webkit tabanlı tarayıcılarda (Google Chrome veya Safari gibi) yerleşik geliştirici araçları vardır. Chrome'da açabilirsiniz Menu->Tools->Developer Tools. NetworkSekmesi her istek ve yanıt hakkında tüm bilgileri görmenizi sağlar:

resim açıklamasını buraya girin

Resmin altında aşağıya filtre uyguladığımı görebilirsiniz XHR- bunlar javascript kodu tarafından yapılan istekler.

İpucu: günlük her sayfa yüklediğinizde silinir, resmin alt kısmında siyah nokta düğmesi günlüğü korur.

İstekleri ve yanıtları analiz ettikten sonra, web tarayıcınızdan bu istekleri taklit edebilir ve değerli veriler elde edebilirsiniz. Çoğu durumda, verilerinizi HTML'yi ayrıştırmaktan daha kolay olacaktır, çünkü bu veriler sunum mantığı içermez ve javascript kodu ile erişilecek şekilde biçimlendirilir.

Firefox'un benzer bir uzantısı var, buna firebug denir . Bazıları kundakçı daha da güçlü olduğunu iddia edecek ama ben webkit basitliğini seviyorum.


141
İçinde 'terapi' kelimesi bile yoksa bu nasıl kabul edilebilir bir cevap olabilir?
Araç Seti

Çalışır ve python'daki json modülünü kullanarak ayrıştırmak kolaydır. Bu bir çözüm! Buna kıyasla, selenyum veya insanların önerdiği diğer şeyleri kullanmayı deneyin, daha fazla baş ağrısı. Alternatif yöntem daha kıvrık olsaydı, o zaman size verebilirdim, ama burada durum böyle değil @Toolkit
Arion_Miles

1
Bu gerçekten alakalı değil. Soru, dinamik web sitelerini kazımak için küfürün nasıl kullanılacağıydı.
E. Erfan

"Bu nasıl kabul edilebilir bir cevap olabilir" - Çünkü pratik kullanım politik doğruluğu yener. İnsanlar CONTEXT'i anlıyor.
Espresso

98

scrapyAJAX isteği ile basit bir örnek . Siteyi görelim rubin-kazan.ru .

Tüm iletiler bir AJAX isteğiyle yüklenir. Amacım bu mesajları tüm nitelikleriyle (yazar, tarih, ...) getirmek:

resim açıklamasını buraya girin

Sayfanın kaynak kodunu analiz ettiğimde, web sayfasında AJAX teknolojisi kullanıldığı için tüm bu mesajları göremiyorum. Ancak web sayfasındaki iletileri üreten HTTP isteğini analiz etmek için Mozilla Firefox'tan (veya diğer tarayıcılarda eşdeğer bir araç) Firebug ile yapabilirim:

resim açıklamasını buraya girin

Tüm sayfayı yeniden yüklemez, yalnızca sayfanın ileti içeren bölümlerini yeniden yükler. Bu amaçla alt kısımdaki isteğe bağlı sayıda sayfayı tıklıyorum:

resim açıklamasını buraya girin

Ve ileti gövdesinden sorumlu HTTP isteğini gözlemliyorum:

resim açıklamasını buraya girin

Bitirdikten sonra, isteğin başlıklarını analiz ediyorum (var bölümünden kaynak sayfadan çıkaracağım bu URL'yi alıntılamalıyım, aşağıdaki koda bakın):

resim açıklamasını buraya girin

Ve isteğin form veri içeriği (HTTP yöntemi "Post"):

resim açıklamasını buraya girin

Ve bir JSON dosyası olan yanıtın içeriği:

resim açıklamasını buraya girin

Aradığım tüm bilgileri sunar.

Bundan sonra, tüm bu bilgileri terapide uygulamalıyım. Örümceği bu amaçla tanımlayalım:

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

İşlevde parseilk istek için yanıt var. In RubiGuessItemI tüm bilgileri JSON dosyası var.


6
Selam. 'Url_list_gb_messages' 'nin ne olduğunu açıklar mısınız? Anlayamıyorum. Teşekkürler.
polarize

4
Bu kesinlikle daha iyi.
1a1a11a

1
@polarise Bu kod remodülü kullanıyor (normal ifadeler), dizeyi arar 'url_list_gb_messages="(.*)"'ve aynı ad değişkenindeki parantez içeriğini yalıtır. Bu güzel bir giriş: guru99.com/python-regular-expressions-complete-tutorial.html
MGP

42

Çoğu kez tarama yaparken, sayfada oluşturulan içeriğin Javascript ile oluşturulduğu ve dolayısıyla terapinin bunun için tarayamadığı (örn. Ajax istekleri, jQuery deliliği) sorunlarla karşılaşırız.

Ancak, Scrapy'yi web test çerçevesi Selenium ile birlikte kullanırsanız, normal bir web tarayıcısında görüntülenen her şeyi tarayabiliriz.

Dikkat edilmesi gereken bazı noktalar:

  • Bunun çalışması için Selenium RC'nin Python sürümüne sahip olmanız ve Selenium'u doğru şekilde ayarlamış olmanız gerekir. Ayrıca bu sadece bir şablon tarayıcıdır. İşlerle çok daha çılgın ve daha gelişmiş olabilirsiniz ama sadece temel fikri göstermek istedim. Kod şimdi dururken herhangi bir URL için iki istekte bulunacaksınız. Bir istek Scrapy tarafından, diğeri Selenyum tarafından yapılır. Eminim ki Selenyum'un sadece bir tane ve tek isteği yapmasını sağlayabileceğin yolların var olduğuna eminim ama bunu uygulamak için uğraşmadım ve iki istek yaparak sayfayı da Scrapy ile tara.

  • Bu oldukça güçlü çünkü artık taramanız için tüm oluşturulmuş DOM'unuz var ve Scrapy'deki tüm güzel tarama özelliklerini kullanmaya devam edebilirsiniz. Bu elbette daha yavaş tarama yapacaktır, ancak oluşturulan DOM'a ne kadar ihtiyacınız olduğuna bağlı olarak beklemeye değer olabilir.

    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

Referans: http://snipplr.com/view/66998/


Düzgün bir çözüm! Bu betiği Firefox'a bağlamakla ilgili ipuçlarınız var mı? (OS Linux Mint'dir). "[Errno 111] Bağlantı reddedildi" mesajı alıyorum.
Andrew

1
Bu kod artık çalışır selenium=3.3.1ve python=2.7.10selenyum selenyumun aktarırken hata,
benjaminz

1
Selenyum'un bu sürümünde içe aktarma ifadeniz: from selenium import webdriverveya chromedriverkullandığınız her şey olacaktır. Google Dokümanlar DÜZENLEME: Doküman referansı ekleyin ve korkunç gramerimi değiştirin!
nulltron

Selenium Remote Control, web sitesine
rainbowsorbet

33

Başka bir çözüm, bir indirme işleyicisi veya indirme işleyicisi ara katman yazılımı uygulamak olacaktır. (bakınız terapi dokümanları downloader ara katman yazılımı hakkında daha fazla bilgi için )

1) Kod içinde sınıfı tanımlayın 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) İçindeki JsDownload()değişkene sınıf ekleyin :DOWNLOADER_MIDDLEWAREsettings.py

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

3) entegre HTMLResponseolan your_spider.py. Yanıt gövdesinin deşifre edilmesi size istenen çıktıyı verecektir.

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") 

İsteğe bağlı Addon:
Farklı örümceklere hangi ara katman yazılımını kullanacağını söylemek istedim, bu yüzden bu sarmalayıcıyı uyguladım:

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

sargının çalışması için tüm örümceklerin minimumda olması gerekir:

middleware = set([])

bir ara katman yazılımı eklemek için:

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

Avantajı:
Örümcek yerine bu şekilde uygulamanın ana avantajı, yalnızca bir istekte bulunmanızdır. AT'nin çözümünde örneğin: İndirme işleyicisi isteği işler ve ardından örümceğe verilen yanıtı kaldırır. Örümcek daha sonra parse_page işlevinde yepyeni bir istekte bulunur - Bu, aynı içerik için iki istektir.


Buna rağmen cevaplamak için biraz geç kaldım>. <
rocktheartsm4l

@ rocktheartsm4l sadece ne de kullanılarak nesi process_requests, if spider.name in ['spider1', 'spider2']yerine dekoratör ait
pad

@pad Bunda yanlış bir şey yok. Örümcek sınıflarımın ara katman yazılımı adında bir seti olmasını daha net buldum. Bu şekilde, herhangi bir örümcek sınıfına bakabilir ve bunun için tam olarak hangi ara yazılımların icra edileceğini görebilirim. Projemde çok sayıda ara katman yazılımı uygulandı ve bu mantıklı geldi.
rocktheartsm4l

Bu korkunç bir çözüm. Sadece terapi ile ilgili değil, aynı zamanda kodun kendisi son derece verimsizdir ve genel olarak tüm yaklaşım,
terapi

2
Bir downloader orta eşya kullanmak gibi SO üzerinde gördüğüm diğer herhangi bir çözüm çok daha verimli yapar böylece sayfa için sadece bir istek yapılır .. çok korkunç ise neden daha iyi bir çözüm ile gelip paylaşmıyorsunuz kaba bir şekilde tek taraflı iddialarda bulunmak. "Terapi ile ilgili değil" bir şey mi içiyorsun? Bazı çılgın karmaşık, sağlam ve özel bir çözüm uygulamak dışında, çoğu insanın kullandığı yaklaşım budur. Tek fark, çoğu selenyum parçasını örümcek içinde uygulamak ve bu da birden fazla istekte bulunulmasına neden oluyor ...
rocktheartsm4l

10

Özel bir downloader ara katman yazılımı kullanıyordum, ancak önbellekle çalışmayı başaramadığım için bundan çok memnun değildim.

Daha iyi bir yaklaşım, özel bir indirme işleyici uygulamaktı.

Burada çalışan bir örnek var . Şöyle görünüyor:

# 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()

Sıyırıcıya "kazıyıcı" denir. Belirtilen kodu "kazıyıcı" klasörünün kökündeki handlers.py adlı bir dosyanın içine koyarsanız, settings.py dosyasına ekleyebilirsiniz:

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

Ve voilà, JS DOM'u, önbellek önbelleği, yeniden deneme vb.Ile ayrıştırdı.


Bu çözümü seviyorum!
rocktheartsm4l

Güzel çözüm. Selenium sürücüsü hala tek seçenek mi?
Motheus

Harika bir çözüm. Çok teşekkürler.
19'da CrazyGeek

4

Bu dinamik verileri kullanabilmem için scrapy nasıl kullanılabilir?

Neden hiç kimsenin çözümü sadece Scrapy kullanarak yayınlamadığını merak ediyorum.

Scrapy ekibi SCRAPING INFINITE SCROLLING PAGES blog bloguna göz atın . Örnek , sonsuz kaydırma kullanan http://spidyquotes.herokuapp.com/scroll web sitesini not eder .

Fikir tarayıcınızın Geliştirici Araçlarını kullanmak ve AJAX isteklerini fark etmektir, daha sonra bu bilgilere dayanarak Scrapy isteklerini oluşturur .

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)

Yine aynı sorunla karşı karşıyayız: Scrappy bu amaç için yapılmıyor ve burada aynı sorunla karşı karşıya kalıyoruz. PhantomJS'ye geçin veya başkalarının önerdiği gibi, kendi indirme ara katmanınızı oluşturun
rak007

@ rak007 PhantomJS ve Chrome sürücüsü. Hangisini önerirsiniz?
Chankey Pathak

2

evet, Scrapy javaScript aracılığıyla oluşturulan dinamik web sitelerini, web sitelerini kaldırabilir.

Bu tür web sitelerini taramak için iki yaklaşım vardır.

İlk,

kullanabileceğiniz splashJavaScript kodu işlemek ve sonra işlenen HTML ayrıştırmak. doc ve projeyi burada bulabilirsiniz Scrapy splash, git

İkinci,

Herkesin belirttiği gibi network calls, evet'i izleyerek, verileri getiren api çağrısını bulabilirsiniz ve terapi örümceğinizdeki çağrıyı yapmak istediğiniz verileri elde etmenize yardımcı olabilir.


1

Ajax isteğini Selenium ve Firefox web sürücüsünü kullanarak hallederim. Tarayıcıya bir daemon olarak ihtiyacınız varsa o kadar hızlı değil, ancak herhangi bir manuel çözümden çok daha iyi. Burada referans için kısa bir eğitim yazdım

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.