我正在用 Python 编写一个爬虫程序,它可以爬取给定域中的所有页面,作为特定域搜索引擎的一部分。我正在使用 Django、Scrapy 和 Celery 来实现这一目标。场景如下:
我从用户那里收到一个域名,并在 View 中调用 crawl
任务,将域名作为参数传递:
crawl.delay(domain)
任务本身只是调用一个启动爬行过程的函数:
from .crawler.crawl import run_spider
from celery import shared_task
@shared_task
def crawl(domain):
return run_spider(domain)
run_spider
开始抓取过程,as in this SO answer , 将 MySpider
替换为 WebSpider
。
WebSpider
继承自 CrawlSpider
,我现在使用它只是为了测试功能。定义的唯一规则采用一个 SgmlLinkExtractor
实例和一个回调函数 parse_page
,它简单地提取响应 url 和页面标题,填充一个新的 DjangoItem(HTMLPageItem
) 并将其保存到数据库中(效率不高,我知道)。
from urlparse import urlparse
from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor
from ..items import HTMLPageItem
from scrapy.selector import Selector
from scrapy.contrib.spiders import Rule, CrawlSpider
class WebSpider(CrawlSpider):
name = "web"
def __init__(self, **kw):
super(WebSpider, self).__init__(**kw)
url = kw.get('domain') or kw.get('url')
if not (url.startswith('http://') or url.startswith('https://')):
url = "http://%s/" % url
self.url = url
self.allowed_domains = [urlparse(url).hostname.lstrip('www.')]
self.start_urls = [url]
self.rules = [
Rule(SgmlLinkExtractor(
allow_domains=self.allowed_domains,
unique=True), callback='parse_page', follow=True)
]
def parse_start_url(self, response):
return self.parse_page(response)
def parse_page(self, response):
sel = Selector(response)
item = HTMLPageItem()
item['url'] = response.request.url
item['title'] = sel.xpath('//title/text()').extract()[0]
item.save()
return item
问题 是爬虫只抓取 start_urls
并且在遵循这种情况并使用 Celery 时不跟踪链接(或调用回调函数)。然而,通过 python manage.py shell
调用 run_spider
工作得很好!
另一个问题是 Item Pipelines 和日志记录不适用于 Celery。这使调试变得更加困难。我认为这些问题可能是相关的。
最佳答案
所以在检查 Scrapy 的代码并启用 Celery 日志记录之后,通过在 web_spider.py
中插入这两行:
from celery.utils.log import get_task_logger
logger = get_task_logger(__name__)
我能够找到问题所在:
在WebSpider
的初始化函数中:
super(WebSpider, self).__init__(**kw)
父CrawlSpider
的__init__
函数调用_compile_rules
简而言之,该函数将规则从 self.rules
复制到 self._rules
,同时进行一些更改。 self._rules
是蜘蛛在检查规则时使用的。在定义规则之前调用CrawlSpider
的初始化函数导致一个空的self._rules
,因此没有遵循任何链接。
将 super(WebSpider, self).__init__(**kw)
行移动到 WebSpider
的 __init__
的最后一行已修复问题。
更新 the previously mentioned SO answer 中的代码有一点错误.它会导致 react 器在第二次调用后挂起。修复很简单,在 WebCrawlerScript
的 __init__
方法中,只需移动这一行:
self.crawler.signals.connect(reactor.stop, signal=signals.spider_closed)
按照评论中的建议,在 if 语句之外。
更新 2: 我终于可以使用管道了!这不是 celery 问题。我意识到设置模块没有被读取。这只是一个导入问题。要修复它:
在 django 项目的设置模块 myproject/settings.py
中设置环境变量 SCRAPY_SETTINGS_MODULE
:
import os
os.environ['SCRAPY_SETTINGS_MODULE'] = 'myapp.crawler.crawler.settings'
在您的 Scrapy 设置模块 crawler/settings.py
中,将您的 Scrapy 项目路径添加到 sys.path
以便设置文件中的相对导入工作:
import sys
sys.path.append('/absolute/path/to/scrapy/project')
根据您的情况更改路径。
关于python - 使用 Celery 时 Scrapy 蜘蛛不跟踪链接,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24232744/