2016-09-15 15 views
0

私は、データをスクラップする必要がある既存のスクリプト(main.py)を持っています。アイテムジェネレータとしての治療を使用

私はこのデータを取得するための治療プロジェクトを開始しました。さて、item.pyを使ってデータを永続化するのではなく、アイテムジェネレータとしてデータを検索する方法はありますか?

このようなものは本当に便利ですが、実現可能であれば、それを行う方法を見つけることができませんでした。

for item in scrapy.process(): 

ここには潜在的な解決策があります:https://tryolabs.com/blog/2011/09/27/calling-scrapy-python-script/、マルチスレッドのキューを使用しています。

この動作は、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

ありがとう!サブプロセスは、2つのモジュール間でデータを送信するための1つの方法であり、永続化する必要はありません。その場合、ストリームのような振る舞いのために、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で動作します。