web-dev-qa-db-fra.com

Comment intégrer Flask & Scrapy?

J'utilise scrapy pour obtenir des données et je veux utiliser le framework web de flask pour afficher les résultats dans une page Web. Mais je ne sais pas comment appeler les araignées dans l'application du flacon. J'ai essayé d'utiliser CrawlerProcess pour appeler mes araignées, mais l'erreur est la suivante:

ValueError
ValueError: signal only works in main thread

Traceback (most recent call last)
File "/Library/Python/2.7/site-packages/flask/app.py", line 1836, in __call__
return self.wsgi_app(environ, start_response)
File "/Library/Python/2.7/site-packages/flask/app.py", line 1820, in wsgi_app
response = self.make_response(self.handle_exception(e))
File "/Library/Python/2.7/site-packages/flask/app.py", line 1403, in handle_exception
reraise(exc_type, exc_value, tb)
File "/Library/Python/2.7/site-packages/flask/app.py", line 1817, in wsgi_app
response = self.full_dispatch_request()
File "/Library/Python/2.7/site-packages/flask/app.py", line 1477, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/Library/Python/2.7/site-packages/flask/app.py", line 1381, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "/Library/Python/2.7/site-packages/flask/app.py", line 1475, in full_dispatch_request
rv = self.dispatch_request()
File "/Library/Python/2.7/site-packages/flask/app.py", line 1461, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "/Users/Rabbit/PycharmProjects/Flask_template/FlaskTemplate.py", line 102, in index
process = CrawlerProcess()
File "/Library/Python/2.7/site-packages/scrapy/crawler.py", line 210, in __init__
install_shutdown_handlers(self._signal_shutdown)
File "/Library/Python/2.7/site-packages/scrapy/utils/ossignal.py", line 21, in install_shutdown_handlers
reactor._handleSignals()
File "/Library/Python/2.7/site-packages/twisted/internet/posixbase.py", line 295, in _handleSignals
_SignalReactorMixin._handleSignals(self)
File "/Library/Python/2.7/site-packages/twisted/internet/base.py", line 1154, in _handleSignals
signal.signal(signal.SIGINT, self.sigInt)
ValueError: signal only works in main thread

Mon code de scrapy comme ceci:

class EPGD(Item):

genID = Field()
genID_url = Field()
taxID = Field()
taxID_url = Field()
familyID = Field()
familyID_url = Field()
chromosome = Field()
symbol = Field()
description = Field()

class EPGD_spider(Spider):
    name = "EPGD"
    allowed_domains = ["epgd.biosino.org"]
    term = "man"
    start_urls = ["http://epgd.biosino.org/EPGD/search/textsearch.jsp?textquery="+term+"&submit=Feeling+Lucky"]

db = DB_Con()
collection = db.getcollection(name, term)

def parse(self, response):
    sel = Selector(response)
    sites = sel.xpath('//tr[@class="odd"]|//tr[@class="even"]')
    url_list = []
    base_url = "http://epgd.biosino.org/EPGD"

    for site in sites:
        item = EPGD()
        item['genID'] = map(unicode.strip, site.xpath('td[1]/a/text()').extract())
        item['genID_url'] = base_url+map(unicode.strip, site.xpath('td[1]/a/@href').extract())[0][2:]
        item['taxID'] = map(unicode.strip, site.xpath('td[2]/a/text()').extract())
        item['taxID_url'] = map(unicode.strip, site.xpath('td[2]/a/@href').extract())
        item['familyID'] = map(unicode.strip, site.xpath('td[3]/a/text()').extract())
        item['familyID_url'] = base_url+map(unicode.strip, site.xpath('td[3]/a/@href').extract())[0][2:]
        item['chromosome'] = map(unicode.strip, site.xpath('td[4]/text()').extract())
        item['symbol'] = map(unicode.strip, site.xpath('td[5]/text()').extract())
        item['description'] = map(unicode.strip, site.xpath('td[6]/text()').extract())
        self.collection.update({"genID":item['genID']}, dict(item),  upsert=True)
        yield item

    sel_tmp = Selector(response)
    link = sel_tmp.xpath('//span[@id="quickPage"]')

    for site in link:
        url_list.append(site.xpath('a/@href').extract())

    for i in range(len(url_list[0])):
        if cmp(url_list[0][i], "#") == 0:
            if i+1 < len(url_list[0]):
                print url_list[0][i+1]
                actual_url = "http://epgd.biosino.org/EPGD/search/" + url_list[0][i+1]
                yield Request(actual_url, callback=self.parse)
                break
            else:
                print "The index is out of range!"

