Scrapy CrawlSpider 基于 start_urls 的动态规则?

Sim*_*mon 1 python web-crawler scrapy web-scraping scrapy-spider

我正在编写一个 Scrapy 抓取器,它使用 CrawlSpider 来抓取站点,查看其内部链接,并抓取任何外部链接(域与原始域不同的域的链接)的内容。

我设法用 2 条规则做到了这一点,但它们基于被抓取的站点的域。如果我想在多个网站上运行它,我会遇到一个问题,因为我不知道我目前在哪个“start_url”上,所以我无法适当地更改规则。

到目前为止,这是我想到的,它适用于一个网站,但我不确定如何将其应用于网站列表:

class HomepagesSpider(CrawlSpider):
    name = 'homepages'

    homepage = 'http://www.somesite.com'

    start_urls = [homepage]

    # strip http and www
    domain = homepage.replace('http://', '').replace('https://', '').replace('www.', '')
    domain = domain[:-1] if domain[-1] == '/' else domain

    rules = (
        Rule(LinkExtractor(allow_domains=(domain), deny_domains=()), callback='parse_internal', follow=True),
        Rule(LinkExtractor(allow_domains=(), deny_domains=(domain)), callback='parse_external', follow=False),
    )

    def parse_internal(self, response):

        # log internal page...

    def parse_external(self, response):

        # parse external page...
Run Code Online (Sandbox Code Playgroud)

这可能可以通过在调用刮刀时将 start_url 作为参数传递来完成,但我正在寻找一种在刮刀本身内以编程方式执行此操作的方法。

有任何想法吗?谢谢!

西蒙。

Sim*_*mon 5

我发现了一个非常相似的问题,并使用了已接受答案中提供的第二个选项来开发解决此问题的方法,因为在 scrapy 中不支持开箱即用。

我创建了一个函数,它获取一个 url 作为输入并为其创建规则:

def rules_for_url(self, url):

    domain = Tools.get_domain(url)

    rules = (
        Rule(LinkExtractor(allow_domains=(domain), deny_domains=()), callback='parse_internal', follow=True),
        Rule(LinkExtractor(allow_domains=(), deny_domains=(domain)), callback='parse_external', follow=False),
    )

    return rules
Run Code Online (Sandbox Code Playgroud)

然后我覆盖了 CrawlSpider 的一些功能。

  1. 我将 _rules 更改为字典,其中键是不同的网站域,值是该域的规则(使用rules_for_url)。_rules 的填充是在_compile_rules

  2. 然后,我进行了适当的更改_requests_to_follow_response_downloaded支持使用_rules.

_rules = {}

def _requests_to_follow(self, response):
    if not isinstance(response, HtmlResponse):
        return
    seen = set()

    domain = Tools.get_domain(response.url)
    for n, rule in enumerate(self._rules[domain]):
        links = [lnk for lnk in rule.link_extractor.extract_links(response) 
                 if lnk not in seen]
        if links and rule.process_links:
            links = rule.process_links(links)
        for link in links:
            seen.add(link)
            r = self._build_request(domain + ';' + str(n), link)
            yield rule.process_request(r)

def _response_downloaded(self, response):

    meta_rule = response.meta['rule'].split(';')
    domain = meta_rule[0]
    rule_n = int(meta_rule[1])

    rule = self._rules[domain][rule_n]
    return self._parse_response(response, rule.callback, rule.cb_kwargs, rule.follow)

def _compile_rules(self):
    def get_method(method):
        if callable(method):
            return method
        elif isinstance(method, six.string_types):
            return getattr(self, method, None)

    for url in self.start_urls:
        url_rules = self.rules_for_url(url)
        domain = Tools.get_domain(url)
        self._rules[domain] = [copy.copy(r) for r in url_rules]
        for rule in self._rules[domain]:
            rule.callback = get_method(rule.callback)
            rule.process_links = get_method(rule.process_links)
            rule.process_request = get_method(rule.process_request)
Run Code Online (Sandbox Code Playgroud)

此处查看原始函数。

现在蜘蛛将简单地检查 start_urls 中的每个 url 并创建一组特定于该 url 的规则。然后为每个被抓取的网站使用适当的规则。

希望这可以帮助将来偶然发现此问题的任何人。

西蒙。