it-swarm-pt.tech

O scrapy pode ser usado para coletar conteúdo dinâmico de sites que usam AJAX?

Eu tenho aprendido recentemente Python e estou mergulhando minha mão na construção de um web-scraper. Não é nada extravagante; Seu único objetivo é obter os dados de um site de apostas e colocar esses dados no Excel.

A maioria dos problemas é solucionável e estou tendo uma boa bagunça por aí. No entanto, estou atingindo um enorme obstáculo em relação a um problema. Se um site carregar uma tabela de cavalos e listar os preços de apostas atuais, essas informações não estarão em nenhum arquivo de origem. A pista é que esses dados são ao vivo, às vezes, com os números sendo atualizados, obviamente, de algum servidor remoto. O HTML no meu PC simplesmente tem um buraco onde seus servidores estão empurrando todos os dados interessantes que eu preciso.

Agora, minha experiência com conteúdo dinâmico da web é baixa, então isso é algo que estou tendo dificuldade em entender minha opinião. 

Eu acho que Java ou Javascript é uma chave, isso aparece com freqüência. 

O raspador é simplesmente um mecanismo de comparação de probabilidades. Alguns sites têm APIs, mas eu preciso disso para quem não tem. Eu estou usando a biblioteca scrapy com Python 2.7

Eu peço desculpas se esta questão é muito aberta. Em suma, minha pergunta é: como o scrapy pode ser usado para copiar esses dados dinâmicos para que eu possa usá-los? Para que eu possa copiar esses dados de probabilidades de apostas em tempo real?

124
Joseph

Os navegadores baseados em Webkit (como o Google Chrome ou o Safari) possuem ferramentas de desenvolvedor integradas. No Chrome, você pode abri-lo Menu->Tools->Developer Tools. A guia Network permite que você veja todas as informações sobre cada solicitação e resposta:

enter image description here

Na parte inferior da imagem, você pode ver que filtrou a solicitação até XHR - essas são solicitações feitas por código javascript.

Dica: o log é apagado toda vez que você carrega uma página, na parte inferior da imagem, o botão de ponto preto preservará o log.

Depois de analisar solicitações e respostas, você pode simular essas solicitações do seu rastreador da Web e extrair dados valiosos. Em muitos casos, será mais fácil obter seus dados do que analisar HTML, porque esses dados não contêm lógica de apresentação e são formatados para serem acessados ​​por código javascript.

Firefox tem extensão similar, é chamado firebug . Alguns argumentam que o firebug é ainda mais poderoso, mas eu gosto da simplicidade do webkit.

74
Ski

Aqui está um exemplo simples de usar scrapy com pedido ajax. Vamos ver o site http://www.rubin-kazan.ru/guestbook.html Todas as mensagens são carregadas com um pedido de ajax. Meu objetivo é buscar essas mensagens com todos os seus atributos (autor, data, ...).

enter image description here

Quando analiso o código-fonte da página, não consigo ver todas essas mensagens porque a página da Web usa a tecnologia ajax. Mas eu posso com o Firebug do Mozila Firefox (ou um instrumento de analogia em outro navegador) para analisar a requisição Http que gera as mensagens na página web. enter image description here

Para isso, não recarrego toda a página, mas apenas a parte da página que contém mensagens. Para este efeito eu clico em um número arbitrário de página na parte inferior enter image description heree eu observo a solicitação HTTP que é responsável pelo corpo da mensagem enter image description here

Após o término analiso os cabeçalhos de solicitação (devo citar que esta url vou extrair da página de origem da seção var, veja o código abaixo). enter image description here

e o conteúdo dos dados do formulário de solicitação (o método Http é "Post")

enter image description here

e o conteúdo da resposta, que é um arquivo Json,

enter image description here

que apresentam todas as informações que estou procurando.

A partir de agora, devo implementar todo esse conhecimento em escassez. Vamos definir a aranha para este propósito.

  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

Na função de análise, tenho a resposta para o primeiro pedido. No RubiGuessItem, eu tenho o arquivo json com todas as informações. 

82
Badarau Petru