Mon code de flacon ressemble à ceci:

@app.route('/', methods=['GET', 'POST'])
def index():
    process = CrawlerProcess()
    process.crawl(EPGD_spider)
    return redirect(url_for('details'))


@app.route('/details', methods = ['GET'])
def epgd():
    if request.method == 'GET':
        results = db['EPGD_test'].find()
        json_results= []
        for result in results:
            json_results.append(result)
        return toJson(json_results)

Comment puis-je appeler mes araignées raclées lorsque j'utilise un framework web de flacon?

12
Coding_Rabbit

Ajouter un serveur HTTP devant vos araignées n'est pas si facile. Il y a plusieurs options.

1. sous-processus Python

Si vous êtes vraiment limité à Flask, si vous ne pouvez utiliser rien d'autre, le seul moyen d'intégrer Scrapy avec Flask consiste à lancer un processus externe pour chaque analyse de l'araignée, comme recommandé par une autre réponse (notez que votre sous-processus doit être généré dans le projet Scrapy approprié). annuaire).

La structure de répertoire pour tous les exemples devrait ressembler à ceci, j'utilise dirbot test project

> tree -L 1                                                                                                                                                              

├── dirbot
├── README.rst
├── scrapy.cfg
├── server.py
└── setup.py

Voici un exemple de code pour lancer Scrapy dans un nouveau processus:

# server.py
import subprocess

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    """
    Run spider in another process and store items in file. Simply issue command:

    > scrapy crawl dmoz -o "output.json"

    wait for  this command to finish, and read output.json to client.
    """
    spider_name = "dmoz"
    subprocess.check_output(['scrapy', 'crawl', spider_name, "-o", "output.json"])
    with open("output.json") as items_file:
        return items_file.read()

if __== '__main__':
    app.run(debug=True)

Enregistrez ci-dessus sous server.py et visitez localhost: 5000, vous devriez être en mesure de voir les éléments grattés.

2. Twisted-Klein + Scrapy

Une autre façon, plus efficace, consiste à utiliser un projet existant qui intègre Twisted à Werkzeug et affiche une API similaire à Flask, par exemple. Twisted-Klein . Twisted-Klein vous permettrait d’exécuter vos spiders de manière asynchrone dans le même processus que votre serveur Web. Mieux, il ne bloque pas toutes les demandes et vous permet simplement de renvoyer des reports différés Scrapy/Twisted à partir du gestionnaire de demandes de route HTTP.

Après que snippet intègre Twisted-Klein avec Scrapy, notez que vous devez créer votre propre classe de base de CrawlerRunner afin que le robot puisse collecter les éléments et les renvoyer à l'appelant. Cette option est un peu plus avancée, vous utilisez des spiders Scrapy dans le même processus que le serveur Python, les éléments ne sont pas stockés dans un fichier, mais stockés en mémoire (il n'y a donc pas d'écriture/lecture de disque comme dans l'exemple précédent). La chose la plus importante est qu’elle est asynchrone et qu’elle fonctionne dans un seul réacteur Twisted.

# server.py
import json

from klein import route, run
from scrapy import signals
from scrapy.crawler import CrawlerRunner

from dirbot.spiders.dmoz import DmozSpider


