[javascript] AJAX를 사용하는 웹 사이트에서 동적 콘텐츠를 스크 레이 핑하는 데 scrapy를 사용할 수 있습니까?

나는 최근에 파이썬을 배우고 있으며 웹 스크레이퍼를 만드는 데 손을 내밀고 있습니다. 전혀 공상이 아닙니다. 그것의 유일한 목적은 도박 웹 사이트에서 데이터를 가져 와서이 데이터를 Excel에 넣는 것입니다.

대부분의 문제는 해결할 수 있으며 나는 약간의 혼란을 겪고 있습니다. 그러나 나는 한 가지 문제에 대해 큰 장애물을 치고 있습니다. 사이트가 말 테이블을로드하고 현재 베팅 가격을 표시하는 경우이 정보는 소스 파일에 없습니다. 단서는이 데이터가 때때로 존재하며 일부 원격 서버에서 숫자가 업데이트된다는 것입니다. 내 PC의 HTML에는 서버가 필요한 모든 흥미로운 데이터를 푸시하는 구멍이 있습니다.

이제 동적 웹 콘텐츠에 대한 내 경험이 낮으므로이 문제는 고개를 끄는 데 어려움을 겪고 있습니다.

Java 또는 Javascript가 핵심이라고 생각합니다.이 팝업이 자주 나타납니다.

스크레이퍼는 단순히 확률 비교 엔진입니다. 일부 사이트에는 API가 있지만 그렇지 않은 사이트에는 필요합니다. Python 2.7에서 scrapy library를 사용하고 있습니다.

이 질문이 너무 개방적인 경우 사과드립니다. 요컨대, 내 질문은 : 어떻게 동적 데이터를 스크랩하는 데 scrapy를 사용하여 사용할 수 있습니까? 이 베팅 배당률 데이터를 실시간으로 긁을 수 있습니까?



답변

웹킷 기반 브라우저 (예 : Chrome 또는 Safari)에는 내장 개발자 도구가 있습니다. Chrome에서는 열 수 Menu->Tools->Developer Tools있습니다. 이 Network탭에서는 모든 요청 및 응답에 대한 모든 정보를 볼 수 있습니다.

여기에 이미지 설명을 입력하십시오

그림 하단에서 요청을 필터링 한 것을 볼 수 있습니다 XHR. 이는 자바 스크립트 코드로 작성된 요청입니다.

팁 : 페이지를로드 할 때마다 그림 하단에 검은 점 단추가 로그를 유지합니다.

요청 및 응답을 분석 한 후 웹 크롤러에서 이러한 요청을 시뮬레이션하고 중요한 데이터를 추출 할 수 있습니다. 대부분의 경우 HTML을 구문 분석하는 것보다 데이터를 얻는 것이 더 쉽습니다. 데이터에는 프리젠 테이션 로직이없고 Javascript 코드로 액세스 할 수 있도록 형식화되어 있기 때문입니다.

Firefox의 확장명은 firebug 입니다. 어떤 사람들은 방화범이 훨씬 강력하다고 주장하지만 웹킷의 단순함을 좋아합니다.


답변

다음은 scrapyAJAX 요청 의 간단한 예입니다 . rubin-kazan.ru 사이트를 보자 .

모든 메시지는 AJAX 요청과 함께로드됩니다. 내 목표는 이러한 속성을 모든 속성 (작성자, 날짜 등)으로 가져 오는 것입니다.

여기에 이미지 설명을 입력하십시오

웹 페이지가 AJAX 기술을 사용하기 때문에 페이지의 소스 코드를 분석 할 때 이러한 메시지를 모두 볼 수 없습니다. 그러나 Mozilla Firefox의 Firebug (또는 다른 브라우저의 동등한 도구)를 사용하여 웹 페이지에서 메시지를 생성하는 HTTP 요청을 분석 할 수 있습니다.

여기에 이미지 설명을 입력하십시오

전체 페이지를 다시로드하지 않고 메시지를 포함하는 페이지의 일부만 다시로드합니다. 이를 위해 하단의 임의의 페이지 수를 클릭합니다.

여기에 이미지 설명을 입력하십시오

메시지 본문을 담당하는 HTTP 요청을 관찰합니다.

여기에 이미지 설명을 입력하십시오

