2016-09-15 3 views
0

데이터 스크래핑이 필요한 기존 스크립트 (main.py)가 있습니다.항목 생성기로 scrapy 사용

이 데이터를 검색하기위한 치료 프로젝트를 시작했습니다. 이제는 item.py가 항목 파이프 라인을 사용하여 데이터를 지속하는 대신 항목 생성기로 데이터를 검색 할 수있는 방법이 있습니까?

이와 같은 것은 정말 편리 할 것입니다. 그러나 실현 가능성이있는 경우 어떻게해야하는지 알 수 없었습니다. 멀티 스레딩의 큐를 사용하여, https://tryolabs.com/blog/2011/09/27/calling-scrapy-python-script/ :

for item in scrapy.process(): 

나는 거기에 잠재적 인 해결책을 발견.

이 동작이 분산 크롤링 (Scrapy가 의도 한 것)과 호환되지 않는다고 생각하지만, 소규모 프로젝트에서이 기능을 사용할 수 없다는 사실에 대해서는 아직 조금 놀랐습니다.

+0

심각한 해킹이 없으면 main.py를 비동기로 사용해야 할 방법이 없습니다. 왜'scrap crawl myspider -o items.json' 파일을 크롤링하지 않고'main.py' 파일에서 반복합니다. 또는 이상적으로 전체 main.py 로직을 거미 자체로 옮기시겠습니까? – Granitosaurus

답변

0

json 데이터를 크롤러에서 보내고 결과를 가져올 수 있습니다. 다음과 같이 수행 할 수 있습니다 :

class MySpider(scrapy.Spider): 
    # some attributes 
    accomulated=[] 

    def parse(self, response): 
     # do your logic here 
     page_text = response.xpath('//text()').extract() 
     for text in page_text: 
      if conditionsAreOk(text): 
       self.accomulated.append(text) 

    def closed(self, reason): 
     # call when the crawler process ends 
     print JSON.dumps(self.accomulated) 

이 같은 runner.py 스크립트를 작성 :

import sys 
from twisted.internet import reactor 

import scrapy 

from scrapy.crawler import CrawlerRunner 
from scrapy.utils.log import configure_logging  
from scrapy.utils.project import get_project_settings 

from spiders import MySpider 

def main(argv): 

    url = argv[0] 

    configure_logging({'LOG_FORMAT': '%(levelname)s: %(message)s', 'LOG_ENABLED':False }) 
    runner = CrawlerRunner(get_project_settings()) 

    d = runner.crawl(MySpider, url=url) 

    # For Multiple in the same process 
    # 
    # runner.crawl('craw') 
    # runner.crawl('craw2') 
    # d = runner.join() 

    d.addBoth(lambda _: reactor.stop()) 
    reactor.run() # the script will block here until the crawling is finished 


if __name__ == "__main__": 
    main(sys.argv[1:]) 

그리고 같은 main.py에서 호출 :

는 거미를 갖는

import json, subprocess, sys, time 

def main(argv): 

    # urlArray has http:// or https:// like urls 
    for url in urlArray:  
     p = subprocess.Popen(['python', 'runner.py', url ], stdout=subprocess.PIPE, stderr=subprocess.PIPE) 
     out, err = p.communicate() 

     # do something with your data 
     print out 
     print json.loads(out) 

     # This just helps to watch logs 
     time.sleep(0.5) 


if __name__ == "__main__": 
    main(sys.argv[1:]) 

이것은 알고있는 것처럼 Scrapy를 사용하는 최선의 방법은 아니지만 복잡한 후 처리가 필요없는 빠른 결과를 위해이 솔루션은 필요한 것을 제공 할 수 있습니다.

도움이되기를 바랍니다.

+0

