27

Scrapyを使用して、冗長な情報を共有している可能性のあるいくつかのWebサイトをクロールしています。

スクレイプするページごとに、ページのURL、タイトル、およびHTMLコードをmongoDBに保存します。データベースでの重複を避けたいので、同様のアイテムがすでに保存されているかどうかを確認するためにパイプラインを実装します。そのような場合、私は例外を提起しDropItemます。

私の問題は、DropItem例外を発生させてアイテムをドロップするたびに、Scrapyがアイテムのコンテンツ全体をログ(stdoutまたはファイル)に表示することです。スクレイピングされた各ページのHTMLコード全体を抽出しているので、ドロップした場合は、HTMLコード全体がログに表示されます。

コンテンツを表示せずにアイテムをサイレントにドロップするにはどうすればよいですか?

お時間をいただきありがとうございます!

class DatabaseStorage(object):
    """ Pipeline in charge of database storage.

    The 'whole' item (with HTML and text) will be stored in mongoDB.
    """

    def __init__(self):
        self.mongo = MongoConnector().collection

    def process_item(self, item, spider):
        """ Method in charge of item valdation and processing. """
        if item['html'] and item['title'] and item['url']:
            # insert item in mongo if not already present
            if self.mongo.find_one({'title': item['title']}):
                raise DropItem('Item already in db')
            else:
                self.mongo.insert(dict(item))
                log.msg("Item %s scraped" % item['title'],
                    level=log.INFO, spider=spider)
        else:
            raise DropItem('Missing information on item %s' % (
                'scraped from ' + item.get('url')
                or item.get('title')))
        return item
4

6 に答える 6

24

これを行う適切な方法は、プロジェクトにカスタムLogFormatterを実装し、ドロップされたアイテムのログレベルを変更することです。

例:

from scrapy import log
from scrapy import logformatter

class PoliteLogFormatter(logformatter.LogFormatter):
    def dropped(self, item, exception, response, spider):
        return {
            'level': log.DEBUG,
            'format': logformatter.DROPPEDFMT,
            'exception': exception,
            'item': item,
        }

次に、設定ファイルで、次のようになります。

LOG_FORMATTER = 'apps.crawler.spiders.PoliteLogFormatter'

「None」を返すだけで運が悪かったため、将来のパイプラインで例外が発生しました。

于 2014-03-11T18:14:16.107 に答える
20

最近のScrapyバージョンでは、これが少し変更されています。@jimmytheleafからコードをコピーし、最近のScrapyで動作するように修正しました。

import logging
from scrapy import logformatter


class PoliteLogFormatter(logformatter.LogFormatter):
    def dropped(self, item, exception, response, spider):
        return {
            'level': logging.INFO,
            'msg': logformatter.DROPPEDMSG,
            'args': {
                'exception': exception,
                'item': item,
            }
        }
于 2015-12-16T19:56:44.277 に答える
12

わかりました。質問を投稿する前に答えを見つけました。私はまだ同じ問題を抱えている人にとって答えは価値があるかもしれないと思います。

例外を除いてオブジェクトをドロップする代わりにDropItem、None値を返す必要があります。

def process_item(self, item, spider):
    """ Method in charge of item valdation and processing. """
    if item['html'] and item['title'] and item['url']:
        # insert item in mongo if not already present
        if self.mongo.find_one({'url': item['url']}):
            return
        else:
            self.mongo.insert(dict(item))
            log.msg("Item %s scraped" % item['title'],
                level=log.INFO, spider=spider)
    else:
        raise DropItem('Missing information on item %s' % (
           'scraped from ' + item.get('url')
            or item.get('title')))
    return item
于 2012-11-23T11:13:06.907 に答える
1

この問題の別の解決策は、サブクラスreprのメソッドを調整することですscrapy.Item

class SomeItem(scrapy.Item):
    scrape_date = scrapy.Field()
    spider_name = scrapy.Field()
    ...

    def __repr__(self):
        return ""

このように、アイテムはログにまったく表示されません。

于 2019-11-21T20:52:49.137 に答える
1

Levonが前のコメントで示しているように、処理しているアイテムの__repr__関数をオーバーロードすることも可能です。

このように、メッセージはScrapyログに表示されますが、ログに表示するコードの長さ(たとえば、Webページの最初の150文字)を制御することはできません。このようなHTMLページを表すアイテムがあるとすると、__repr__のオーバーロードは次のようになります。

class MyHTMLItem(Scrapy.Item):
    url = scrapy.Field()
    htmlcode = scrapy.Field()
    [...]
    def __repr__(self):
        s = ""
        s += "URL: %s\n" % self.get('URL')
        s += "Code (chunk): %s\n" % ((self.get('htmlcode'))[0:100])
        return s
于 2020-03-10T16:34:49.973 に答える
0

私の場合、ItemAdapterを使用してItemパラメーターをリストに変換する必要がありました。そのため、データベースにクエリを実行することができました。

from itemadapter import ItemAdapter, adapter
import pymongo
from scrapy.exceptions import DropItem

collection_name = 'myCollection'
    
    def __init__(self, mongo_uri, mongo_db):
        self.mongo_uri = mongo_uri
        self.mongo_db = mongo_db
    @classmethod
    def from_crawler(cls, crawler):
        return cls(
            mongo_uri=crawler.settings.get('MONGO_URI'),
            mongo_db=crawler.settings.get('MONGO_DATABASE', 'items')
        )

def open_spider(self, spider):
    self.client = pymongo.MongoClient(self.mongo_uri)
    self.db = self.client[self.mongo_db]

def close_spider(self, spider):
    self.client.close()

def process_item(self, item, spider):
    adapter = ItemAdapter(item)
    if self.db[self.collection_name].find_one({'id':adapter['id']}) != None:
        dado = self.db[self.collection_name].find_one_and_update({'id':adapter['id']})
        ## ----> raise DropItem(f"Duplicate item found: {item!r}") <------
        print(f"Duplicate item found: {dado!r}")
    else:
        self.db[self.collection_name].insert_one(ItemAdapter(item).asdict())
    return item
于 2021-12-27T18:53:37.027 に答える