2012-12-05 20 views
33

Sono un principiante di scrapy ed è un incredibile crawler framework che conosco!Come ottenere gli URL di errore scrapy?

Nel mio progetto, ho inviato più di 90.000 richieste, ma alcune di esse sono fallite. Ho impostato il livello di registro in INFO, e posso solo vedere alcune statistiche ma nessun dettaglio.

2012-12-05 21:03:04+0800 [pd_spider] INFO: Dumping spider stats: 
{'downloader/exception_count': 1, 
'downloader/exception_type_count/twisted.internet.error.ConnectionDone': 1, 
'downloader/request_bytes': 46282582, 
'downloader/request_count': 92383, 
'downloader/request_method_count/GET': 92383, 
'downloader/response_bytes': 123766459, 
'downloader/response_count': 92382, 
'downloader/response_status_count/200': 92382, 
'finish_reason': 'finished', 
'finish_time': datetime.datetime(2012, 12, 5, 13, 3, 4, 836000), 
'item_scraped_count': 46191, 
'request_depth_max': 1, 
'scheduler/memory_enqueued': 92383, 
'start_time': datetime.datetime(2012, 12, 5, 12, 23, 25, 427000)} 

C'è un modo per ottenere un rapporto più dettagliato? Ad esempio, mostra gli URL non riusciti. Grazie!

risposta

42

Sì, questo è possibile.

Ho aggiunto un elenco failed_urls alla mia classe spider e gli ho aggiunto degli URL se lo stato della risposta era 404 (questo dovrà essere esteso per coprire altri stati di errore).

Quindi ho aggiunto un handle che unisce l'elenco in una stringa singola e lo aggiunge alle statistiche quando lo spider viene chiuso.

In base ai commenti, è possibile tenere traccia degli errori di Twisted.

from scrapy.spider import BaseSpider 
from scrapy.xlib.pydispatch import dispatcher 
from scrapy import signals 

class MySpider(BaseSpider): 
    handle_httpstatus_list = [404] 
    name = "myspider" 
    allowed_domains = ["example.com"] 
    start_urls = [ 
     'http://www.example.com/thisurlexists.html', 
     'http://www.example.com/thisurldoesnotexist.html', 
     'http://www.example.com/neitherdoesthisone.html' 
    ] 

    def __init__(self, category=None): 
     self.failed_urls = [] 

    def parse(self, response): 
     if response.status == 404: 
      self.crawler.stats.inc_value('failed_url_count') 
      self.failed_urls.append(response.url) 

    def handle_spider_closed(spider, reason): 
     self.crawler.stats.set_value('failed_urls', ','.join(spider.failed_urls)) 

    def process_exception(self, response, exception, spider): 
     ex_class = "%s.%s" % (exception.__class__.__module__, exception.__class__.__name__) 
     self.crawler.stats.inc_value('downloader/exception_count', spider=spider) 
     self.crawler.stats.inc_value('downloader/exception_type_count/%s' % ex_class, spider=spider) 

    dispatcher.connect(handle_spider_closed, signals.spider_closed) 

uscita (il downloader/exception_count * statistiche verranno visualizzati solo se le eccezioni sono in realtà gettati - li ho simulato cercando di eseguire il ragno dopo che avevo spento la mia scheda di rete wireless):

2012-12-10 11:15:26+0000 [myspider] INFO: Dumping Scrapy stats: 
    {'downloader/exception_count': 15, 
    'downloader/exception_type_count/twisted.internet.error.DNSLookupError': 15, 
    'downloader/request_bytes': 717, 
    'downloader/request_count': 3, 
    'downloader/request_method_count/GET': 3, 
    'downloader/response_bytes': 15209, 
    'downloader/response_count': 3, 
    'downloader/response_status_count/200': 1, 
    'downloader/response_status_count/404': 2, 
    'failed_url_count': 2, 
    'failed_urls': 'http://www.example.com/thisurldoesnotexist.html, http://www.example.com/neitherdoesthisone.html' 
    'finish_reason': 'finished', 
    'finish_time': datetime.datetime(2012, 12, 10, 11, 15, 26, 874000), 
    'log_count/DEBUG': 9, 
    'log_count/ERROR': 2, 
    'log_count/INFO': 4, 
    'response_received_count': 3, 
    'scheduler/dequeued': 3, 
    'scheduler/dequeued/memory': 3, 
    'scheduler/enqueued': 3, 
    'scheduler/enqueued/memory': 3, 
    'spider_exceptions/NameError': 2, 
    'start_time': datetime.datetime(2012, 12, 10, 11, 15, 26, 560000)} 