감사합니다! 하위 프로세스는 두 모듈간에 데이터를 전송하는 한 가지 방법이며 지속하지 않아도됩니다. 이 경우 스트림과 같은 동작을 위해 JSON을 구문 분석 함수로 덤핑 할 수 있습니까? (또는 전용 아이템 파이프 라인). 당신의 해결책은 발전기처럼 행동하는 것이 아니라 크롤링이 완료 될 때까지 기다려야하기 때문입니다. – bsuire

+0

예, 직렬 크롤링이 아닌 경우 [파이프 라인] (http://doc.scrapy.org/en/latest/topics/item-pipeline.html?highlight=pipelines)이 필요합니다. – Kruser

0

당신은 트위스트 또는 토네이도 응용 프로그램에서이 방법을 수행 할 수 있습니다

import collections 

from twisted.internet.defer import Deferred 
from scrapy.crawler import Crawler 
from scrapy import signals 


def scrape_items(crawler_runner, crawler_or_spidercls, *args, **kwargs): 
    """ 
    Start a crawl and return an object (ItemCursor instance) 
    which allows to retrieve scraped items and wait for items 
    to become available. 

    Example: 

    .. code-block:: python 

     @inlineCallbacks 
     def f(): 
      runner = CrawlerRunner() 
      async_items = scrape_items(runner, my_spider) 
      while (yield async_items.fetch_next): 
       item = async_items.next_item() 
       # ... 
      # ... 

    This convoluted way to write a loop should become unnecessary 
    in Python 3.5 because of ``async for``. 
    """ 
    # this requires scrapy >= 1.1rc1 
    crawler = crawler_runner.create_crawler(crawler_or_spidercls) 
    # for scrapy < 1.1rc1 the following code is needed: 
    # crawler = crawler_or_spidercls 
    # if not isinstance(crawler_or_spidercls, Crawler): 
    # crawler = crawler_runner._create_crawler(crawler_or_spidercls) 

    d = crawler_runner.crawl(crawler, *args, **kwargs) 
    return ItemCursor(d, crawler) 


class ItemCursor(object): 
    def __init__(self, crawl_d, crawler): 
     self.crawl_d = crawl_d 
     self.crawler = crawler 

     crawler.signals.connect(self._on_item_scraped, signals.item_scraped) 

     crawl_d.addCallback(self._on_finished) 
     crawl_d.addErrback(self._on_error) 

     self.closed = False 
     self._items_available = Deferred() 
     self._items = collections.deque() 

    def _on_item_scraped(self, item): 
     self._items.append(item) 
     self._items_available.callback(True) 
     self._items_available = Deferred() 

    def _on_finished(self, result): 
     self.closed = True 
     self._items_available.callback(False) 

    def _on_error(self, failure): 
     self.closed = True 
     self._items_available.errback(failure) 

    @property 
    def fetch_next(self): 
     """ 
     A Deferred used with ``inlineCallbacks`` or ``gen.coroutine`` to 
     asynchronously retrieve the next item, waiting for an item to be 
     crawled if necessary. Resolves to ``False`` if the crawl is finished, 
     otherwise :meth:`next_item` is guaranteed to return an item 
     (a dict or a scrapy.Item instance). 
     """ 
     if self.closed: 
      # crawl is finished 
      d = Deferred() 
      d.callback(False) 
      return d 

     if self._items: 
      # result is ready 
      d = Deferred() 
      d.callback(True) 
      return d 

     # We're active, but item is not ready yet. Return a Deferred which 
     # resolves to True if item is scraped or to False if crawl is stopped. 
     return self._items_available 

    def next_item(self): 
     """Get a document from the most recently fetched batch, or ``None``. 
     See :attr:`fetch_next`. 
     """ 
     if not self._items: 
      return None 
     return self._items.popleft() 

주요 아이디어는 item_scraped 신호를 듣고, 다음 더 좋은 API를 사용하여 객체로 래핑하는 것입니다.

이 기능을 사용하려면 main.py 스크립트에 이벤트 루프가 필요합니다. 위의 예는 twisted.defer.inlineCallbacks 또는 tornado.gen.coroutine에서 작동합니다.