仅爬取一次URL的Scrapy爬虫

6
我正在编写一个 Scrapy 爬虫,每天会爬取一组 URL。然而,其中一些网站非常大,因此我无法每天全站爬取,也不想产生必要的大量流量。
一个旧问题(这里)问了类似的问题。然而,得到赞同的回答只是指向了一个代码片段(这里),它似乎需要请求实例的某些内容,但在回答中没有解释,也没有在包含代码片段的页面中说明。
我正在努力理解这个问题,但中间件有点令人困惑。一个完整的示例爬虫程序,可以多次运行而不会重新抓取URL,无论是否使用链接的中间件都将非常有用。
我发布了下面的代码来开始探讨,但我不一定需要使用这个中间件。任何能够每天爬取并提取新URL的Scrapy爬虫都可以。显然,一种解决方案是只需编写一个已爬取URL字典,然后检查每个新URL是否在字典中,但这似乎非常缓慢/低效。 爬虫
from scrapy.contrib.spiders import CrawlSpider, Rule
from scrapy.contrib.linkextractors import LinkExtractor
from cnn_scrapy.items import NewspaperItem



class NewspaperSpider(CrawlSpider):
    name = "newspaper"
    allowed_domains = ["cnn.com"]
    start_urls = [
        "http://www.cnn.com/"
    ]

    rules = (
        Rule(LinkExtractor(), callback="parse_item", follow=True),
    )

    def parse_item(self, response):
        self.log("Scraping: " + response.url)
        item = NewspaperItem()
        item["url"] = response.url
        yield item

项目

import scrapy


class NewspaperItem(scrapy.Item):
    url = scrapy.Field()
    visit_id = scrapy.Field()
    visit_status = scrapy.Field()

Middlewares (ignore.py)

from scrapy import log
from scrapy.http import Request
from scrapy.item import BaseItem
from scrapy.utils.request import request_fingerprint

from cnn_scrapy.items import NewspaperItem

class IgnoreVisitedItems(object):
    """Middleware to ignore re-visiting item pages if they were already visited
    before. The requests to be filtered by have a meta['filter_visited'] flag
    enabled and optionally define an id to use for identifying them, which
    defaults the request fingerprint, although you'd want to use the item id,
    if you already have it beforehand to make it more robust.
    """

    FILTER_VISITED = 'filter_visited'
    VISITED_ID = 'visited_id'
    CONTEXT_KEY = 'visited_ids'

    def process_spider_output(self, response, result, spider):
        context = getattr(spider, 'context', {})
        visited_ids = context.setdefault(self.CONTEXT_KEY, {})
        ret = []
        for x in result:
            visited = False
            if isinstance(x, Request):
                if self.FILTER_VISITED in x.meta:
                    visit_id = self._visited_id(x)
                    if visit_id in visited_ids:
                        log.msg("Ignoring already visited: %s" % x.url,
                                level=log.INFO, spider=spider)
                        visited = True
            elif isinstance(x, BaseItem):
                visit_id = self._visited_id(response.request)
                if visit_id:
                    visited_ids[visit_id] = True
                    x['visit_id'] = visit_id
                    x['visit_status'] = 'new'
            if visited:
                ret.append(NewspaperItem(visit_id=visit_id, visit_status='old'))
            else:
                ret.append(x)
        return ret

    def _visited_id(self, request):
        return request.meta.get(self.VISITED_ID) or request_fingerprint(request)

那么需要在其他响应中找到的URL呢? - eLRuLL
我假设在访问了一个URL之后,在该页面上不会发现新的URL(除了start_urls)。或者我误解了你的问题? - Henry David Thorough
1
不,那没关系,我认为你的方法(或类似的方法)是可以的,关键是要保存已经完成的任务。如果任务量很大,建议使用单独的数据库。此外,Scrapy会像指纹一样保存请求,这有助于它们自己的去重组件。 - eLRuLL
啊,你的意思是将所有 URL 写入数据库,并对于每个新的 URL,如果它在数据库中,则跳过它?使用某种查找? - Henry David Thorough
1
是的,那是唯一的方法,当然仅保存URL将适用于GET请求,如果您有POST请求,Scrapy的请求指纹可能会有所帮助。 - eLRuLL
1个回答

1
这样吧,你想做的是拥有一个数据库来安排/定时爬取。无论使用dupflier.middleware与否,你仍然需要爬取整个网站...尽管提供的代码显然不可能是整个项目,但我认为它太长了。
我不确定你要爬取的是什么,但我现在假设你正在爬取CNN作为项目URL,你正在爬取文章?
我会使用CNN的RSS源或甚至是网站地图,因为它提供了文章元数据的到期日期,并使用OS模块...
定义每个爬取实例的日期 使用正则表达式将爬虫定义日期与发布文章的日期进行限制 部署和安排爬虫到scrapinghub中 使用scrapinghubs python api客户端迭代项
仍然会爬取整个站点的内容,但使用xmlspider或rssspider类可以更快地解析所有数据...现在数据库可用于“云”中...我觉得人们可以更加模块化地处理项目的可伸缩性以及更容易的可移植性/跨兼容性
我相信我所描述的流程需要一些调整,但思路很直接。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接