class MyCrawlerRunner(CrawlerRunner):
    """
    Crawler object that collects items and returns output after finishing crawl.
    """
    def crawl(self, crawler_or_spidercls, *args, **kwargs):
        # keep all items scraped
        self.items = []

        # create crawler (Same as in base CrawlerProcess)
        crawler = self.create_crawler(crawler_or_spidercls)

        # handle each item scraped
        crawler.signals.connect(self.item_scraped, signals.item_scraped)

        # create Twisted.Deferred launching crawl
        dfd = self._crawl(crawler, *args, **kwargs)

        # add callback - when crawl is done cal return_items
        dfd.addCallback(self.return_items)
        return dfd

    def item_scraped(self, item, response, spider):
        self.items.append(item)

    def return_items(self, result):
        return self.items


def return_spider_output(output):
    """
    :param output: items scraped by CrawlerRunner
    :return: json with list of items
    """
    # this just turns items into dictionaries
    # you may want to use Scrapy JSON serializer here
    return json.dumps([dict(item) for item in output])


@route("/")
def schedule(request):
    runner = MyCrawlerRunner()
    spider = DmozSpider()
    deferred = runner.crawl(spider)
    deferred.addCallback(return_spider_output)
    return deferred


run("localhost", 8080)

Enregistrez ci-dessus dans le fichier server.py et localisez-le dans votre répertoire de projet Scrapy. Ouvrez maintenant localhost: 8080, il lancera dmoz spider et renverra les éléments grattés au navigateur.

3. ScrapyRT

Des problèmes surviennent lorsque vous essayez d'ajouter une application HTTP devant vos araignées. Par exemple, vous devez parfois gérer les journaux d'araignées (vous pouvez en avoir besoin dans certains cas), vous devez également gérer les exceptions d'araignées, etc. Il existe des projets qui vous permettent d'ajouter une API HTTP aux araignées plus facilement, par exemple. ScrapyRT . Ceci est une application qui ajoute un serveur HTTP à vos spiders Scrapy et gère tous les problèmes pour vous (par exemple, la gestion de la journalisation, la gestion des erreurs de spider, etc.).

Donc, après avoir installé ScrapyRT, il vous suffit de faire:

> scrapyrt 

dans votre répertoire de projet Scrapy, et il lancera le serveur HTTP à l'écoute des demandes pour vous. Vous visiterez ensuite http: // localhost: 9080/crawl.json? Nom_pilote = dmoz & url = http: //alfa.com et il devrait lancer votre araignée pour votre URL d'exploration donnée.

Disclaimer: Je suis l'un des auteurs de ScrapyRt.

19
Pawel Miech

Cela ne fonctionne que si vous utilisez un robot de manière autonome. Pourquoi ne pas utiliser le module subprocess avec subprocess.call ().

J'ai changé ton araignée de la manière suivante et cela a fonctionné. Je n'ai pas la configuration de la base de données, donc ces lignes ont été commentées.

    import scrapy 
from scrapy.crawler import CrawlerProcess
from scrapy.selector import Selector
from scrapy import Request


class EPGD(scrapy.Item):
    genID = scrapy.Field()
    genID_url = scrapy.Field()
    taxID = scrapy.Field()
    taxID_url = scrapy.Field()
    familyID = scrapy.Field()
    familyID_url = scrapy.Field()
    chromosome = scrapy.Field()
    symbol = scrapy.Field()
    description = scrapy.Field()

