web-dev-qa-db-fra.com

Comment obtenir les URL d'échec de scrapy?

Je suis un débutant de scrapy et son cadre de robotique incroyable que j'ai connu! 

Dans mon projet, j'ai envoyé plus de 90 000 demandes, mais certaines ont échoué. J'ai défini le niveau de journalisation sur INFO et je peux juste voir des statistiques mais pas de détails. 

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)}

Est-il possible d'obtenir un rapport plus détaillé? Par exemple, affichez ces URL ayant échoué. Merci!

36
Joe Wu

Oui, c'est possible 

J'ai ajouté une liste failed_urls à ma classe spider et y ai ajouté des URL si le statut de la réponse était 404 (il faudra l'étendre pour couvrir d'autres statuts d'erreur). 

Ensuite, j'ai ajouté un descripteur qui rejoint la liste en une seule chaîne et l'ajoute aux statistiques lorsque l'araignée est fermée.

Sur la base de vos commentaires, il est possible de suivre les erreurs 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)

Sortie (les statistiques downloader/exception_count * n'apparaîtront que si des exceptions sont réellement levées. Je les ai simulées en essayant de lancer l'araignée après avoir désactivé mon adaptateur sans fil):

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)}
47
Talvalin

Voici un autre exemple sur la façon de traiter et de collecter 404 erreurs (en consultant les pages d’aide de github):

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

Lancez simplement scrapy runspider avec -o output.json et consultez la liste des éléments dans le fichier output.json.

15
alecxe

Les réponses de @Talvalin et @alecxe m'ont beaucoup aidé, mais elles ne semblent pas capturer les événements du téléchargeur qui ne génèrent pas d'objet de réponse (par exemple, twisted.internet.error.TimeoutError et twisted.web.http.PotentialDataLoss). Ces erreurs apparaissent dans le vidage des statistiques à la fin de l'exécution, mais sans aucune information méta. 

Comme je l'ai découvert ici , les erreurs sont suivies par le stats.py middleware, capturées dans la méthode process_exception de DownloaderStats class, et plus précisément dans la variable ex_class, qui incrémente chaque type d'erreur selon les besoins, et puis jette les comptages à la fin de la série. 

Pour faire correspondre ces erreurs aux informations de l'objet requête correspondant, vous pouvez ajouter un identifiant unique à chaque requête (via request.meta), puis l'extraire dans la méthode process_exception de stats.py:

self.stats.set_value('downloader/my_errs/{0}'.format(request.meta), ex_class)

Cela générera une chaîne unique pour chaque erreur liée au téléchargeur non accompagnée d'une réponse. Vous pouvez ensuite enregistrer le stats.py modifié sous un autre nom (par exemple, my_stats.py), l'ajouter au fichier downloadermiddlewares (avec la priorité correcte) et désactiver le stock stats.py:

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

La sortie à la fin de l'exécution ressemble à ceci (ici, en utilisant des méta-informations où chaque URL de demande est mappée sur un groupe_id et un membre_id séparés par une barre oblique, comme '0/14'):

{'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'....}

Cette réponse traite des erreurs non liées au téléchargeur.

12
scharfmn

Scrapy ignore 404 par défaut et ne pas analyser. Pour gérer l'erreur 404, faites ceci . C'est très facile, si vous obtenez le code d'erreur 404 en réponse, vous pouvez le faire très facilement ... 

HTTPERROR_ALLOWED_CODES = [404,403]

puis gérez le code d’état de la réponse dans votre fonction d’analyse.

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

dans les paramètres et obtenir une réponse dans la fonction d'analyse

9
harivans kumar

Depuis la version 0.24.6, la méthode suggérée par alecxe ne récupérera pas les erreurs avec les URL de début. Pour enregistrer les erreurs avec les URL de démarrage, vous devez remplacer parse_start_urls. En adaptant la réponse de alexce à cette fin, vous obtiendrez:

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
Louis

Ceci est une mise à jour sur cette question. Je me suis heurté à un problème similaire et je devais utiliser les signaux décrochants pour appeler une fonction de mon pipeline. J'ai édité le code de @ Talvalin, mais je voulais apporter une réponse pour plus de clarté. 

En gros, vous devez ajouter self en tant qu'argument pour handle_spider_closed . Vous devez également appeler le répartiteur in init afin de pouvoir transmettre l'instance d'araignée (self) à la méthode de traitement. 

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)

J'espère que cela aidera quelqu'un avec le même problème à l'avenir.

5
Mattias

En plus de certaines de ces réponses, si vous souhaitez suivre les erreurs Twisted, je voudrais examiner le paramètre errback de l'objet Request, sur lequel vous pouvez définir une fonction de rappel à appeler avec le Twisted Failure sur un request failure . En plus de l'URL, cette méthode peut vous permettre de suivre le type d'échec.

Vous pouvez ensuite vous connecter les URL en utilisant: failure.request.url (où failure est l'objet Twisted Failure passé à 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 documentation Scrapy donne un exemple complet de la façon dont cela peut être réalisé, à l'exception du fait que les appels à l'enregistreur Scrapy sont maintenant depreciated , j'ai donc adapté mon exemple pour utiliser le script logging de Python:

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

1
Michael Yang

Vous pouvez capturer les URL échouées de deux manières.

  1. Définir une requête scrapy avec errback

    class TestSpider(scrapy.Spider):
        def start_requests(self):
            yield scrapy.Request(url, callback=self.parse, errback=self.errback)
    
        def errback(self, failure):
            '''handle failed url (failure.request.url)'''
            pass
    
  2. Utilisez signaux.item_dropped

    class TestSpider(scrapy.Spider):
        def __init__(self):
            crawler.signals.connect(self.request_dropped, signal=signals.request_dropped)
    
        def request_dropped(self, request, spider):
            '''handle failed url (request.url)'''
            pass
    

[! Notice] Une requête Scrapy avec errback ne peut pas intercepter un échec de la tentative automatique, comme une erreur de connexion, RETRY_HTTP_CODES dans les paramètres.

1
jdxin0

En gros, Scrapy ignore l'erreur 404 par défaut. Il a été défini dans le middleware httperror.

Ajoutez donc HTTPERROR_ALLOW_ALL = True à votre fichier de paramètres.

Après cela, vous pouvez accéder à response.status via votre fonction d'analyse .

Vous pouvez le gérer comme ça.

def parse(self,response):
    if response.status==404:
        print(response.status)
    else:
        do something
0
Mohan B E