Muitas vezes, ao rastrear, nos deparamos com problemas em que o conteúdo que é renderizado na página é gerado com Javascript e, portanto, não é possível rastreá-lo (por exemplo, solicitações ajax, jQuery craziness).

No entanto, se você usar o Scrapy junto com a estrutura de teste da Web Selenium, poderemos rastrear qualquer coisa exibida em um navegador da web normal.

Algumas coisas para anotar:

  • Você deve ter a versão Python do Selenium RC instalada para que isso funcione e você deve ter configurado o Selenium corretamente. Além disso, este é apenas um rastreador de modelo. Você poderia ficar muito mais louco e mais avançado com as coisas, mas eu só queria mostrar a ideia básica. Como o código está agora, você fará duas solicitações para qualquer URL. Um pedido é feito por Scrapy e o outro é feito por Selenium. Tenho certeza de que existem maneiras de contornar isso, para que você possa fazer o Selenium fazer a primeira e única requisição, mas eu não me preocupei em implementar isso e fazendo dois pedidos, você também pode rastrear a página com o Scrapy.

  • Isso é muito poderoso porque agora você tem todo o DOM renderizado disponível para você rastrear e ainda pode usar todos os recursos de rastreamento do Nice em Scrapy. Isso fará com que o rastreamento seja mais lento, mas dependendo de quanto você precisa do DOM renderizado, pode valer a pena esperar.

    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
    

Referência: http://snipplr.com/view/66998/

35
A T

Outra solução seria implementar um manipulador de download ou um middleware de manipulador de download. A seguir, um exemplo de middleware que usa o Selenium com o webdriver phantomjs headless:

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

Eu queria poder dizer às diferentes aranhas que middleware usar, então eu implementei este wrapper:

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

settings.py:

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

para o wrapper funcionar, todas as aranhas devem ter no mínimo:

middleware = set([])

para incluir um middleware:

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

A principal vantagem de implementá-lo desta maneira, e não na aranha, é que você só acaba fazendo uma solicitação. Na solução de A T, por exemplo: O manipulador de download processa a solicitação e, em seguida, entrega a resposta à aranha. A aranha faz uma nova solicitação na função parse_page - São duas solicitações para o mesmo conteúdo.

24
rocktheartsm4l

Eu estava usando um middleware de downloader personalizado, mas não estava muito feliz com isso, pois não consegui fazer o cache funcionar com ele.

Uma abordagem melhor era implementar um manipulador de download personalizado.

Há um exemplo de trabalho aqui . Se parece com isso:

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

Suponha que o seu raspador seja chamado de "raspador". Se você colocar o código mencionado dentro de um arquivo chamado handlers.py na raiz da pasta "scraper", você poderá adicionar ao seu settings.py:

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

E voilà, o JS analisou DOM, com cache escasso, tentativas, etc.

6
Ivan Chaer

como o scrapy pode ser usado para copiar esses dados dinâmicos para que eu possa usar isso?

Eu me pergunto por que ninguém postou a solução usando somente Scrapy. 

Confira a postagem no blog da equipe Scrapy SCRAPING INFINITE SCROLLING PAGES . O exemplo recorta http://spidyquotes.herokuapp.com/scroll website que usa rolagem infinita. 

A idéia é usar as Ferramentas do Desenvolvedor do seu navegador e observar as solicitações AJAX e, então, com base nessas informações, criar as solicitações para o 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)
1
Chankey Pathak

Eu lido com o pedido de ajax usando o Selenium e o driver da web do Firefox. Não é tão rápido se você precisar do rastreador como um daemon, mas muito melhor do que qualquer solução manual. Eu escrevi um pequeno tutorial aqui para referência

1
narko

sim, o Scrapy pode remover sites dinâmicos, que são renderizados por meio de javaScript.

Existem duas abordagens para scrapy esses tipos de sites.

Primeiro,

você pode usar splash para renderizar código Javascript e então analisar o HTML renderizado. você pode encontrar o documento e o projeto aqui Scrapy splash, git

Segundo, 

Como todos estão dizendo, monitorando o network calls, sim, você pode encontrar a chamada de api que busca os dados e zombar dessa chamada em sua aranha escamosa que pode ajudá-lo a obter os dados desejados.

0
ThunderMind