2014-05-14 12 views
5

Ho lavorato con Scrapy ma ho avuto un po 'di problemi.Come aggiornare DjangoItem in Scrapy

DjangoItem ha un metodo save per mantenere gli elementi utilizzando l'ORM Django. Questo è grandioso, tranne che se eseguo un raschietto più volte, nuovi elementi verranno creati nel database anche se potrei voler semplicemente aggiornare un valore precedente.

Dopo aver consultato la documentazione e il codice sorgente, non vedo alcun mezzo per aggiornare gli elementi esistenti.

So che potrei chiamare l'ORM per vedere se un elemento esiste e aggiornarlo, ma significherebbe chiamare il database per ogni singolo oggetto e poi di nuovo per salvare l'oggetto.

Come posso aggiornare gli articoli se già esistono?

+0

se l'applicazione deve verificare la presenza di un oggetto duplicato, deve verificare gli oggetti già creati. Se la tua struttura db può supportare una colonna univoca, puoi provare a scrivere, se c'è un errore di integrità rispetto alla chiave univoca, potresti aggiornare – dm03514

+0

Se nessun altro scrive nel tuo database, puoi interrogare il database all'avvio (in ' __init__' del tuo spider o catturando 'spider_opened' in alcuni middleware per esempio) e mantieni un elenco di ID o un elenco di tuple che rappresentano i tuoi articoli db. quindi, quando si dispone di un elemento da salvare o aggiornare, si controlla l'elenco per sapere quale operazione eseguire –

+0

paultrmbrth: Sembra che funzioni. Stavo guardando [questo bit] (http://doc.scrapy.org/en/latest/topics/item-pipeline.html#duplicates-filter) sul rilevamento dei duplicati nella pipeline, ma sfortunatamente copre solo i duplicati di all'interno della corrente raschiatura. dm03514: Potrebbe funzionare anche, ma la sfida è quindi eseguire query sul database per i duplicati. – NT3RP

risposta

10

Sfortunatamente, il modo migliore che ho trovato per fare questo è quello di fare esattamente quello che è stato dichiarato: Verificare se l'elemento esiste nel database utilizzando django_model.objects.get, quindi aggiornarlo se lo fa.

Nel mio file di impostazioni, ho aggiunto il nuovo gasdotto:

ITEM_PIPELINES = { 
    # ... 
    # Last pipeline, because further changes won't be saved. 
    'apps.scrapy.pipelines.ItemPersistencePipeline': 999 
} 

ho creato alcuni metodi di supporto per gestire il lavoro di creazione del modello di oggetto, e la creazione di uno nuovo se necessario:

def item_to_model(item): 
    model_class = getattr(item, 'django_model') 
    if not model_class: 
     raise TypeError("Item is not a `DjangoItem` or is misconfigured") 

    return item.instance 


def get_or_create(model): 
    model_class = type(model) 
    created = False 

    # Normally, we would use `get_or_create`. However, `get_or_create` would 
    # match all properties of an object (i.e. create a new object 
    # anytime it changed) rather than update an existing object. 
    # 
    # Instead, we do the two steps separately 
    try: 
     # We have no unique identifier at the moment; use the name for now. 
     obj = model_class.objects.get(name=model.name) 
    except model_class.DoesNotExist: 
     created = True 
     obj = model # DjangoItem created a model for us. 

    return (obj, created) 


def update_model(destination, source, commit=True): 
    pk = destination.pk 

    source_dict = model_to_dict(source) 
    for (key, value) in source_dict.items(): 
     setattr(destination, key, value) 

    setattr(destination, 'pk', pk) 

    if commit: 
     destination.save() 

    return destination 

Poi, la pipeline finale è abbastanza semplice:

class ItemPersistencePipeline(object): 
    def process_item(self, item, spider): 
     try: 
      item_model = item_to_model(item) 
     except TypeError: 
      return item 

     model, created = get_or_create(item_model) 

     update_model(model, item_model) 

     return item 
1

per i modelli correlati con chiavi estere

def update_model(destination, source, commit=True): 
    pk = destination.pk 

    source_fields = fields_for_model(source) 
    for key in source_fields.keys(): 
     setattr(destination, key, getattr(source, key)) 

    setattr(destination, 'pk', pk) 

    if commit: 
     destination.save() 

    return destination