class EPGD_spider(scrapy.Spider):
    name = "EPGD"
    allowed_domains = ["epgd.biosino.org"]
    term = "man"
    start_urls = ["http://epgd.biosino.org/EPGD/search/textsearch.jsp?textquery="+term+"&submit=Feeling+Lucky"]


    def parse(self, response):
        sel = Selector(response)
        sites = sel.xpath('//tr[@class="odd"]|//tr[@class="even"]')
        url_list = []
        base_url = "http://epgd.biosino.org/EPGD"

        for site in sites:
            item = EPGD()
            item['genID'] = map(unicode.strip, site.xpath('td[1]/a/text()').extract())
            item['genID_url'] = base_url+map(unicode.strip, site.xpath('td[1]/a/@href').extract())[0][2:]
            item['taxID'] = map(unicode.strip, site.xpath('td[2]/a/text()').extract())
            item['taxID_url'] = map(unicode.strip, site.xpath('td[2]/a/@href').extract())
            item['familyID'] = map(unicode.strip, site.xpath('td[3]/a/text()').extract())
            item['familyID_url'] = base_url+map(unicode.strip, site.xpath('td[3]/a/@href').extract())[0][2:]
            item['chromosome'] = map(unicode.strip, site.xpath('td[4]/text()').extract())
            item['symbol'] = map(unicode.strip, site.xpath('td[5]/text()').extract())
            item['description'] = map(unicode.strip, site.xpath('td[6]/text()').extract())
            #self.collection.update({"genID":item['genID']}, dict(item),  upsert=True)
            yield item

            sel_tmp = Selector(response)
            link = sel_tmp.xpath('//span[@id="quickPage"]')

            for site in link:
                url_list.append(site.xpath('a/@href').extract())

            for i in range(len(url_list[0])):
                if cmp(url_list[0][i], "#") == 0:
                    if i+1 < len(url_list[0]):
                        print url_list[0][i+1]
                        actual_url = "http://epgd.biosino.org/EPGD/search/" + url_list[0][i+1]
                        yield Request(actual_url, callback=self.parse)
                        break
                    else:
                        print "The index is out of range!"



process = CrawlerProcess()
process.crawl(EPGD_spider)
process.start()

Vous devriez pouvoir exécuter ce qui précède dans:

subprocess.check_output(['scrapy', 'runspider', "epgd.py"])
1
pgwalsh

Le problème est que le réacteur ne peut pas être redémarré. Concernant 3 solutions: a. CrawlerProcess b. CrawlerRunner c. Sous-processus Nous pouvons utiliser CrawlerRunner et Sous-processus, mais nous devons contrôler manuellement le mode de démarrage/arrêt du réacteur.

J'ai utilisé Flask (@ app.before_first_request) pour injecter une logique de démarrage du réacteur avant toute demande,

    @app.before_first_request
    def activate_job():
        def run_job():
            #time.sleep(0.5)
            try:
                if not reactor.running:
                    reactor.run()
            except:
                pass
        
        thread = Thread(target=run_job)
        thread.start()

Ensuite, si vous souhaitez utiliser le sous-processus:

    # how to pass parameters: https://stackoverflow.com/questions/15611605/how-to-pass-a-user-defined-argument-in-scrapy-spider
    def crawl_by_process(self):
        crawlSettings = {};
        subprocess.check_output(['scrapy', 'crawl', "demoSpider", '-a', 'cs='+json.dumps(crawlSettings)])

Ou si vous souhaitez utiliser CrawlerProcess

    # async, will return immediately and won't wait crawl finished
    def crawl(self):
        crawlSettings = {}

        configure_logging()

        s = get_project_settings()
        for a in inspect.getmembers(settings):
            if not a[0].startswith('_'):
                # Ignores methods
                if not inspect.ismethod(a[1]):
                    s.update({a[0]:a[1]})

        # if you want to use CrawlerRunner, when you want to integrate Scrapy to existing Twisted Application
        runner = CrawlerRunner(s)
        d = runner.crawl(demoSpider.DemoSpider, crawlSettings)
        d.addCallback(return_spider_output)
        return d
 
 def return_spider_output(output):
    """
    :param output: items scraped by CrawlerRunner
    :return: json with list of items
    """
    # this just turns items into dictionaries
    # you may want to use Scrapy JSON serializer here
    return json.dumps([dict(item) for item in output])

Voici mes billets de blog pour expliquer la logique ci-dessus: https://dingyuliang.me/scrapy-how-to-build-scrapy-with-flask-rest-api-2/

1
Robin Ding