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


145

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

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

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

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

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

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


1
역동적이고 생생한 데이터 인이 데이터를 어떻게 얻을 수 있습니까?
Joseph

1
페이지에 자바 스크립트가있는 경우 시도해보세요
reclosedev

3
일부에서 시도 Firefox같은 확장 httpFox또는 liveHttpHeaders과 아약스 요청을 사용하는 페이지를로드합니다. Scrapy는 자동으로 Ajax 요청을 식별하지 않으므로 적절한 Ajax URL을 수동으로 검색 한 후 요청을 수행해야합니다.
Aamir Adnan

환호, 파이어 폭스 확장 기능에 재즈를 줄 것이다
조셉

수많은 오픈 소스 솔루션이 있습니다. 그러나 특히 대규모 워크로드에 대해이 작업을 쉽고 빠르게 수행하려면 SnapSearch ( snapsearch.io )를 확인하십시오 . 검색 엔진 크롤링이 필요한 JS, HTML5 및 SPA 사이트 용으로 제작되었습니다. 데모를 시도하십시오 (빈 컨텐츠가있는 경우 사이트가 실제로 본문 컨텐츠를 리턴하지 않았으며 이는 잠재적으로 301 경로 재 지정을 의미 함).
CMCDragonkai

답변:


74

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

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

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

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

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

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


141
'scrapy'라는 단어가 없다면 어떻게 대답이 될 수 있습니까?
툴킷

작동하며 파이썬에서 json 모듈을 사용하여 구문 분석하기 쉽습니다. 해결책입니다! 그것에 비해, 사람들이 제안하는 셀레늄 또는 다른 것들을 사용해보십시오. 더 두통입니다. 대체 방법이 더 복잡하다면 내가 당신에게 줄 것이지만, 여기에 해당되지 않습니다. @Toolkit
Arion_Miles

1
이것은 실제로 관련이 없습니다. 문제는 귀찮은 방법을 사용하여 동적 웹 사이트를 긁는 방법이었습니다.
E. Erfan

"도대체 어떻게 받아 들여질 수 있는가?"-실제적인 사용은 정치적 정확성을 능가하기 때문이다. 인간은 문맥을 이해합니다.
에스프레소

98

다음은 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 파일이 있습니다.


6
안녕하세요. 'url_list_gb_messages'가 무엇인지 설명해 주시겠습니까? 이해할 수 없습니다. 감사.
편광

4
이것은 확실히 더 낫습니다.
1a1a11a

1
@polarise이 코드는 re모듈 (정규 표현식)을 사용하고 문자열을 검색하고 'url_list_gb_messages="(.*)"'괄호의 내용을 동일한 이름의 변수로 분리합니다. 이것은 좋은 소개입니다 : guru99.com/python-regular-expressions-complete-tutorial.html
MGP

42

크롤링 할 때 여러 번 페이지에 렌더링되는 컨텐츠가 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/


깔끔한 솔루션! 이 스크립트를 Firefox에 연결하는 방법에 대한 팁이 있습니까? (OS는 Linux Mint입니다). "[Errno 111] 연결이 거부되었습니다."라는 메시지가 나타납니다.
Andrew

1
이 코드는 더 이상 작동하지 않습니다 selenium=3.3.1python=2.7.10셀레늄에서 셀레늄을 가져올 때 오류
benjaminz

1
셀레늄의 버전 가져 오기 문은 다음과 같습니다 from selenium import webdriver하거나 chromedriver또는 무엇이든 당신이 사용하는 일이. 문서 편집 : 문서 참조를 추가하고 끔찍한 문법을 ​​변경하십시오!
nulltron

셀레늄은 원격 제어에 따라, 셀레늄 WebDriver로 대체되었습니다 자신의 웹 사이트
rainbowsorbet

33

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

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 함수에서 새로운 요청을합니다. 이는 동일한 콘텐츠에 대한 두 가지 요청입니다.



@ rocktheartsm4l 무엇을 그냥 사용하는 잘못 process_requests, if spider.name in ['spider1', 'spider2']대신 장식의
패드

@pad 아무 문제가 없습니다. 방금 스파이더 클래스에 미들웨어라는 이름의 세트가있는 것이 더 분명하다는 것을 알았습니다. 이 방법으로 스파이더 클래스를보고 어떤 미들웨어가 실행될 것인지 정확히 알 수 있습니다. 내 프로젝트에는 많은 미들웨어가 구현되어 있었으므로 이것이 의미가 있습니다.
rocktheartsm4l 2009

이것은 끔찍한 해결책입니다. 스크래피와 관련이있을뿐만 아니라 코드 자체는 매우 비효율적 일뿐만 아니라 일반적으로 전체 접근 방식이 스크랩 인 비동기 웹 스크래핑 프레임 워크의 전체 목적을 무효화합니다.
Granitosaurus

2
다운 로더 미들웨어를 사용하면 페이지에 대한 요청이 하나만 만들어 지므로 SO에서 본 다른 솔루션보다 훨씬 효율적입니다. 끔찍한 경우 더 나은 솔루션을 찾고 공유하지 않는 이유는 무엇입니까? 솔직하게 일방적 인 주장을한다. "스크래피와 관련이 없습니다"담배를 피우고 있습니까? 복잡하고 강력하며 사용자 정의 된 솔루션을 구현하는 것 외에 이것은 대부분의 사람들이 사용하는 접근 방식입니다. 차이점은 대부분 스파이더에서 셀레늄 부분을 구현하여 여러 요청을하는 것입니다.
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',
}

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


나는이 해결책을 좋아한다!
rocktheartsm4l

좋은 해결책. Selenium 드라이버가 여전히 유일한 옵션입니까?
Motheus

훌륭한 솔루션. 고마워
CrazyGeek

4

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)

우리는 다시 같은 문제에 직면하게됩니다.이 목적을 위해 스크래피는 만들어지지 않았으며, 여기서 우리는 같은 문제에 직면하게됩니다. phantomJS로 이동하거나 다른 사람들이 제안한대로 나만의 다운로드 미들웨어
rak007 작성

@ rak007 PhantomJS와 Chrome 드라이버. 어느 쪽을 제안 하시겠습니까?
Chankey Pathak

2

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

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

먼저,

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

둘째,

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


1

Selenium과 Firefox 웹 드라이버를 사용하여 ajax 요청을 처리합니다. 크롤러가 데몬으로 필요한 경우 빠르지는 않지만 수동 솔루션보다 훨씬 좋습니다. 여기 에 참조를 위해 간단한 튜토리얼을 작성했습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.