Scrapy每个项目多页处理
免责声明:我对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 个回答
如果我理解得没错,你的情况至少有两种:
- 被抓取的页面链接到另一个页面,这个页面里有数据 (需要再请求一次)
- 被抓取的页面本身就包含数据 (不需要再请求)
在你现在的代码中,你对这两种情况都调用了 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()
与这个问题相关的是 请求链式调用的问题,我注意到有类似的解决方案。