10

我已经设法用 Scrapy 编写了一个非常简单的爬虫,具有这些给定的约束:

  • 存储所有链接信息(例如:锚文本、页面标题),因此有 2 个回调
  • 使用 CrawlSpider 来利用规则,因此没有 BaseSpider

它运行良好,但如果我向第一个请求添加回调,它不会执行规则!

这是我的代码:(工作但不正确,有一个活生生的例子)

from scrapy.contrib.spiders import CrawlSpider,Rule
from scrapy.selector import HtmlXPathSelector
from scrapy.http import Request
from scrapySpider.items import SPage
from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor

class TestSpider4(CrawlSpider):
    name = "spiderSO"
    allowed_domains = ["cumulodata.com"]
    start_urls = ["http://www.cumulodata.com"]
    extractor = SgmlLinkExtractor()

    def parse_start_url(self, response):
        #3
        print('----------manual call of',response)
        self.parse_links(response)
        print('----------manual call done')
        # 1 return Request(self.start_urls[0]) # does not call parse_links(example.com)
        # 2 return Request(self.start_urls[0],callback = self.parse_links) # does not call parse_links(example.com)

    rules = (
        Rule(extractor,callback='parse_links',follow=True),
        )

    def parse_links(self, response):
        hxs = HtmlXPathSelector(response)
        print('----------- manual parsing links of',response.url)
        links = hxs.select('//a')
        for link in links:
                title = link.select('@title')
                url = link.select('@href').extract()[0]
                meta={'title':title,}
                yield Request(url, callback = self.parse_page,meta=meta)

    def parse_page(self, response):
        print('----------- parsing page: ',response.url)
        hxs = HtmlXPathSelector(response)
        item=SPage()
        item['url'] = str(response.request.url)
        item['title']=response.meta['title']
        item['h1']=hxs.select('//h1/text()').extract()
        yield item

我尝试通过 3 种方式解决此问题:

  • 1:返回带有起始 url 的请求 -不执行规则
  • 2:与上述相同,但有回调parse_links- 相同的问题
  • 3:抓取起始地址parse_links parse_start_url调用,通过实现,函数不会被调用

以下是日志:

----------manual call of <200 http://www.cumulodata.com>)

----------manual call done

#No '----------- manual parsing links', so `parse_links` is never called!

版本

  • Python 2.7.2
  • Scrapy 0.14.4
4

1 回答 1

19

这是一个完美运行的刮刀:

from scrapy.contrib.spiders import CrawlSpider,Rule
from scrapy.selector import HtmlXPathSelector
from scrapy.http import Request
from scrapySpider.items import SPage
from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor

class TestSpider4(CrawlSpider):
    name = "spiderSO"
    allowed_domains = ["cumulodata.com"]
    start_urls = ["http://www.cumulodata.com/"]

    extractor = SgmlLinkExtractor()

    rules = (
        Rule(extractor,callback='parse_links',follow=True),
        )

    def parse_start_url(self, response):
        list(self.parse_links(response))

    def parse_links(self, response):
        hxs = HtmlXPathSelector(response)
        links = hxs.select('//a')
        for link in links:
            title = ''.join(link.select('./@title').extract())
            url = ''.join(link.select('./@href').extract())
            meta={'title':title,}
            cleaned_url = "%s/?1" % url if not '/' in url.partition('//')[2] else "%s?1" % url
            yield Request(cleaned_url, callback = self.parse_page, meta=meta,)

    def parse_page(self, response):
        hxs = HtmlXPathSelector(response)
        item=SPage()
        item['url'] = response.url
        item['title']=response.meta['title']
        item['h1']=hxs.select('//h1/text()').extract()
        return item

变化

  1. 已实现parse_start_url- 不幸的是,当您为第一个请求指定回调时,不会执行规则。这是内置在 Scrapy 中的,我们只能通过一种解决方法来管理它。所以我们 list(self.parse_links(response))在这个函数里面做了一个。为什么list()?因为parse_links是生成器,而生成器是惰性的。所以我们需要明确地完全调用它。

  2. cleaned_url = "%s/?1" % url if not '/' in url.partition('//')[2] else "%s?1" % url- 这里有几件事:

    一个。我们在 URL 的末尾添加 '/?1' - 由于parse_links返回重复的 URL,Scrapy 将它们过滤掉。避免这种情况的更简单方法是传递dont_filter=True给 Request()。但是,您的所有页面都是相互链接的(从 pageAA 回到索引等),并且dont_filter此处会导致过多的重复请求和项目。

    湾。if not '/' in url.partition('//')[2]- 同样,这是因为您网站中的链接。其中一个内部链接是“www.cumulodata.com”,另一个是“www.cumulodata.com/”。由于我们明确添加了一种允许重复的机制,这导致了一个额外的项目。因为我们需要完美,所以我实现了这个 hack。

  3. title = ''.join(link.select('./@title').extract())- 您不想返回节点,而是返回数据。另外:在空列表的情况下,''.join(list) 比 list[0] 更好。

恭喜您创建了一个提出了一个奇怪问题的测试网站 - 重复既是必要的,也是不需要的!

于 2012-10-04T21:35:03.610 回答