Scrapy每个项目多页处理

11 投票
1 回答
5444 浏览
提问于 2025-04-17 20:57

免责声明:我对Scrapy还比较陌生。

简单来说,我的问题是:如何从页面上的一个链接中获取一个项目的属性,并把结果放回到同一个项目里?

下面是一个示例爬虫:

class SiteSpider(Spider):
    site_loader = SiteLoader
    ...
    def parse(self, response):
        item = Place()
        sel = Selector(response)
        bl = self.site_loader(item=item, selector=sel)
        bl.add_value('domain', self.parent_domain)
        bl.add_value('origin', response.url)
        for place_property in item.fields:
            parse_xpath = self.template.get(place_property)

            # parse_xpath will look like either:
            # '//path/to/property/text()'
            # or
            # {'url': '//a[@id="Location"]/@href', 
            #  'xpath': '//div[@class="directions"]/span[@class="address"]/text()'}
            if isinstance(parse_xpath, dict):  # place_property is at a URL
                url = sel.xpath(parse_xpath['url_elem']).extract()
                yield Request(url, callback=self.get_url_property,
                              meta={'loader': bl, 'parse_xpath': parse_xpath,
                                    'place_property': place_property})
            else:  # parse_xpath is just an xpath; process normally
                bl.add_xpath(place_property, parse_xpath)
        yield bl.load_item()

    def get_url_property(self, response):
        loader = response.meta['loader']
        parse_xpath = response.meta['parse_xpath']
        place_property = response.meta['place_property']
        sel = Selector(response)
        loader.add_value(place_property, sel.xpath(parse_xpath['xpath'])
        return loader

我在多个网站上运行这些爬虫,大部分网站的数据都在一个页面上,这样运行得很好。不过,有些网站的某些属性在子页面上(比如,“获取路线”链接里的“地址”数据)。

问题主要出在“yield Request”这一行。我看到这些项目在处理流程中移动,但它们缺少那些在其他网址找到的属性(换句话说,就是那些通过“yield Request”获取的属性)。get_url_property这个回调函数基本上就是在新的response变量中查找一个xpath,然后把找到的内容加到项目加载器实例里。

有没有办法实现我想要的,或者有没有更好的方法?我希望能避免同步调用来获取我需要的数据(如果这里可能的话),但如果这是最好的方法,那也许就是正确的做法。谢谢。

1 个回答

9

如果我理解得没错,你的情况至少有两种:

  1. 被抓取的页面链接到另一个页面,这个页面里有数据 (需要再请求一次)
  2. 被抓取的页面本身就包含数据 (不需要再请求)

在你现在的代码中,你对这两种情况都调用了 yield bl.load_item(),但是是在 parse 回调中。要注意的是,你调用的请求会在稍后的时间执行,因此这个项目是不完整的,这就是为什么在第一种情况下你缺少 place_property 这个键的原因。

可能的解决方案

一个可能的解决方案(如果我理解得对的话)是利用 Scrapy 的异步特性。你只需要对代码做一些小的修改。

对于第一种情况,你需要把项目加载器传递给另一个请求,然后在那个请求中再返回它。这就是你在 isinstance 的条件语句中所做的。你需要修改 get_url_property 回调的返回值,以便实际返回加载好的项目。

对于第二种情况,你可以直接返回这个项目,因此只需在 else 条件中返回这个项目即可。

下面的代码包含了对你示例的修改。这能解决你的问题吗?

def parse(self, response):

    # ...

    if isinstance(parse_xpath, dict):  # place_property is at a URL
        url = sel.xpath(parse_xpath['url_elem']).extract()
        yield Request(url, callback=self.get_url_property,
                      meta={'loader': bl, 'parse_xpath': parse_xpath,
                            'place_property': place_property})
    else:  # parse_xpath is just an xpath; process normally
        bl.add_xpath(place_property, parse_xpath)
        yield bl.load_item()

def get_url_property(self, response):

    loader = response.meta['loader']
    # ...
    loader.add_value(place_property, sel.xpath(parse_xpath['xpath'])
    yield loader.load_item()

与这个问题相关的是 请求链式调用的问题,我注意到有类似的解决方案。

撰写回答