완료 후 요청 헤더를 분석합니다 (이 URL을 var 섹션의 소스 페이지에서 추출 할 것이라고 인용해야합니다. 아래 코드 참조).

여기에 이미지 설명을 입력하십시오

그리고 요청의 양식 데이터 내용 (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 파일이 있습니다.


답변

크롤링 할 때 여러 번 페이지에 렌더링되는 컨텐츠가 Javascript로 생성되므로 스크랩이 크롤링 할 수없는 문제 (예 : 아약스 요청, jQuery 크 래거)가 발생합니다.

그러나 Scrapy를 웹 테스트 프레임 워크 인 Selenium과 함께 사용하면 일반 웹 브라우저에 표시된 항목을 크롤링 할 수 있습니다.

참고할 사항 :

  • 이 기능을 사용하려면 Python 버전의 Selenium RC가 설치되어 있어야하며 Selenium을 올바르게 설정해야합니다. 또한 이것은 단지 템플릿 크롤러입니다. 당신은 사물로 훨씬 더 화려하고 진보 할 수 있지만 나는 단지 기본 아이디어를 보여주고 싶었습니다. 코드가 이제 서서 주어진 URL에 대해 두 번 요청합니다. 하나는 Scrapy에 의해 요청되고 다른 하나는 Selenium에 의해 요청됩니다. 이 문제를 해결할 수있는 방법이 있다고 확신하므로 Selenium이 하나의 요청 만 할 수는 있지만 구현을 귀찮게하지 않았으며 두 요청을 수행하면 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/


답변

다른 해결책은 다운로드 핸들러 또는 다운로드 핸들러 미들웨어를 구현하는 것입니다. ( 다운로더 미들웨어에 대한 자세한 내용 은 스크랩 문서 를 참조하십시오.) 다음은 헤드리스 팬텀 스 웹 드라이버와 함께 셀레늄을 사용하는 예제 클래스입니다.

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_MIDDLEWAREsettings.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])

이점 :
스파이더가 아닌 이러한 방식으로 구현할 때의 주요 이점은 하나의 요청 만 작성한다는 것입니다. 예를 들어 AT의 솔루션에서 : 다운로드 핸들러는 요청을 처리 한 다음 스파이더에 대한 응답을 전달합니다. 그러면 스파이더는 parse_page 함수에서 새로운 요청을합니다. 이는 동일한 콘텐츠에 대한 두 가지 요청입니다.


답변

나는 커스텀 다운로더 미들웨어를 사용하고 있었지만 캐시가 작동하지 않기 때문에 그다지 만족하지 못했습니다.

더 나은 방법은 사용자 정의 다운로드 핸들러를 구현하는 것이 었습니다.

여기에 실례가 있습니다 . 다음과 같이 보입니다 :

# 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',
}

그리고 voilà, JS는 파싱 캐시, 재시도 등으로 DOM을 파싱했습니다.


답변

scrapy를 사용하여이 동적 데이터를 스크래핑하여 어떻게 사용할 수 있습니까?

왜 아무도 Scrapy를 사용하여 솔루션을 게시하지 않았는지 궁금합니다.

Scrapy 팀 SCRAPING INFINITE SCROLLING PAGES
에서 블로그 게시물을 확인하십시오 . 이 예제 는 무한 스크롤을 사용하는 http://spidyquotes.herokuapp.com/scroll 웹 사이트를 폐기 합니다.

아이디어는 브라우저의 개발자 도구사용하고 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)


답변

예, Scrapy는 동적 웹 사이트, javaScript를 통해 렌더링되는 웹 사이트를 스크랩 할 수 있습니다.

이러한 종류의 웹 사이트를 긁는 방법에는 두 가지가 있습니다.

먼저,

splashJavascript 코드를 렌더링 한 다음 렌더링 된 HTML을 구문 분석하는 데 사용할 수 있습니다 . 여기에서 문서와 프로젝트를 찾을 수 있습니다. Scrapy splash, git

둘째,

모두가 말하고 있듯이 network calls, 예 를 모니터링하면 데이터를 가져 오는 api 호출을 찾을 수 있고 scrapy spider에서 해당 호출을 모의하면 원하는 데이터를 얻는 데 도움이 될 수 있습니다.