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?
Ajouter un serveur HTTP devant vos araignées n'est pas si facile. Il y a plusieurs options.
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.
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.
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.
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"])
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/