+0

Questo non funziona più. 'exceptions.NameError: il nome globale 'self' non è definito 'si verifica un errore. 'BaseSpider' ora è semplicemente' Spider' http://doc.scrapy.org/en/0.24/news.html?highlight = basespider # id2 https://github.com/scrapy/dirbot/blob/master/dirbot/spiders/dmoz.py ma non riesco a trovare la soluzione per far funzionare il codice ancora @Talvalin. – Mikeumus

15

Ecco un altro esempio come gestire e raccogliere errori 404 (pagine controllo github aiuto):

from scrapy.selector import HtmlXPathSelector 
from scrapy.contrib.spiders import CrawlSpider, Rule 
from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor 
from scrapy.item import Item, Field 


class GitHubLinkItem(Item): 
    url = Field() 
    referer = Field() 
    status = Field() 


class GithubHelpSpider(CrawlSpider): 
    name = "github_help" 
    allowed_domains = ["help.github.com"] 
    start_urls = ["https://help.github.com", ] 
    handle_httpstatus_list = [404] 
    rules = (Rule(SgmlLinkExtractor(), callback='parse_item', follow=True),) 

    def parse_item(self, response): 
     if response.status == 404: 
      item = GitHubLinkItem() 
      item['url'] = response.url 
      item['referer'] = response.request.headers.get('Referer') 
      item['status'] = response.status 

      return item 

basta eseguire scrapy runspider con -o output.json e vedere elenco di elementi nel file output.json.

10

Le risposte da @Talvalin e @alecxe mi hanno aiutato molto, ma non sembrano catturare eventi di downloader che non generano un oggetto risposta (ad esempio, twisted.internet.error.TimeoutError e twisted.web.http.PotentialDataLoss). Questi errori compaiono nel dump delle statistiche alla fine della corsa, ma senza alcuna meta informazione.

Come ho scoperto here, gli errori sono tracciati dal Stats.py middleware, catturato nella classe DownloaderStatsprocess_exception metodo, e precisamente nella variabile ex_class, che incrementa tipo necessarie ogni errore, e quindi discariche i conteggi al fine della corsa.

per abbinare tali errori con le informazioni dall'oggetto richiesta corrispondente, può aggiungere meta informazioni per ogni richiesta (via request.meta), successivamente spingerla nel metodo process_exception di Stats.py:

self.stats.set_value('downloader/my_errs/%s' % request.meta, ex_class) 

che genererà una stringa univoca per ogni errore di questo tipo.Può salvare il alterato Stats.py come Mystats.py, aggiungerlo al middleware (con la precedenza a destra), e disattivare il regolare Stats.py:

DOWNLOADER_MIDDLEWARES = { 
    'myproject.mystats.MyDownloaderStats': 850, 
    'scrapy.downloadermiddleware.stats.DownloaderStats': None, 
    } 

L'uscita alla fine della corsa si presenta così (qui utilizzando meta-informazioni dove URL/richieste vengono mappati a numero intero a base di meta str di groupID/memberID, come '0/14'):

offerte
{'downloader/exception_count': 3, 
'downloader/exception_type_count/twisted.web.http.PotentialDataLoss': 3, 
'downloader/my_errs/0/1': 'twisted.web.http.PotentialDataLoss', 
'downloader/my_errs/0/38': 'twisted.web.http.PotentialDataLoss', 
'downloader/my_errs/0/86': 'twisted.web.http.PotentialDataLoss', 
'downloader/request_bytes': 47583, 
'downloader/request_count': 133, 
'downloader/request_method_count/GET': 133, 
'downloader/response_bytes': 3416996, 
'downloader/response_count': 130, 
'downloader/response_status_count/200': 95, 
'downloader/response_status_count/301': 24, 
'downloader/response_status_count/302': 8, 
'downloader/response_status_count/500': 3, 
'finish_reason': 'finished'....} 

This answer con errori non a base di downloader.

+0

Esattamente quello che sto cercando. Penso che Scrapy dovrebbe aggiungere questa funzionalità per fornire un comodo accesso alle informazioni di errore come l'URL. – wlnirvana

+1

Usa 'scrapy.downloadermiddlewares.stats' invece di deprecato sull'ultima versione (1.0.5)' scrapy.contrib.downloadermiddleware.stats' –

+0

@ElRuso grazie - ho aggiornato la risposta – bahmait

6

A partire da scrapy 0.24.6, il metodo suggerito da alecxe non rileva errori con gli URL di avvio. Per registrare gli errori con gli URL di inizio è necessario eseguire l'override di parse_start_urls. Adattando la risposta di alexce per questo scopo, otterresti:

from scrapy.selector import HtmlXPathSelector 
from scrapy.contrib.spiders import CrawlSpider, Rule 
from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor 
from scrapy.item import Item, Field 

class GitHubLinkItem(Item): 
    url = Field() 
    referer = Field() 
    status = Field() 

class GithubHelpSpider(CrawlSpider): 
    name = "github_help" 
    allowed_domains = ["help.github.com"] 
    start_urls = ["https://help.github.com", ] 
    handle_httpstatus_list = [404] 
    rules = (Rule(SgmlLinkExtractor(), callback='parse_item', follow=True),) 

    def parse_start_url(self, response): 
     return self.handle_response(response) 

    def parse_item(self, response): 
     return self.handle_response(response) 

    def handle_response(self, response): 
     if response.status == 404: 
      item = GitHubLinkItem() 
      item['url'] = response.url 
      item['referer'] = response.request.headers.get('Referer') 
      item['status'] = response.status 

      return item 
5

Questo è un aggiornamento su questa domanda. Mi sono imbattuto in un problema simile e avevo bisogno di usare i segnali di scrapy per chiamare una funzione nella mia pipeline. Ho modificato il codice di @ Talvalin, ma volevo dare una risposta solo per maggiore chiarezza.

Fondamentalmente, è necessario aggiungere in se come argomento per handle_spider_closed. Si dovrebbe anche chiamare il dispatcher in init in modo da poter passare l'istanza spider (self) al metodo di gestione.

from scrapy.spider import Spider 
from scrapy.xlib.pydispatch import dispatcher 
from scrapy import signals 

class MySpider(Spider): 
    handle_httpstatus_list = [404] 
    name = "myspider" 
    allowed_domains = ["example.com"] 
    start_urls = [ 
     'http://www.example.com/thisurlexists.html', 
     'http://www.example.com/thisurldoesnotexist.html', 
     'http://www.example.com/neitherdoesthisone.html' 
    ] 

    def __init__(self, category=None): 
     self.failed_urls = [] 
     # the dispatcher is now called in init 
     dispatcher.connect(self.handle_spider_closed,signals.spider_closed) 


    def parse(self, response): 
     if response.status == 404: 
      self.crawler.stats.inc_value('failed_url_count') 
      self.failed_urls.append(response.url) 

    def handle_spider_closed(self, spider, reason): # added self 
     self.crawler.stats.set_value('failed_urls',','.join(spider.failed_urls)) 

    def process_exception(self, response, exception, spider): 
     ex_class = "%s.%s" % (exception.__class__.__module__, exception.__class__.__name__) 
     self.crawler.stats.inc_value('downloader/exception_count', spider=spider) 
     self.crawler.stats.inc_value('downloader/exception_type_count/%s' % ex_class, spider=spider) 

Spero che questo aiuti chiunque con lo stesso problema in futuro.

8

Scrapy ignora 404 per impostazione predefinita e non analizza. Per gestire l'errore 404 farlo. Questo è molto facile, se hai trovato il codice di errore 404 in risposta, è possibile gestire questo è con modo molto semplice ..... nelle impostazioni scrivere

HTTPERROR_ALLOWED_CODES = [404,403] 

e quindi gestire il codice di stato di risposta nella tua funzione di analisi.

def parse(self,response): 
    if response.status == 404: 
     #your action on error 

nelle impostazioni e ottenere la risposta in funzione di parsing

0

Oltre ad alcune di queste risposte, se si desidera tenere traccia di errori torti, vorrei dare un'occhiata al utilizzando il parametro dell'oggetto Request errback, su cui è possibile impostare una funzione di richiamata da chiamare con lo Twisted Failure in caso di errore di una richiesta. Oltre all'url, questo metodo consente di tracciare il tipo di errore.

È possibile quindi accedere gli URL utilizzando: failure.request.url (dove failure è l'oggetto Failure ritorto passato in errback).

# these would be in a Spider 
def start_requests(self): 
    for url in self.start_urls: 
     yield scrapy.Request(url, callback=self.parse, 
            errback=self.handle_error) 

def handle_error(self, failure): 
    url = failure.request.url 
    logging.error('Failure type: %s, URL: %s', failure.type, 
               url) 

La documentazione Scrapy invia un esempio completo di come questo può essere fatto, se non che le chiamate al logger Scrapy sono ora depreciated, così ho adattato il mio esempio per utilizzare Python costruito nel logging):

https://doc.scrapy.org/en/latest/topics/request-response.html#topics-request-response-ref-errbacks

Problemi correlati