我在Python中编写一个爬虫程序,它抓取给定域中的所有页面,作为特定于域的搜索引擎的一部分。我正在使用Django,Scrapy和Celery实现这一目标。方案如下:
我从用户那里收到一个域名,并在视图中调用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
问题是抓取工具在跟踪此方案并使用Celery时仅抓取start_urls
并且不跟踪链接(或调用回调函数)。但是通过run_spider
调用python manage.py shell
可以正常工作!
另一个问题是项目管道和日志记录无法与Celery一起使用。这使得调试变得更加困难。我认为这些问题可能是相关的。
答案 0 :(得分:2)
因此,在检查Scrapy的代码并启用Celery日志记录后,在web_spider.py
中插入这两行:
from celery.utils.log import get_task_logger
logger = get_task_logger(__name__)
我找到了问题所在:
在WebSpider
:
super(WebSpider, self).__init__(**kw)
父__init__
的{{1}}功能会调用_compile_rules
函数,在执行某些更改时,会将规则从CrawlSpider
复制到self.rules
。 self._rules
是蜘蛛在检查规则时使用的内容。在定义规则之前调用self._rules
的初始化函数导致空CrawlSpider
,因此没有遵循链接。
将self._rules
行移至super(WebSpider, self).__init__(**kw)
' WebSpider
的最后一行解决了问题。
更新: the previously mentioned SO answer的代码存在一些错误。它会导致反应堆在第二次调用后挂起。修复很简单,在__init__
' WebCrawlerScript
方法中,只需移动此行:
__init__
出自if语句,如那里的评论所示。
更新2:我终于让管道工作了!这不是芹菜问题。我意识到设置模块没有被读取。这只是一个进口问题。解决它:
在django项目的设置模块self.crawler.signals.connect(reactor.stop, signal=signals.spider_closed)
中设置环境变量SCRAPY_SETTINGS_MODULE
:
myproject/settings.py
在Scrapy设置模块import os
os.environ['SCRAPY_SETTINGS_MODULE'] = 'myapp.crawler.crawler.settings'
中,将您的Scrapy项目路径添加到crawler/settings.py
,以便设置文件中的相对导入可用:
sys.path
更改路径以适合您的情况。